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. diff --git a/CMakeLists.txt b/CMakeLists.txt index 2a2d3fa..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) @@ -180,6 +187,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() 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 diff --git a/samples/demo_website/www/contact/background.jpg b/samples/demo_website/www/contact/background.jpg new file mode 100644 index 0000000..79a2596 Binary files /dev/null and b/samples/demo_website/www/contact/background.jpg differ diff --git a/samples/demo_website/www/contact/index.html b/samples/demo_website/www/contact/index.html index 1f5a44c..7ff78ad 100644 --- a/samples/demo_website/www/contact/index.html +++ b/samples/demo_website/www/contact/index.html @@ -1,21 +1,30 @@ - - - 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 0000000..1d8891e Binary files /dev/null and b/samples/demo_website/www/favicon.ico differ 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 0000000..66dbca7 Binary files /dev/null and b/samples/demo_website/www/static/banner.jpg differ 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 0000000..08500a9 Binary files /dev/null and b/samples/demo_website/www/static/profile-picture.jpg differ 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/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/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/src/bin2cpp/main.cpp b/src/bin2cpp/main.cpp index 53e53c4..4ccb6ae 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 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" + " --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; iThe 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; + } + + 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 new file mode 100644 index 0000000..1439178 --- /dev/null +++ b/src/bin2cpp/wildcard.h @@ -0,0 +1,91 @@ +/********************************************************************************** + * 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. + /// + ///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. + /// + ///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); + } + + /// + ///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/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/TestCLI.cpp b/test/bin2cpp_unittest/TestCLI.cpp index 77e7258..5a6ca0b 100755 --- a/test/bin2cpp_unittest/TestCLI.cpp +++ b/test/bin2cpp_unittest/TestCLI.cpp @@ -848,6 +848,229 @@ TEST_F(TestCLI, testDir) ASSERT_TRUE(deleteFile(outputFilePath.c_str())); } +TEST_F(TestCLI, testDirIncludeFilter) +{ + #define THIS_TEST_CASE_NAME "testDirIncludeFilter" + 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=\"*.svg:*.jpg\""); + 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_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/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 << "`."; + } +} diff --git a/test/bin2cpp_unittest/TestWildcard.cpp b/test/bin2cpp_unittest/TestWildcard.cpp new file mode 100644 index 0000000..95d5d95 --- /dev/null +++ b/test/bin2cpp_unittest/TestWildcard.cpp @@ -0,0 +1,188 @@ +/********************************************************************************** + * 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 TEST_HELPER + { + const char* value; + const char* pattern; + bool expected_result; + std::vector expected_captures; + }; + static const TEST_HELPER 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 TEST_HELPER& 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."; + } +} + +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 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 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}