From c74fa592e404e35701e4623b9bc6aa8005aed77a Mon Sep 17 00:00:00 2001 From: Antoine Beauchamp Date: Fri, 2 May 2025 17:57:03 -0400 Subject: [PATCH 01/10] Updated the demo website sample. --- .../demo_website/www/contact/background.jpg | Bin 0 -> 21166 bytes samples/demo_website/www/contact/index.html | 39 +- samples/demo_website/www/favicon.ico | Bin 0 -> 3262 bytes samples/demo_website/www/index.html | 73 +- samples/demo_website/www/index.html.old | 74 ++ samples/demo_website/www/static/banner.jpg | Bin 0 -> 43361 bytes samples/demo_website/www/static/dark-mode.css | 41 + .../demo_website/www/static/light-mode.css | 41 + samples/demo_website/www/static/logo.svg | 809 ++++++++++++++++++ .../www/static/profile-picture.jpg | Bin 0 -> 53945 bytes 10 files changed, 1058 insertions(+), 19 deletions(-) create mode 100644 samples/demo_website/www/contact/background.jpg create mode 100644 samples/demo_website/www/favicon.ico create mode 100644 samples/demo_website/www/index.html.old create mode 100644 samples/demo_website/www/static/banner.jpg create mode 100644 samples/demo_website/www/static/dark-mode.css create mode 100644 samples/demo_website/www/static/light-mode.css create mode 100644 samples/demo_website/www/static/logo.svg create mode 100644 samples/demo_website/www/static/profile-picture.jpg diff --git a/samples/demo_website/www/contact/background.jpg b/samples/demo_website/www/contact/background.jpg new file mode 100644 index 0000000000000000000000000000000000000000..79a2596acf49a7ee928d95d8bffa0a431ed47b36 GIT binary patch literal 21166 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 - - - Contact + + + Contact Us + -
-
-

Contact

-
-
-

Feel free to contact me about your DIY projects!

-
-
-

-
    + +
    +

    Contact Me

    +
    +

    Your Name (required)
    @@ -37,7 +46,7 @@

    Contact

    -
    -
    + + diff --git a/samples/demo_website/www/favicon.ico b/samples/demo_website/www/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..1d8891e3169695a139f8ffb493d8c983abe60c97 GIT binary patch literal 3262 zcmds3YgANK6h4qtq-H(_69Z^C9ADWynNpI5+#}%+Qu6%ojy)F?rEnH#NK4MXr*LF+h5(Z zI$vL>>j&sANtj0V$s%&Kef{0)zOX$RrX*mQ!@I!&=k510F=PEutS(Wa5UWB)-5!at#VAD zWLSDraO&$GOe9-;-5!<(e}(h@X(K%DR5lAuJzp1n2m~~&z)dk2)R_|(fnc%y3H`3l zKD)8;(gle$#JBew+oui--0U6?5F)UWg`b)Y_o}OHpEKFVMYNNVH>?XANf!K~sG!Ir zeXS0OlDWr4QxY}ZTBg(5(%Igo{`0=l!g$xRg$Gp~y`W{Bx-!XV!z(r+Jv}Vs`_+V?tu>3Kl_7oEJ$pi*D2a$%b)6(EuuXnNe^1oGmZC5 zHVArQ;$BDV&nA#fLc#Vc#dk44+bMq7QI-Nvhgp6Cqh+HFUyF^&rp-fy^-l#=%AOSt zX(+Kon7m4qESeT;M#92EvT#QdyO<9)rET#@d{cDD$2GR>S}jES`v;`reebS_4E9fO z5$$qYzCSO!jEYk`q;FCH3$CJ^^t95C{mw^)DjMqBXi8UC&mVVM=-b(&(aORL zDsD8;66lA{3Tmw_s)L~!n>~)MUmLy6CoVoFx20JHnt_3#q2UuCte%%Q$DrVO!(1WB znng0jljUbpt^wa0nBzR-+sg7L`1fjC%}K-#-_!8bUA+=}*=NB&z{g7Zx)7UoJXt?` zy#G{V?{VbqIi{PAL}t_eLGXAbcMeemd1o~@DNiO}oj>~ALV>*PL3i}$KU$LHgqR|5 zD$47olP`B}K7+<|+5yq@NU?P!UVMa9%I*N~B(tZtF0k00m021cF1I%iaC_qm-h2w2 z1ecW^+%FN<=R5Op7Y9E4v9hAxlB6EknFBC8qihC|#YX<5rwnXdlVm?kK4c; zA@jO$Y|w1M_Ufuuj8v`e#dJ^tA@3yxV!>6FQiEm?s;+9bc`|F`@~npXc58AX$U6f# z?!|Ug(#8DS;N$Y}f%7=b3on%GQ#b^x{6@X`GyYDqKT&sSL1XjTASo0Ju6L?>z(ICR zb&HKruD2w+`My$UnC|Ds%{j#H+)Lw9f4kYp2@MX$M;GGl&dw~Qm&}HKbP9)g;(1cA`~8CZjU7V6Oy89m0JnR}XOd&FQ3^d}V3*%%b8@Jp zxQbppUG2-HGb>U0`qer`UKI{feIA#X5?6Ais2Z2we_QsJ zog7DfX-REj%q7f$a;UvrRJHu2BQuOb@%KP%9scWOE(Q9-o_G>x=HllX++O64Rys!P zYB;zozusW~)WsFH1v(uz;Q(GM{Z8aq?4YA5u)RIHV#VFFXG&>lAhdt{0rPacCENFC zC(sn=*H*VmEQ;L4d3sp@4;ui1`W&POsl&HaA0^<>u2^qWpIT*y25$=WsTAMmBm69i ZL!9dKQy!-Qq5OYRdpw1&PpkY-?cW)5E!_YB literal 0 HcmV?d00001 diff --git a/samples/demo_website/www/index.html b/samples/demo_website/www/index.html index 0a9eda9..17e0d60 100644 --- a/samples/demo_website/www/index.html +++ b/samples/demo_website/www/index.html @@ -1,11 +1,76 @@ - - - Website root + + + Nature & Outdoors Blog + + -Website root! + + + +
    +

    Welcome to the Nature & Outdoors Blog

    +

    Immerse yourself in the beauty of nature. Discover breathtaking landscapes, essential hiking tips, and the wonders of wildlife.

    + +

    Our goal is to inspire adventure and share knowledge on preserving the great outdoors. From national parks to hidden gems, we've got it all.

    + +

    Whether you're a seasoned explorer or a beginner, join us in appreciating the natural world like never before.

    +
    + + + diff --git a/samples/demo_website/www/index.html.old b/samples/demo_website/www/index.html.old new file mode 100644 index 0000000..af805eb --- /dev/null +++ b/samples/demo_website/www/index.html.old @@ -0,0 +1,74 @@ + + + + + + Nature & Outdoors Blog + + + + + + + + +
    +

    Welcome to the Nature & Outdoors Blog

    +

    Immerse yourself in the beauty of nature. Discover breathtaking landscapes, essential hiking tips, and the wonders of wildlife.

    + +

    Our goal is to inspire adventure and share knowledge on preserving the great outdoors. From national parks to hidden gems, we've got it all.

    + +

    Whether you're a seasoned explorer or a beginner, join us in appreciating the natural world like never before.

    + + +
    + + + + + diff --git a/samples/demo_website/www/static/banner.jpg b/samples/demo_website/www/static/banner.jpg new file mode 100644 index 0000000000000000000000000000000000000000..66dbca7c7abb0b617ca645943f64cf9078eb46a3 GIT binary patch literal 43361 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⁣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 literal 0 HcmV?d00001 diff --git a/samples/demo_website/www/static/dark-mode.css b/samples/demo_website/www/static/dark-mode.css new file mode 100644 index 0000000..61ba07a --- /dev/null +++ b/samples/demo_website/www/static/dark-mode.css @@ -0,0 +1,41 @@ +body { + font-family: 'Arial', sans-serif; + background-color: #1B1F22; /* Deep charcoal */ + color: #E3E6D5; /* Soft cream text */ + margin: 0; + padding: 0; +} + +.container { + max-width: 800px; + margin: auto; + padding: 20px; +} + +h1, h2, h3 { + color: #A5C882; /* Muted pastel green */ +} + +a { + color: #CFAE6D; /* Warm golden brown for links */ + text-decoration: none; + transition: color 0.3s ease; +} + +a:hover { + color: #E0C085; /* Lightened golden shade */ +} + +.button { + background-color: #689F38; /* Earthy green */ + color: white; + padding: 10px 15px; + border: none; + border-radius: 5px; + cursor: pointer; + transition: background 0.3s ease; +} + +.button:hover { + background-color: #4E7931; /* Slightly darker green */ +} diff --git a/samples/demo_website/www/static/light-mode.css b/samples/demo_website/www/static/light-mode.css new file mode 100644 index 0000000..f811c0d --- /dev/null +++ b/samples/demo_website/www/static/light-mode.css @@ -0,0 +1,41 @@ +body { + font-family: 'Arial', sans-serif; + background-color: #F8F5E1; /* Soft earthy tone */ + color: #2E4B30; /* Deep forest green */ + margin: 0; + padding: 0; +} + +.container { + max-width: 800px; + margin: auto; + padding: 20px; +} + +h1, h2, h3 { + color: #4D6B3C; /* Muted green for headers */ +} + +a { + color: #A15F34; /* Warm brown for links */ + text-decoration: none; + transition: color 0.3s ease; +} + +a:hover { + color: #C47C45; /* Lightened brown on hover */ +} + +.button { + background-color: #8BC34A; /* Nature-inspired green */ + color: white; + padding: 10px 15px; + border: none; + border-radius: 5px; + cursor: pointer; + transition: background 0.3s ease; +} + +.button:hover { + background-color: #689F38; /* Slightly deeper green */ +} diff --git a/samples/demo_website/www/static/logo.svg b/samples/demo_website/www/static/logo.svg new file mode 100644 index 0000000..db73c68 --- /dev/null +++ b/samples/demo_website/www/static/logo.svg @@ -0,0 +1,809 @@ + + + + +Created by potrace 1.16, written by Peter Selinger 2001-2019 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/samples/demo_website/www/static/profile-picture.jpg b/samples/demo_website/www/static/profile-picture.jpg new file mode 100644 index 0000000000000000000000000000000000000000..08500a9064ec24286f40c422bc106cf3c5d51dbe GIT binary patch literal 53945 zcmb5Vd0bLk7e9PJG(;>QQ8X(+Fhn#bOs(!k1OtaI5Sl1;op5eYv$-BLHN_!8#VHFB z&4El%OUru96m!g+ZMsc1Shw47>)yK0eeUn{yzl#YKkuLK;rzwkXMfMyd#&$Ud+l}p zS@`EMfC}*E`U4Q~qCsc?@Xvj~4_Ix!3cU)s8jV6*Vpm(Oby>f5&6>5FsZMq-{yzk8 z**~!Sf_LuQ9V|%P;m3-~j!sNJbmHWRz+L%@yd&~`M^7C7J_rPBX}Q*Vt@rx%-iLWC z-r@h>*FXIL8gA4A{vc=oiiQ}WA^!{jHUMDs{bhmw_kuu;V8$k38}56<|ECG~-uBNu z0BHmPpeQ3006=~}G8ssMPjx!Dy9tX+)S)BZtOQOjzY4Z!!3Ho?gp6LHrq$spMIJ*y zzKT%90Q_AsR1^++3X<&zQP>%UOgZ>kYlL~M9UkSkLQ5Hs5hG(-Kd3)%0KfxAua3OV z%VH(QW z49PPAgbDkc<@N{iVb%Nl7y#e`0dfKA|7Hm%P0KEWqKhQWd`)|>@TwmQE`-B7Hlz9& zuF^gx1paN^(MVrA=O#_gK(;VTrnzRM$P3yo$1Pc`w|05wjarm2>2Qv zI5J)X|33dxL>by*B{T`C?dnz_BC;*8s~NfM$^~;@ysLBoijw}}y*z?v_|zFPs;WHX zD|a_dE~(v`nz(hirPwfFw$P7WP;TLhMHUeAfKuv2iFC@-BijgNjE6D66@w=|MqA_> z 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 Date: Sat, 3 May 2025 15:52:46 -0400 Subject: [PATCH 02/10] Implemented a wildcard matching function for filtering files in a directory. --- src/bin2cpp/CMakeLists.txt | 2 + src/bin2cpp/wildcard.cpp | 143 +++++++++++++++++++++++++ src/bin2cpp/wildcard.h | 75 +++++++++++++ test/bin2cpp_unittest/CMakeLists.txt | 11 +- test/bin2cpp_unittest/TestWildcard.cpp | 114 ++++++++++++++++++++ test/bin2cpp_unittest/TestWildcard.h | 37 +++++++ 6 files changed, 381 insertions(+), 1 deletion(-) create mode 100644 src/bin2cpp/wildcard.cpp create mode 100644 src/bin2cpp/wildcard.h create mode 100644 test/bin2cpp_unittest/TestWildcard.cpp create mode 100644 test/bin2cpp_unittest/TestWildcard.h diff --git a/src/bin2cpp/CMakeLists.txt b/src/bin2cpp/CMakeLists.txt index fffb214..324d89c 100644 --- a/src/bin2cpp/CMakeLists.txt +++ b/src/bin2cpp/CMakeLists.txt @@ -28,6 +28,8 @@ add_executable(bin2cpp SegmentGenerator.h StringGenerator.cpp StringGenerator.h + wildcard.cpp + wildcard.h Win32ResourceGenerator.cpp Win32ResourceGenerator.h ) diff --git a/src/bin2cpp/wildcard.cpp b/src/bin2cpp/wildcard.cpp new file mode 100644 index 0000000..4aba9bd --- /dev/null +++ b/src/bin2cpp/wildcard.cpp @@ -0,0 +1,143 @@ +/********************************************************************************** + * MIT License + * + * Copyright (c) 2018 Antoine Beauchamp + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + *********************************************************************************/ + +#include "wildcard.h" +#include "bin2cpp/version.h" + +namespace bin2cpp +{ + static inline bool isdigit(const char& c) + { + return '0' <= c && c <= '9'; + } + + ///The search index within the value string. + ///The search index within the pattern string. + bool wildcard_match_helper(const std::string& value, const std::string& pattern, size_t value_index, size_t pattern_index, std::vector& captures) + { + // Base case: value and pattern are both exhausted. All characrters matches. + if ( value_index == value.size() && pattern_index == pattern.size() ) + { + return true; + } + + // If pattern is exhausted but value has more characters, no match + if ( pattern_index == pattern.size() ) return false; + + // Handle '*': Capture a variable-length substring + if ( pattern[pattern_index] == '*' ) + { + for ( size_t i = value_index; i <= value.size(); ++i ) + { + captures.push_back(value.substr(value_index, i - value_index)); + + // Recurse to resolve for the remaining characters. + bool match = wildcard_match_helper(value, pattern, i, pattern_index + 1, captures); + if ( match ) return true; + captures.pop_back(); // Remove last match if unsuccessful + } + return false; + } + + // Handle '?': Capture a single character. + if ( value_index < value.size() && pattern[pattern_index] == '?' ) + { + captures.push_back(std::string(1, value[value_index])); // Store single-character match + + // Recurse to resolve for the remaining characters. + bool match = wildcard_match_helper(value, pattern, value_index + 1, pattern_index + 1, captures); + return match; + } + + // Handle '#': Capture any single digit (0-9) + if ( value_index < value.size() && pattern[pattern_index] == '#' && isdigit(value[value_index]) ) + { + captures.push_back(std::string(1, value[value_index])); + + // Recurse to resolve for the remaining characters. + bool match = wildcard_match_helper(value, pattern, value_index + 1, pattern_index + 1, captures); + return match; + } + + // Handling character lists like '[xyz]' or ranges like '[a-z]'. + // This assumes that first range character is smaller than second range character. + if ( pattern[pattern_index] == '[' ) + { + size_t closing_bracket_pos = pattern.find(']', pattern_index); + if ( closing_bracket_pos == std::string::npos ) return false; // Malformed pattern + + char matchChar = value[value_index]; + bool found = false; + + // For each characters in within the brackets + for ( size_t i = pattern_index + 1; i < closing_bracket_pos; ++i ) + { + // Is this a range? + if ( pattern[i] == '-' && i > pattern_index + 1 && i < closing_bracket_pos - 1 ) + { + // Handle range [x-y] + if ( matchChar >= pattern[i - 1] && matchChar <= pattern[i + 1] ) found = true; + } + else if ( pattern[i] == matchChar ) + { + found = true; + } + } + + // If match is found, capture it and continue recursion + if ( found ) + { + captures.push_back(std::string(1, matchChar)); + + // Recurse to resolve for the remaining characters. + bool match = wildcard_match_helper(value, pattern, value_index + 1, closing_bracket_pos + 1, captures); + return match; + } + else + { + return false; + } + } + + // Exact character match + if ( value_index < value.size() && pattern[pattern_index] == value[value_index] ) + { + // Recurse to resolve for the remaining characters. + bool match = wildcard_match_helper(value, pattern, value_index + 1, pattern_index + 1, captures); + return match; + } + + return false; + } + + bool wildcard_match(const std::string& value, const std::string& pattern, std::vector& captures) + { + captures.clear(); // Ensure captures vector is empty before starting + bool match = wildcard_match_helper(value, pattern, 0, 0, captures); + if ( !match ) + captures.clear(); + return match; + } + +}; //bin2cpp diff --git a/src/bin2cpp/wildcard.h b/src/bin2cpp/wildcard.h new file mode 100644 index 0000000..a8884b7 --- /dev/null +++ b/src/bin2cpp/wildcard.h @@ -0,0 +1,75 @@ +/********************************************************************************** + * MIT License + * + * Copyright (c) 2018 Antoine Beauchamp + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + *********************************************************************************/ + +#ifndef BIN2CPP_WILDCARD_H +#define BIN2CPP_WILDCARD_H + +#include +#include + +namespace bin2cpp +{ + + /// + ///Checks if a given value matches a pattern containing wildcard characters definition. + /// + /// + /// Supported Wildcards : + /// -'?' matches any single character. + /// -'*' matches zero or more characters. + /// -'#' matches any single digit(0 - 9). + /// -'[charlist]' matches any character in the provided set. + /// -'[a-z]', '[A-Z]', '[0-9]' match characters in respective ranges. + /// -'[a-zA-Z0-9]' matches any alphanumeric character. + ///< / remarks> + ///The file path, value or string to match. + ///The pattern containing wildcards. + ///The captured value of wildcard within the input value. The number of captures matches the number of wildcard in the pattern. + ///Returns true if the value matches the pattern, otherwise false. + bool wildcard_match(const std::string& value, const std::string& pattern, std::vector& captures); + + /// + ///Checks if a given value matches a pattern containing wildcard characters definition. + /// + /// + /// Supported Wildcards : + /// -'?' matches any single character. + /// -'*' matches zero or more characters. + /// -'#' matches any single digit(0 - 9). + /// -'[charlist]' matches any character in the provided set. + /// -'[a-z]', '[A-Z]', '[0-9]' match characters in respective ranges. + /// -'[a-zA-Z0-9]' matches any alphanumeric character. + ///< / remarks> + ///The file path, value or string to match. + ///The pattern containing wildcards. + ///Returns true if the value matches the pattern, otherwise false. + inline bool wildcard_match(const std::string& value, const std::string& pattern) + { + std::vector tmp_captures; + return wildcard_match(value, pattern, tmp_captures); + } + +}; //bin2cpp + +#endif //BIN2CPP_COMMON_H diff --git a/test/bin2cpp_unittest/CMakeLists.txt b/test/bin2cpp_unittest/CMakeLists.txt index 8c8ee58..3e6760c 100644 --- a/test/bin2cpp_unittest/CMakeLists.txt +++ b/test/bin2cpp_unittest/CMakeLists.txt @@ -228,13 +228,20 @@ add_custom_target(build_test_files ALL # Show all generated files in a common folder source_group("Generated Files" FILES ${GENERATED_TEST_FILES}) -source_group("External Files" FILES ${CMAKE_SOURCE_DIR}/src/bin2cpp/common.cpp ${CMAKE_SOURCE_DIR}/src/bin2cpp/common.h) +source_group("External Files" FILES + ${CMAKE_SOURCE_DIR}/src/bin2cpp/common.cpp + ${CMAKE_SOURCE_DIR}/src/bin2cpp/common.h + ${CMAKE_SOURCE_DIR}/src/bin2cpp/wildcard.cpp + ${CMAKE_SOURCE_DIR}/src/bin2cpp/wildcard.h +) add_executable(bin2cpp_unittest ${BIN2CPP_VERSION_HEADER} ${BIN2CPP_CONFIG_HEADER} ${CMAKE_SOURCE_DIR}/src/bin2cpp/common.cpp ${CMAKE_SOURCE_DIR}/src/bin2cpp/common.h + ${CMAKE_SOURCE_DIR}/src/bin2cpp/wildcard.cpp + ${CMAKE_SOURCE_DIR}/src/bin2cpp/wildcard.h application.cpp application.h CMakeLists.txt @@ -248,6 +255,8 @@ add_executable(bin2cpp_unittest TestCommon.h TestExtraction.cpp TestExtraction.h + TestWildcard.cpp + TestWildcard.h ${GENERATED_TEST_FILES} ) diff --git a/test/bin2cpp_unittest/TestWildcard.cpp b/test/bin2cpp_unittest/TestWildcard.cpp new file mode 100644 index 0000000..5e0cada --- /dev/null +++ b/test/bin2cpp_unittest/TestWildcard.cpp @@ -0,0 +1,114 @@ +/********************************************************************************** + * MIT License + * + * Copyright (c) 2018 Antoine Beauchamp + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + *********************************************************************************/ + +#include "TestWildcard.h" + +#include "wildcard.h" + +void TestWildcard::SetUp() +{ +} + +void TestWildcard::TearDown() +{ +} + +std::string to_boolean_str(bool value) +{ + return (value ? "true" : "false"); +} + +TEST_F(TestWildcard, testBasicExamples) +{ + struct TESTVALUE + { + const char* value; + const char* pattern; + bool expected_result; + std::vector expected_captures; + }; + static const TESTVALUE test_values[] = { + // ============================== matches ============================== + // ? + {"a", "?", true, {"a"}}, + {"abc", "???", true, {"a", "b", "c"}}, + {"kernel32.dll", "kernel??.dll", true, {"3", "2"}}, + {"kernel32.dll", "kernel32.???", true, {"d", "l", "l"}}, + {"kernel32.dll", "???nel32.dll", true, {"k", "e", "r"}}, + + // * + {"kernel32.dll", "*", true, {"kernel32.dll"}}, + {"kernel32.dll", "ker*.dll", true, {"nel32"}}, + {"kernel32.dll", "kernel32.*", true, {"dll"}}, + {"kernel32.dll", "*.dll", true, {"kernel32"}}, + + // empty '*' wildcard + {"kernel32.dll", "*kernel32.dll", true, {""}}, + {"kernel32.dll", "kernel32*.dll", true, {""}}, + {"kernel32.dll", "kernel32.dll*", true, {""}}, + + // # + {"kernel32.dll", "kernel##.dll", true, {"3", "2"}}, + + // [abc] + {"kernel32.dll", "[Kk]ernel32.dll", true, {"k"}}, + {"kernel32.dll", "kernel32.[dD][lL][lL]", true, {"d", "l", "l"}}, + {"kernel32.dll", "ke[r]nel32.dll", true, {"r"}}, + + // [ranges] + {"kernel32.dll", "kernel[0-9][0-9].dll", true, {"3", "2"}}, + {"kernel32.dll", "kernel32.[a-zA-Z0-9][a-zA-Z0-9][a-zA-Z0-9]", true, {"d", "l", "l"}}, + + // complex, multiple wildcard + {"kernel32.dll", "ker*.*", true, {"nel32", "dll"}}, + {"kernel32.dll", "*##.???", true, {"kernel", "3", "2", "d", "l", "l"}}, + {"aabbccdd", "*??*dd", true, {"", "a", "a", "bbcc"}}, + + // ============================== failing matches ============================== + {"kernel32.dll", "ker*.txt", false, {}}, + + // too many '?' character + {"kernel32.dll", "kernel32?.dll", false, {}}, + {"kernel32.dll", "kernel32.dll?", false, {}}, + {"kernel32.dll", "?kernel32.dll", false, {}}, + + {"kernel32.dll", "k##nel32.dll", false, {}}, + {"kernel32.dll", "[aA]ernel32.dll", false, {}}, + {"kernel32.dll", "k[0-9]ernel32.dll", false, {}}, + {"!", "[a-zA-Z0-9]", false, {}}, + }; + static const size_t num_test_values = sizeof(test_values) / sizeof(test_values[0]); + + for ( size_t i = 0; i < num_test_values; i++ ) + { + const TESTVALUE& t = test_values[i]; + + std::vector actual_captures; + bool actual_result = bin2cpp::wildcard_match(t.value, t.pattern, actual_captures); + + ASSERT_EQ(actual_result, t.expected_result) << "Test fail with test_values[" << i << "]. The match between value '" << t.value << "' and pattern '" << t.pattern << "' is supposed to return '" << to_boolean_str(t.expected_result) << "' but it actually retuned '" << to_boolean_str(actual_result) << "'."; + ASSERT_EQ(actual_captures, t.expected_captures) << "Test fail with test_values[" << i << "]. The match between value '" << t.value << "' and pattern '" << t.pattern << "' is has returned '" << to_boolean_str(t.expected_result) << "' but the expected captures does not match."; + } + +} diff --git a/test/bin2cpp_unittest/TestWildcard.h b/test/bin2cpp_unittest/TestWildcard.h new file mode 100644 index 0000000..ece6a3f --- /dev/null +++ b/test/bin2cpp_unittest/TestWildcard.h @@ -0,0 +1,37 @@ +/********************************************************************************** + * MIT License + * + * Copyright (c) 2018 Antoine Beauchamp + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + *********************************************************************************/ + +#ifndef TESTWILDCARD_H +#define TESTWILDCARD_H + +#include + +class TestWildcard : public ::testing::Test +{ +public: + virtual void SetUp(); + virtual void TearDown(); +}; + +#endif //TESTWILDCARD_H From 26113bdf13bfa4fd0c04882c56934d6a8aad8a0c Mon Sep 17 00:00:00 2001 From: Antoine Beauchamp Date: Sat, 3 May 2025 18:29:18 -0400 Subject: [PATCH 03/10] Creating a 'temp' directory under bin2cpp's build directory for quickly running command line samples from bin2cpp.samples.txt. --- CMakeLists.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 2a2d3fa..9370cd6 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -180,6 +180,9 @@ add_subdirectory(src/bin2cpp) # unit tests if(BIN2CPP_BUILD_TEST) + # Create a temp directory under the build directory to output temporary files. + make_directory(${CMAKE_CURRENT_BINARY_DIR}/temp) + add_subdirectory(test/testfilegenerator) add_subdirectory(test/bin2cpp_unittest) endif() From 5f29e1e0ff36f98d501c4964b80d2ec8221d6056 Mon Sep 17 00:00:00 2001 From: Antoine Beauchamp Date: Sun, 4 May 2025 07:57:45 -0400 Subject: [PATCH 04/10] Implemented wildcard_match_any() and wildcard_match_all() with tests. --- src/bin2cpp/wildcard.cpp | 28 +++++++++ src/bin2cpp/wildcard.h | 20 ++++++- test/bin2cpp_unittest/TestWildcard.cpp | 80 +++++++++++++++++++++++++- 3 files changed, 123 insertions(+), 5 deletions(-) diff --git a/src/bin2cpp/wildcard.cpp b/src/bin2cpp/wildcard.cpp index 4aba9bd..24e2056 100644 --- a/src/bin2cpp/wildcard.cpp +++ b/src/bin2cpp/wildcard.cpp @@ -140,4 +140,32 @@ namespace bin2cpp return match; } + bool wildcard_match_any(const std::string& value, const std::vector& patterns) + { + bool result = false; + std::vector captures; + for ( size_t i = 0; i < patterns.size(); i++ ) + { + const std::string& pattern = patterns[i]; + bool match = wildcard_match(value, pattern, captures); + if ( match ) + return true; + } + return result; + } + + bool wildcard_match_all(const std::string& value, const std::vector& patterns) + { + bool result = true; + std::vector captures; + for ( size_t i = 0; i < patterns.size(); i++ ) + { + const std::string& pattern = patterns[i]; + bool match = wildcard_match(value, pattern, captures); + if ( !match ) + return false; + } + return result; + } + }; //bin2cpp diff --git a/src/bin2cpp/wildcard.h b/src/bin2cpp/wildcard.h index a8884b7..1439178 100644 --- a/src/bin2cpp/wildcard.h +++ b/src/bin2cpp/wildcard.h @@ -42,7 +42,7 @@ namespace bin2cpp /// -'[charlist]' matches any character in the provided set. /// -'[a-z]', '[A-Z]', '[0-9]' match characters in respective ranges. /// -'[a-zA-Z0-9]' matches any alphanumeric character. - ///< / remarks> + /// ///The file path, value or string to match. ///The pattern containing wildcards. ///The captured value of wildcard within the input value. The number of captures matches the number of wildcard in the pattern. @@ -60,7 +60,7 @@ namespace bin2cpp /// -'[charlist]' matches any character in the provided set. /// -'[a-z]', '[A-Z]', '[0-9]' match characters in respective ranges. /// -'[a-zA-Z0-9]' matches any alphanumeric character. - ///< / remarks> + /// ///The file path, value or string to match. ///The pattern containing wildcards. ///Returns true if the value matches the pattern, otherwise false. @@ -70,6 +70,22 @@ namespace bin2cpp return wildcard_match(value, pattern, tmp_captures); } + /// + ///Checks if a given value matches at least one of the given patterns. + /// + ///The file path, value or string to match. + ///The list of patterns containing wildcards. + ///Returns true if the value matches the any pattern, otherwise false. + bool wildcard_match_any(const std::string& value, const std::vector& patterns); + + /// + ///Checks if a given value matches at of the given patterns. + /// + ///The file path, value or string to match. + ///The list of patterns containing wildcards. + ///Returns true if the value matches the any pattern, otherwise false. + bool wildcard_match_all(const std::string& value, const std::vector& patterns); + }; //bin2cpp #endif //BIN2CPP_COMMON_H diff --git a/test/bin2cpp_unittest/TestWildcard.cpp b/test/bin2cpp_unittest/TestWildcard.cpp index 5e0cada..95d5d95 100644 --- a/test/bin2cpp_unittest/TestWildcard.cpp +++ b/test/bin2cpp_unittest/TestWildcard.cpp @@ -41,14 +41,14 @@ std::string to_boolean_str(bool value) TEST_F(TestWildcard, testBasicExamples) { - struct TESTVALUE + struct TEST_HELPER { const char* value; const char* pattern; bool expected_result; std::vector expected_captures; }; - static const TESTVALUE test_values[] = { + static const TEST_HELPER test_values[] = { // ============================== matches ============================== // ? {"a", "?", true, {"a"}}, @@ -102,7 +102,7 @@ TEST_F(TestWildcard, testBasicExamples) for ( size_t i = 0; i < num_test_values; i++ ) { - const TESTVALUE& t = test_values[i]; + const TEST_HELPER& t = test_values[i]; std::vector actual_captures; bool actual_result = bin2cpp::wildcard_match(t.value, t.pattern, actual_captures); @@ -110,5 +110,79 @@ TEST_F(TestWildcard, testBasicExamples) ASSERT_EQ(actual_result, t.expected_result) << "Test fail with test_values[" << i << "]. The match between value '" << t.value << "' and pattern '" << t.pattern << "' is supposed to return '" << to_boolean_str(t.expected_result) << "' but it actually retuned '" << to_boolean_str(actual_result) << "'."; ASSERT_EQ(actual_captures, t.expected_captures) << "Test fail with test_values[" << i << "]. The match between value '" << t.value << "' and pattern '" << t.pattern << "' is has returned '" << to_boolean_str(t.expected_result) << "' but the expected captures does not match."; } +} + +TEST_F(TestWildcard, testWildcardMatchAny) +{ + const std::string path = "path/to/a/file.jpg"; + std::vector patterns; + + { + // test for no matches + patterns.clear(); + patterns.push_back("idonotmatch"); + patterns.push_back("idonotmatcheither"); + + ASSERT_FALSE(bin2cpp::wildcard_match_any(path, patterns)); + } + + { + // test matches from first element + patterns.clear(); + patterns.push_back("*.jpg"); + patterns.push_back("idonotmatch"); + + ASSERT_TRUE(bin2cpp::wildcard_match_any(path, patterns)); + } + + { + // test matches from second element + patterns.clear(); + patterns.push_back("idonotmatch"); + patterns.push_back("*.jpg"); + ASSERT_TRUE(bin2cpp::wildcard_match_any(path, patterns)); + } } + +TEST_F(TestWildcard, testWildcardMatchAll) +{ + const std::string path = "path/to/a/file.jpg"; + std::vector patterns; + + { + // test for no matches + patterns.clear(); + patterns.push_back("idonotmatch"); + patterns.push_back("idonotmatcheither"); + + ASSERT_FALSE(bin2cpp::wildcard_match_all(path, patterns)); + } + + { + // test matches from first element + patterns.clear(); + patterns.push_back("*.jpg"); + patterns.push_back("idonotmatch"); + + ASSERT_FALSE(bin2cpp::wildcard_match_all(path, patterns)); + } + + { + // test matches from second element + patterns.clear(); + patterns.push_back("idonotmatch"); + patterns.push_back("*.jpg"); + + ASSERT_FALSE(bin2cpp::wildcard_match_all(path, patterns)); + } + + { + // test matches from all elements + patterns.clear(); + patterns.push_back("*/to/*"); + patterns.push_back("*.jpg"); + + ASSERT_TRUE(bin2cpp::wildcard_match_all(path, patterns)); + } +} \ No newline at end of file From 18c79a2816972683d7a337f7621b0b3e1412298c Mon Sep 17 00:00:00 2001 From: Antoine Beauchamp Date: Sun, 4 May 2025 07:59:14 -0400 Subject: [PATCH 05/10] Implemented strSplit() and strJoin() with tests. --- src/bin2cpp/common.cpp | 42 +++++++++++++++++++++++ src/bin2cpp/common.h | 17 ++++++++++ test/bin2cpp_unittest/TestCommon.cpp | 50 ++++++++++++++++++++++++++++ 3 files changed, 109 insertions(+) diff --git a/src/bin2cpp/common.cpp b/src/bin2cpp/common.cpp index f8f8a24..a94f6fd 100755 --- a/src/bin2cpp/common.cpp +++ b/src/bin2cpp/common.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #include "rapidassist/strings.h" #include "rapidassist/filesystem.h" @@ -322,4 +323,45 @@ namespace bin2cpp return tmp; } + void strSplit(const std::string& value, char separator, std::vector& values) + { + values.clear(); + size_t start = 0; + size_t end = std::string::npos; + size_t length = 0; + + // find first separator + end = value.find(separator, start); + while ( end != std::string::npos ) + { + length = end - start; + std::string item = value.substr(start, length); + values.push_back(item); + + // find next separator + start = end + 1; + end = value.find(separator, start); + } + + // Capture last token + values.push_back(value.substr(start)); + } + + std::string strJoin(const std::vector& values, char separator) + { + std::string output; + + for ( size_t i = 0; i < values.size(); i++ ) + { + const std::string& element = values[i]; + output += element; + + bool is_last = (i == (values.size() - 1)); + if ( !is_last ) + output.append(1, separator); + } + + return output; + } + }; //bin2cpp diff --git a/src/bin2cpp/common.h b/src/bin2cpp/common.h index ce1164e..cec0925 100644 --- a/src/bin2cpp/common.h +++ b/src/bin2cpp/common.h @@ -29,6 +29,7 @@ #include #include #include +#include namespace bin2cpp { @@ -124,6 +125,22 @@ namespace bin2cpp ///Returns the path matching all given components. std::string pathJoin(const std::string & directory, const std::string & file_name, const std::string & file_extension); + /// + ///Split a string into a list of values based on a specified separator character. + /// + ///The input string that contains values to split. + ///The character that separate the values in the string. + ///The output individual values in the input string. + void strSplit(const std::string & value, char separator, std::vector & values); + + /// + ///"Combine a list of values into a single string, using a specified separator character. + /// + ///The list of values to join to a single string. + ///The character that separate the values in the string. + ///Returns the combined string. + std::string strJoin(const std::vector& values, char separator); + }; //bin2cpp #endif //BIN2CPP_COMMON_H diff --git a/test/bin2cpp_unittest/TestCommon.cpp b/test/bin2cpp_unittest/TestCommon.cpp index e6845a1..d4cc984 100644 --- a/test/bin2cpp_unittest/TestCommon.cpp +++ b/test/bin2cpp_unittest/TestCommon.cpp @@ -253,3 +253,53 @@ TEST_F(TestCommon, testPathSplitJoin) "file_ext=\"" << file_ext << "\"\n"; } } + +TEST_F(TestCommon, testStringSplitJoin) +{ + struct TEST_HELPER + { + char separator; + std::string value; + std::vector expected_values; + }; + static const char SEPARATOR = ':'; + + // arrange + static const TEST_HELPER test_values[] = { + {SEPARATOR, "", {""}}, // empty string + {SEPARATOR, "abc", {"abc"}}, // no separator + {SEPARATOR, "a:b:c", {"a", "b", "c"}}, // multiple + {SEPARATOR, ":b:c", {"", "b", "c"}}, // start with separator + {SEPARATOR, "a:b:", {"a", "b", ""}}, // end with separator + {SEPARATOR, "a::", {"a", "", ""}}, // 2 consecutive separators + {SEPARATOR, "::", {"", "", ""}}, // only consecutive separators + }; + static const size_t num_test_values = sizeof(test_values) / sizeof(test_values[0]); + + // act (strSplit) + for ( size_t i = 0; i < num_test_values; i++ ) + { + const TEST_HELPER& t = test_values[i]; + + std::vector actual_values; + bin2cpp::strSplit(t.value, t.separator, actual_values); + + // assert + ASSERT_EQ(actual_values, t.expected_values) << "Test fail for strSplit() with test_values[" << i << "]. Splitting value `" << t.value << "` with separator '" << t.separator << "' should result in " << t.expected_values.size() << " elements."; + } + + // act (strJoin) + for ( size_t i = 0; i < num_test_values; i++ ) + { + const TEST_HELPER& t = test_values[i]; + + std::vector tmp_values; + bin2cpp::strSplit(t.value, t.separator, tmp_values); + + std::string expected_value = t.value; + std::string actual_value = bin2cpp::strJoin(tmp_values, t.separator); + + // assert + ASSERT_EQ(actual_value, expected_value) << "Test fail for strJoin() with test_values[" << i << "]. Joining values with separator '" << t.separator << "' should result in `" << expected_value << "` but we got `" << actual_value << "`."; + } +} From 4d9aabca3f3596204cc4c434ddbb0f4425659e00 Mon Sep 17 00:00:00 2001 From: Antoine Beauchamp Date: Sun, 4 May 2025 08:45:03 -0400 Subject: [PATCH 06/10] Implemented new command line options `--dirincludefilter` and `--direxcludefilter` with tests. #29 --- src/bin2cpp/Context.cpp | 9 + src/bin2cpp/Context.h | 5 + src/bin2cpp/bin2cpp.samples.txt | 10 + src/bin2cpp/main.cpp | 119 +++++++--- test/bin2cpp_unittest/TestCLI.cpp | 223 ++++++++++++++++++ .../generate_test_files.bat.in | 16 ++ .../generate_test_files.sh.in | 16 ++ 7 files changed, 368 insertions(+), 30 deletions(-) diff --git a/src/bin2cpp/Context.cpp b/src/bin2cpp/Context.cpp index ba8f69e..fcd5e6a 100644 --- a/src/bin2cpp/Context.cpp +++ b/src/bin2cpp/Context.cpp @@ -49,6 +49,8 @@ namespace bin2cpp this->hasOutputDir = other.hasOutputDir ; this->hasReportedFilePath = other.hasReportedFilePath ; this->hasManagerFile = other.hasManagerFile ; + this->hasDirectoryIncludeFilters = other.hasDirectoryIncludeFilters ; + this->hasDirectoryExcludeFilters = other.hasDirectoryExcludeFilters ; this->keepDirectoryStructure = other.keepDirectoryStructure ; this->overrideExistingFiles = other.overrideExistingFiles ; this->registerFiles = other.registerFiles ; @@ -65,6 +67,8 @@ namespace bin2cpp this->managerHeaderFilename = other.managerHeaderFilename ; this->cppEncoder = other.cppEncoder ; this->generatorName = other.generatorName ; + this->directoryIncludeFilters = other.directoryIncludeFilters ; + this->directoryExcludeFilters = other.directoryExcludeFilters ; } return *this; } @@ -80,6 +84,8 @@ namespace bin2cpp overrideExistingFiles = false; registerFiles = false; plainOutput = false; + hasDirectoryIncludeFilters = false; + hasDirectoryExcludeFilters = false; inputFilePath.clear(); inputDirPath.clear(); @@ -93,8 +99,11 @@ namespace bin2cpp managerHeaderFilename.clear(); cppEncoder = CppEncoderEnum::CPP_ENCODER_HEX; generatorName.clear(); + directoryIncludeFilters.clear(); + directoryExcludeFilters.clear(); } + //------------------------------- //protected methods //------------------------------- diff --git a/src/bin2cpp/Context.h b/src/bin2cpp/Context.h index d17ac1b..315d32f 100644 --- a/src/bin2cpp/Context.h +++ b/src/bin2cpp/Context.h @@ -26,6 +26,7 @@ #define CONTEXT_H #include +#include #include "enums.h" namespace bin2cpp @@ -53,6 +54,8 @@ namespace bin2cpp bool overrideExistingFiles; bool registerFiles; bool plainOutput; + bool hasDirectoryIncludeFilters; + bool hasDirectoryExcludeFilters; // public attributes std::string inputFilePath; // The path of the input file (resource) to embeded as C++ source code. @@ -67,6 +70,8 @@ namespace bin2cpp std::string managerHeaderFilename; CppEncoderEnum cppEncoder; std::string generatorName; + std::vector directoryIncludeFilters; + std::vector directoryExcludeFilters; void reset(); diff --git a/src/bin2cpp/bin2cpp.samples.txt b/src/bin2cpp/bin2cpp.samples.txt index e912fdc..88c7520 100644 --- a/src/bin2cpp/bin2cpp.samples.txt +++ b/src/bin2cpp/bin2cpp.samples.txt @@ -17,6 +17,16 @@ --dir=..\..\test\bin2cpp_unittest\generated_files\testIssue56b\input_files\www --output=..\..\test\bin2cpp_unittest\generated_files\testIssue56b\generated_sources --namespace=issue56b --override --dir=..\..\test\bin2cpp_unittest\generated_files\testKeepDirectories\input_files\www --output=..\..\test\bin2cpp_unittest\generated_files\testKeepDirectories\generated_sources --override --keepdirs +--dir=..\..\..\samples\demo_website\www --output=..\..\temp --managerfile=PagesFileManager.h --namespace=myblog --keepdirs +--dir=..\..\..\samples\demo_website\www --output=..\..\temp --dirincludefilter="*\static\*.css:*.jpg" +--dir=..\..\..\samples\demo_website\www --output=..\..\temp --dirincludefilter="*\static\*.css:*.jpg" --direxcludefilter="*\light-mode.css" +--dir=..\..\..\samples\demo_website\www --output=..\..\temp --direxcludefilter="*.html" + +Bug: +--dir=..\..\..\samples\demo_website\www --output=..\..\temp --dirincludefilter="*.old" +--file=..\..\..\samples\demo_website\www\index.html.old --output=..\..\temp + + --file=D:\Temp\bin2cpp\issue51\input_files\IMG_0001.jpg --output=D:\Temp\bin2cpp\issue51\generated_sources --headerfile="IMG_0001.h" --identifier=testIssue51 --namespace=testIssue51 --managerfile=FileManager51.h --override --reportedfilepath=foo\bar\IMG_0001.h --dir=D:\Temp\bin2cpp\testIssue56b\input_files\www --output=D:\Temp\bin2cpp\testIssue56b\generated_sources --namespace=testIssue51 --managerfile=FileManager51.h --override diff --git a/src/bin2cpp/main.cpp b/src/bin2cpp/main.cpp index 53e53c4..e81a1eb 100755 --- a/src/bin2cpp/main.cpp +++ b/src/bin2cpp/main.cpp @@ -44,6 +44,7 @@ #include "rapidassist/timing.h" #include "common.h" +#include "wildcard.h" using namespace bin2cpp; @@ -74,6 +75,8 @@ static const char * DEFAULT_BASECLASSNAME = "File"; static const CppEncoderEnum DEFAULT_ENCODING = CPP_ENCODER_OCT; static Dictionary identifiers_dictionary; // unique values for identifiers static Dictionary output_files_dictionary; // unique values for output file names +#define DIRECTORY_FILTER_SEPARATOR_STR ":" +static const char DIRECTORY_FILTER_SEPARATOR = DIRECTORY_FILTER_SEPARATOR_STR[0]; const char * getErrorCodeDescription(const APP_ERROR_CODES & error_code) { @@ -159,40 +162,61 @@ void printUsage() #endif //usage string in docopt format. See http://docopt.org/ - static const char usage[] = + static const char usage[] = "Usage:\n" " bin2cpp --file= --output= [--headerfile=] [--identifier=] [--generator=] [--encoding=] [--chunksize=] [--namespace=] [--baseclass=] [--managerfile=] [--registerfile] [--reportedfilepath=] [--override] [--noheader] [--quiet]\n" - " bin2cpp --dir= --output= [--keepdirs] [--generator=] [--encoding=] [--chunksize=] [--namespace=] [--baseclass=] [--managerfile=] [--registerfile] [--override] [--noheader] [--quiet]\n" + " bin2cpp --dir= --output= [--keepdirs] [--generator=] [--encoding=] [--chunksize=] [--namespace=] [--baseclass=] [--managerfile=] [--registerfile] [--dirincludefilter=] [--direxcludefilter=] [--override] [--noheader] [--quiet]\n" " bin2cpp --help\n" " bin2cpp --version\n" "\n" "Options:\n" - " --help Display this help message.\n" - " --version Display this application version.\n" - " --file= Path of the input file used for embedding as C++ source code.\n" - " --dir= Path of the input directory used for embedding all files of the directory as C++ source code.\n" - " --output= Path of the output directory where to create generated code. ie: ." SEPARATOR "generated_files\n" - " --headerfile= File name or relative path of the generated C++ header file. If a relative path from the output directory is specified,\n" - " the #include statement in the generated cpp file will match the relative path. ie: SplashScreen.h\n" - " Default value: input file name (without extension)\n" - " --identifier= Identifier of the function name that is used to get an instance of the file. ie: SplashScreen\n" - " Default value is based on input file with format 'NameExt'.\n" - " --generator= Name of the generator to use. Possible values are 'segment', 'string', 'array' and 'win32'. [default: segment]\n" - " --encoding= Name of the binary to string literal encoding to use. Possible values are 'oct' and 'hex'. [default: oct]\n" - " --chunksize= Size in bytes of each string segments (bytes per LoC). [default: 200]\n" - " --baseclass= The name of the interface for embedded files. [default: File]\n" - " --namespace= The namespace of the generated source code. [default: bin2cpp]\n" - " --reportedfilepath= The relative reported path of the File. Path returned when calling method getFilePath() of the File class. ie: images" SEPARATOR "DCIM" SEPARATOR "IMG_0001.jpg.\n" - " Automatically calculated when --dir mode is used.\n" - " --managerfile= File name or relative path of the generated C++ header file for the FileManager class. ie: FileManager.h\n" - " --registerfile Register the generated file to the FileManager class.\n" - " This flags is automatically set when parameter 'managerfile' is specified.\n" - " --keepdirs Keep the directory structure. Forces the output files to have the same\n" - " directory structure as the input files. Valid only when --dir is used.\n" - " --plainoutput Print the encoded string in plain format to stdout. Useful for scripts and integration with third party application.\n" - " --override Tells bin2cpp to overwrite the destination files.\n" - " --noheader Do not print program header to standard output.\n" - " --quiet Do not log any message to standard output.\n" + " --help Display this help message.\n" + " --version Display this application version.\n" + " --file= Path of the input file used for embedding as C++ source code.\n" + " --dir= Path of the input directory used for embedding all files of the directory as C++ source code.\n" + " --output= Path of the output directory where to create generated code. ie: ." SEPARATOR "generated_files\n" + " --headerfile= File name or relative path of the generated C++ header file. If a relative path from the output directory is specified,\n" + " the #include statement in the generated cpp file will match the relative path. ie: SplashScreen.h\n" + " Default value: input file name (without extension)\n" + " --identifier= Identifier of the function name that is used to get an instance of the file. ie: SplashScreen\n" + " Default value is based on input file with format 'NameExt'.\n" + " --generator= Name of the generator to use. Possible values are 'segment', 'string', 'array' and 'win32'. [default: segment]\n" + " --encoding= Name of the binary to string literal encoding to use. Possible values are 'oct' and 'hex'. [default: oct]\n" + " --chunksize= Size in bytes of each string segments (bytes per LoC). [default: 200]\n" + " --baseclass= The name of the interface for embedded files. [default: File]\n" + " --namespace= The namespace of the generated source code. [default: bin2cpp]\n" + " --reportedfilepath= The relative reported path of the File. Path returned when calling method getFilePath() of the File class. ie: images" SEPARATOR "DCIM" SEPARATOR "IMG_0001.jpg.\n" + " Automatically calculated when --dir mode is used.\n" + " --managerfile= File name or relative path of the generated C++ header file for the FileManager class. ie: FileManager.h\n" + " --registerfile Register the generated file to the FileManager class.\n" + " This flags is automatically set when parameter 'managerfile' is specified.\n" + " --dirincludefilter= Set a positive filter on the input directory to only select files matching the filter. Wildcard characters are accepted.\n" + " Separate each filter with the character '" DIRECTORY_FILTER_SEPARATOR_STR "'. Valid only when --dir is used. See wildcard characters definition below.\n" + " --direxcludefilter= Set a negative filter on the input directory to skip files matching the filter. Wildcard characters are accepted.\n" + " Separate each filter with the character '" DIRECTORY_FILTER_SEPARATOR_STR "'. Valid only when --dir is used. See wildcard characters definition below.\n" + " The exclude filter has precedence over the inclusion filter.\n" + " --keepdirs Keep the directory structure. Forces the output files to have the same\n" + " directory structure as the input files. Valid only when --dir is used.\n" + " --plainoutput Print the encoded string in plain format to stdout. Useful for scripts and integration with third party application.\n" + " --override Tells bin2cpp to overwrite the destination files.\n" + " --noheader Do not print program header to standard output.\n" + " --quiet Do not log any message to standard output.\n" + "\n" + " Wildcard characters:\n" + " '?' Matches any single character.\n" + " '*' Matches zero or more characters.\n" + " '#' Matches exactly one numeric digit (0-9).\n" + " [charlist] Matches any single character inside the brackets.\n" + " [a-z] Matches any single lowercase letter between 'a' and 'z'.\n" + " [A-Z] Matches any single uppercase letter between 'A' and 'A'.\n" + " [0-9] Matches any single digit between '0' and '9'.\n" + " [a-zA-Z0-9] Matches any single letter (uppercase or lowercase) or digit.\n" + "\n" + " For example:\n" + " 'ker*##.???' would match files that starts with 'ker', and ends with 2 digits, a dot and then 3 characters." + " --dir-include-filter=\"*.jpg:*.png\" would include all files whose file extension is 'jpg' or 'png'.\n" + " --dir-exclude-filter=\"*.bak\" would exclude all backup files.\n" + " --dir-include-filter=\"*.log\" --dir-exclude-filter=\"debug.log\" would include all log files but not the one named 'debug.log'." "\n"; printf("%s", usage); } @@ -352,6 +376,21 @@ int main(int argc, char* argv[]) c.registerFiles = ra::cli::ParseArgument("registerfile", dummy, argc, argv); + // directory include filters + std::string filter; + c.hasDirectoryIncludeFilters = ra::cli::ParseArgument("dirincludefilter", filter, argc, argv); + if ( c.hasDirectoryIncludeFilters ) + { + strSplit(filter, DIRECTORY_FILTER_SEPARATOR, c.directoryIncludeFilters); + } + + // directory exclude filters + c.hasDirectoryExcludeFilters = ra::cli::ParseArgument("direxcludefilter", filter, argc, argv); + if ( c.hasDirectoryExcludeFilters ) + { + strSplit(filter, DIRECTORY_FILTER_SEPARATOR, c.directoryExcludeFilters); + } + //force registerfile if managerfile is specified if (c.hasManagerFile) { @@ -599,8 +638,28 @@ APP_ERROR_CODES processInputDirectory(const Context& c, bin2cpp::IGenerator * ge for(size_t i=0; i"); + cmdline.append(outputFilePath.c_str()); + +#if defined(__linux__) || defined(__APPLE__) + //fix path separator + ra::strings::Replace(cmdline, "\\", "/"); +#endif + + //build the list of generated files + const char* generated_files_tmp[] = { + "generated_files\\testDir02\\" THIS_TEST_CASE_NAME "\\IMG_0001.h" , + "generated_files\\testDir02\\" THIS_TEST_CASE_NAME "\\IMG_0002.h" , + "generated_files\\testDir02\\" THIS_TEST_CASE_NAME "\\IMG_0003.h" , + "generated_files\\testDir02\\" THIS_TEST_CASE_NAME "\\IMG_0007.h" , + "generated_files\\testDir02\\" THIS_TEST_CASE_NAME "\\IMG_0008.h" , + "generated_files\\testDir02\\" THIS_TEST_CASE_NAME "\\IMG_0001.cpp", + "generated_files\\testDir02\\" THIS_TEST_CASE_NAME "\\IMG_0002.cpp", + "generated_files\\testDir02\\" THIS_TEST_CASE_NAME "\\IMG_0003.cpp", + "generated_files\\testDir02\\" THIS_TEST_CASE_NAME "\\IMG_0007.cpp", + "generated_files\\testDir02\\" THIS_TEST_CASE_NAME "\\IMG_0008.cpp", + }; + const size_t num_generated_files = sizeof(generated_files_tmp) / sizeof(generated_files_tmp[0]); + ra::strings::StringVector generated_files; + for ( size_t i = 0; i < num_generated_files; i++ ) + { + generated_files.push_back(generated_files_tmp[i]); +#if defined(__linux__) || defined(__APPLE__) + ra::strings::Replace(generated_files[i], "\\", "/"); //fix path separator +#endif + } + + //delete generated files + for ( size_t i = 0; i < generated_files.size(); i++ ) + { + const std::string& generated_file = generated_files[i]; + ASSERT_TRUE(deleteFile(generated_file.c_str())); + } + + //run the command + int returnCode = system(cmdline.c_str()); +#if defined(__linux__) || defined(__APPLE__) + returnCode = WEXITSTATUS(returnCode); +#endif + ASSERT_EQ(0, returnCode) << "The command line '" << cmdline.c_str() << "' returned " << returnCode; + + //assert that expected files were generated + for ( size_t i = 0; i < generated_files.size(); i++ ) + { + const std::string& generates_file = generated_files[i]; + ASSERT_TRUE(ra::filesystem::FileExists(generates_file.c_str())) << "File not found: '" << generates_file.c_str() << "'."; + } + + //cleanup + ASSERT_TRUE(deleteFile(outputFilePath.c_str())); + +#undef THIS_TEST_CASE_NAME +} + +TEST_F(TestCLI, testDirExcludeFilter) +{ + #define THIS_TEST_CASE_NAME "testDirExcludeFilter" + static const std::string expectedFilePath = getExpectedFilePath(); + static const std::string outputFilePath = getActualFilePath(); + + std::string headerFileName = std::string("_") + ra::testing::GetTestCaseName().c_str() + ".h"; + std::string headerFilePath = gGeneratedFilesDir + headerFileName; + std::string cppFilePath = headerFilePath; ra::strings::Replace(cppFilePath, ".h", ".cpp"); + + //build command line + std::string cmdline; + cmdline.append(getBin2cppPath()); + cmdline.append(" --dir=generated_files\\testDir02\\images"); //use windows path separator + cmdline.append(" --output=generated_files\\testDir02\\" THIS_TEST_CASE_NAME); + cmdline.append(" --direxcludefilter=\"*.jpg:*IMG_000[67]*\""); + cmdline.append(" --noheader"); + + cmdline.append(" >"); + cmdline.append(outputFilePath.c_str()); + +#if defined(__linux__) || defined(__APPLE__) + //fix path separator + ra::strings::Replace(cmdline, "\\", "/"); +#endif + + //build the list of generated files + const char* generated_files_tmp[] = { + "generated_files\\testDir02\\" THIS_TEST_CASE_NAME "\\IMG_0004.h" , + "generated_files\\testDir02\\" THIS_TEST_CASE_NAME "\\IMG_0005.h" , + "generated_files\\testDir02\\" THIS_TEST_CASE_NAME "\\IMG_0008.h" , + "generated_files\\testDir02\\" THIS_TEST_CASE_NAME "\\IMG_0004.cpp", + "generated_files\\testDir02\\" THIS_TEST_CASE_NAME "\\IMG_0005.cpp", + "generated_files\\testDir02\\" THIS_TEST_CASE_NAME "\\IMG_0008.cpp", + }; + const size_t num_generated_files = sizeof(generated_files_tmp) / sizeof(generated_files_tmp[0]); + ra::strings::StringVector generated_files; + for ( size_t i = 0; i < num_generated_files; i++ ) + { + generated_files.push_back(generated_files_tmp[i]); +#if defined(__linux__) || defined(__APPLE__) + ra::strings::Replace(generated_files[i], "\\", "/"); //fix path separator +#endif + } + + //delete generated files + for ( size_t i = 0; i < generated_files.size(); i++ ) + { + const std::string& generated_file = generated_files[i]; + ASSERT_TRUE(deleteFile(generated_file.c_str())); + } + + //run the command + int returnCode = system(cmdline.c_str()); +#if defined(__linux__) || defined(__APPLE__) + returnCode = WEXITSTATUS(returnCode); +#endif + ASSERT_EQ(0, returnCode) << "The command line '" << cmdline.c_str() << "' returned " << returnCode; + + //assert that expected files were generated + for ( size_t i = 0; i < generated_files.size(); i++ ) + { + const std::string& generates_file = generated_files[i]; + ASSERT_TRUE(ra::filesystem::FileExists(generates_file.c_str())) << "File not found: '" << generates_file.c_str() << "'."; + } + + //cleanup + ASSERT_TRUE(deleteFile(outputFilePath.c_str())); + +#undef THIS_TEST_CASE_NAME +} + +TEST_F(TestCLI, testDirMultipleFilter) +{ + #define THIS_TEST_CASE_NAME "testDirMultipleFilter" + static const std::string expectedFilePath = getExpectedFilePath(); + static const std::string outputFilePath = getActualFilePath(); + + std::string headerFileName = std::string("_") + ra::testing::GetTestCaseName().c_str() + ".h"; + std::string headerFilePath = gGeneratedFilesDir + headerFileName; + std::string cppFilePath = headerFilePath; ra::strings::Replace(cppFilePath, ".h", ".cpp"); + + //build command line + std::string cmdline; + cmdline.append(getBin2cppPath()); + cmdline.append(" --dir=generated_files\\testDir02\\images"); //use windows path separator + cmdline.append(" --output=generated_files\\testDir02\\" THIS_TEST_CASE_NAME); + cmdline.append(" --dirincludefilter=\"*.jpg:*IMG_000[678].*\""); // includes 1.jpg, 2.jpg, 3.jpg, 6.png, 7.svg 8.svg + cmdline.append(" --direxcludefilter=\"*01*:*07*\""); // excludes 1.jpg and 7.svg + cmdline.append(" --noheader"); + + cmdline.append(" >"); + cmdline.append(outputFilePath.c_str()); + +#if defined(__linux__) || defined(__APPLE__) + //fix path separator + ra::strings::Replace(cmdline, "\\", "/"); +#endif + + //build the list of generated files + const char* generated_files_tmp[] = { + "generated_files\\testDir02\\" THIS_TEST_CASE_NAME "\\IMG_0002.h" , + "generated_files\\testDir02\\" THIS_TEST_CASE_NAME "\\IMG_0003.h" , + "generated_files\\testDir02\\" THIS_TEST_CASE_NAME "\\IMG_0006.h" , + "generated_files\\testDir02\\" THIS_TEST_CASE_NAME "\\IMG_0008.h" , + "generated_files\\testDir02\\" THIS_TEST_CASE_NAME "\\IMG_0002.cpp", + "generated_files\\testDir02\\" THIS_TEST_CASE_NAME "\\IMG_0003.cpp", + "generated_files\\testDir02\\" THIS_TEST_CASE_NAME "\\IMG_0006.cpp", + "generated_files\\testDir02\\" THIS_TEST_CASE_NAME "\\IMG_0008.cpp", + }; + const size_t num_generated_files = sizeof(generated_files_tmp) / sizeof(generated_files_tmp[0]); + ra::strings::StringVector generated_files; + for ( size_t i = 0; i < num_generated_files; i++ ) + { + generated_files.push_back(generated_files_tmp[i]); +#if defined(__linux__) || defined(__APPLE__) + ra::strings::Replace(generated_files[i], "\\", "/"); //fix path separator +#endif + } + + //delete generated files + for ( size_t i = 0; i < generated_files.size(); i++ ) + { + const std::string& generated_file = generated_files[i]; + ASSERT_TRUE(deleteFile(generated_file.c_str())); + } + + //run the command + int returnCode = system(cmdline.c_str()); +#if defined(__linux__) || defined(__APPLE__) + returnCode = WEXITSTATUS(returnCode); +#endif + ASSERT_EQ(0, returnCode) << "The command line '" << cmdline.c_str() << "' returned " << returnCode; + + //assert that expected files were generated + for ( size_t i = 0; i < generated_files.size(); i++ ) + { + const std::string& generates_file = generated_files[i]; + ASSERT_TRUE(ra::filesystem::FileExists(generates_file.c_str())) << "File not found: '" << generates_file.c_str() << "'."; + } + + //cleanup + ASSERT_TRUE(deleteFile(outputFilePath.c_str())); + +#undef THIS_TEST_CASE_NAME +} + TEST_F(TestCLI, testErrorMissingArgumentFileOrDir) { static const std::string expectedFilePath = getExpectedFilePath(); diff --git a/test/bin2cpp_unittest/generate_test_files.bat.in b/test/bin2cpp_unittest/generate_test_files.bat.in index 31dc378..d30366f 100644 --- a/test/bin2cpp_unittest/generate_test_files.bat.in +++ b/test/bin2cpp_unittest/generate_test_files.bat.in @@ -222,6 +222,22 @@ if %errorlevel% neq 0 exit /b %errorlevel% @BIN2CPP_TARGET_FILE@ --noheader --file=%OUTDIR%\images\IMG_0005.jpg --output=%OUTDIR%\sources --headerfile=_img0005.h --identifier=Img0005 --chunksize=200 --override if %errorlevel% neq 0 exit /b %errorlevel% +set TEST_NAME=testDir02 +set OUTDIR=.\generated_files\%TEST_NAME% +mkdir %OUTDIR% 1>NUL 2>NUL +mkdir %OUTDIR%\images 1>NUL 2>NUL +mkdir %OUTDIR%\testDirIncludeFilter 1>NUL 2>NUL +mkdir %OUTDIR%\testDirExcludeFilter 1>NUL 2>NUL +mkdir %OUTDIR%\testDirMultipleFilter 1>NUL 2>NUL +@TESTFILEGENERATOR_TARGET_FILE@ --file=%OUTDIR%\images\IMG_0001.jpg --size=1010 --fill=random --seed=1 +@TESTFILEGENERATOR_TARGET_FILE@ --file=%OUTDIR%\images\IMG_0002.jpg --size=1020 --fill=random --seed=2 +@TESTFILEGENERATOR_TARGET_FILE@ --file=%OUTDIR%\images\IMG_0003.jpg --size=1030 --fill=random --seed=3 +@TESTFILEGENERATOR_TARGET_FILE@ --file=%OUTDIR%\images\IMG_0004.png --size=1040 --fill=random --seed=4 +@TESTFILEGENERATOR_TARGET_FILE@ --file=%OUTDIR%\images\IMG_0005.png --size=1050 --fill=random --seed=5 +@TESTFILEGENERATOR_TARGET_FILE@ --file=%OUTDIR%\images\IMG_0006.png --size=1060 --fill=random --seed=6 +@TESTFILEGENERATOR_TARGET_FILE@ --file=%OUTDIR%\images\IMG_0007.svg --size=0170 --fill=html --seed=7 +@TESTFILEGENERATOR_TARGET_FILE@ --file=%OUTDIR%\images\IMG_0008.svg --size=0180 --fill=html --seed=8 + set TEST_NAME=testIssue47 set OUTDIR=.\generated_files\%TEST_NAME% mkdir %OUTDIR% 1>NUL 2>NUL diff --git a/test/bin2cpp_unittest/generate_test_files.sh.in b/test/bin2cpp_unittest/generate_test_files.sh.in index dc68869..30f8f16 100755 --- a/test/bin2cpp_unittest/generate_test_files.sh.in +++ b/test/bin2cpp_unittest/generate_test_files.sh.in @@ -190,6 +190,22 @@ mkdir -p ${OUTDIR}/sources @BIN2CPP_TARGET_FILE@ --noheader --file=$OUTDIR/images/IMG_0004.jpg --output=$OUTDIR/sources --headerfile=_img0004.h --identifier=Img0004 --chunksize=200 --override @BIN2CPP_TARGET_FILE@ --noheader --file=$OUTDIR/images/IMG_0005.jpg --output=$OUTDIR/sources --headerfile=_img0005.h --identifier=Img0005 --chunksize=200 --override +export TEST_NAME=testDir02 +export OUTDIR=./generated_files/$TEST_NAME +mkdir -p ${OUTDIR} +mkdir -p ${OUTDIR}/images +mkdir -p ${OUTDIR}/testDirIncludeFilter +mkdir -p ${OUTDIR}/testDirExcludeFilter +mkdir -p ${OUTDIR}/testDirMultipleFilter +@TESTFILEGENERATOR_TARGET_FILE@ --file=$OUTDIR/images/IMG_0001.jpg --size=1010 --fill=random --seed=1 +@TESTFILEGENERATOR_TARGET_FILE@ --file=$OUTDIR/images/IMG_0002.jpg --size=1020 --fill=random --seed=2 +@TESTFILEGENERATOR_TARGET_FILE@ --file=$OUTDIR/images/IMG_0003.jpg --size=1030 --fill=random --seed=3 +@TESTFILEGENERATOR_TARGET_FILE@ --file=$OUTDIR/images/IMG_0004.png --size=1040 --fill=random --seed=4 +@TESTFILEGENERATOR_TARGET_FILE@ --file=$OUTDIR/images/IMG_0005.png --size=1050 --fill=random --seed=5 +@TESTFILEGENERATOR_TARGET_FILE@ --file=$OUTDIR/images/IMG_0006.png --size=1060 --fill=random --seed=6 +@TESTFILEGENERATOR_TARGET_FILE@ --file=$OUTDIR/images/IMG_0007.svg --size=0170 --fill=html --seed=7 +@TESTFILEGENERATOR_TARGET_FILE@ --file=$OUTDIR/images/IMG_0008.svg --size=0180 --fill=html --seed=8 + export TEST_NAME=testIssue47 export OUTDIR=./generated_files/$TEST_NAME mkdir -p ${OUTDIR} From e4610214d21595c89fc9462a3cd4d896be9a7d30 Mon Sep 17 00:00:00 2001 From: Antoine Beauchamp Date: Sun, 4 May 2025 09:09:08 -0400 Subject: [PATCH 07/10] Forcing c++ 11 standard. --- CMakeLists.txt | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 9370cd6..edcf920 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -102,6 +102,13 @@ SET(BIN2CPP_VERSION_PATCH 0) set(BIN2CPP_VERSION ${BIN2CPP_VERSION_MAJOR}.${BIN2CPP_VERSION_MINOR}.${BIN2CPP_VERSION_PATCH}) FILE(WRITE ${CMAKE_BINARY_DIR}/version "${BIN2CPP_VERSION}") +# Force c++ 11 +# https://www.reddit.com/r/cpp_questions/comments/1d4tt8a/comment/l6gqea5/ +# My guess is that you are on mac and using g++ to compile. For godforsaken reasons, g++ by default is an alias to clang++ forced into some crazy C++98 mode. +# https://stackoverflow.com/questions/10851247/how-do-i-activate-c-11-in-cmake +set (CMAKE_CXX_STANDARD 11) +set (CMAKE_CXX_STANDARD_REQUIRED TRUE) + # Create a c++ file header from the project LICENSE file. # The c++ header will be added to all generated files. include(MakeCplusplusHeader) From 4830ed2eb460f9835f09e0e37179227bc63803cb Mon Sep 17 00:00:00 2001 From: Antoine Beauchamp Date: Sun, 4 May 2025 09:26:14 -0400 Subject: [PATCH 08/10] * Fixed issue #29: Create arguments to filter files found with '--dir' argument. Updated CHANGES. --- CHANGES | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES b/CHANGES index 8f89842..1597b32 100644 --- a/CHANGES +++ b/CHANGES @@ -1,5 +1,6 @@ Changes for 3.1.0: +* Fixed issue #29: Create arguments to filter files found with '--dir' argument. * Fixed issue #63: Context class redesign. Move arguments handling into a Context class. * Fixed issue #64: Remove build artifacts of samples from installation packages. * Fixed issue #72: Move the code FileManager class generation code into its own IGenerator implementation. From e4cd76fa39f757d31be4f2d6fd925a61a3390166 Mon Sep 17 00:00:00 2001 From: Antoine Beauchamp Date: Sun, 4 May 2025 15:56:06 -0400 Subject: [PATCH 09/10] Fixed typo. --- src/bin2cpp/main.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bin2cpp/main.cpp b/src/bin2cpp/main.cpp index e81a1eb..4ccb6ae 100755 --- a/src/bin2cpp/main.cpp +++ b/src/bin2cpp/main.cpp @@ -194,7 +194,7 @@ void printUsage() " Separate each filter with the character '" DIRECTORY_FILTER_SEPARATOR_STR "'. Valid only when --dir is used. See wildcard characters definition below.\n" " --direxcludefilter= Set a negative filter on the input directory to skip files matching the filter. Wildcard characters are accepted.\n" " Separate each filter with the character '" DIRECTORY_FILTER_SEPARATOR_STR "'. Valid only when --dir is used. See wildcard characters definition below.\n" - " The exclude filter has precedence over the inclusion filter.\n" + " The exclude filter has precedence over the include filter.\n" " --keepdirs Keep the directory structure. Forces the output files to have the same\n" " directory structure as the input files. Valid only when --dir is used.\n" " --plainoutput Print the encoded string in plain format to stdout. Useful for scripts and integration with third party application.\n" From 2c4f032580e41b3b7bc3683fdd7fb43e4066ec0e Mon Sep 17 00:00:00 2001 From: Antoine Beauchamp Date: Sun, 4 May 2025 15:59:55 -0400 Subject: [PATCH 10/10] Updated README.md for #29 --- README.md | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index ab0f4a8..d714cb0 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,6 @@ Build: | Service/Platform | Build | Tests | | ------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | AppVeyor | [![Build status](https://img.shields.io/appveyor/ci/end2endzone/bin2cpp/master.svg?logo=AppVeyor&logoColor=white)](https://ci.appveyor.com/project/end2endzone/bin2cpp) | [![Tests status](https://img.shields.io/appveyor/tests/end2endzone/bin2cpp/master.svg?logo=AppVeyor&logoColor=white)](https://ci.appveyor.com/project/end2endzone/bin2cpp/branch/master/tests) | -| Travis CI | [![Build Status](https://img.shields.io/travis/end2endzone/bin2cpp/master.svg?logo=Travis-CI&style=flat&logoColor=white)](https://travis-ci.org/end2endzone/bin2cpp) | | | Windows Server 2019 | [![Build on Windows](https://github.com/end2endzone/bin2cpp/actions/workflows/build_windows.yml/badge.svg)](https://github.com/end2endzone/bin2cpp/actions/workflows/build_windows.yml) | [![Tests on Windows](https://img.shields.io/endpoint?url=https://gist.githubusercontent.com/end2endzone/58cf6c72c08e706335337d5ef9ca48e8/raw/bin2cpp.master.Windows.json)](https://github.com/end2endzone/bin2cpp/actions/workflows/build_windows.yml) | | Ubuntu 20.04 | [![Build on Linux](https://github.com/end2endzone/bin2cpp/actions/workflows/build_linux.yml/badge.svg)](https://github.com/end2endzone/bin2cpp/actions/workflows/build_linux.yml) | [![Tests on Linux](https://img.shields.io/endpoint?url=https://gist.githubusercontent.com/end2endzone/58cf6c72c08e706335337d5ef9ca48e8/raw/bin2cpp.master.Linux.json)](https://github.com/end2endzone/bin2cpp/actions/workflows/build_linux.yml) | | macOS 10.15 | [![Build on macOS](https://github.com/end2endzone/bin2cpp/actions/workflows/build_macos.yml/badge.svg)](https://github.com/end2endzone/bin2cpp/actions/workflows/build_macos.yml) | [![Tests on macOS](https://img.shields.io/endpoint?url=https://gist.githubusercontent.com/end2endzone/58cf6c72c08e706335337d5ef9ca48e8/raw/bin2cpp.master.macOS.json)](https://github.com/end2endzone/bin2cpp/actions/workflows/build_macos.yml) | @@ -86,7 +85,6 @@ The following section shows how to use bin2cpp with code examples: ## Command Line Usage - ``` bin2cpp --file= --output= [--headerfile=] [--identifier=] [--generator=] [--encoding=] [--chunksize=] [--namespace=] @@ -95,6 +93,7 @@ bin2cpp --file= --output= [--headerfile=] [--identifier= bin2cpp --dir= --output= [--keepdirs] [--generator=] [--encoding=] [--chunksize=] [--namespace=] [--baseclass=] [--managerfile=] [--registerfile] + [--dirincludefilter=] [--direxcludefilter=] [--override] [--noheader] [--quiet] bin2cpp --help bin2cpp --version @@ -117,12 +116,33 @@ bin2cpp --version | --reportedfilepath=<path> | The relative reported path of the File. Path returned when calling method getFilePath() of the File class. Automatically calculated when --dir mode is used.
    ie: images/DCIM/IMG_0001.jpg | | --managerfile=<path> | File name or relative path of the generated C++ header file for the FileManager class.
    ie: FileManager.h. | | --registerfile | Register the generated file to the FileManager class. This flags is automatically set when parameter 'managerfile' is specified. | +| --dirincludefilter=<value>| Set a positive filter on the input directory to only select files matching the filter. Wildcard characters are accepted. Separate each filter with the character ':'. Valid only when --dir is used. See wildcard characters definition below. | +| --direxcludefilter=<value>| Set a negative filter on the input directory to skip files matching the filter. Wildcard characters are accepted. Separate each filter with the character ':'. Valid only when --dir is used. See wildcard characters definition below. The exclude filter has precedence over the include filter. | | --keepdirs | Keep the directory structure. Forces the output files to have the same directory structure as the input files. Valid only when --dir is used. | | --plainoutput | Print the encoded string in plain format to stdout. Useful for scripts and integration with third party application. | | --override | Tells bin2cpp to overwrite the destination files. | | --noheader | Do not print program header to standard output. | | --quiet | Do not log any message to standard output. | +  + +Wildcard characters: +| Wildcard | Description | +|:-----------:|--------------------------------------------------------------| +| `?` | Matches any single character. | +| `*` | Matches zero or more characters. | +| `#` | Matches exactly one numeric digit (0-9). | +| [charlist] | Matches any single character inside the brackets. | +| [a-z] | Matches any single lowercase letter between 'a' and 'z'. | +| [A-Z] | Matches any single uppercase letter between 'A' and 'A'. | +| [0-9] | Matches any single digit between '0' and '9'. | +| [a-zA-Z0-9] | Matches any single letter (uppercase or lowercase) or digit. | + +For example: +* `ker*##.???` matches files that starts with `ker`, and ends with 2 digits, a dot and then 3 characters. +* `--dir-include-filter="*.jpg:*.png"` includes all files whose file extension is `jpg` or `png`. +* `--dir-exclude-filter="*.bak"` excludes all backup files. +* `--dir-include-filter="*.log" --dir-exclude-filter="debug.log"` includes all log files but not the one specificaly named `debug.log`. ## Example 1 - single file