diff --git a/packages/CLI11/.all-contributorsrc b/packages/CLI11/.all-contributorsrc index cb29dab546cf41c298855feed8f3b882621ac22a..abdeb6b26bb8f4303bce81f432ebe8949d83ad41 100644 --- a/packages/CLI11/.all-contributorsrc +++ b/packages/CLI11/.all-contributorsrc @@ -341,6 +341,24 @@ "contributions": [ "doc" ] + }, + { + "login": "jsoref", + "name": "Josh Soref", + "avatar_url": "https://avatars0.githubusercontent.com/u/2119212?v=4", + "profile": "https://github.com/jsoref", + "contributions": [ + "tool" + ] + }, + { + "login": "geir-t", + "name": "geir-t", + "avatar_url": "https://avatars3.githubusercontent.com/u/35292136?v=4", + "profile": "https://github.com/geir-t", + "contributions": [ + "platform" + ] } ], "contributorsPerLine": 7, diff --git a/packages/CLI11/.clang-format b/packages/CLI11/.clang-format index caaefd2df1bc8908e9623385409e9739648c27d6..0879ffa4cea0605f51a216bfcabe49df2e5d5aa1 100644 --- a/packages/CLI11/.clang-format +++ b/packages/CLI11/.clang-format @@ -75,7 +75,7 @@ SortIncludes: true # SpaceBeforeAssignmentOperators: true SpaceBeforeParens: Never # SpaceInEmptyParentheses: false -# SpacesBeforeTrailingComments: 1 +SpacesBeforeTrailingComments: 2 # SpacesInAngles: false # SpacesInContainerLiterals: true # SpacesInCStyleCastParentheses: false diff --git a/packages/CLI11/.github/actions/cmake_config/entrypoint.sh b/packages/CLI11/.github/actions/cmake_config/entrypoint.sh index 42e263ede57b97c868033745cdf4743e9eef0c65..e3bd622e1575375a746dc9425a7012b1777a79f1 100755 --- a/packages/CLI11/.github/actions/cmake_config/entrypoint.sh +++ b/packages/CLI11/.github/actions/cmake_config/entrypoint.sh @@ -10,7 +10,7 @@ rm -rf cmake_dir/* build_tmp/* v=$1 fn=cmake-$v-Linux-x86_64.tar.gz -if [ ! -f cmake_souces/$fn ]; then +if [ ! -f cmake_sources/$fn ]; then wget -qO cmake_sources/$fn "https://cmake.org/files/v${v%.*}/$fn" fi diff --git a/packages/CLI11/.gitrepo b/packages/CLI11/.gitrepo index 2568d95d7891ca97c84ce111c9cae420395722ab..313778e5c16693281c8db1f9cab797506e10822f 100644 --- a/packages/CLI11/.gitrepo +++ b/packages/CLI11/.gitrepo @@ -6,7 +6,7 @@ [subrepo] remote = git@github.com:CLIUtils/CLI11.git branch = master - commit = 34bfa386917c2bed3ffe979580b07064a5586936 - parent = a0bd81eafb1704b59611b899ceb4aa1e7513bb02 + commit = b9a2f320b912aae623c4acddea06cba399b98654 + parent = 73e054c98d4a424c44972eb708495c40dfa0dbac cmdver = 0.4.1 method = merge diff --git a/packages/CLI11/CHANGELOG.md b/packages/CLI11/CHANGELOG.md index b23d69c88bbea6845c1e8c9c2696e3dfa29d08a4..f753569eb09fa75e8653be43579fe83fd2cd4977 100644 --- a/packages/CLI11/CHANGELOG.md +++ b/packages/CLI11/CHANGELOG.md @@ -333,7 +333,7 @@ This is a quick patch release that makes LICENSE part of the single header file, ### Version 1.5.1: Access -This patch release adds better access to the App progromatically, to assist with writing custom converters to other formats. It also improves the help output, and uses a new feature in CLI11 1.5 to fix an old "quirk" in the way unlimited options and positionals interact. +This patch release adds better access to the App programmatically, to assist with writing custom converters to other formats. It also improves the help output, and uses a new feature in CLI11 1.5 to fix an old "quirk" in the way unlimited options and positionals interact. * Make mixing unlimited positionals and options more intuitive [#102] * Add missing getters `get_options` and `get_description` to App [#105] @@ -360,7 +360,7 @@ Note: This is the final release with `requires`, please switch to `needs`. * Support for `std::optional`, `std::experimental::optional`, and `boost::optional` added if `__has_include` is supported [#95] * All macros/CMake variables now start with `CLI11_` instead of just `CLI_` [#95] * The internal stream was not being cleared before use in some cases. Fixed. [#95] -* Using an emum now requires explicit conversion overload [#97] +* Using an enum now requires explicit conversion overload [#97] * The separator `--` now is removed when it ends unlimited arguments [#100] Other, non-user facing changes: @@ -551,7 +551,7 @@ Lots of cleanup and docs additions made it into this release. Parsing is simpler ## Version 0.3: Plumbum compatibility * Added `->requires`, `->excludes`, and `->envname` from [Plumbum](http://plumbum.readthedocs.io/en/latest/) -* Supports `->mandatory` from Plubmum +* Supports `->mandatory` from Plumbum * More tests for help strings, improvements in formatting * Support type and set syntax in positionals help strings * Added help groups, with `->group("name")` syntax diff --git a/packages/CLI11/CMakeLists.txt b/packages/CLI11/CMakeLists.txt index 03398433e4a4eff1102d09221f3402997b0cba66..e7c2695e1e3d0311f38abf2aa2768df521cd1012 100644 --- a/packages/CLI11/CMakeLists.txt +++ b/packages/CLI11/CMakeLists.txt @@ -51,7 +51,7 @@ list(APPEND build-docs "EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/docs") option(CLI11_WARNINGS_AS_ERRORS "Turn all warnings into errors (for CI)") option(CLI11_SINGLE_FILE "Generate a single header file") cmake_dependent_option(CLI11_SANITIZERS - "Download the sanatizers CMake config" OFF + "Download the sanitizers CMake config" OFF "NOT CMAKE_VERSION VERSION_LESS 3.11" OFF) cmake_dependent_option(CLI11_BUILD_DOCS @@ -89,7 +89,7 @@ cmake_dependent_option(CLI11_CUDA_TESTS cmake_dependent_option(CLI11_CLANG_TIDY "Look for and use Clang-Tidy" OFF "CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME;NOT CMAKE_VERSION VERSION_LESS 3.6" OFF) -set(CLI11_CLANG_TIDY_OPTIONS "" CACHE STRING "Clang tidy options, such as -fix, simicolon separated") +set(CLI11_CLANG_TIDY_OPTIONS "" CACHE STRING "Clang tidy options, such as -fix, semicolon separated") if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME AND NOT DEFINED CMAKE_CXX_STANDARD) set(CMAKE_CXX_STANDARD 11) diff --git a/packages/CLI11/CPPLINT.cfg b/packages/CLI11/CPPLINT.cfg index c55a5f24665ce84d7f98548eb0644b3270ba83ae..d497667bbc899dc8056d25387413cd382d7737f9 100644 --- a/packages/CLI11/CPPLINT.cfg +++ b/packages/CLI11/CPPLINT.cfg @@ -1,13 +1,12 @@ set noparent linelength=120 # As in .clang-format -# Non-used filters +# Unused filters +filter=-build/c++11 # Reports e.g. chrono and thread, which overlap with Chromium's API. Not applicable to general C++ projects. filter=-build/include_order # Requires unusual include order that encourages creating not self-contained headers -filter=-readability/nolint # Conficts with clang-tidy +filter=-readability/nolint # Conflicts with clang-tidy filter=-runtime/references # Requires fundamental change of API, don't see need for this filter=-whitespace/blank_line # Unnecessarily strict with blank lines that otherwise help with readability +filter=-whitespace/indent # Requires strange 3-space indent of private/protected/public markers filter=-whitespace/parens,-whitespace/braces # Conflict with clang-format -# Filters to be included in future -filter=-whitespace/indent,-whitespace/comments,-readability/braces - diff --git a/packages/CLI11/README.md b/packages/CLI11/README.md index 7891fa98a64cba4d7f10fa62cc6ea914cf44e20e..e98f2175a8519bc6b67b31039f6995e72fb8885a 100644 --- a/packages/CLI11/README.md +++ b/packages/CLI11/README.md @@ -151,6 +151,21 @@ make GTEST_COLOR=1 CTEST_OUTPUT_ON_FAILURE=1 make test ``` +<details><summary>Note: Special instructions for GCC 8</summary><p> + +If you are using GCC 8 and using it in C++17 mode with CLI11. CLI11 makes use of the `<filesystem>` header if available, but specifically for this compiler, the `filesystem` library is separate from the standard library and needs to be linked separately. So it is available but CLI11 doesn't use it by default. + +Specifically `libstdc++fs` needs to be added to the linking list and `CLI11_HAS_FILESYSTEM=1` has to be defined. Then the filesystem variant of the Validators could be used on GCC 8. GCC 9+ does not have this issue so the `<filesystem>` is used by default. + +There may also be other cases where a specific library needs to be linked. + +Defining `CLI11_HAS_FILESYSTEM=0` which will remove the usage and hence any linking issue. + +In some cases certain clang compilations may require linking against `libc++fs`. These situations have not been encountered so the specific situations requiring them are unknown yet. + +</p></details> +</br> + ## Usage ### Adding options @@ -195,14 +210,15 @@ While all options internally are the same type, there are several ways to add an app.add_option(option_name, help_str="") app.add_option(option_name, - variable_to_bind_to, // bool, int, float, vector, enum, or string-like, or anything with a defined conversion from a string or that takes an int π, double π, or string in a constructor. Also allowed are tuples π, std::array π or std::pair π. + variable_to_bind_to, // bool, int, float, vector, enum, or string-like, or anything with a defined conversion from a string or that takes an int π, double π, or string in a constructor. Also allowed are tuples π, std::array π or std::pair π. Also supported are complex numbersπ§, wrapper typesπ§, and containers besides vectorπ§ of any other supported type. help_string="") app.add_option_function<type>(option_name, function <void(const type &value)>, // type can be any type supported by add_option help_string="") -app.add_complex(... // Special case: support for complex numbers +app.add_complex(... // Special case: support for complex numbers β οΈ. Complex numbers are now fully supported in the add_option so this function is redundant. + // π There is a template overload which takes two template parameters the first is the type of object to assign the value to, the second is the conversion type. The conversion type should have a known way to convert from a string, such as any of the types that work in the non-template version. If XC is a std::pair and T is some non pair type. Then a two argument constructor for T is called to assign the value. For tuples or other multi element types, XC must be a single type or a tuple like object of the same size as the assignment type app.add_option<typename T, typename XC>(option_name, T &output, // output must be assignable or constructible from a value of type XC @@ -213,7 +229,7 @@ app.add_flag(option_name, help_string="") app.add_flag(option_name, - variable_to_bind_to, // bool, int, float, vector, enum, or string-like, or any singular object with a defined conversion from a string like add_option + variable_to_bind_to, // bool, int, float, complex, containers, enum, or string-like, or any singular object with a defined conversion from a string like add_option help_string="") app.add_flag_function(option_name, @@ -245,9 +261,9 @@ app.add_option<vtype,std:string>("--vs",v1); app.add_option<vtype,int>("--vi",v1); app.add_option<vtype,double>("--vf",v1); ``` -otherwise the output would default to a string. The `add_option` can be used with any integral or floating point types, enumerations, or strings. Or any type that takes an int, double, or std::string in an assignment operator or constructor. If an object can take multiple varieties of those, std::string takes precedence, then double then int. To better control which one is used or to use another type for the underlying conversions use the two parameter template to directly specify the conversion type. +otherwise the output would default to a string. The `add_option` can be used with any integral or floating point types, enumerations, or strings. Or any type that takes an int, double, or std\::string in an assignment operator or constructor. If an object can take multiple varieties of those, std::string takes precedence, then double then int. To better control which one is used or to use another type for the underlying conversions use the two parameter template to directly specify the conversion type. -Types such as (std or boost) `optional<int>`, `optional<double>`, and `optional<string>` are supported directly, other optional types can be added using the two parameter template. See [CLI11 Advanced Topics/Custom Converters][] for information on how this could be done and how you can add your own converters for additional types. +Types such as (std or boost) `optional<int>`, `optional<double>`, and `optional<string>` and any other wrapper types are supported directly. For purposes of CLI11 wrapper types are those which `value_type` definition. See [CLI11 Advanced Topics/Custom Converters][] for information on how you can add your own converters for additional types. Vector types can also be used in the two parameter template overload ``` @@ -308,7 +324,7 @@ Before parsing, you can set the following options: - `->disable_flag_override()`: From the command line long form flag options can be assigned a value on the command line using the `=` notation `--flag=value`. If this behavior is not desired, the `disable_flag_override()` disables it and will generate an exception if it is done on the command line. The `=` does not work with short form flag options. - `->delimiter(char)`: Allows specification of a custom delimiter for separating single arguments into vector arguments, for example specifying `->delimiter(',')` on an option would result in `--opt=1,2,3` producing 3 elements of a vector and the equivalent of --opt 1 2 3 assuming opt is a vector value. - `->description(str)`: Set/change the description. -- `->multi_option_policy(CLI::MultiOptionPolicy::Throw)`: Set the multi-option policy. Shortcuts available: `->take_last()`, `->take_first()`, and `->join()`. This will only affect options expecting 1 argument or bool flags (which do not inherit their default but always start with a specific policy). +- `->multi_option_policy(CLI::MultiOptionPolicy::Throw)`: Set the multi-option policy. Shortcuts available: `->take_last()`, `->take_first()`,`->take_all()`, and `->join()`. This will only affect options expecting 1 argument or bool flags (which do not inherit their default but always start with a specific policy). - `->check(std::string(const std::string &), validator_name="",validator_description="")`: Define a check function. The function should return a non empty string with the error message if the check fails - `->check(Validator)`: Use a Validator object to do the check see [Validators](#validators) for a description of available Validators and how to create new ones. - `->transform(std::string(std::string &), validator_name="",validator_description=")`: Converts the input string into the output string, in-place in the parsed options. @@ -319,7 +335,7 @@ Before parsing, you can set the following options: - `->default_function(std::string())`: Advanced: Change the function that `capture_default_str()` uses. - `->always_capture_default()`: Always run `capture_default_str()` when creating new options. Only useful on an App's `option_defaults`. - `default_str(string)`: Set the default string directly. This string will also be used as a default value if no arguments are passed and the value is requested. -- `default_val(value)`: π Generate the default string from a value and validate that the value is also valid. For options that assign directly to a value type the value in that type is also updated. Value must be convertible to a string(one of known types or a stream operator). +- `default_val(value)`: π Generate the default string from a value and validate that the value is also valid. For options that assign directly to a value type the value in that type is also updated. Value must be convertible to a string(one of known types or have a stream operator). These options return the `Option` pointer, so you can chain them together, and even skip storing the pointer entirely. The `each` function takes any function that has the signature `void(const std::string&)`; it should throw a `ValidationError` when validation fails. The help message will have the name of the parent option prepended. Since `each`, `check` and `transform` use the same underlying mechanism, you can chain as many as you want, and they will be executed in order. Operations added through `transform` are executed first in reverse order of addition, and `check` and `each` are run following the transform functions in order of addition. If you just want to see the unconverted values, use `.results()` to get the `std::vector<std::string>` of results. @@ -679,42 +695,42 @@ app.set_config(option_name="", required=false) ``` -If this is called with no arguments, it will remove the configuration file option (like `set_help_flag`). Setting a configuration option is special. If it is present, it will be read along with the normal command line arguments. The file will be read if it exists, and does not throw an error unless `required` is `true`. Configuration files are in `ini` format by default, The reader can also accept many files in [TOML] format π. (other formats can be added by an adept user, some variations are available through customization points in the default formatter). An example of a file: +If this is called with no arguments, it will remove the configuration file option (like `set_help_flag`). Setting a configuration option is special. If it is present, it will be read along with the normal command line arguments. The file will be read if it exists, and does not throw an error unless `required` is `true`. Configuration files are in [TOML] format by default π§, though the default reader can also accept files in INI format as well π. It should be noted that CLI11 does not contain a full TOML parser but can read strings from most TOML file and run them through the CLI11 parser. Other formats can be added by an adept user, some variations are available through customization points in the default formatter. An example of a TOML file π: ```ini -; Comments are supported, using a ; -; The default section is [default], case insensitive +# Comments are supported, using a # +# The default section is [default], case insensitive value = 1 str = "A string" -vector = 1 2 3 -str_vector = "one" "two" "and three" +vector = [1,2,3] +str_vector = ["one","two","and three"] -; Sections map to subcommands +# Sections map to subcommands [subcommand] in_subcommand = Wow sub.subcommand = true ``` - or equivalently in TOML π -```toml -# Comments are supported, using a # -# The default section is [default], case insensitive +or equivalently in INI format +```ini +; Comments are supported, using a ; +; The default section is [default], case insensitive value = 1 str = "A string" -vector = [1,2,3] -str_vector = ["one","two","and three"] +vector = 1 2 3 +str_vector = "one" "two" "and three" -# Sections map to subcommands +; Sections map to subcommands [subcommand] in_subcommand = Wow sub.subcommand = true ``` -Spaces before and after the name and argument are ignored. Multiple arguments are separated by spaces. One set of quotes will be removed, preserving spaces (the same way the command line works). Boolean options can be `true`, `on`, `1`, `yes`, `enable`; or `false`, `off`, `0`, `no`, `disable` (case insensitive). Sections (and `.` separated names) are treated as subcommands (note: this does not necessarily mean that subcommand was passed, it just sets the "defaults"). You cannot set positional-only arguments. π Subcommands can be triggered from config files if the `configurable` flag was set on the subcommand. Then use `[subcommand]` notation will trigger a subcommand and cause it to act as if it were on the command line. +Spaces before and after the name and argument are ignored. Multiple arguments are separated by spaces. One set of quotes will be removed, preserving spaces (the same way the command line works). Boolean options can be `true`, `on`, `1`, `yes`, `enable`; or `false`, `off`, `0`, `no`, `disable` (case insensitive). Sections (and `.` separated names) are treated as subcommands (note: this does not necessarily mean that subcommand was passed, it just sets the "defaults"). You cannot set positional-only arguments. π Subcommands can be triggered from configuration files if the `configurable` flag was set on the subcommand. Then the use of `[subcommand]` notation will trigger a subcommand and cause it to act as if it were on the command line. To print a configuration file from the passed -arguments, use `.config_to_str(default_also=false, prefix="", write_description=false)`, where `default_also` will also show any defaulted arguments, `prefix` will add a prefix, and `write_description` will include option descriptions. See [Config files](https://cliutils.github.io/CLI11/book/chapters/config.html) for some additional details. +arguments, use `.config_to_str(default_also=false, write_description=false)`, where `default_also` will also show any defaulted arguments, and `write_description` will include the app and option descriptions. See [Config files](https://cliutils.github.io/CLI11/book/chapters/config.html) for some additional details. ### Inheriting defaults @@ -744,7 +760,7 @@ The App class was designed allow toolkits to subclass it, to provide preset defa but before run behavior, while still giving the user freedom to `callback` on the main app. -The most important parse function is `parse(std::vector<std::string>)`, which takes a reversed list of arguments (so that `pop_back` processes the args in the correct order). `get_help_ptr` and `get_config_ptr` give you access to the help/config option pointers. The standard `parse` manually sets the name from the first argument, so it should not be in this vector. You can also use `parse(string, bool)` to split up and parse a string; the optional bool should be set to true if you are +The most important parse function is `parse(std::vector<std::string>)`, which takes a reversed list of arguments (so that `pop_back` processes the args in the correct order). `get_help_ptr` and `get_config_ptr` give you access to the help/config option pointers. The standard `parse` manually sets the name from the first argument, so it should not be in this vector. You can also use `parse(string, bool)` to split up and parse a string; the optional boolean should be set to true if you are including the program name in the string, and false otherwise. Also, in a related note, the `App` you get a pointer to is stored in the parent `App` in a `shared_ptr`s (similar to `Option`s) and are deleted when the main `App` goes out of scope unless the object has another owner. @@ -907,6 +923,10 @@ This project was created by [Henry Schreiner](https://github.com/henryiii) and m <td align="center"><a href="https://github.com/KOLANICH"><img src="https://avatars1.githubusercontent.com/u/240344?v=4" width="100px;" alt=""/><br /><sub><b>KOLANICH</b></sub></a><br /><a href="#platform-KOLANICH" title="Packaging/porting to new platform">π¦</a></td> <td align="center"><a href="https://github.com/jgerityneurala"><img src="https://avatars2.githubusercontent.com/u/57360646?v=4" width="100px;" alt=""/><br /><sub><b>James Gerity</b></sub></a><br /><a href="https://github.com/CLIUtils/CLI11/commits?author=jgerityneurala" title="Documentation">π</a></td> </tr> + <tr> + <td align="center"><a href="https://github.com/jsoref"><img src="https://avatars0.githubusercontent.com/u/2119212?v=4" width="100px;" alt=""/><br /><sub><b>Josh Soref</b></sub></a><br /><a href="#tool-jsoref" title="Tools">π§</a></td> + <td align="center"><a href="https://github.com/geir-t"><img src="https://avatars3.githubusercontent.com/u/35292136?v=4" width="100px;" alt=""/><br /><sub><b>geir-t</b></sub></a><br /><a href="#platform-geir-t" title="Packaging/porting to new platform">π¦</a></td> + </tr> </table> <!-- markdownlint-enable --> diff --git a/packages/CLI11/azure-pipelines.yml b/packages/CLI11/azure-pipelines.yml index d01a240690edbc7499ca7fa71495e5fde747ed7d..7d2d7f400f413001a4660e48e8fe7cdce6b9c24f 100644 --- a/packages/CLI11/azure-pipelines.yml +++ b/packages/CLI11/azure-pipelines.yml @@ -6,6 +6,9 @@ trigger: - master +pr: +- master + variables: cli11.single: ON cli11.std: 14 @@ -36,7 +39,7 @@ jobs: vmImage: 'ubuntu-latest' container: sharaku/cpplint:latest steps: - - bash: cpplint --counting=detailed --recursive examples include/CLI + - bash: cpplint --counting=detailed --recursive examples include/CLI tests displayName: Checking against google style guide # TODO: Fix macOS error and windows warning in c++17 mode @@ -57,6 +60,9 @@ jobs: Windows11: vmImage: 'vs2017-win2016' cli11.std: 11 + Windowslatest: + vmImage: 'windows-2019' + cli11.std: 17 pool: vmImage: $(vmImage) steps: @@ -89,6 +95,9 @@ jobs: gcc9: containerImage: gcc:9 cli11.std: 17 + gcc8: + containerImage: gcc:8 + cli11.std: 17 gcc4.8: containerImage: gcc:4.8 cli11.std: 11 @@ -100,6 +109,10 @@ jobs: containerImage: silkeh/clang:8 cli11.std: 14 cli11.options: -DCLI11_FORCE_LIBCXX=ON + clang8_17: + containerImage: silkeh/clang:8 + cli11.std: 17 + cli11.options: -DCLI11_FORCE_LIBCXX=ON container: $[ variables['containerImage'] ] steps: - template: .ci/azure-cmake.yml diff --git a/packages/CLI11/book/chapters/config.md b/packages/CLI11/book/chapters/config.md index cfd570acd0cc4fbff91cde5372b2951ea0e03b16..a9edf9aac521d44f522662fd45a3ecb82a063b83 100644 --- a/packages/CLI11/book/chapters/config.md +++ b/packages/CLI11/book/chapters/config.md @@ -41,58 +41,78 @@ If it is needed to get the configuration file name used this can be obtained via ## Configure file format -Here is an example configuration file, in INI format: +Here is an example configuration file, in [TOML](https://github.com/toml-lang/toml) format: ```ini -; Comments are supported, using a ; -; The default section is [default], case insensitive +# Comments are supported, using a # +# The default section is [default], case insensitive value = 1 str = "A string" -vector = 1 2 3 +vector = [1,2,3] -; Section map to subcommands +# Section map to subcommands [subcommand] in_subcommand = Wow -sub.subcommand = true +[subcommand.sub] +subcommand = true # could also be give as sub.subcommand=true ``` Spaces before and after the name and argument are ignored. Multiple arguments are separated by spaces. One set of quotes will be removed, preserving spaces (the same way the command line works). Boolean options can be `true`, `on`, `1`, `y`, `t`, `+`, `yes`, `enable`; or `false`, `off`, `0`, `no`, `n`, `f`, `-`, `disable`, (case insensitive). Sections (and `.` separated names) are treated as subcommands (note: this does not necessarily mean that subcommand was passed, it just sets the "defaults". If a subcommand is set to `configurable` then passing the subcommand using `[sub]` in a configuration file will trigger the subcommand.) -CLI11 also supports configuration file in [TOML](https://github.com/toml-lang/toml) format. +CLI11 also supports configuration file in INI format. -```toml -# Comments are supported, using a # -# The default section is [default], case insensitive +```ini +; Comments are supported, using a ; +; The default section is [default], case insensitive value = 1 str = "A string" -vector = [1,2,3] +vector = 1 2 3 -# Section map to subcommands +; Section map to subcommands [subcommand] in_subcommand = Wow -[subcommand.sub] -subcommand = true # could also be give as sub.subcommand=true +sub.subcommand = true ``` The main differences are in vector notation and comment character. Note: CLI11 is not a full TOML parser as it just reads values as strings. It is possible (but not recommended) to mix notation. ## Writing out a configure file -To print a configuration file from the passed arguments, use `.config_to_str(default_also=false, prefix="", write_description=false)`, where `default_also` will also show any defaulted arguments, `prefix` will add a prefix, and `write_description` will include option descriptions. +To print a configuration file from the passed arguments, use `.config_to_str(default_also=false, write_description=false)`, where `default_also` will also show any defaulted arguments, and `write_description` will include option descriptions and the App description + +```cpp + + CLI::App app; + app.add_option(...); + // several other options + CLI11_PARSE(app, argc, argv); + //the config printout should be after the parse to capture the given arguments + std::cout<<app.config_to_str(true,true); +``` + +if a prefix is needed to print before the options, for example to print a config for just a subcommand, the config formatter can be obtained directly. + +```cpp + + auto fmtr=app.get_config_formatter(); + //std::string to_config(const App *app, bool default_also, bool write_description, std::string prefix) + fmtr->to_config(&app,true,true,"sub."); + //prefix can be used to set a prefix before each argument, like "sub." +``` ### Customization of configure file output -The default config parser/generator has some customization points that allow variations on the INI format. The default formatter has a base configuration that matches the INI format. It defines 5 characters that define how different aspects of the configuration are handled +The default config parser/generator has some customization points that allow variations on the TOML format. The default formatter has a base configuration that matches the TOML format. It defines 5 characters that define how different aspects of the configuration are handled ```cpp /// the character used for comments -char commentChar = ';'; +char commentChar = '#'; /// the character used to start an array '\0' is a default to not use -char arrayStart = '\0'; +char arrayStart = '['; /// the character used to end an array '\0' is a default to not use -char arrayEnd = '\0'; +char arrayEnd = ']'; /// the character used to separate elements in an array -char arraySeparator = ' '; +char arraySeparator = ','; /// the character used separate the name from the value char valueDelimiter = '='; ``` @@ -111,18 +131,14 @@ auto config_base=app.get_config_formatter_base(); config_base->valueSeparator(':'); ``` -The default configuration file will read TOML files, but will write out files in the INI format. To specify outputting TOML formatted files use +The default configuration file will read INI files, but will write out files in the TOML format. To specify outputting INI formatted files use ```cpp -app.config_formatter(std::make_shared<CLI::ConfigTOML>()); +app.config_formatter(std::make_shared<CLI::ConfigINI>()); ``` -which makes use of a predefined modification of the ConfigBase class which INI also uses. +which makes use of a predefined modification of the ConfigBase class which TOML also uses. If a custom formatter is used that is not inheriting from the from ConfigBase class `get_config_formatter_base() will return a nullptr, so some care must be exercised in its us with custom configurations. ## Custom formats -{% hint style='info' %} -New in CLI11 1.6 -{% endhint %} - You can invent a custom format and set that instead of the default INI formatter. You need to inherit from `CLI::Config` and implement the following two functions: ```cpp @@ -145,3 +161,11 @@ See [`examples/json.cpp`](https://github.com/CLIUtils/CLI11/blob/master/examples Configuration files can be used to trigger subcommands if a subcommand is set to configure. By default configuration file just set the default values of a subcommand. But if the `configure()` option is set on a subcommand then the if the subcommand is utilized via a `[subname]` block in the configuration file it will act as if it were called from the command line. Subsubcommands can be triggered via [subname.subsubname]. Using the `[[subname]]` will be as if the subcommand were triggered multiple times from the command line. This functionality can allow the configuration file to act as a scripting file. For custom configuration files this behavior can be triggered by specifying the parent subcommands in the structure and `++` as the name to open a new subcommand scope and `--` to close it. These names trigger the different callbacks of configurable subcommands. + +## Implementation Notes +The config file input works with any form of the option given: Long, short, positional, or the environment variable name. When generating a config file it will create a name in following priority. + +1. First long name +1. Positional name +1. First short name +1. Environment name diff --git a/packages/CLI11/book/chapters/options.md b/packages/CLI11/book/chapters/options.md index e98cf4a2a08b2071910e347ab59d2a0e05b766fc..149cd654bd93522ee0662cd4a5fa1a01f6b0402e 100644 --- a/packages/CLI11/book/chapters/options.md +++ b/packages/CLI11/book/chapters/options.md @@ -9,7 +9,7 @@ int int_option{0}; app.add_option("-i", int_option, "Optional description"); ``` -This will bind the option `-i` to the integer `int_option`. On the command line, a single value that can be converted to an integer will be expected. Non-integer results will fail. If that option is not given, CLI11 will not touch the initial value. This allows you to set up defaults by simply setting your value beforehand. If you want CLI11 to display your default value, you can add the optional final argument `true` when you add the option. If you do not add this, you do not even need your option value to be printable[^1]. +This will bind the option `-i` to the integer `int_option`. On the command line, a single value that can be converted to an integer will be expected. Non-integer results will fail. If that option is not given, CLI11 will not touch the initial value. This allows you to set up defaults by simply setting your value beforehand. If you want CLI11 to display your default value, you can add the optional final argument `true` when you add the option. ```cpp int int_option{0}; @@ -20,11 +20,15 @@ You can use any C++ int-like type, not just `int`. CLI11 understands the followi | Type | CLI11 | |-------------|-------| -| int-like | Integer conversion up to 64-bit, can be unsigned | -| float-like | Floating point conversions | -| string-like | Anything else that can be shifted into a StringStream | -| vector-like | A vector of the above three types (see below) | +| number like | Integers, floats, bools, or any type that can be constructed from an integer or floating point number | +| string-like | std\::string, or anything that can be constructed from or assigned a std\::string | +| complex-number | std::complex or any type which has a real(), and imag() operations available, will allow 1 or 2 string definitions like "1+2j" or two arguments "1","2" | +| enumeration | any enum or enum class type is supported through conversion from the underlying type(typically int, though it can be specified otherwise) | +| container-like | a container(like vector) of any available types including other containers | +| wrapper | any other object with a `value_type` static definition where the type specified by `value_type` is one of type in this list | +| tuple | a tuple, pair, or array, or other type with a tuple size and tuple_type operations defined and the members being a type contained in this list | | function | A function that takes an array of strings and returns a string that describes the conversion failure or empty for success. May be the empty function. (`{}`) | +| streamable | any other type with a `<<` operator will also work | By default, CLI11 will assume that an option is optional, and one value is expected if you do not use a vector. You can change this on a specific option using option modifiers. @@ -46,15 +50,15 @@ To make a positional option, you simply give CLI11 one name that does not start This would make two short option aliases, two long option alias, and the option would be also be accepted as a positional. -## Vectors of options +## Containers of options -If you use a vector instead of a plain option, you can accept more than one value on the command line. By default, a vector accepts as many options as possible, until the next value that could be a valid option name. You can specify a set number using an option modifier `->expected(N)`. (The default unlimited behavior on vectors is restore with `N=-1`) CLI11 does not differentiate between these two methods for unlimited acceptance options:[^2] +If you use a vector or other container instead of a plain option, you can accept more than one value on the command line. By default, a container accepts as many options as possible, until the next value that could be a valid option name. You can specify a set number using an option modifier `->expected(N)`. (The default unlimited behavior on vectors is restored with `N=-1`) CLI11 does not differentiate between these two methods for unlimited acceptance options. | Separate names | Combined names | |-------------------|-----------------| | `--vec 1 --vec 2` | `--vec 1 2` | -The original version did allow the option system to access information on the grouping of options received, but was removed for simplicity. +It is also possible to specify a minimum and maximum number through `->expected(Min,Max)`. It is also possible to specify a min and max type size for the elements of the container. It most cases these values will be automatically determined but a user can manually restrict them. An example of setting up a vector option: @@ -65,6 +69,43 @@ app.add_option("--vec", int_vec, "My vector option"); Vectors will be replaced by the parsed content if the option is given on the command line. +A definition of a container for purposes of CLI11 is a type with a `end()`, `insert(...)`, `clear()` and `value_type` definitions. This includes `vector`, `set`, `deque`, `list`, `forward_iist`, `map`, `unordered_map` and a few others from the standard library, and many other containers from the boost library. + +### containers of containers +Containers of containers are also supported. +```cpp +std::vector<std::vector<int>> int_vec; +app.add_option("--vec", int_vec, "My vector of vectors option"); +``` +CLI11 inserts a separator sequence at the start of each argument call to separate the vectors. So unless the separators are injected as part of the command line each call of the option on the command line will result in a separate element of the outer vector. This can be manually controlled via `inject_separator(true|false)` but in nearly all cases this should be left to the defaults. To insert of a separator from the command line add a `%%` where the separation should occur. +``` +cmd --vec_of_vec 1 2 3 4 %% 1 2 +``` +would then result in a container of size 2 with the first element containing 4 values and the second 2. + +This separator is also the only way to get values into something like +```cpp +std::pair<std::vector<int>,std::vector<int>> two_vecs; +app.add_option("--vec", two_vecs, "pair of vectors"); +``` +without calling the argument twice. + +Further levels of nesting containers should compile but intermediate layers will only have a single element in the container, so is probably not that useful. + +### Nested types +Types can be nested For example +```cpp +std::map<int, std::pair<int,std::string>> map; +app.add_option("--dict", map, "map of pairs"); +``` + +will require 3 arguments for each invocation, and multiple sets of 3 arguments can be entered for a single invocation on the command line. + +```cpp +std::map<int, std::pair<int,std::vector<std::string>>> map; +app.add_option("--dict", map, "map of pairs"); +``` +will result in a requirement for 2 integers on each invocation and absorb an unlimited number of strings including 0. ## Option modifiers @@ -75,25 +116,32 @@ When you call `add_option`, you get a pointer to the added option. You can use t | `->required()` | The program will quit if this option is not present. This is `mandatory` in Plumbum, but required options seems to be a more standard term. For compatibility, `->mandatory()` also works. | | `->expected(N)` | Take `N` values instead of as many as possible, mainly for vector args. | | `->expected(Nmin,Nmax)` | Take between `Nmin` and `Nmax` values. | +| `->type_size(N)` | specify that each block of values would consist of N elements | +| `->type_size(Nmin,Nmax)` | specify that each block of values would consist of between Nmin and Nmax elements | | `->needs(opt)` | This option requires another option to also be present, opt is an `Option` pointer. | | `->excludes(opt)` | This option cannot be given with `opt` present, opt is an `Option` pointer. | | `->envname(name)` | Gets the value from the environment if present and not passed on the command line. | | `->group(name)` | The help group to put the option in. No effect for positional options. Defaults to `"Options"`. `"Hidden"` will not show up in the help print. | +| `->description(string)` | Set/change the description | | `->ignore_case()` | Ignore the case on the command line (also works on subcommands, does not affect arguments). | -| `->ignore_underscore()` | Ignore any underscores on the command line (also works on subcommands, does not affect arguments, new in CLI11 1.7). | +| `->ignore_underscore()` | Ignore any underscores on the command line (also works on subcommands, does not affect arguments). | | `->allow_extra_args()` | Allow extra argument values to be included when an option is passed. Enabled by default for vector options. | -| `->multi_option_policy(CLI::MultiOptionPolicy::Throw)` | Sets the policy for handling multiple arguments if the option was received on the command line several times. `Throw`ing an error is the default, but `TakeLast`, `TakeFirst`, `TakeAll`, and `Join` are also available. See the next three lines for shortcuts to set this more easily. | +| `->disable_flag_override()` | specify that flag options cannot be overridden on the command line use `=<newval>` | +| `->delimiter('<CH>')` | specify a character that can be used to separate elements in a command line argument, default is <none>, common values are ',', and ';' | +| `->multi_option_policy(CLI::MultiOptionPolicy::Throw)` | Sets the policy for handling multiple arguments if the option was received on the command line several times. `Throw`ing an error is the default, but `TakeLast`, `TakeFirst`, `TakeAll`, and `Join` are also available. See the next four lines for shortcuts to set this more easily. | | `->take_last()` | Only use the last option if passed several times. This is always true by default for bool options, regardless of the app default, but can be set to false explicitly with `->multi_option_policy()`. | | `->take_first()` | sets `->multi_option_policy(CLI::MultiOptionPolicy::TakeFirst)` | +| `->take_all()` | sets `->multi_option_policy(CLI::MultiOptionPolicy::TakeAll)` | | `->join()` | sets `->multi_option_policy(CLI::MultiOptionPolicy::Join)`, which uses newlines or the specified delimiter to join all arguments into a single string output. | -| `->join(delim)` | sets `->multi_option_policy(CLI::MultiOptionPolicy::Join)`, which uses `delim` to join all arguments into a single string output. | -| `->check(CLI::ExistingFile)` | Requires that the file exists if given. | -| `->check(CLI::ExistingDirectory)` | Requires that the directory exists. | -| `->check(CLI::NonexistentPath)` | Requires that the path does not exist. | -| `->check(CLI::Range(min,max))` | Requires that the option be between min and max (make sure to use floating point if needed). Min defaults to 0. | +| `->join(delim)` | sets `->multi_option_policy(CLI::MultiOptionPolicy::Join)`, which uses `delim` to join all arguments into a single string output. this also sets the delimiter | +| `->check(Validator)` | perform a check on the returned results to verify they meet some criteria. See [Validators](./validators.md) for more info | +| `->transform(Validator)` | Run a transforming validator on each value passed. See [Validators](./validators.md) for more info | | `->each(void(std::string))` | Run a function on each parsed value, *in order*. | +| `->default_str(string)` | set a default string for use in the help and as a default value if no arguments are passed and a value is requested | +| `->default_function(string())` | Advanced: Change the function that `capture_default_str()` uses. | +| `->default_val(value)` | Generate the default string from a value and validate that the value is also valid. For options that assign directly to a value type the value in that type is also updated. Value must be convertible to a string(one of known types or have a stream operator). | -The `->check(...)` modifiers adds a callback function of the form `bool function(std::string)` that runs on every value that the option receives, and returns a value that tells CLI11 whether the check passed or failed. +The `->check(...)` and `->transform(...)` modifiers can also take a callback function of the form `bool function(std::string)` that runs on every value that the option receives, and returns a value that tells CLI11 whether the check passed or failed. ## Using the `CLI::Option` pointer @@ -105,17 +153,22 @@ CLI::Option* opt = app.add_flag("--opt"); CLI11_PARSE(app, argv, argc); if(* opt) - std::cout << "Flag recieved " << opt->count() << " times." << std::endl; + std::cout << "Flag received " << opt->count() << " times." << std::endl; ``` ## Inheritance of defaults -One of CLI11's systems to allow customizability without high levels of verbosity is the inheritance system. You can set default values on the parent `App`, and all options and subcommands created from it remember the default values at the point of creation. The default value for Options, specifically, are accessible through the `option_defaults()` method. There are four settings that can be set and inherited: +One of CLI11's systems to allow customizability without high levels of verbosity is the inheritance system. You can set default values on the parent `App`, and all options and subcommands created from it remember the default values at the point of creation. The default value for Options, specifically, are accessible through the `option_defaults()` method. There are a number of settings that can be set and inherited: * `group`: The group name starts as "Options" * `required`: If the option must be given. Defaults to `false`. Is ignored for flags. * `multi_option_policy`: What to do if several copies of an option are passed and one value is expected. Defaults to `CLI::MultiOptionPolicy::Throw`. This is also used for bool flags, but they always are created with the value `CLI::MultiOptionPolicy::TakeLast` regardless of the default, so that multiple bool flags does not cause an error. But you can override that flag by flag. * `ignore_case`: Allow any mixture of cases for the option or flag name +* `ignore_underscore`: Allow any number of underscores in the option or flag name +* `configurable`: Specify whether an option can be configured through a config file +* `disable_flag_override`: do not allow flag values to be overridden on the command line +* `always_capture_default`: specify that the default values should be automatically captured. +* `delimiter`: A delimiter to use for capturing multiple values in a single command line string (e.g. --flag="flag,-flag2,flag3") An example of usage: @@ -129,29 +182,7 @@ app.get_group() // is "Required" Groups are mostly for visual organization, but an empty string for a group name will hide the option. -## Listing of specialty options: - -Besides `add_option` and `add_flag`, there are several special ways to create options for sets and complex numbers. - -### Sets - -You can add a set with `add_set`, where you give a variable to set and a `std::set` of choices to pick from. There also is a `add_set_ignore_case` version which ignores case when set matching. If you use an existing set instead of an inline one, you can edit the set after adding it and changes will be reflected in the set checking and help message. - -```cpp -int val{0}; -app.add_set("--even", val, {0,2,4,6,8}); -``` - -### Complex numbers - -You can also add a complex number. This type just needs to support a `(T x, T y)` constructor and be printable. You can also pass one extra argument that will set the label of the type; by default it is "COMPLEX". - -```cpp -std::complex<float> val{0.0F,0.0F}; -app.add_complex("--cplx", val); -``` - -### Windows style options (New in CLI11 1.7) +### Windows style options You can also set the app setting `app->allow_windows_style_options()` to allow windows style options to also be recognized on the command line: @@ -160,12 +191,13 @@ You can also set the app setting `app->allow_windows_style_options()` to allow w * `/long` (long flag) * `/file filename` (space) * `/file:filename` (colon) +* `/long_flag:false` (long flag with : to override the default value) -Windows style options do not allow combining short options or values not separated from the short option like with `-` options. You still specify option names in the same manor as on Linux with single and double dashes when you use the `add_*` functions, and the Linux style on the command line will still work. If a long and a short option share the same name, the option will match on the first one defined. +Windows style options do not allow combining short options or values not separated from the short option like with `-` options. You still specify option names in the same manner as on Linux with single and double dashes when you use the `add_*` functions, and the Linux style on the command line will still work. If a long and a short option share the same name, the option will match on the first one defined. ## Parse configuration -How an option and its arguments are parsed depends on a set of controls that are part of the option structure. In most circumstances these controls are set automatically based on the function used to create the option and the type the arguments are parsed into. The variables define the size of the underlying type (essentially how many strings make up the type), the expected size (how many groups are expected) and a flag indicating if multiple groups are allowed with a single option. And these interact with the `multi_option_policy` when it comes time to parse. +How an option and its arguments are parsed depends on a set of controls that are part of the option structure. In most circumstances these controls are set automatically based on the type or function used to create the option and the type the arguments are parsed into. The variables define the size of the underlying type (essentially how many strings make up the type), the expected size (how many groups are expected) and a flag indicating if multiple groups are allowed with a single option. And these interact with the `multi_option_policy` when it comes time to parse. ### examples How options manage this is best illustrated through some examples @@ -173,7 +205,7 @@ How options manage this is best illustrated through some examples std::string val; app.add_option("--opt",val,"description"); ``` -creates an option that assigns a value to a `std::string` When this option is constructed it sets a type_size of 1. meaning that the assignment uses a single string. The Expected size is also set to 1 by default, and `allow_extra_args` is set to false. meaning that each time this option is called 1 argument is expected. This would also be the case if val were a `double`, `int` or any other single argument types. +creates an option that assigns a value to a `std::string` When this option is constructed it sets a type_size min and max of 1. Meaning that the assignment uses a single string. The Expected size is also set to 1 by default, and `allow_extra_args` is set to false. meaning that each time this option is called 1 argument is expected. This would also be the case if val were a `double`, `int` or any other single argument types. now for example ```cpp @@ -203,6 +235,20 @@ app.add_flag("--opt",val,"description"); Using the add_flag methods for creating options creates an option with an expected size of 0, implying no arguments can be passed. +```cpp +std::complex<double> val; +app.add_option("--opt",val,"description"); +``` + +triggers the complex number type which has a min of 1 and max of 2, so 1 or 2 strings can be passed. Complex number conversion supports arguments of the form "1+2j" or "1","2", or "1" "2i". The imaginary number symbols `i` and `j` are interchangeable in this context. + + +```cpp +std::vector<std::vector<int>> val; +app.add_option("--opt",val,"description"); +``` +has a type size of 1 to (1<<30). + ### Customization The `type_size(N)`, `type_size(Nmin, Nmax)`, `expected(N)`, `expected(Nmin,Nmax)`, and `allow_extra_args()` can be used to customize an option. For example @@ -214,5 +260,9 @@ opt->expected(0,1); ``` will create a hybrid option, that can exist on its own in which case the value "vvv" is used or if a value is given that value will be used. -[^1]: For example, enums are not printable to `std::cout`. -[^2]: There is a small difference. An combined unlimited option will not prioritize over a positional that could still accept values. +There are some additional options that can be specified to modify an option for specific cases +- `->run_callback_for_default()` will specify that the callback should be executed when a default_val is set. This is set automatically when appropriate though it can be turned on or off and any user specified callback for an option will be executed when the default value for an option is set. + +## Unusual circumstances +There are a few cases where some things break down in the type system managing options and definitions. Using the `add_option` method defines a lambda function to extract a default value if required. In most cases this either straightforward or a failure is detected automatically and handled. But in a few cases a streaming template is available that several layers down may not actually be defined. The conditions in CLI11 cannot detect this circumstance automatically and will result in compile error. One specific known case is `boost::optional` if the boost optional_io header is included. This header defines a template for all boost optional values even if they do no actually have a streaming operator. For example `boost::optional<std::vector>` does not have a streaming operator but one is detected since it is part of a template. For these cases a secondary method `app->add_option_no_stream(...)` is provided that bypasses this operation completely and should compile in these cases. + diff --git a/packages/CLI11/conanfile.py b/packages/CLI11/conanfile.py index 6ce8a01e3ddabd2db86e63d554e82a1b208eb7aa..377cd014c7feb521b8398cedceccde2445594a5f 100644 --- a/packages/CLI11/conanfile.py +++ b/packages/CLI11/conanfile.py @@ -1,5 +1,5 @@ from conans import ConanFile, CMake -from conans.tools import load +from conans.tools import load, cross_building import re @@ -40,7 +40,8 @@ class CLI11Conan(ConanFile): cmake.definitions["CLI11_SINGLE_FILE"] = "OFF" cmake.configure() cmake.build() - cmake.test() + if not cross_building(self.settings): + cmake.test() cmake.install() def package_id(self): diff --git a/packages/CLI11/docs/Doxyfile b/packages/CLI11/docs/Doxyfile index 06de30493b7692cb9d11071af740a3d0f167dd41..d08a2902e11ab318fce026f068dfb412780554f0 100644 --- a/packages/CLI11/docs/Doxyfile +++ b/packages/CLI11/docs/Doxyfile @@ -280,7 +280,7 @@ OPTIMIZE_OUTPUT_VHDL = NO # parses. With this tag you can assign which parser to use for a given # extension. Doxygen has a built-in mapping, but you can override or extend it # using this tag. The format is ext=language, where ext is a file extension, and -# language is one of the parsers supported by doxygen: IDL, Java, Javascript, +# language is one of the parsers supported by doxygen: IDL, Java, JavaScript, # C#, C, C++, D, PHP, Objective-C, Python, Fortran (fixed format Fortran: # FortranFixed, free formatted Fortran: FortranFree, unknown formatted Fortran: # Fortran. In the later case the parser tries to guess whether the code is fixed @@ -1508,7 +1508,7 @@ FORMULA_FONTSIZE = 10 FORMULA_TRANSPARENT = YES # Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see -# http://www.mathjax.org) which uses client side Javascript for the rendering +# http://www.mathjax.org) which uses client side JavaScript for the rendering # instead of using pre-rendered bitmaps. Use this if you do not have LaTeX # installed or if you want to formulas look prettier in the HTML output. When # enabled you may also need to install MathJax separately and configure the path @@ -1578,7 +1578,7 @@ MATHJAX_CODEFILE = SEARCHENGINE = YES # When the SERVER_BASED_SEARCH tag is enabled the search engine will be -# implemented using a web server instead of a web client using Javascript. There +# implemented using a web server instead of a web client using JavaScript. There # are two flavors of web server based searching depending on the EXTERNAL_SEARCH # setting. When disabled, doxygen will generate a PHP script for searching and # an index file used by the script. When EXTERNAL_SEARCH is enabled the indexing diff --git a/packages/CLI11/examples/groups.cpp b/packages/CLI11/examples/groups.cpp index 863976b4ab2b48b5cfc35ddde9b9fa4a0ffed4a6..22d628948ecd5a6fb01871cb9217b30d8a136c99 100644 --- a/packages/CLI11/examples/groups.cpp +++ b/packages/CLI11/examples/groups.cpp @@ -20,7 +20,7 @@ int main(int argc, char **argv) { int count{0}; CLI::Option *copt = app.add_flag("-c,--count", count, "Counter")->required()->group("Important"); - double value{0.0}; // = 3.14; + double value{0.0}; // = 3.14; app.add_option("-d,--double", value, "Some Value")->group("Other"); try { diff --git a/packages/CLI11/examples/inter_argument_order.cpp b/packages/CLI11/examples/inter_argument_order.cpp index 8655f5994189eb0447884d5e1f4e846135a0c910..deecd057e699529ab0e7c7c35134778aa6b1bf52 100644 --- a/packages/CLI11/examples/inter_argument_order.cpp +++ b/packages/CLI11/examples/inter_argument_order.cpp @@ -17,7 +17,7 @@ int main(int argc, char **argv) { auto foo = app.add_option("--foo,-f", foos, "Some unlimited argument"); std::vector<int> bars; - auto bar = app.add_option("--bar", bars, "Some unlimited arggument"); + auto bar = app.add_option("--bar", bars, "Some unlimited argument"); app.add_flag("--z,--x", "Random other flags"); diff --git a/packages/CLI11/examples/nested.cpp b/packages/CLI11/examples/nested.cpp index 7cd49367e3e8d6295f264e0b9fbfdfd96addcc84..23f428514568e02b9ee24fe0b1a316aabf48677a 100644 --- a/packages/CLI11/examples/nested.cpp +++ b/packages/CLI11/examples/nested.cpp @@ -14,7 +14,7 @@ int main(int argc, char **argv) { app.add_flag("--version", "Get version"); CLI::App *cameraApp = app.add_subcommand("camera", "Configure the app camera"); - cameraApp->require_subcommand(0, 1); // 0 (default) or 1 camera + cameraApp->require_subcommand(0, 1); // 0 (default) or 1 camera std::string mvcamera_config_file = "mvcamera_config.json"; CLI::App *mvcameraApp = cameraApp->add_subcommand("mvcamera", "MatrixVision Camera Configuration"); diff --git a/packages/CLI11/examples/simple.cpp b/packages/CLI11/examples/simple.cpp index 58cf4b6c7e9c3754ce29fb673ac56895071af26a..2d465cb223575a7d6d7a3ea2049b195c5253e333 100644 --- a/packages/CLI11/examples/simple.cpp +++ b/packages/CLI11/examples/simple.cpp @@ -21,7 +21,7 @@ int main(int argc, char **argv) { int v{0}; CLI::Option *flag = app.add_flag("--flag", v, "Some flag that can be passed multiple times"); - double value{0.0}; // = 3.14; + double value{0.0}; // = 3.14; app.add_option("-d,--double", value, "Some Value"); CLI11_PARSE(app, argc, argv); diff --git a/packages/CLI11/examples/subcom_partitioned.cpp b/packages/CLI11/examples/subcom_partitioned.cpp index 5010678177b3f1a52e90eca395dfd0d4ef5b4839..20dd362326901686e0263244ee7e887ce0225384 100644 --- a/packages/CLI11/examples/subcom_partitioned.cpp +++ b/packages/CLI11/examples/subcom_partitioned.cpp @@ -23,7 +23,7 @@ int main(int argc, char **argv) { CLI::Option *copt = impOpt->add_flag("-c,--count", count, "Counter")->required(); CLI::App_p otherOpt = std::make_shared<CLI::App>("Other"); - double value{0.0}; // = 3.14; + double value{0.0}; // = 3.14; otherOpt->add_option("-d,--double", value, "Some Value"); // add the subapps to the main one diff --git a/packages/CLI11/examples/subcommands.cpp b/packages/CLI11/examples/subcommands.cpp index ce71eae94f02cbd2e0c96b138fa29e54fd1099af..937eaeaf41ad0fd3026aa938d5dea617451dff20 100644 --- a/packages/CLI11/examples/subcommands.cpp +++ b/packages/CLI11/examples/subcommands.cpp @@ -15,7 +15,7 @@ int main(int argc, char **argv) { app.add_flag("--random", "Some random flag"); CLI::App *start = app.add_subcommand("start", "A great subcommand"); CLI::App *stop = app.add_subcommand("stop", "Do you really want to stop?"); - app.require_subcommand(); // 1 or more + app.require_subcommand(); // 1 or more std::string file; start->add_option("-f,--file", file, "File name"); diff --git a/packages/CLI11/extern/googletest b/packages/CLI11/extern/googletest index 2fe3bd994b3189899d93f1d5a881e725e046fdc2..703bd9caab50b139428cea1aaff9974ebee5742e 160000 --- a/packages/CLI11/extern/googletest +++ b/packages/CLI11/extern/googletest @@ -1 +1 @@ -Subproject commit 2fe3bd994b3189899d93f1d5a881e725e046fdc2 +Subproject commit 703bd9caab50b139428cea1aaff9974ebee5742e diff --git a/packages/CLI11/include/CLI/App.hpp b/packages/CLI11/include/CLI/App.hpp index 5fa93f06b89f1fba983cd3e10c4a3cbc5f66ea14..2a2414d13f144299a3276c59a2d132791716c17f 100644 --- a/packages/CLI11/include/CLI/App.hpp +++ b/packages/CLI11/include/CLI/App.hpp @@ -43,12 +43,12 @@ namespace CLI { namespace detail { enum class Classifier { NONE, POSITIONAL_MARK, SHORT, LONG, WINDOWS, SUBCOMMAND, SUBCOMMAND_TERMINATOR }; struct AppFriend; -} // namespace detail +} // namespace detail namespace FailureMessage { std::string simple(const App *app, const Error &e); std::string help(const App *app, const Error &e); -} // namespace FailureMessage +} // namespace FailureMessage /// enumeration of modes of how to deal with extras in config files @@ -247,7 +247,7 @@ class App { Option *config_ptr_{nullptr}; /// This is the formatter for help printing. Default provided. INHERITABLE (same pointer) - std::shared_ptr<Config> config_formatter_{new ConfigINI()}; + std::shared_ptr<Config> config_formatter_{new ConfigTOML()}; ///@} @@ -464,7 +464,7 @@ class App { auto *p = (parent_ != nullptr) ? _get_fallthrough_parent() : this; auto &match = _compare_subcommand_names(*this, *p); if(!match.empty()) { - ignore_case_ = false; // we are throwing so need to be exception invariant + ignore_case_ = false; // we are throwing so need to be exception invariant throw OptionAlreadyAdded("ignore case would cause subcommand name conflicts: " + match); } } @@ -586,7 +586,7 @@ class App { } } // this line should not be reached the above loop should trigger the throw - throw(OptionAlreadyAdded("added option matched existing option name")); // LCOV_EXCL_LINE + throw(OptionAlreadyAdded("added option matched existing option name")); // LCOV_EXCL_LINE } /// Add option for assigning to a variable @@ -594,11 +594,11 @@ class App { typename ConvertTo = AssignTo, enable_if_t<!std::is_const<ConvertTo>::value, detail::enabler> = detail::dummy> Option *add_option(std::string option_name, - AssignTo &variable, ///< The variable to set + AssignTo &variable, ///< The variable to set std::string option_description = "", bool defaulted = false) { - auto fun = [&variable](const CLI::results_t &res) { // comment for spacing + auto fun = [&variable](const CLI::results_t &res) { // comment for spacing return detail::lexical_conversion<AssignTo, ConvertTo>(res, variable); }; @@ -610,21 +610,39 @@ class App { // to structs used in the evaluation can be temporary so that would cause issues. auto Tcount = detail::type_count<AssignTo>::value; auto XCcount = detail::type_count<ConvertTo>::value; - opt->type_size((std::max)(Tcount, XCcount)); + opt->type_size(detail::type_count_min<ConvertTo>::value, (std::max)(Tcount, XCcount)); opt->expected(detail::expected_count<ConvertTo>::value); opt->run_callback_for_default(); return opt; } + /// Add option for assigning to a variable + template <typename AssignTo, enable_if_t<!std::is_const<AssignTo>::value, detail::enabler> = detail::dummy> + Option *add_option_no_stream(std::string option_name, + AssignTo &variable, ///< The variable to set + std::string option_description = "") { + + auto fun = [&variable](const CLI::results_t &res) { // comment for spacing + return detail::lexical_conversion<AssignTo, AssignTo>(res, variable); + }; + + Option *opt = add_option(option_name, fun, option_description, false, []() { return std::string{}; }); + opt->type_name(detail::type_name<AssignTo>()); + opt->type_size(detail::type_count_min<AssignTo>::value, detail::type_count<AssignTo>::value); + opt->expected(detail::expected_count<AssignTo>::value); + opt->run_callback_for_default(); + return opt; + } + /// Add option for a callback of a specific type - template <typename T> + template <typename ArgType> Option *add_option_function(std::string option_name, - const std::function<void(const T &)> &func, ///< the callback to execute + const std::function<void(const ArgType &)> &func, ///< the callback to execute std::string option_description = "") { auto fun = [func](const CLI::results_t &res) { - T variable; - bool result = detail::lexical_conversion<T, T>(res, variable); + ArgType variable; + bool result = detail::lexical_conversion<ArgType, ArgType>(res, variable); if(result) { func(variable); } @@ -632,15 +650,15 @@ class App { }; Option *opt = add_option(option_name, std::move(fun), option_description, false); - opt->type_name(detail::type_name<T>()); - opt->type_size(detail::type_count<T>::value); - opt->expected(detail::expected_count<T>::value); + opt->type_name(detail::type_name<ArgType>()); + opt->type_size(detail::type_count_min<ArgType>::value, detail::type_count<ArgType>::value); + opt->expected(detail::expected_count<ArgType>::value); return opt; } /// Add option with no description or variable assignment Option *add_option(std::string option_name) { - return add_option(option_name, CLI::callback_t(), std::string{}, false); + return add_option(option_name, CLI::callback_t{}, std::string{}, false); } /// Add option with description but with no variable assignment or callback @@ -731,7 +749,7 @@ class App { template <typename T, enable_if_t<std::is_integral<T>::value && !is_bool<T>::value, detail::enabler> = detail::dummy> Option *add_flag(std::string flag_name, - T &flag_count, ///< A variable holding the count + T &flag_count, ///< A variable holding the count std::string flag_description = "") { flag_count = 0; CLI::callback_t fun = [&flag_count](const CLI::results_t &res) { @@ -749,12 +767,12 @@ class App { /// Other type version accepts all other types that are not vectors such as bool, enum, string or other classes /// that can be converted from a string template <typename T, - enable_if_t<!is_vector<T>::value && !std::is_const<T>::value && + enable_if_t<!detail::is_mutable_container<T>::value && !std::is_const<T>::value && (!std::is_integral<T>::value || is_bool<T>::value) && !std::is_constructible<std::function<void(int)>, T>::value, detail::enabler> = detail::dummy> Option *add_flag(std::string flag_name, - T &flag_result, ///< A variable holding true if passed + T &flag_result, ///< A variable holding true if passed std::string flag_description = "") { CLI::callback_t fun = [&flag_result](const CLI::results_t &res) { @@ -768,7 +786,7 @@ class App { typename T, enable_if_t<!std::is_assignable<std::function<void(std::int64_t)>, T>::value, detail::enabler> = detail::dummy> Option *add_flag(std::string flag_name, - std::vector<T> &flag_results, ///< A vector of values with the flag results + std::vector<T> &flag_results, ///< A vector of values with the flag results std::string flag_description = "") { CLI::callback_t fun = [&flag_results](const CLI::results_t &res) { bool retval = true; @@ -785,7 +803,7 @@ class App { /// Add option for callback that is triggered with a true flag and takes no arguments Option *add_flag_callback(std::string flag_name, - std::function<void(void)> function, ///< A function to call, void(void) + std::function<void(void)> function, ///< A function to call, void(void) std::string flag_description = "") { CLI::callback_t fun = [function](const CLI::results_t &res) { @@ -801,7 +819,7 @@ class App { /// Add option for callback with an integer value Option *add_flag_function(std::string flag_name, - std::function<void(std::int64_t)> function, ///< A function to call, void(int) + std::function<void(std::int64_t)> function, ///< A function to call, void(int) std::string flag_description = "") { CLI::callback_t fun = [function](const CLI::results_t &res) { @@ -817,7 +835,7 @@ class App { #ifdef CLI11_CPP14 /// Add option for callback (C++14 or better only) Option *add_flag(std::string flag_name, - std::function<void(std::int64_t)> function, ///< A function to call, void(std::int64_t) + std::function<void(std::int64_t)> function, ///< A function to call, void(std::int64_t) std::string flag_description = "") { return add_flag_function(std::move(flag_name), std::move(function), std::move(flag_description)); } @@ -826,8 +844,8 @@ class App { /// Add set of options (No default, temp reference, such as an inline set) DEPRECATED template <typename T> Option *add_set(std::string option_name, - T &member, ///< The selected member of the set - std::set<T> options, ///< The set of possibilities + T &member, ///< The selected member of the set + std::set<T> options, ///< The set of possibilities std::string option_description = "") { Option *opt = add_option(option_name, member, std::move(option_description)); @@ -838,8 +856,8 @@ class App { /// Add set of options (No default, set can be changed afterwards - do not destroy the set) DEPRECATED template <typename T> Option *add_mutable_set(std::string option_name, - T &member, ///< The selected member of the set - const std::set<T> &options, ///< The set of possibilities + T &member, ///< The selected member of the set + const std::set<T> &options, ///< The set of possibilities std::string option_description = "") { Option *opt = add_option(option_name, member, std::move(option_description)); @@ -850,8 +868,8 @@ class App { /// Add set of options (with default, static set, such as an inline set) DEPRECATED template <typename T> Option *add_set(std::string option_name, - T &member, ///< The selected member of the set - std::set<T> options, ///< The set of possibilities + T &member, ///< The selected member of the set + std::set<T> options, ///< The set of possibilities std::string option_description, bool defaulted) { @@ -863,8 +881,8 @@ class App { /// Add set of options (with default, set can be changed afterwards - do not destroy the set) DEPRECATED template <typename T> Option *add_mutable_set(std::string option_name, - T &member, ///< The selected member of the set - const std::set<T> &options, ///< The set of possibilities + T &member, ///< The selected member of the set + const std::set<T> &options, ///< The set of possibilities std::string option_description, bool defaulted) { @@ -873,7 +891,7 @@ class App { return opt; } - /// Add a complex number + /// Add a complex number DEPRECATED --use add_option instead template <typename T, typename XC = double> Option *add_complex(std::string option_name, T &variable, @@ -932,7 +950,7 @@ class App { // Remove existing config if present if(config_ptr_ != nullptr) { remove_option(config_ptr_); - config_ptr_ = nullptr; // need to remove the config_ptr completely + config_ptr_ = nullptr; // need to remove the config_ptr completely } // Only add config if option passed @@ -984,7 +1002,7 @@ class App { } ///@} - /// @name Subcommmands + /// @name Subcommands ///@{ /// Add a subcommand. Inherits INHERITABLE and OptionDefaults, and help flag @@ -1107,7 +1125,7 @@ class App { for(auto &sub : subcommands_) { cnt += sub->count_all(); } - if(!get_name().empty()) { // for named subcommands add the number of times the subcommand was called + if(!get_name().empty()) { // for named subcommands add the number of times the subcommand was called cnt += parsed_; } return cnt; @@ -1246,8 +1264,9 @@ class App { name_ = nstr.first; } commandline = std::move(nstr.second); - } else + } else { detail::trim(commandline); + } // the next section of code is to deal with quoted arguments after an '=' or ':' for windows like operations if(!commandline.empty()) { commandline = detail::find_and_modify(commandline, "=", detail::escape_detect); @@ -1490,7 +1509,7 @@ class App { return this; } /// Produce a string that could be read in as a config of the current values of the App. Set default_also to - /// include default arguments. Prefix will add a string to the beginning of each option. + /// include default arguments. write_descriptions will print a description for the App and for each option. std::string config_to_str(bool default_also = false, bool write_description = false) const { return config_formatter_->to_config(this, default_also, write_description, ""); } @@ -1880,7 +1899,7 @@ class App { app->name_.clear(); } if(app->name_.empty()) { - app->fallthrough_ = false; // make sure fallthrough_ is false to prevent infinite loop + app->fallthrough_ = false; // make sure fallthrough_ is false to prevent infinite loop app->prefix_command_ = false; } // make sure the parent is set to be this object in preparation for parse @@ -2334,6 +2353,14 @@ class App { return true; } Option *op = get_option_no_throw("--" + item.name); + if(op == nullptr) { + if(item.name.size() == 1) { + op = get_option_no_throw("-" + item.name); + } + } + if(op == nullptr) { + op = get_option_no_throw(item.name); + } if(op == nullptr) { // If the option was not present if(get_allow_config_extras() == config_extras_mode::capture) @@ -2651,19 +2678,24 @@ class App { // Get a reference to the pointer to make syntax bearable Option_p &op = *op_ptr; - + /// if we require a separator add it here + if(op->get_inject_separator()) { + if(!op->results().empty() && !op->results().back().empty()) { + op->add_result(std::string{}); + } + } int min_num = (std::min)(op->get_type_size_min(), op->get_items_expected_min()); int max_num = op->get_items_expected_max(); // Make sure we always eat the minimum for unlimited vectors - int collected = 0; // total number of arguments collected - int result_count = 0; // local variable for number of results in a single arg string + int collected = 0; // total number of arguments collected + int result_count = 0; // local variable for number of results in a single arg string // deal with purely flag like things if(max_num == 0) { auto res = op->get_flag_value(arg_name, value); op->add_result(res); parse_order_.push_back(op.get()); - } else if(!value.empty()) { // --this=value + } else if(!value.empty()) { // --this=value op->add_result(value, result_count); parse_order_.push_back(op.get()); collected += result_count; @@ -2684,11 +2716,11 @@ class App { collected += result_count; } - if(min_num > collected) { // if we have run out of arguments and the minimum was not met + if(min_num > collected) { // if we have run out of arguments and the minimum was not met throw ArgumentMismatch::TypedAtLeast(op->get_name(), min_num, op->get_type_name()); } - if(max_num > collected || op->get_allow_extra_args()) { // we allow optional arguments + if(max_num > collected || op->get_allow_extra_args()) { // we allow optional arguments auto remreqpos = _count_remaining_positionals(true); // we have met the minimum now optionally check up to the maximum while((collected < max_num || op->get_allow_extra_args()) && !args.empty() && @@ -2716,7 +2748,7 @@ class App { } // if we only partially completed a type then add an empty string for later processing - if(min_num > 0 && op->get_type_size_max() != min_num && collected % op->get_type_size_max() != 0) { + if(min_num > 0 && op->get_type_size_max() != min_num && (collected % op->get_type_size_max()) != 0) { op->add_result(std::string{}); } @@ -2865,7 +2897,7 @@ class App { throw OptionNotFound("could not locate the given Option"); } } -}; // namespace CLI +}; // namespace CLI /// Extension of App to better manage groups of options class Option_group : public App { @@ -3049,7 +3081,7 @@ inline std::string help(const App *app, const Error &e) { return header; } -} // namespace FailureMessage +} // namespace FailureMessage namespace detail { /// This class is simply to allow tests access to App's protected functions @@ -3071,6 +3103,6 @@ struct AppFriend { /// Wrap the fallthrough parent function to make sure that is working correctly static App *get_fallthrough_parent(App *app) { return app->_get_fallthrough_parent(); } }; -} // namespace detail +} // namespace detail -} // namespace CLI +} // namespace CLI diff --git a/packages/CLI11/include/CLI/Config.hpp b/packages/CLI11/include/CLI/Config.hpp index eafb6218a51609d268dfd119fe7d6e7dbcfa7694..8ef40552ecd3027e76bee6be19f825c753618318 100644 --- a/packages/CLI11/include/CLI/Config.hpp +++ b/packages/CLI11/include/CLI/Config.hpp @@ -160,17 +160,18 @@ inline void checkParentSegments(std::vector<ConfigItem> &output, const std::stri output.back().parents = std::move(parents); output.back().name = "++"; } -} // namespace detail +} // namespace detail inline std::vector<ConfigItem> ConfigBase::from_config(std::istream &input) const { std::string line; std::string section = "default"; std::vector<ConfigItem> output; - bool defaultArray = (arrayStart == '\0' || arrayStart == ' ') && arrayStart == arrayEnd; - char aStart = (defaultArray) ? '[' : arrayStart; - char aEnd = (defaultArray) ? ']' : arrayEnd; - char aSep = (defaultArray && arraySeparator == ' ') ? ',' : arraySeparator; + bool isDefaultArray = (arrayStart == '[' && arrayEnd == ']' && arraySeparator == ','); + bool isINIArray = (arrayStart == '\0' || arrayStart == ' ') && arrayStart == arrayEnd; + char aStart = (isINIArray) ? '[' : arrayStart; + char aEnd = (isINIArray) ? ']' : arrayEnd; + char aSep = (isINIArray && arraySeparator == ' ') ? ',' : arraySeparator; while(getline(input, line)) { std::vector<std::string> items_buffer; @@ -212,9 +213,9 @@ inline std::vector<ConfigItem> ConfigBase::from_config(std::istream &input) cons std::string item = detail::trim_copy(line.substr(pos + 1)); if(item.size() > 1 && item.front() == aStart && item.back() == aEnd) { items_buffer = detail::split_up(item.substr(1, item.length() - 2), aSep); - } else if(defaultArray && item.find_first_of(aSep) != std::string::npos) { + } else if((isDefaultArray || isINIArray) && item.find_first_of(aSep) != std::string::npos) { items_buffer = detail::split_up(item, aSep); - } else if(defaultArray && item.find_first_of(' ') != std::string::npos) { + } else if((isDefaultArray || isINIArray) && item.find_first_of(' ') != std::string::npos) { items_buffer = detail::split_up(item); } else { items_buffer = {item}; @@ -281,14 +282,14 @@ ConfigBase::to_config(const App *app, bool default_also, bool write_description, } for(const Option *opt : app->get_options({})) { - // Only process option with a long-name and configurable - if(!opt->get_lnames().empty() && opt->get_configurable()) { + // Only process options that are configurable + if(opt->get_configurable()) { if(opt->get_group() != group) { if(!(group == "Options" && opt->get_group().empty())) { continue; } } - std::string name = prefix + opt->get_lnames()[0]; + std::string name = prefix + opt->get_single_name(); std::string value = detail::ini_join(opt->reduced_results(), arraySeparator, arrayStart, arrayEnd); if(value.empty() && default_also) { @@ -343,4 +344,4 @@ ConfigBase::to_config(const App *app, bool default_also, bool write_description, return out.str(); } -} // namespace CLI +} // namespace CLI diff --git a/packages/CLI11/include/CLI/ConfigFwd.hpp b/packages/CLI11/include/CLI/ConfigFwd.hpp index 369458fd2184e2124e49a697b47b093b39502897..49ca1b288b9349befbc8757232fd003e96d02c70 100644 --- a/packages/CLI11/include/CLI/ConfigFwd.hpp +++ b/packages/CLI11/include/CLI/ConfigFwd.hpp @@ -71,17 +71,17 @@ class Config { virtual ~Config() = default; }; -/// This converter works with INI/TOML files; to write proper TOML files use ConfigTOML +/// This converter works with INI/TOML files; to write INI files use ConfigINI class ConfigBase : public Config { protected: /// the character used for comments - char commentChar = ';'; + char commentChar = '#'; /// the character used to start an array '\0' is a default to not use - char arrayStart = '\0'; + char arrayStart = '['; /// the character used to end an array '\0' is a default to not use - char arrayEnd = '\0'; + char arrayEnd = ']'; /// the character used to separate elements in an array - char arraySeparator = ' '; + char arraySeparator = ','; /// the character used separate the name from the value char valueDelimiter = '='; @@ -113,19 +113,19 @@ class ConfigBase : public Config { } }; -/// the default Config is the INI file format -using ConfigINI = ConfigBase; +/// the default Config is the TOML file format +using ConfigTOML = ConfigBase; -/// ConfigTOML generates a TOML compliant output -class ConfigTOML : public ConfigINI { +/// ConfigINI generates a "standard" INI compliant output +class ConfigINI : public ConfigTOML { public: - ConfigTOML() { - commentChar = '#'; - arrayStart = '['; - arrayEnd = ']'; - arraySeparator = ','; + ConfigINI() { + commentChar = ';'; + arrayStart = '\0'; + arrayEnd = '\0'; + arraySeparator = ' '; valueDelimiter = '='; } }; -} // namespace CLI +} // namespace CLI diff --git a/packages/CLI11/include/CLI/Error.hpp b/packages/CLI11/include/CLI/Error.hpp index 6d09c973ead31e67a3c04fdebe3a83a0d0ef190e..0adf15c50454a6188ec8e6897fa14930bb87aa84 100644 --- a/packages/CLI11/include/CLI/Error.hpp +++ b/packages/CLI11/include/CLI/Error.hpp @@ -246,11 +246,11 @@ class RequiredError : public ParseError { class ArgumentMismatch : public ParseError { CLI11_ERROR_DEF(ParseError, ArgumentMismatch) CLI11_ERROR_SIMPLE(ArgumentMismatch) - ArgumentMismatch(std::string name, int expected, std::size_t recieved) + ArgumentMismatch(std::string name, int expected, std::size_t received) : ArgumentMismatch(expected > 0 ? ("Expected exactly " + std::to_string(expected) + " arguments to " + name + - ", got " + std::to_string(recieved)) + ", got " + std::to_string(received)) : ("Expected at least " + std::to_string(-expected) + " arguments to " + name + - ", got " + std::to_string(recieved)), + ", got " + std::to_string(received)), ExitCodes::ArgumentMismatch) {} static ArgumentMismatch AtLeast(std::string name, int num, std::size_t received) { @@ -337,4 +337,4 @@ class OptionNotFound : public Error { /// @} -} // namespace CLI +} // namespace CLI diff --git a/packages/CLI11/include/CLI/Formatter.hpp b/packages/CLI11/include/CLI/Formatter.hpp index 748fcb36f32c60ea3091ee9f7998bda974d88352..6ac4e4d1a090fbd5d9cafcb9755123929b6c5a90 100644 --- a/packages/CLI11/include/CLI/Formatter.hpp +++ b/packages/CLI11/include/CLI/Formatter.hpp @@ -44,11 +44,11 @@ inline std::string Formatter::make_groups(const App *app, AppFormatMode mode) co // Options for(const std::string &group : groups) { std::vector<const Option *> opts = app->get_options([app, mode, &group](const Option *opt) { - return opt->get_group() == group // Must be in the right group - && opt->nonpositional() // Must not be a positional - && (mode != AppFormatMode::Sub // If mode is Sub, then - || (app->get_help_ptr() != opt // Ignore help pointer - && app->get_help_all_ptr() != opt)); // Ignore help all pointer + return opt->get_group() == group // Must be in the right group + && opt->nonpositional() // Must not be a positional + && (mode != AppFormatMode::Sub // If mode is Sub, then + || (app->get_help_ptr() != opt // Ignore help pointer + && app->get_help_all_ptr() != opt)); // Ignore help all pointer }); if(!group.empty() && !opts.empty()) { out << make_group(group, false, opts); @@ -220,7 +220,7 @@ inline std::string Formatter::make_expanded(const App *sub) const { // Drop blank spaces std::string tmp = detail::find_and_replace(out.str(), "\n\n", "\n"); - tmp = tmp.substr(0, tmp.size() - 1); // Remove the final '\n' + tmp = tmp.substr(0, tmp.size() - 1); // Remove the final '\n' // Indent all but the first line (the name) return detail::find_and_replace(tmp, "\n", "\n ") + "\n"; @@ -278,4 +278,4 @@ inline std::string Formatter::make_option_usage(const Option *opt) const { return opt->get_required() ? out.str() : "[" + out.str() + "]"; } -} // namespace CLI +} // namespace CLI diff --git a/packages/CLI11/include/CLI/FormatterFwd.hpp b/packages/CLI11/include/CLI/FormatterFwd.hpp index 1dda9e1c88b6580f4c17c6776d7c9dc64f227ed3..4c4a5de05652373fe54c5e608bd5d7aeecac699c 100644 --- a/packages/CLI11/include/CLI/FormatterFwd.hpp +++ b/packages/CLI11/include/CLI/FormatterFwd.hpp @@ -24,9 +24,9 @@ class App; /// the second argument. enum class AppFormatMode { - Normal, //< The normal, detailed help - All, //< A fully expanded help - Sub, //< Used when printed as part of expanded subcommand + Normal, ///< The normal, detailed help + All, ///< A fully expanded help + Sub, ///< Used when printed as part of expanded subcommand }; /// This is the minimum requirements to run a formatter. @@ -55,7 +55,7 @@ class FormatterBase { FormatterBase(FormatterBase &&) = default; /// Adding a destructor in this form to work around bug in GCC 4.7 - virtual ~FormatterBase() noexcept {} // NOLINT(modernize-use-equals-default) + virtual ~FormatterBase() noexcept {} // NOLINT(modernize-use-equals-default) /// This is the key method that puts together help virtual std::string make_help(const App *, std::string, AppFormatMode) const = 0; @@ -100,7 +100,7 @@ class FormatterLambda final : public FormatterBase { explicit FormatterLambda(funct_t funct) : lambda_(std::move(funct)) {} /// Adding a destructor (mostly to make GCC 4.7 happy) - ~FormatterLambda() noexcept override {} // NOLINT(modernize-use-equals-default) + ~FormatterLambda() noexcept override {} // NOLINT(modernize-use-equals-default) /// This will simply call the lambda function std::string make_help(const App *app, std::string name, AppFormatMode mode) const override { @@ -177,4 +177,4 @@ class Formatter : public FormatterBase { ///@} }; -} // namespace CLI +} // namespace CLI diff --git a/packages/CLI11/include/CLI/Option.hpp b/packages/CLI11/include/CLI/Option.hpp index c1ab02397a3f46043d6e6a9a154138ee2902d67e..911dd64b213536adfa942a293b70d70d5a81aff9 100644 --- a/packages/CLI11/include/CLI/Option.hpp +++ b/packages/CLI11/include/CLI/Option.hpp @@ -33,11 +33,11 @@ class App; using Option_p = std::unique_ptr<Option>; /// Enumeration of the multiOption Policy selection enum class MultiOptionPolicy : char { - Throw, //!< Throw an error if any extra arguments were given - TakeLast, //!< take only the last Expected number of arguments - TakeFirst, //!< take only the first Expected number of arguments - Join, //!< merge all the arguments together into a single string via the delimiter character default('\n') - TakeAll //!< just get all the passed argument regardless + Throw, //!< Throw an error if any extra arguments were given + TakeLast, //!< take only the last Expected number of arguments + TakeFirst, //!< take only the first Expected number of arguments + Join, //!< merge all the arguments together into a single string via the delimiter character default('\n') + TakeAll //!< just get all the passed argument regardless }; /// This is the CRTP base class for Option and OptionDefaults. It was designed this way @@ -300,7 +300,7 @@ class Option : public OptionBase<Option> { /// @name Other ///@{ - /// Remember the parent app + /// link back up to the parent App for fallthrough App *parent_{nullptr}; /// Options store a callback to do all the work @@ -315,11 +315,11 @@ class Option : public OptionBase<Option> { /// results after reduction results_t proc_results_{}; /// enumeration for the option state machine - enum class option_state { - parsing = 0, //!< The option is currently collecting parsed results - validated = 2, //!< the results have been validated - reduced = 4, //!< a subset of results has been generated - callback_run = 6, //!< the callback has been executed + enum class option_state : char { + parsing = 0, //!< The option is currently collecting parsed results + validated = 2, //!< the results have been validated + reduced = 4, //!< a subset of results has been generated + callback_run = 6, //!< the callback has been executed }; /// Whether the callback has run (needed for INI parsing) option_state current_option_state_{option_state::parsing}; @@ -329,6 +329,8 @@ class Option : public OptionBase<Option> { bool flag_like_{false}; /// Control option to run the callback to set the default bool run_callback_for_default_{false}; + /// flag indicating a separator needs to be injected after each argument call + bool inject_separator_{false}; ///@} /// Making an option by hand is not defined, it must be made by the App class @@ -631,8 +633,8 @@ class Option : public OptionBase<Option> { Option *multi_option_policy(MultiOptionPolicy value = MultiOptionPolicy::Throw) { if(value != multi_option_policy_) { if(multi_option_policy_ == MultiOptionPolicy::Throw && expected_max_ == detail::expected_max_vector_size && - expected_min_ > 1) { // this bizarre condition is to maintain backwards compatibility - // with the previous behavior of expected_ with vectors + expected_min_ > 1) { // this bizarre condition is to maintain backwards compatibility + // with the previous behavior of expected_ with vectors expected_max_ = expected_min_; } multi_option_policy_ = value; @@ -658,6 +660,9 @@ class Option : public OptionBase<Option> { /// The maximum number of arguments the option expects int get_type_size_max() const { return type_size_max_; } + /// The number of arguments the option expects + int get_inject_separator() const { return inject_separator_; } + /// The environment variable associated to this value std::string get_envname() const { return envname_; } @@ -681,7 +686,19 @@ class Option : public OptionBase<Option> { /// Get the flag names with specified default values const std::vector<std::string> &get_fnames() const { return fnames_; } - + /// Get a single name for the option, first of lname, pname, sname, envname + const std::string &get_single_name() const { + if(!lnames_.empty()) { + return lnames_[0]; + } + if(!pname_.empty()) { + return pname_; + } + if(!snames_.empty()) { + return snames_[0]; + } + return envname_; + } /// The number of times the option expects to be included int get_expected() const { return expected_min_; } @@ -727,11 +744,11 @@ class Option : public OptionBase<Option> { /// Will include / prefer the positional name if positional is true. /// If all_options is false, pick just the most descriptive name to show. /// Use `get_name(true)` to get the positional name (replaces `get_pname`) - std::string get_name(bool positional = false, //<[input] Show the positional name - bool all_options = false //<[input] Show every option + std::string get_name(bool positional = false, ///< Show the positional name + bool all_options = false ///< Show every option ) const { if(get_group().empty()) - return {}; // Hidden + return {}; // Hidden if(all_options) { @@ -822,7 +839,7 @@ class Option : public OptionBase<Option> { return lname; if(ignore_case_ || - ignore_underscore_) { // We need to do the inverse, in case we are ignore_case or ignore underscore + ignore_underscore_) { // We need to do the inverse, in case we are ignore_case or ignore underscore for(const std::string &sname : other.snames_) if(check_sname(sname)) return sname; @@ -836,23 +853,33 @@ class Option : public OptionBase<Option> { bool operator==(const Option &other) const { return !matching_name(other).empty(); } /// Check a name. Requires "-" or "--" for short / long, supports positional name - bool check_name(std::string name) const { + bool check_name(const std::string &name) const { if(name.length() > 2 && name[0] == '-' && name[1] == '-') return check_lname(name.substr(2)); if(name.length() > 1 && name.front() == '-') return check_sname(name.substr(1)); - - std::string local_pname = pname_; - if(ignore_underscore_) { - local_pname = detail::remove_underscore(local_pname); - name = detail::remove_underscore(name); + if(!pname_.empty()) { + std::string local_pname = pname_; + std::string local_name = name; + if(ignore_underscore_) { + local_pname = detail::remove_underscore(local_pname); + local_name = detail::remove_underscore(local_name); + } + if(ignore_case_) { + local_pname = detail::to_lower(local_pname); + local_name = detail::to_lower(local_name); + } + if(local_name == local_pname) { + return true; + } } - if(ignore_case_) { - local_pname = detail::to_lower(local_pname); - name = detail::to_lower(name); + + if(!envname_.empty()) { + // this needs to be the original since envname_ shouldn't match on case insensitivity + return (name == envname_); } - return name == local_pname; + return false; } /// Requires "-" to be removed from string @@ -941,8 +968,8 @@ class Option : public OptionBase<Option> { return this; } - /// Get a copy of the results - results_t results() const { return results_; } + /// Get the current complete results set + const results_t &results() const { return results_; } /// Get a copy of the results results_t reduced_results() const { @@ -964,8 +991,7 @@ class Option : public OptionBase<Option> { } /// Get the results as a specified type - template <typename T, enable_if_t<!std::is_const<T>::value, detail::enabler> = detail::dummy> - void results(T &output) const { + template <typename T> void results(T &output) const { bool retval; if(current_option_state_ >= option_state::reduced || (results_.size() == 1 && validators_.empty())) { const results_t &res = (proc_results_.empty()) ? results_ : proc_results_; @@ -974,7 +1000,7 @@ class Option : public OptionBase<Option> { results_t res; if(results_.empty()) { if(!default_str_.empty()) { - //_add_results takes an rvalue only + // _add_results takes an rvalue only _add_result(std::string(default_str_), res); _validate_results(res); results_t extra; @@ -1032,6 +1058,8 @@ class Option : public OptionBase<Option> { type_size_max_ = option_type_size; if(type_size_max_ < detail::expected_max_vector_size) { type_size_min_ = option_type_size; + } else { + inject_separator_ = true; } if(type_size_max_ == 0) required_ = false; @@ -1057,9 +1085,15 @@ class Option : public OptionBase<Option> { if(type_size_max_ == 0) { required_ = false; } + if(type_size_max_ >= detail::expected_max_vector_size) { + inject_separator_ = true; + } return this; } + /// Set the value of the separator injection flag + void inject_separator(bool value = true) { inject_separator_ = value; } + /// Set a capture function for the default. Mostly used by App. Option *default_function(const std::function<std::string()> &func) { default_function_ = func; @@ -1090,7 +1124,7 @@ class Option : public OptionBase<Option> { try { add_result(val_str); if(run_callback_for_default_) { - run_callback(); // run callback sets the state we need to reset it again + run_callback(); // run callback sets the state we need to reset it again current_option_state_ = option_state::parsing; } else { _validate_results(results_); @@ -1126,7 +1160,7 @@ class Option : public OptionBase<Option> { void _validate_results(results_t &res) const { // Run the Validators (can change the string) if(!validators_.empty()) { - if(type_size_max_ > 1) { // in this context index refers to the index in the type + if(type_size_max_ > 1) { // in this context index refers to the index in the type int index = 0; if(get_items_expected_max() < static_cast<int>(res.size()) && multi_option_policy_ == CLI::MultiOptionPolicy::TakeLast) { @@ -1135,8 +1169,8 @@ class Option : public OptionBase<Option> { } for(std::string &result : res) { - if(result.empty() && type_size_max_ != type_size_min_ && index >= 0) { - index = 0; // reset index for variable size chunks + if(detail::is_separator(result) && type_size_max_ != type_size_min_ && index >= 0) { + index = 0; // reset index for variable size chunks continue; } auto err_msg = _validate(result, (index >= 0) ? (index % type_size_max_) : index); @@ -1242,7 +1276,7 @@ class Option : public OptionBase<Option> { int _add_result(std::string &&result, std::vector<std::string> &res) const { int result_count = 0; if(allow_extra_args_ && !result.empty() && result.front() == '[' && - result.back() == ']') { // this is now a vector string likely from the default or user entry + result.back() == ']') { // this is now a vector string likely from the default or user entry result.pop_back(); for(auto &var : CLI::detail::split(result.substr(1), ',')) { @@ -1270,6 +1304,6 @@ class Option : public OptionBase<Option> { } return result_count; } -}; // namespace CLI +}; // namespace CLI -} // namespace CLI +} // namespace CLI diff --git a/packages/CLI11/include/CLI/Split.hpp b/packages/CLI11/include/CLI/Split.hpp index 3994c277f23b0faace7692cbfa5f8194d9f9d892..7e534732d5a5ac936363454e93e7d17139682d1d 100644 --- a/packages/CLI11/include/CLI/Split.hpp +++ b/packages/CLI11/include/CLI/Split.hpp @@ -134,5 +134,5 @@ get_names(const std::vector<std::string> &input) { short_names, long_names, pos_name); } -} // namespace detail -} // namespace CLI +} // namespace detail +} // namespace CLI diff --git a/packages/CLI11/include/CLI/StringTools.hpp b/packages/CLI11/include/CLI/StringTools.hpp index 903af1087ce3588c7ce5e157d937b1fb46f33f65..cd861ba2dbb436fa45af03a63d10a810fd1082e8 100644 --- a/packages/CLI11/include/CLI/StringTools.hpp +++ b/packages/CLI11/include/CLI/StringTools.hpp @@ -28,7 +28,7 @@ std::ostream &operator<<(std::ostream &in, const T &item) { return in << static_cast<typename std::underlying_type<T>::type>(item); } -} // namespace enums +} // namespace enums /// Export to CLI namespace using enums::operator<<; @@ -42,9 +42,9 @@ constexpr int expected_max_vector_size{1 << 29}; inline std::vector<std::string> split(const std::string &s, char delim) { std::vector<std::string> elems; // Check to see if empty string, give consistent result - if(s.empty()) + if(s.empty()) { elems.emplace_back(); - else { + } else { std::stringstream ss; ss.str(s); std::string item; @@ -190,6 +190,12 @@ inline bool valid_name_string(const std::string &str) { return true; } +/// check if a string is a container segment separator (empty or "%%" +inline bool is_separator(const std::string &str) { + static const std::string sep("%%"); + return (str.empty() || str == sep); +} + /// Verify that str consists of letters only inline bool isalpha(const std::string &str) { return std::all_of(str.begin(), str.end(), [](char c) { return std::isalpha(c, std::locale()); }); @@ -264,8 +270,9 @@ inline std::ptrdiff_t find_member(std::string name, it = std::find_if(std::begin(names), std::end(names), [&name](std::string local_name) { return detail::remove_underscore(local_name) == name; }); - } else + } else { it = std::find(std::begin(names), std::end(names), name); + } return (it != std::end(names)) ? (it - std::begin(names)) : (-1); } @@ -297,7 +304,7 @@ inline std::vector<std::string> split_up(std::string str, char delimiter = '\0') if(delims.find_first_of(str[0]) != std::string::npos) { keyChar = str[0]; auto end = str.find_first_of(keyChar, 1); - while((end != std::string::npos) && (str[end - 1] == '\\')) { // deal with escaped quotes + while((end != std::string::npos) && (str[end - 1] == '\\')) { // deal with escaped quotes end = str.find_first_of(keyChar, end + 1); embeddedQuote = true; } @@ -355,7 +362,7 @@ inline std::size_t escape_detect(std::string &str, std::size_t offset) { auto astart = str.find_last_of("-/ \"\'`", offset - 1); if(astart != std::string::npos) { if(str[astart] == ((str[offset] == '=') ? '-' : '/')) - str[offset] = ' '; // interpret this as a space so the split_up works properly + str[offset] = ' '; // interpret this as a space so the split_up works properly } } return offset + 1; @@ -373,6 +380,6 @@ inline std::string &add_quotes_if_needed(std::string &str) { return str; } -} // namespace detail +} // namespace detail -} // namespace CLI +} // namespace CLI diff --git a/packages/CLI11/include/CLI/Timer.hpp b/packages/CLI11/include/CLI/Timer.hpp index 1177bc13e0349baa359ea718b3823bc7c4561592..69728b7dc89a4df2bed42755730a1b9f9184465a 100644 --- a/packages/CLI11/include/CLI/Timer.hpp +++ b/packages/CLI11/include/CLI/Timer.hpp @@ -13,7 +13,7 @@ #endif #include <array> -#include <chrono> // NOLINT(build/c++11) +#include <chrono> #include <functional> #include <iostream> #include <string> @@ -128,7 +128,7 @@ class AutoTimer : public Timer { ~AutoTimer() { std::cout << to_string() << std::endl; } }; -} // namespace CLI +} // namespace CLI /// This prints out the time if shifted into a std::cout like stream. inline std::ostream &operator<<(std::ostream &in, const CLI::Timer &timer) { return in << timer.to_string(); } diff --git a/packages/CLI11/include/CLI/TypeTools.hpp b/packages/CLI11/include/CLI/TypeTools.hpp index 115235bd18c1c3e26265e1071988f943e518c945..a4297b2ff866c21202ad6703a669e941ede8b5f4 100644 --- a/packages/CLI11/include/CLI/TypeTools.hpp +++ b/packages/CLI11/include/CLI/TypeTools.hpp @@ -27,7 +27,7 @@ enum class enabler {}; /// An instance to use in EnableIf constexpr enabler dummy = {}; -} // namespace detail +} // namespace detail /// A copy of enable_if_t from C++14, compatible with C++11. /// @@ -45,15 +45,6 @@ template <typename... Ts> using void_t = typename make_void<Ts...>::type; /// A copy of std::conditional_t from C++14 - same reasoning as enable_if_t, it does not hurt to redefine template <bool B, class T, class F> using conditional_t = typename std::conditional<B, T, F>::type; -/// Check to see if something is a vector (fail check by default) -template <typename T> struct is_vector : std::false_type {}; - -/// Check to see if something is a vector (true if actually a vector) -template <class T, class A> struct is_vector<std::vector<T, A>> : std::true_type {}; - -/// Check to see if something is a vector (true if actually a const vector) -template <class T, class A> struct is_vector<const std::vector<T, A>> : std::true_type {}; - /// Check to see if something is bool (fail check by default) template <typename T> struct is_bool : std::false_type {}; @@ -195,6 +186,17 @@ template <typename T, typename S = std::istringstream> class is_istreamable { static constexpr bool value = decltype(test<T, S>(0))::value; }; +/// Check for complex +template <typename T> class is_complex { + template <typename TT> + static auto test(int) -> decltype(std::declval<TT>().real(), std::declval<TT>().imag(), std::true_type()); + + template <typename> static auto test(...) -> std::false_type; + + public: + static constexpr bool value = decltype(test<T>(0))::value; +}; + /// Templated operation to get a value from a stream template <typename T, enable_if_t<is_istreamable<T>::value, detail::enabler> = detail::dummy> bool from_stream(const std::string &istring, T &obj) { @@ -209,12 +211,49 @@ bool from_stream(const std::string & /*istring*/, T & /*obj*/) { return false; } +// check to see if an object is a mutable container (fail by default) +template <typename T, typename _ = void> struct is_mutable_container : std::false_type {}; + +/// type trait to test if a type is a mutable container meaning it has a value_type, it has an iterator, a clear, and +/// end methods and an insert function. And for our purposes we exclude std::string and types that can be constructed +/// from a std::string +template <typename T> +struct is_mutable_container< + T, + conditional_t<false, + void_t<typename T::value_type, + decltype(std::declval<T>().end()), + decltype(std::declval<T>().clear()), + decltype(std::declval<T>().insert(std::declval<decltype(std::declval<T>().end())>(), + std::declval<const typename T::value_type &>()))>, + void>> + : public conditional_t<std::is_constructible<T, std::string>::value, std::false_type, std::true_type> {}; + +// check to see if an object is a mutable container (fail by default) +template <typename T, typename _ = void> struct is_readable_container : std::false_type {}; + +/// type trait to test if a type is a container meaning it has a value_type, it has an iterator, a clear, and an en end +/// methods and an insert function. And for our purposes we exclude std::string and types that can be constructed from +/// a std::string +template <typename T> +struct is_readable_container< + T, + conditional_t<false, void_t<decltype(std::declval<T>().end()), decltype(std::declval<T>().begin())>, void>> + : public std::true_type {}; + +// check to see if an object is a wrapper (fail by default) +template <typename T, typename _ = void> struct is_wrapper : std::false_type {}; + +// check if an object is a is a wrapper (it has a value_type defined) +template <typename T> +struct is_wrapper<T, conditional_t<false, void_t<typename T::value_type>, void>> : public std::true_type {}; + // Check for tuple like types, as in classes with a tuple_size type trait template <typename S> class is_tuple_like { template <typename SS> // static auto test(int) // -> decltype(std::conditional<(std::tuple_size<SS>::value > 0), std::true_type, std::false_type>::type()); - static auto test(int) -> decltype(std::tuple_size<SS>::value, std::true_type{}); + static auto test(int) -> decltype(std::tuple_size<typename std::decay<SS>::type>::value, std::true_type{}); template <typename> static auto test(...) -> std::false_type; public: @@ -249,20 +288,19 @@ std::string to_string(T &&value) { /// If conversion is not supported, return an empty string (streaming is not supported for that type) template <typename T, enable_if_t<!std::is_constructible<std::string, T>::value && !is_ostreamable<T>::value && - !is_vector<typename std::remove_reference<typename std::remove_const<T>::type>::type>::value, + !is_readable_container<typename std::remove_const<T>::type>::value, detail::enabler> = detail::dummy> std::string to_string(T &&) { return std::string{}; } -/// convert a vector to a string +/// convert a readable container to a string template <typename T, enable_if_t<!std::is_constructible<std::string, T>::value && !is_ostreamable<T>::value && - is_vector<typename std::remove_reference<typename std::remove_const<T>::type>::type>::value, + is_readable_container<T>::value, detail::enabler> = detail::dummy> std::string to_string(T &&variable) { std::vector<std::string> defaults; - defaults.reserve(variable.size()); auto cval = variable.begin(); auto end = variable.end(); while(cval != end) { @@ -306,25 +344,142 @@ auto value_string(const T &value) -> decltype(to_string(value)) { return to_string(value); } +/// temple to get the underlying value type if it exists or use a default +template <typename T, typename def, typename Enable = void> struct wrapped_type { using type = def; }; + +/// Type size for regular object types that do not look like a tuple +template <typename T, typename def> struct wrapped_type<T, def, typename std::enable_if<is_wrapper<T>::value>::type> { + using type = typename T::value_type; +}; + /// This will only trigger for actual void type -template <typename T, typename Enable = void> struct type_count { static const int value{0}; }; +template <typename T, typename Enable = void> struct type_count_base { static const int value{0}; }; + +/// Type size for regular object types that do not look like a tuple +template <typename T> +struct type_count_base<T, + typename std::enable_if<!is_tuple_like<T>::value && !is_mutable_container<T>::value && + !std::is_void<T>::value>::type> { + static constexpr int value{1}; +}; + +/// the base tuple size +template <typename T> +struct type_count_base<T, typename std::enable_if<is_tuple_like<T>::value && !is_mutable_container<T>::value>::type> { + static constexpr int value{std::tuple_size<T>::value}; +}; + +/// Type count base for containers is the type_count_base of the individual element +template <typename T> struct type_count_base<T, typename std::enable_if<is_mutable_container<T>::value>::type> { + static constexpr int value{type_count_base<typename T::value_type>::value}; +}; /// Set of overloads to get the type size of an object + +/// forward declare the subtype_count structure +template <typename T> struct subtype_count; + +/// forward declare the subtype_count_min structure +template <typename T> struct subtype_count_min; + +/// This will only trigger for actual void type +template <typename T, typename Enable = void> struct type_count { static const int value{0}; }; + +/// Type size for regular object types that do not look like a tuple +template <typename T> +struct type_count<T, + typename std::enable_if<!is_wrapper<T>::value && !is_tuple_like<T>::value && !is_complex<T>::value && + !std::is_void<T>::value>::type> { + static constexpr int value{1}; +}; + +/// Type size for complex since it sometimes looks like a wrapper +template <typename T> struct type_count<T, typename std::enable_if<is_complex<T>::value>::type> { + static constexpr int value{2}; +}; + +/// Type size of types that are wrappers,except complex and tuples(which can also be wrappers sometimes) +template <typename T> struct type_count<T, typename std::enable_if<is_mutable_container<T>::value>::type> { + static constexpr int value{subtype_count<typename T::value_type>::value}; +}; + +/// Type size of types that are wrappers,except containers complex and tuples(which can also be wrappers sometimes) +template <typename T> +struct type_count<T, + typename std::enable_if<is_wrapper<T>::value && !is_complex<T>::value && !is_tuple_like<T>::value && + !is_mutable_container<T>::value>::type> { + static constexpr int value{type_count<typename T::value_type>::value}; +}; + +/// 0 if the index > tuple size +template <typename T, std::size_t I> +constexpr typename std::enable_if<I == type_count_base<T>::value, int>::type tuple_type_size() { + return 0; +} + +/// Recursively generate the tuple type name +template <typename T, std::size_t I> + constexpr typename std::enable_if < I<type_count_base<T>::value, int>::type tuple_type_size() { + return subtype_count<typename std::tuple_element<I, T>::type>::value + tuple_type_size<T, I + 1>(); +} + +/// Get the type size of the sum of type sizes for all the individual tuple types template <typename T> struct type_count<T, typename std::enable_if<is_tuple_like<T>::value>::type> { - static constexpr int value{std::tuple_size<T>::value}; + static constexpr int value{tuple_type_size<T, 0>()}; +}; + +/// definition of subtype count +template <typename T> struct subtype_count { + static constexpr int value{is_mutable_container<T>::value ? expected_max_vector_size : type_count<T>::value}; }; + +/// This will only trigger for actual void type +template <typename T, typename Enable = void> struct type_count_min { static const int value{0}; }; + /// Type size for regular object types that do not look like a tuple template <typename T> -struct type_count< +struct type_count_min< T, - typename std::enable_if<!is_vector<T>::value && !is_tuple_like<T>::value && !std::is_void<T>::value>::type> { + typename std::enable_if<!is_mutable_container<T>::value && !is_tuple_like<T>::value && !is_wrapper<T>::value && + !is_complex<T>::value && !std::is_void<T>::value>::type> { + static constexpr int value{type_count<T>::value}; +}; + +/// Type size for complex since it sometimes looks like a wrapper +template <typename T> struct type_count_min<T, typename std::enable_if<is_complex<T>::value>::type> { static constexpr int value{1}; }; -/// Type size of types that look like a vector -template <typename T> struct type_count<T, typename std::enable_if<is_vector<T>::value>::type> { - static constexpr int value{is_vector<typename T::value_type>::value ? expected_max_vector_size - : type_count<typename T::value_type>::value}; +/// Type size min of types that are wrappers,except complex and tuples(which can also be wrappers sometimes) +template <typename T> +struct type_count_min< + T, + typename std::enable_if<is_wrapper<T>::value && !is_complex<T>::value && !is_tuple_like<T>::value>::type> { + static constexpr int value{subtype_count_min<typename T::value_type>::value}; +}; + +/// 0 if the index > tuple size +template <typename T, std::size_t I> +constexpr typename std::enable_if<I == type_count_base<T>::value, int>::type tuple_type_size_min() { + return 0; +} + +/// Recursively generate the tuple type name +template <typename T, std::size_t I> + constexpr typename std::enable_if < I<type_count_base<T>::value, int>::type tuple_type_size_min() { + return subtype_count_min<typename std::tuple_element<I, T>::type>::value + tuple_type_size_min<T, I + 1>(); +} + +/// Get the type size of the sum of type sizes for all the individual tuple types +template <typename T> struct type_count_min<T, typename std::enable_if<is_tuple_like<T>::value>::type> { + static constexpr int value{tuple_type_size_min<T, 0>()}; +}; + +/// definition of subtype count +template <typename T> struct subtype_count_min { + static constexpr int value{is_mutable_container<T>::value + ? ((type_count<T>::value < expected_max_vector_size) ? type_count<T>::value : 0) + : type_count_min<T>::value}; }; /// This will only trigger for actual void type @@ -332,14 +487,22 @@ template <typename T, typename Enable = void> struct expected_count { static con /// For most types the number of expected items is 1 template <typename T> -struct expected_count<T, typename std::enable_if<!is_vector<T>::value && !std::is_void<T>::value>::type> { +struct expected_count<T, + typename std::enable_if<!is_mutable_container<T>::value && !is_wrapper<T>::value && + !std::is_void<T>::value>::type> { static constexpr int value{1}; }; /// number of expected items in a vector -template <typename T> struct expected_count<T, typename std::enable_if<is_vector<T>::value>::type> { +template <typename T> struct expected_count<T, typename std::enable_if<is_mutable_container<T>::value>::type> { static constexpr int value{expected_max_vector_size}; }; +/// number of expected items in a vector +template <typename T> +struct expected_count<T, typename std::enable_if<!is_mutable_container<T>::value && is_wrapper<T>::value>::type> { + static constexpr int value{expected_count<typename T::value_type>::value}; +}; + // Enumeration of the different supported categorizations of objects enum class object_category : int { integral_value = 2, @@ -350,12 +513,15 @@ enum class object_category : int { number_constructible = 12, double_constructible = 14, integer_constructible = 16, - vector_value = 30, - tuple_value = 35, - // string assignable or greater used in a condition so anything string like must come last - string_assignable = 50, - string_constructible = 60, - other = 200, + // string like types + string_assignable = 23, + string_constructible = 24, + other = 45, + // special wrapper or container types + wrapper_value = 50, + complex_number = 60, + tuple_value = 70, + container_value = 80, }; @@ -392,10 +558,9 @@ template <typename T> struct classify_object<T, typename std::enable_if<std::is_ /// String and similar direct assignment template <typename T> -struct classify_object< - T, - typename std::enable_if<!std::is_floating_point<T>::value && !std::is_integral<T>::value && - std::is_assignable<T &, std::string>::value && !is_vector<T>::value>::type> { +struct classify_object<T, + typename std::enable_if<!std::is_floating_point<T>::value && !std::is_integral<T>::value && + std::is_assignable<T &, std::string>::value>::type> { static constexpr object_category value{object_category::string_assignable}; }; @@ -404,8 +569,8 @@ template <typename T> struct classify_object< T, typename std::enable_if<!std::is_floating_point<T>::value && !std::is_integral<T>::value && - !std::is_assignable<T &, std::string>::value && - std::is_constructible<T, std::string>::value && !is_vector<T>::value>::type> { + !std::is_assignable<T &, std::string>::value && (type_count<T>::value == 1) && + std::is_constructible<T, std::string>::value>::type> { static constexpr object_category value{object_category::string_constructible}; }; @@ -414,23 +579,35 @@ template <typename T> struct classify_object<T, typename std::enable_if<std::is_ static constexpr object_category value{object_category::enumeration}; }; +template <typename T> struct classify_object<T, typename std::enable_if<is_complex<T>::value>::type> { + static constexpr object_category value{object_category::complex_number}; +}; + /// Handy helper to contain a bunch of checks that rule out many common types (integers, string like, floating point, /// vectors, and enumerations template <typename T> struct uncommon_type { using type = typename std::conditional<!std::is_floating_point<T>::value && !std::is_integral<T>::value && !std::is_assignable<T &, std::string>::value && - !std::is_constructible<T, std::string>::value && !is_vector<T>::value && - !std::is_enum<T>::value, + !std::is_constructible<T, std::string>::value && !is_complex<T>::value && + !is_mutable_container<T>::value && !std::is_enum<T>::value, std::true_type, std::false_type>::type; static constexpr bool value = type::value; }; +/// wrapper type +template <typename T> +struct classify_object<T, + typename std::enable_if<(!is_mutable_container<T>::value && is_wrapper<T>::value && + !is_tuple_like<T>::value && uncommon_type<T>::value)>::type> { + static constexpr object_category value{object_category::wrapper_value}; +}; + /// Assignable from double or int template <typename T> struct classify_object<T, typename std::enable_if<uncommon_type<T>::value && type_count<T>::value == 1 && - is_direct_constructible<T, double>::value && + !is_wrapper<T>::value && is_direct_constructible<T, double>::value && is_direct_constructible<T, int>::value>::type> { static constexpr object_category value{object_category::number_constructible}; }; @@ -439,7 +616,7 @@ struct classify_object<T, template <typename T> struct classify_object<T, typename std::enable_if<uncommon_type<T>::value && type_count<T>::value == 1 && - !is_direct_constructible<T, double>::value && + !is_wrapper<T>::value && !is_direct_constructible<T, double>::value && is_direct_constructible<T, int>::value>::type> { static constexpr object_category value{object_category::integer_constructible}; }; @@ -448,24 +625,30 @@ struct classify_object<T, template <typename T> struct classify_object<T, typename std::enable_if<uncommon_type<T>::value && type_count<T>::value == 1 && - is_direct_constructible<T, double>::value && + !is_wrapper<T>::value && is_direct_constructible<T, double>::value && !is_direct_constructible<T, int>::value>::type> { static constexpr object_category value{object_category::double_constructible}; }; /// Tuple type template <typename T> -struct classify_object<T, - typename std::enable_if<(type_count<T>::value >= 2 && !is_vector<T>::value) || - (is_tuple_like<T>::value && uncommon_type<T>::value && - !is_direct_constructible<T, double>::value && - !is_direct_constructible<T, int>::value)>::type> { +struct classify_object< + T, + typename std::enable_if<is_tuple_like<T>::value && + ((type_count<T>::value >= 2 && !is_wrapper<T>::value) || + (uncommon_type<T>::value && !is_direct_constructible<T, double>::value && + !is_direct_constructible<T, int>::value))>::type> { static constexpr object_category value{object_category::tuple_value}; + // the condition on this class requires it be like a tuple, but on some compilers (like Xcode) tuples can be + // constructed from just the first element so tuples of <string, int,int> can be constructed from a string, which + // could lead to issues so there are two variants of the condition, the first isolates things with a type size >=2 + // mainly to get tuples on Xcode with the exception of wrappers, the second is the main one and just separating out + // those cases that are caught by other object classifications }; -/// Vector type -template <typename T> struct classify_object<T, typename std::enable_if<is_vector<T>::value>::type> { - static constexpr object_category value{object_category::vector_value}; +/// container type +template <typename T> struct classify_object<T, typename std::enable_if<is_mutable_container<T>::value>::type> { + static constexpr object_category value{object_category::container_value}; }; // Type name print @@ -511,31 +694,53 @@ constexpr const char *type_name() { return "BOOLEAN"; } +/// Print name for enumeration types +template <typename T, + enable_if_t<classify_object<T>::value == object_category::complex_number, detail::enabler> = detail::dummy> +constexpr const char *type_name() { + return "COMPLEX"; +} + /// Print for all other types template <typename T, - enable_if_t<classify_object<T>::value >= object_category::string_assignable, detail::enabler> = detail::dummy> + enable_if_t<classify_object<T>::value >= object_category::string_assignable && + classify_object<T>::value <= object_category::other, + detail::enabler> = detail::dummy> constexpr const char *type_name() { return "TEXT"; } +/// typename for tuple value +template <typename T, + enable_if_t<classify_object<T>::value == object_category::tuple_value && type_count_base<T>::value >= 2, + detail::enabler> = detail::dummy> +std::string type_name(); // forward declaration + +/// Generate type name for a wrapper or container value +template <typename T, + enable_if_t<classify_object<T>::value == object_category::container_value || + classify_object<T>::value == object_category::wrapper_value, + detail::enabler> = detail::dummy> +std::string type_name(); // forward declaration /// Print name for single element tuple types template <typename T, - enable_if_t<classify_object<T>::value == object_category::tuple_value && type_count<T>::value == 1, + enable_if_t<classify_object<T>::value == object_category::tuple_value && type_count_base<T>::value == 1, detail::enabler> = detail::dummy> inline std::string type_name() { - return type_name<typename std::tuple_element<0, T>::type>(); + return type_name<typename std::decay<typename std::tuple_element<0, T>::type>::type>(); } /// Empty string if the index > tuple size template <typename T, std::size_t I> -inline typename std::enable_if<I == type_count<T>::value, std::string>::type tuple_name() { +inline typename std::enable_if<I == type_count_base<T>::value, std::string>::type tuple_name() { return std::string{}; } /// Recursively generate the tuple type name template <typename T, std::size_t I> - inline typename std::enable_if < I<type_count<T>::value, std::string>::type tuple_name() { - std::string str = std::string(type_name<typename std::tuple_element<I, T>::type>()) + ',' + tuple_name<T, I + 1>(); +inline typename std::enable_if<(I < type_count_base<T>::value), std::string>::type tuple_name() { + std::string str = std::string(type_name<typename std::decay<typename std::tuple_element<I, T>::type>::type>()) + + ',' + tuple_name<T, I + 1>(); if(str.back() == ',') str.pop_back(); return str; @@ -543,17 +748,19 @@ template <typename T, std::size_t I> /// Print type name for tuples with 2 or more elements template <typename T, - enable_if_t<classify_object<T>::value == object_category::tuple_value && type_count<T>::value >= 2, - detail::enabler> = detail::dummy> -std::string type_name() { + enable_if_t<classify_object<T>::value == object_category::tuple_value && type_count_base<T>::value >= 2, + detail::enabler>> +inline std::string type_name() { auto tname = std::string(1, '[') + tuple_name<T, 0>(); tname.push_back(']'); return tname; } -/// This one should not be used normally, since vector types print the internal type +/// get the type name for a type that has a value_type member template <typename T, - enable_if_t<classify_object<T>::value == object_category::vector_value, detail::enabler> = detail::dummy> + enable_if_t<classify_object<T>::value == object_category::container_value || + classify_object<T>::value == object_category::wrapper_value, + detail::enabler>> inline std::string type_name() { return type_name<typename T::value_type>(); } @@ -624,7 +831,7 @@ template <typename T, enable_if_t<classify_object<T>::value == object_category::unsigned_integral, detail::enabler> = detail::dummy> bool lexical_cast(const std::string &input, T &output) { if(!input.empty() && input.front() == '-') - return false; // std::stoull happily converts negative values to junk without any errors. + return false; // std::stoull happily converts negative values to junk without any errors. try { std::size_t n = 0; @@ -671,6 +878,38 @@ bool lexical_cast(const std::string &input, T &output) { } } +/// complex +template <typename T, + enable_if_t<classify_object<T>::value == object_category::complex_number, detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + using XC = typename wrapped_type<T, double>::type; + XC x{0.0}, y{0.0}; + auto str1 = input; + bool worked = false; + auto nloc = str1.find_last_of("+-"); + if(nloc != std::string::npos && nloc > 0) { + worked = detail::lexical_cast(str1.substr(0, nloc), x); + str1 = str1.substr(nloc); + if(str1.back() == 'i' || str1.back() == 'j') + str1.pop_back(); + worked = worked && detail::lexical_cast(str1, y); + } else { + if(str1.back() == 'i' || str1.back() == 'j') { + str1.pop_back(); + worked = detail::lexical_cast(str1, y); + x = XC{0}; + } else { + worked = detail::lexical_cast(str1, x); + y = XC{0}; + } + } + if(worked) { + output = T{x, y}; + return worked; + } + return from_stream(input, output); +} + /// String and similar direct assignment template <typename T, enable_if_t<classify_object<T>::value == object_category::string_assignable, detail::enabler> = detail::dummy> @@ -701,6 +940,18 @@ bool lexical_cast(const std::string &input, T &output) { return true; } +/// wrapper types +template <typename T, + enable_if_t<classify_object<T>::value == object_category::wrapper_value, detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + typename T::value_type val; + if(lexical_cast(input, val)) { + output = T{val}; + return true; + } + return from_stream(input, output); +} + /// Assignable from double or int template < typename T, @@ -756,38 +1007,40 @@ bool lexical_cast(const std::string &input, T &output) { } /// Assign a value through lexical cast operations -template < - typename T, - typename XC, - enable_if_t<std::is_same<T, XC>::value && (classify_object<T>::value == object_category::string_assignable || - classify_object<T>::value == object_category::string_constructible), - detail::enabler> = detail::dummy> -bool lexical_assign(const std::string &input, T &output) { +/// Strings can be empty so we need to do a little different +template <typename AssignTo, + typename ConvertTo, + enable_if_t<std::is_same<AssignTo, ConvertTo>::value && + (classify_object<AssignTo>::value == object_category::string_assignable || + classify_object<AssignTo>::value == object_category::string_constructible), + detail::enabler> = detail::dummy> +bool lexical_assign(const std::string &input, AssignTo &output) { return lexical_cast(input, output); } /// Assign a value through lexical cast operations -template <typename T, - typename XC, - enable_if_t<std::is_same<T, XC>::value && classify_object<T>::value != object_category::string_assignable && - classify_object<T>::value != object_category::string_constructible, +template <typename AssignTo, + typename ConvertTo, + enable_if_t<std::is_same<AssignTo, ConvertTo>::value && + classify_object<AssignTo>::value != object_category::string_assignable && + classify_object<AssignTo>::value != object_category::string_constructible, detail::enabler> = detail::dummy> -bool lexical_assign(const std::string &input, T &output) { +bool lexical_assign(const std::string &input, AssignTo &output) { if(input.empty()) { - output = T{}; + output = AssignTo{}; return true; } return lexical_cast(input, output); } /// Assign a value converted from a string in lexical cast to the output value directly -template < - typename T, - typename XC, - enable_if_t<!std::is_same<T, XC>::value && std::is_assignable<T &, XC &>::value, detail::enabler> = detail::dummy> -bool lexical_assign(const std::string &input, T &output) { - XC val{}; - bool parse_result = (!input.empty()) ? lexical_cast<XC>(input, val) : true; +template <typename AssignTo, + typename ConvertTo, + enable_if_t<!std::is_same<AssignTo, ConvertTo>::value && std::is_assignable<AssignTo &, ConvertTo &>::value, + detail::enabler> = detail::dummy> +bool lexical_assign(const std::string &input, AssignTo &output) { + ConvertTo val{}; + bool parse_result = (!input.empty()) ? lexical_cast<ConvertTo>(input, val) : true; if(parse_result) { output = val; } @@ -795,195 +1048,325 @@ bool lexical_assign(const std::string &input, T &output) { } /// Assign a value from a lexical cast through constructing a value and move assigning it -template <typename T, - typename XC, - enable_if_t<!std::is_same<T, XC>::value && !std::is_assignable<T &, XC &>::value && - std::is_move_assignable<T>::value, - detail::enabler> = detail::dummy> -bool lexical_assign(const std::string &input, T &output) { - XC val{}; - bool parse_result = input.empty() ? true : lexical_cast<XC>(input, val); +template < + typename AssignTo, + typename ConvertTo, + enable_if_t<!std::is_same<AssignTo, ConvertTo>::value && !std::is_assignable<AssignTo &, ConvertTo &>::value && + std::is_move_assignable<AssignTo>::value, + detail::enabler> = detail::dummy> +bool lexical_assign(const std::string &input, AssignTo &output) { + ConvertTo val{}; + bool parse_result = input.empty() ? true : lexical_cast<ConvertTo>(input, val); if(parse_result) { - output = T(val); // use () form of constructor to allow some implicit conversions + output = AssignTo(val); // use () form of constructor to allow some implicit conversions } return parse_result; } -/// Lexical conversion if there is only one element -template < - typename T, - typename XC, - enable_if_t<!is_tuple_like<T>::value && !is_tuple_like<XC>::value && !is_vector<T>::value && !is_vector<XC>::value, - detail::enabler> = detail::dummy> -bool lexical_conversion(const std::vector<std ::string> &strings, T &output) { - return lexical_assign<T, XC>(strings[0], output); + +/// primary lexical conversion operation, 1 string to 1 type of some kind +template <typename AssignTo, + typename ConvertTo, + enable_if_t<classify_object<ConvertTo>::value <= object_category::other && + classify_object<AssignTo>::value <= object_category::wrapper_value, + detail::enabler> = detail::dummy> +bool lexical_conversion(const std::vector<std ::string> &strings, AssignTo &output) { + return lexical_assign<AssignTo, ConvertTo>(strings[0], output); } -/// Lexical conversion if there is only one element but the conversion type is for two call a two element constructor -template <typename T, - typename XC, - enable_if_t<type_count<T>::value == 1 && type_count<XC>::value == 2, detail::enabler> = detail::dummy> -bool lexical_conversion(const std::vector<std ::string> &strings, T &output) { - typename std::tuple_element<0, XC>::type v1; - typename std::tuple_element<1, XC>::type v2; +/// Lexical conversion if there is only one element but the conversion type is for two, then call a two element +/// constructor +template <typename AssignTo, + typename ConvertTo, + enable_if_t<(type_count<AssignTo>::value <= 2) && expected_count<AssignTo>::value == 1 && + is_tuple_like<ConvertTo>::value && type_count_base<ConvertTo>::value == 2, + detail::enabler> = detail::dummy> +bool lexical_conversion(const std::vector<std ::string> &strings, AssignTo &output) { + // the remove const is to handle pair types coming from a container + typename std::remove_const<typename std::tuple_element<0, ConvertTo>::type>::type v1; + typename std::tuple_element<1, ConvertTo>::type v2; bool retval = lexical_assign<decltype(v1), decltype(v1)>(strings[0], v1); if(strings.size() > 1) { retval = retval && lexical_assign<decltype(v2), decltype(v2)>(strings[1], v2); } if(retval) { - output = T{v1, v2}; + output = AssignTo{v1, v2}; } return retval; } -/// Lexical conversion of a vector types -template <class T, - class XC, - enable_if_t<expected_count<T>::value == expected_max_vector_size && - expected_count<XC>::value == expected_max_vector_size && type_count<XC>::value == 1, +/// Lexical conversion of a container types of single elements +template <class AssignTo, + class ConvertTo, + enable_if_t<is_mutable_container<AssignTo>::value && is_mutable_container<ConvertTo>::value && + type_count<ConvertTo>::value == 1, detail::enabler> = detail::dummy> -bool lexical_conversion(const std::vector<std ::string> &strings, T &output) { - output.clear(); - output.reserve(strings.size()); +bool lexical_conversion(const std::vector<std ::string> &strings, AssignTo &output) { + output.erase(output.begin(), output.end()); for(const auto &elem : strings) { - - output.emplace_back(); - bool retval = lexical_assign<typename T::value_type, typename XC::value_type>(elem, output.back()); + typename AssignTo::value_type out; + bool retval = lexical_assign<typename AssignTo::value_type, typename ConvertTo::value_type>(elem, out); if(!retval) { return false; } + output.insert(output.end(), std::move(out)); } return (!output.empty()); } -/// Lexical conversion of a vector types with type size of two -template <class T, - class XC, - enable_if_t<expected_count<T>::value == expected_max_vector_size && - expected_count<XC>::value == expected_max_vector_size && type_count<XC>::value == 2, - detail::enabler> = detail::dummy> -bool lexical_conversion(const std::vector<std ::string> &strings, T &output) { - output.clear(); - for(std::size_t ii = 0; ii < strings.size(); ii += 2) { +/// Lexical conversion for complex types +template <class AssignTo, class ConvertTo, enable_if_t<is_complex<ConvertTo>::value, detail::enabler> = detail::dummy> +bool lexical_conversion(const std::vector<std::string> &strings, AssignTo &output) { - typename std::tuple_element<0, typename XC::value_type>::type v1; - typename std::tuple_element<1, typename XC::value_type>::type v2; - bool retval = lexical_assign<decltype(v1), decltype(v1)>(strings[ii], v1); - if(strings.size() > ii + 1) { - retval = retval && lexical_assign<decltype(v2), decltype(v2)>(strings[ii + 1], v2); + if(strings.size() >= 2 && !strings[1].empty()) { + using XC2 = typename wrapped_type<ConvertTo, double>::type; + XC2 x{0.0}, y{0.0}; + auto str1 = strings[1]; + if(str1.back() == 'i' || str1.back() == 'j') { + str1.pop_back(); } - if(retval) { - output.emplace_back(v1, v2); - } else { - return false; + auto worked = detail::lexical_cast(strings[0], x) && detail::lexical_cast(str1, y); + if(worked) { + output = ConvertTo{x, y}; } + return worked; + } else { + return lexical_assign<AssignTo, ConvertTo>(strings[0], output); } - return (!output.empty()); } /// Conversion to a vector type using a particular single type as the conversion type -template <class T, - class XC, - enable_if_t<(expected_count<T>::value == expected_max_vector_size) && (expected_count<XC>::value == 1) && - (type_count<XC>::value == 1), +template <class AssignTo, + class ConvertTo, + enable_if_t<is_mutable_container<AssignTo>::value && (expected_count<ConvertTo>::value == 1) && + (type_count<ConvertTo>::value == 1), detail::enabler> = detail::dummy> -bool lexical_conversion(const std::vector<std ::string> &strings, T &output) { +bool lexical_conversion(const std::vector<std ::string> &strings, AssignTo &output) { bool retval = true; output.clear(); output.reserve(strings.size()); for(const auto &elem : strings) { output.emplace_back(); - retval = retval && lexical_assign<typename T::value_type, XC>(elem, output.back()); + retval = retval && lexical_assign<typename AssignTo::value_type, ConvertTo>(elem, output.back()); } return (!output.empty()) && retval; } -// This one is last since it can call other lexical_conversion functions -/// Lexical conversion if there is only one element but the conversion type is a vector -template <typename T, - typename XC, - enable_if_t<!is_tuple_like<T>::value && !is_vector<T>::value && is_vector<XC>::value, detail::enabler> = - detail::dummy> -bool lexical_conversion(const std::vector<std ::string> &strings, T &output) { + +// forward declaration + +/// Lexical conversion of a container types with conversion type of two elements +template <class AssignTo, + class ConvertTo, + enable_if_t<is_mutable_container<AssignTo>::value && is_mutable_container<ConvertTo>::value && + type_count_base<ConvertTo>::value == 2, + detail::enabler> = detail::dummy> +bool lexical_conversion(std::vector<std::string> strings, AssignTo &output); + +/// Lexical conversion of a vector types with type_size >2 forward declaration +template <class AssignTo, + class ConvertTo, + enable_if_t<is_mutable_container<AssignTo>::value && is_mutable_container<ConvertTo>::value && + type_count_base<ConvertTo>::value != 2 && + ((type_count<ConvertTo>::value > 2) || + (type_count<ConvertTo>::value > type_count_base<ConvertTo>::value)), + detail::enabler> = detail::dummy> +bool lexical_conversion(const std::vector<std::string> &strings, AssignTo &output); + +/// Conversion for tuples +template <class AssignTo, + class ConvertTo, + enable_if_t<is_tuple_like<AssignTo>::value && is_tuple_like<ConvertTo>::value && + (type_count_base<ConvertTo>::value != type_count<ConvertTo>::value || + type_count<ConvertTo>::value > 2), + detail::enabler> = detail::dummy> +bool lexical_conversion(const std::vector<std::string> &strings, AssignTo &output); // forward declaration + +/// Conversion for operations where the assigned type is some class but the conversion is a mutable container or large +/// tuple +template <typename AssignTo, + typename ConvertTo, + enable_if_t<!is_tuple_like<AssignTo>::value && !is_mutable_container<AssignTo>::value && + classify_object<ConvertTo>::value != object_category::wrapper_value && + (is_mutable_container<ConvertTo>::value || type_count<ConvertTo>::value > 2), + detail::enabler> = detail::dummy> +bool lexical_conversion(const std::vector<std ::string> &strings, AssignTo &output) { if(strings.size() > 1 || (!strings.empty() && !(strings.front().empty()))) { - XC val; - auto retval = lexical_conversion<XC, XC>(strings, val); - output = T{val}; + ConvertTo val; + auto retval = lexical_conversion<ConvertTo, ConvertTo>(strings, val); + output = AssignTo{val}; return retval; } - output = T{}; + output = AssignTo{}; return true; } /// function template for converting tuples if the static Index is greater than the tuple size -template <class T, class XC, std::size_t I> -inline typename std::enable_if<I >= type_count<T>::value, bool>::type tuple_conversion(const std::vector<std::string> &, - T &) { +template <class AssignTo, class ConvertTo, std::size_t I> +inline typename std::enable_if<(I >= type_count_base<AssignTo>::value), bool>::type +tuple_conversion(const std::vector<std::string> &, AssignTo &) { return true; } + +/// Conversion of a tuple element where the type size ==1 and not a mutable container +template <class AssignTo, class ConvertTo> +inline typename std::enable_if<!is_mutable_container<ConvertTo>::value && type_count<ConvertTo>::value == 1, bool>::type +tuple_type_conversion(std::vector<std::string> &strings, AssignTo &output) { + auto retval = lexical_assign<AssignTo, ConvertTo>(strings[0], output); + strings.erase(strings.begin()); + return retval; +} + +/// Conversion of a tuple element where the type size !=1 but the size is fixed and not a mutable container +template <class AssignTo, class ConvertTo> +inline typename std::enable_if<!is_mutable_container<ConvertTo>::value && (type_count<ConvertTo>::value > 1) && + type_count<ConvertTo>::value == type_count_min<ConvertTo>::value, + bool>::type +tuple_type_conversion(std::vector<std::string> &strings, AssignTo &output) { + auto retval = lexical_conversion<AssignTo, ConvertTo>(strings, output); + strings.erase(strings.begin(), strings.begin() + type_count<ConvertTo>::value); + return retval; +} + +/// Conversion of a tuple element where the type is a mutable container or a type with different min and max type sizes +template <class AssignTo, class ConvertTo> +inline typename std::enable_if<is_mutable_container<ConvertTo>::value || + type_count<ConvertTo>::value != type_count_min<ConvertTo>::value, + bool>::type +tuple_type_conversion(std::vector<std::string> &strings, AssignTo &output) { + + std::size_t index{subtype_count_min<ConvertTo>::value}; + const std::size_t mx_count{subtype_count<ConvertTo>::value}; + const std::size_t mx{(std::max)(mx_count, strings.size())}; + + while(index < mx) { + if(is_separator(strings[index])) { + break; + } + ++index; + } + bool retval = lexical_conversion<AssignTo, ConvertTo>( + std::vector<std::string>(strings.begin(), strings.begin() + static_cast<std::ptrdiff_t>(index)), output); + strings.erase(strings.begin(), strings.begin() + static_cast<std::ptrdiff_t>(index) + 1); + return retval; +} + /// Tuple conversion operation -template <class T, class XC, std::size_t I> - inline typename std::enable_if < - I<type_count<T>::value, bool>::type tuple_conversion(const std::vector<std::string> &strings, T &output) { +template <class AssignTo, class ConvertTo, std::size_t I> +inline typename std::enable_if<(I < type_count_base<AssignTo>::value), bool>::type +tuple_conversion(std::vector<std::string> strings, AssignTo &output) { bool retval = true; - if(strings.size() > I) { - retval = retval && lexical_assign<typename std::tuple_element<I, T>::type, - typename std::conditional<is_tuple_like<XC>::value, - typename std::tuple_element<I, XC>::type, - XC>::type>(strings[I], std::get<I>(output)); + using ConvertToElement = typename std:: + conditional<is_tuple_like<ConvertTo>::value, typename std::tuple_element<I, ConvertTo>::type, ConvertTo>::type; + if(!strings.empty()) { + retval = retval && tuple_type_conversion<typename std::tuple_element<I, AssignTo>::type, ConvertToElement>( + strings, std::get<I>(output)); } - retval = retval && tuple_conversion<T, XC, I + 1>(strings, output); + retval = retval && tuple_conversion<AssignTo, ConvertTo, I + 1>(std::move(strings), output); return retval; } -/// Conversion for tuples -template <class T, class XC, enable_if_t<is_tuple_like<T>::value, detail::enabler> = detail::dummy> -bool lexical_conversion(const std::vector<std ::string> &strings, T &output) { +/// Lexical conversion of a container types with tuple elements of size 2 +template <class AssignTo, + class ConvertTo, + enable_if_t<is_mutable_container<AssignTo>::value && is_mutable_container<ConvertTo>::value && + type_count_base<ConvertTo>::value == 2, + detail::enabler>> +bool lexical_conversion(std::vector<std::string> strings, AssignTo &output) { + output.clear(); + while(!strings.empty()) { + + typename std::remove_const<typename std::tuple_element<0, typename ConvertTo::value_type>::type>::type v1; + typename std::tuple_element<1, typename ConvertTo::value_type>::type v2; + bool retval = tuple_type_conversion<decltype(v1), decltype(v1)>(strings, v1); + if(!strings.empty()) { + retval = retval && tuple_type_conversion<decltype(v2), decltype(v2)>(strings, v2); + } + if(retval) { + output.insert(output.end(), typename AssignTo::value_type{v1, v2}); + } else { + return false; + } + } + return (!output.empty()); +} + +/// lexical conversion of tuples with type count>2 or tuples of types of some element with a type size>=2 +template <class AssignTo, + class ConvertTo, + enable_if_t<is_tuple_like<AssignTo>::value && is_tuple_like<ConvertTo>::value && + (type_count_base<ConvertTo>::value != type_count<ConvertTo>::value || + type_count<ConvertTo>::value > 2), + detail::enabler>> +bool lexical_conversion(const std::vector<std ::string> &strings, AssignTo &output) { static_assert( - !is_tuple_like<XC>::value || type_count<T>::value == type_count<XC>::value, + !is_tuple_like<ConvertTo>::value || type_count_base<AssignTo>::value == type_count_base<ConvertTo>::value, "if the conversion type is defined as a tuple it must be the same size as the type you are converting to"); - return tuple_conversion<T, XC, 0>(strings, output); + return tuple_conversion<AssignTo, ConvertTo, 0>(strings, output); } -/// Lexical conversion of a vector types with type_size >2 -template <class T, - class XC, - enable_if_t<expected_count<T>::value == expected_max_vector_size && - expected_count<XC>::value == expected_max_vector_size && (type_count<XC>::value > 2), - detail::enabler> = detail::dummy> -bool lexical_conversion(const std::vector<std ::string> &strings, T &output) { +/// Lexical conversion of a vector types for everything but tuples of two elements and types of size 1 +template <class AssignTo, + class ConvertTo, + enable_if_t<is_mutable_container<AssignTo>::value && is_mutable_container<ConvertTo>::value && + type_count_base<ConvertTo>::value != 2 && + ((type_count<ConvertTo>::value > 2) || + (type_count<ConvertTo>::value > type_count_base<ConvertTo>::value)), + detail::enabler>> +bool lexical_conversion(const std::vector<std ::string> &strings, AssignTo &output) { bool retval = true; output.clear(); std::vector<std::string> temp; - std::size_t ii = 0; - std::size_t icount = 0; - std::size_t xcm = type_count<XC>::value; - while(ii < strings.size()) { + std::size_t ii{0}; + std::size_t icount{0}; + std::size_t xcm{type_count<ConvertTo>::value}; + auto ii_max = strings.size(); + while(ii < ii_max) { temp.push_back(strings[ii]); ++ii; ++icount; - if(icount == xcm || temp.back().empty()) { - if(static_cast<int>(xcm) == expected_max_vector_size) { + if(icount == xcm || is_separator(temp.back()) || ii == ii_max) { + if(static_cast<int>(xcm) > type_count_min<ConvertTo>::value && is_separator(temp.back())) { temp.pop_back(); } - output.emplace_back(); - retval = retval && lexical_conversion<typename T::value_type, typename XC::value_type>(temp, output.back()); + typename AssignTo::value_type temp_out; + retval = retval && + lexical_conversion<typename AssignTo::value_type, typename ConvertTo::value_type>(temp, temp_out); temp.clear(); if(!retval) { return false; } + output.insert(output.end(), std::move(temp_out)); icount = 0; } } return retval; } + +/// conversion for wrapper types +template < + typename AssignTo, + class ConvertTo, + enable_if_t<classify_object<ConvertTo>::value == object_category::wrapper_value, detail::enabler> = detail::dummy> +bool lexical_conversion(const std::vector<std::string> &strings, AssignTo &output) { + if(strings.empty() || strings.front().empty()) { + output = ConvertTo{}; + return true; + } + typename ConvertTo::value_type val; + if(lexical_conversion<typename ConvertTo::value_type, typename ConvertTo::value_type>(strings, val)) { + output = ConvertTo{val}; + return true; + } + return false; +} + /// Sum a vector of flag representations /// The flag vector produces a series of strings in a vector, simple true is represented by a "1", simple false is /// by /// "-1" an if numbers are passed by some fashion they are captured as well so the function just checks for the most /// common true and false strings then uses stoll to convert the rest for summing -template <typename T, - enable_if_t<std::is_integral<T>::value && std::is_unsigned<T>::value, detail::enabler> = detail::dummy> +template <typename T, enable_if_t<std::is_unsigned<T>::value, detail::enabler> = detail::dummy> void sum_flag_vector(const std::vector<std::string> &flags, T &output) { std::int64_t count{0}; for(auto &flag : flags) { @@ -997,8 +1380,7 @@ void sum_flag_vector(const std::vector<std::string> &flags, T &output) { /// by /// "-1" an if numbers are passed by some fashion they are captured as well so the function just checks for the most /// common true and false strings then uses stoll to convert the rest for summing -template <typename T, - enable_if_t<std::is_integral<T>::value && std::is_signed<T>::value, detail::enabler> = detail::dummy> +template <typename T, enable_if_t<std::is_signed<T>::value, detail::enabler> = detail::dummy> void sum_flag_vector(const std::vector<std::string> &flags, T &output) { std::int64_t count{0}; for(auto &flag : flags) { @@ -1007,5 +1389,5 @@ void sum_flag_vector(const std::vector<std::string> &flags, T &output) { output = static_cast<T>(count); } -} // namespace detail -} // namespace CLI +} // namespace detail +} // namespace CLI diff --git a/packages/CLI11/include/CLI/Validators.hpp b/packages/CLI11/include/CLI/Validators.hpp index 1159e53c2c2c7ab0b1c76e79e7c20d8f477a7abf..9f47477db301fa9ee691891a38683d19746043d0 100644 --- a/packages/CLI11/include/CLI/Validators.hpp +++ b/packages/CLI11/include/CLI/Validators.hpp @@ -33,7 +33,14 @@ #else #include <filesystem> #if defined __cpp_lib_filesystem && __cpp_lib_filesystem >= 201703 +#if defined _GLIBCXX_RELEASE && _GLIBCXX_RELEASE >= 9 #define CLI11_HAS_FILESYSTEM 1 +#elif defined(__GLIBCXX__) +// if we are using gcc and Version <9 default to no filesystem +#define CLI11_HAS_FILESYSTEM 0 +#else +#define CLI11_HAS_FILESYSTEM 1 +#endif #else #define CLI11_HAS_FILESYSTEM 0 #endif @@ -42,7 +49,7 @@ #endif #if defined CLI11_HAS_FILESYSTEM && CLI11_HAS_FILESYSTEM > 0 -#include <filesystem> // NOLINT(build/include) +#include <filesystem> // NOLINT(build/include) #else #include <sys/stat.h> #include <sys/types.h> @@ -107,14 +114,14 @@ class Validator { } } return retstring; - }; + } /// This is the required operator for a Validator - provided to help /// users (CLI11 uses the member `func` directly) std::string operator()(const std::string &str) const { std::string value = str; return (active_) ? func_(value) : std::string{}; - }; + } /// Specify the type string Validator &description(std::string validator_desc) { @@ -168,13 +175,13 @@ class Validator { Validator &application_index(int app_index) { application_index_ = app_index; return *this; - }; + } /// Specify the application index of a validator Validator application_index(int app_index) const { Validator newval(*this); newval.application_index_ = app_index; return newval; - }; + } /// Get the current value of the application index int get_application_index() const { return application_index_; } /// Get a boolean if the validator is active @@ -270,7 +277,7 @@ class Validator { return std::string(1, '(') + f1 + ')' + merger + '(' + f2 + ')'; }; } -}; // namespace CLI +}; // namespace CLI /// Class wrapping some of the accessors of Validator class CustomValidator : public Validator { @@ -282,7 +289,7 @@ class CustomValidator : public Validator { namespace detail { /// CLI enumeration of different file types -enum class path_type { nonexistant, file, directory }; +enum class path_type { nonexistent, file, directory }; #if defined CLI11_HAS_FILESYSTEM && CLI11_HAS_FILESYSTEM > 0 /// get the type of the path from a file name @@ -290,12 +297,12 @@ inline path_type check_path(const char *file) noexcept { std::error_code ec; auto stat = std::filesystem::status(file, ec); if(ec) { - return path_type::nonexistant; + return path_type::nonexistent; } switch(stat.type()) { case std::filesystem::file_type::none: case std::filesystem::file_type::not_found: - return path_type::nonexistant; + return path_type::nonexistent; case std::filesystem::file_type::directory: return path_type::directory; case std::filesystem::file_type::symlink: @@ -323,7 +330,7 @@ inline path_type check_path(const char *file) noexcept { return ((buffer.st_mode & S_IFDIR) != 0) ? path_type::directory : path_type::file; } #endif - return path_type::nonexistant; + return path_type::nonexistent; } #endif /// Check for an existing file (returns error message if check fails) @@ -332,7 +339,7 @@ class ExistingFileValidator : public Validator { ExistingFileValidator() : Validator("FILE") { func_ = [](std::string &filename) { auto path_result = check_path(filename.c_str()); - if(path_result == path_type::nonexistant) { + if(path_result == path_type::nonexistent) { return "File does not exist: " + filename; } if(path_result == path_type::directory) { @@ -349,7 +356,7 @@ class ExistingDirectoryValidator : public Validator { ExistingDirectoryValidator() : Validator("DIR") { func_ = [](std::string &filename) { auto path_result = check_path(filename.c_str()); - if(path_result == path_type::nonexistant) { + if(path_result == path_type::nonexistent) { return "Directory does not exist: " + filename; } if(path_result == path_type::file) { @@ -366,7 +373,7 @@ class ExistingPathValidator : public Validator { ExistingPathValidator() : Validator("PATH(existing)") { func_ = [](std::string &filename) { auto path_result = check_path(filename.c_str()); - if(path_result == path_type::nonexistant) { + if(path_result == path_type::nonexistent) { return "Path does not exist: " + filename; } return std::string(); @@ -380,7 +387,7 @@ class NonexistentPathValidator : public Validator { NonexistentPathValidator() : Validator("PATH(non-existing)") { func_ = [](std::string &filename) { auto path_result = check_path(filename.c_str()); - if(path_result != path_type::nonexistant) { + if(path_result != path_type::nonexistent) { return "Path already exists: " + filename; } return std::string(); @@ -459,7 +466,7 @@ class Number : public Validator { } }; -} // namespace detail +} // namespace detail // Static is not needed here, because global const implies static. @@ -561,7 +568,7 @@ typename std::remove_reference<T>::type &smart_deref(T &value) { /// Generate a string representation of a set template <typename T> std::string generate_set(const T &set) { using element_t = typename detail::element_type<T>::type; - using iteration_type_t = typename detail::pair_adaptor<element_t>::value_type; // the type of the object pair + using iteration_type_t = typename detail::pair_adaptor<element_t>::value_type; // the type of the object pair std::string out(1, '{'); out.append(detail::join( detail::smart_deref(set), @@ -574,7 +581,7 @@ template <typename T> std::string generate_set(const T &set) { /// Generate a string representation of a map template <typename T> std::string generate_map(const T &map, bool key_only = false) { using element_t = typename detail::element_type<T>::type; - using iteration_type_t = typename detail::pair_adaptor<element_t>::value_type; // the type of the object pair + using iteration_type_t = typename detail::pair_adaptor<element_t>::value_type; // the type of the object pair std::string out(1, '{'); out.append(detail::join( detail::smart_deref(map), @@ -685,7 +692,7 @@ typename std::enable_if<std::is_floating_point<T>::value, bool>::type checked_mu return true; } -} // namespace detail +} // namespace detail /// Verify items are in a set class IsMember : public Validator { public: @@ -705,11 +712,11 @@ class IsMember : public Validator { // Get the type of the contained item - requires a container have ::value_type // if the type does not have first_type and second_type, these are both value_type - using element_t = typename detail::element_type<T>::type; // Removes (smart) pointers if needed - using item_t = typename detail::pair_adaptor<element_t>::first_type; // Is value_type if not a map + using element_t = typename detail::element_type<T>::type; // Removes (smart) pointers if needed + using item_t = typename detail::pair_adaptor<element_t>::first_type; // Is value_type if not a map - using local_item_t = typename IsMemberType<item_t>::type; // This will convert bad types to good ones - // (const char * to std::string) + using local_item_t = typename IsMemberType<item_t>::type; // This will convert bad types to good ones + // (const char * to std::string) // Make a local copy of the filter function, using a std::function if not one already std::function<local_item_t(local_item_t)> filter_fn = filter_function; @@ -722,7 +729,7 @@ class IsMember : public Validator { func_ = [set, filter_fn](std::string &input) { local_item_t b; if(!detail::lexical_cast(input, b)) { - throw ValidationError(input); // name is added later + throw ValidationError(input); // name is added later } if(filter_fn) { b = filter_fn(b); @@ -778,10 +785,10 @@ class Transformer : public Validator { "mapping must produce value pairs"); // Get the type of the contained item - requires a container have ::value_type // if the type does not have first_type and second_type, these are both value_type - using element_t = typename detail::element_type<T>::type; // Removes (smart) pointers if needed - using item_t = typename detail::pair_adaptor<element_t>::first_type; // Is value_type if not a map - using local_item_t = typename IsMemberType<item_t>::type; // This will convert bad types to good ones - // (const char * to std::string) + using element_t = typename detail::element_type<T>::type; // Removes (smart) pointers if needed + using item_t = typename detail::pair_adaptor<element_t>::first_type; // Is value_type if not a map + using local_item_t = typename IsMemberType<item_t>::type; // Will convert bad types to good ones + // (const char * to std::string) // Make a local copy of the filter function, using a std::function if not one already std::function<local_item_t(local_item_t)> filter_fn = filter_function; @@ -836,12 +843,11 @@ class CheckedTransformer : public Validator { "mapping must produce value pairs"); // Get the type of the contained item - requires a container have ::value_type // if the type does not have first_type and second_type, these are both value_type - using element_t = typename detail::element_type<T>::type; // Removes (smart) pointers if needed - using item_t = typename detail::pair_adaptor<element_t>::first_type; // Is value_type if not a map - using local_item_t = typename IsMemberType<item_t>::type; // This will convert bad types to good ones - // (const char * to std::string) - using iteration_type_t = typename detail::pair_adaptor<element_t>::value_type; // the type of the object pair // - // the type of the object pair + using element_t = typename detail::element_type<T>::type; // Removes (smart) pointers if needed + using item_t = typename detail::pair_adaptor<element_t>::first_type; // Is value_type if not a map + using local_item_t = typename IsMemberType<item_t>::type; // Will convert bad types to good ones + // (const char * to std::string) + using iteration_type_t = typename detail::pair_adaptor<element_t>::value_type; // the type of the object pair // Make a local copy of the filter function, using a std::function if not one already std::function<local_item_t(local_item_t)> filter_fn = filter_function; @@ -1125,7 +1131,7 @@ inline std::pair<std::string, std::string> split_program_name(std::string comman return vals; } -} // namespace detail +} // namespace detail /// @} -} // namespace CLI +} // namespace CLI diff --git a/packages/CLI11/test_package/conanfile.py b/packages/CLI11/test_package/conanfile.py index 91b91ddd563245284ff96e244c0e70c587f1111e..4c5c028ecee13664aaad7ca19cd9f74f271896ea 100644 --- a/packages/CLI11/test_package/conanfile.py +++ b/packages/CLI11/test_package/conanfile.py @@ -1,4 +1,4 @@ -from conans import ConanFile, CMake +from conans import ConanFile, CMake, tools import os @@ -16,5 +16,6 @@ class HelloTestConan(ConanFile): self.copy("*.dylib*", dst="bin", src="lib") def test(self): - os.chdir("bin") - self.run(".%sexample" % os.sep) + if not tools.cross_building(self.settings): + os.chdir("bin") + self.run(".%sexample" % os.sep) diff --git a/packages/CLI11/test_package/example.cpp b/packages/CLI11/test_package/example.cpp index 2dfa1d2fa344b6cfc76250e3461807c48d06f1be..beb97fe7fafec9e224cc58e97843ed5d8dff6f23 100644 --- a/packages/CLI11/test_package/example.cpp +++ b/packages/CLI11/test_package/example.cpp @@ -6,7 +6,7 @@ int main(int argc, char **argv) { - CLI::App app("Some nice discription"); + CLI::App app("Some nice description"); int x = 0; app.add_option("-x", x, "an integer value", true /* show default */); diff --git a/packages/CLI11/tests/AppTest.cpp b/packages/CLI11/tests/AppTest.cpp index 633072ee836734196bb2a8249b41bbb405de6207..b0ca787af03cab9507ebc5063938288165648681 100644 --- a/packages/CLI11/tests/AppTest.cpp +++ b/packages/CLI11/tests/AppTest.cpp @@ -1,3 +1,9 @@ +// Copyright (c) 2017-2020, University of Cincinnati, developed by Henry Schreiner +// under NSF AWARD 1414736 and by the respective contributors. +// All rights reserved. +// +// SPDX-License-Identifier: BSD-3-Clause + #include "app_helper.hpp" #include <complex> #include <cstdint> @@ -454,98 +460,6 @@ TEST_F(TApp, SepInt) { EXPECT_EQ(i, 4); } -TEST_F(TApp, OneStringAgain) { - std::string str; - app.add_option("-s,--string", str); - args = {"--string", "mystring"}; - run(); - EXPECT_EQ(1u, app.count("-s")); - EXPECT_EQ(1u, app.count("--string")); - EXPECT_EQ(str, "mystring"); -} - -TEST_F(TApp, OneStringFunction) { - std::string str; - app.add_option_function<std::string>("-s,--string", [&str](const std::string &val) { str = val; }); - args = {"--string", "mystring"}; - run(); - EXPECT_EQ(1u, app.count("-s")); - EXPECT_EQ(1u, app.count("--string")); - EXPECT_EQ(str, "mystring"); -} - -TEST_F(TApp, doubleFunction) { - double res{0.0}; - app.add_option_function<double>("--val", [&res](double val) { res = std::abs(val + 54); }); - args = {"--val", "-354.356"}; - run(); - EXPECT_EQ(res, 300.356); - // get the original value as entered as an integer - EXPECT_EQ(app["--val"]->as<float>(), -354.356f); -} - -TEST_F(TApp, doubleFunctionFail) { - double res; - app.add_option_function<double>("--val", [&res](double val) { res = std::abs(val + 54); }); - args = {"--val", "not_double"}; - EXPECT_THROW(run(), CLI::ConversionError); -} - -TEST_F(TApp, doubleVectorFunction) { - std::vector<double> res; - app.add_option_function<std::vector<double>>("--val", [&res](const std::vector<double> &val) { - res = val; - std::transform(res.begin(), res.end(), res.begin(), [](double v) { return v + 5.0; }); - }); - args = {"--val", "5", "--val", "6", "--val", "7"}; - run(); - EXPECT_EQ(res.size(), 3u); - EXPECT_EQ(res[0], 10.0); - EXPECT_EQ(res[2], 12.0); -} - -TEST_F(TApp, doubleVectorFunctionFail) { - std::vector<double> res; - std::string vstring = "--val"; - app.add_option_function<std::vector<double>>(vstring, [&res](const std::vector<double> &val) { - res = val; - std::transform(res.begin(), res.end(), res.begin(), [](double v) { return v + 5.0; }); - }); - args = {"--val", "five", "--val", "nine", "--val", "7"}; - EXPECT_THROW(run(), CLI::ConversionError); - // check that getting the results through the results function generates the same error - EXPECT_THROW(app[vstring]->results(res), CLI::ConversionError); - auto strvec = app[vstring]->as<std::vector<std::string>>(); - EXPECT_EQ(strvec.size(), 3u); -} - -TEST_F(TApp, doubleVectorFunctionRunCallbackOnDefault) { - std::vector<double> res; - auto opt = app.add_option_function<std::vector<double>>("--val", [&res](const std::vector<double> &val) { - res = val; - std::transform(res.begin(), res.end(), res.begin(), [](double v) { return v + 5.0; }); - }); - args = {"--val", "5", "--val", "6", "--val", "7"}; - run(); - EXPECT_EQ(res.size(), 3u); - EXPECT_EQ(res[0], 10.0); - EXPECT_EQ(res[2], 12.0); - EXPECT_FALSE(opt->get_run_callback_for_default()); - opt->run_callback_for_default(); - opt->default_val(std::vector<int>{2, 1, -2}); - EXPECT_EQ(res[0], 7.0); - EXPECT_EQ(res[2], 3.0); - - EXPECT_THROW(opt->default_val("this is a string"), CLI::ConversionError); - auto vec = opt->as<std::vector<double>>(); - ASSERT_EQ(vec.size(), 3U); - EXPECT_EQ(vec[0], 5.0); - EXPECT_EQ(vec[2], 7.0); - opt->check(CLI::Number); - opt->run_callback_for_default(false); - EXPECT_THROW(opt->default_val("this is a string"), CLI::ValidationError); -} - TEST_F(TApp, DefaultStringAgain) { std::string str = "previous"; app.add_option("-s,--string", str); @@ -647,35 +561,6 @@ TEST_F(TApp, LotsOfFlagsSingleStringExtraSpace) { EXPECT_EQ(1u, app.count("-A")); } -TEST_F(TApp, BoolAndIntFlags) { - - bool bflag{false}; - int iflag{0}; - unsigned int uflag{0}; - - app.add_flag("-b", bflag); - app.add_flag("-i", iflag); - app.add_flag("-u", uflag); - - args = {"-b", "-i", "-u"}; - run(); - EXPECT_TRUE(bflag); - EXPECT_EQ(1, iflag); - EXPECT_EQ((unsigned int)1, uflag); - - args = {"-b", "-b"}; - ASSERT_NO_THROW(run()); - EXPECT_TRUE(bflag); - - bflag = false; - - args = {"-iiiuu"}; - run(); - EXPECT_FALSE(bflag); - EXPECT_EQ(3, iflag); - EXPECT_EQ((unsigned int)2, uflag); -} - TEST_F(TApp, FlagLikeOption) { bool val{false}; auto opt = app.add_option("--flag", val)->type_size(0)->default_str("true"); @@ -684,7 +569,7 @@ TEST_F(TApp, FlagLikeOption) { EXPECT_EQ(1u, app.count("--flag")); EXPECT_TRUE(val); val = false; - opt->type_size(0, 0); // should be the same as above + opt->type_size(0, 0); // should be the same as above EXPECT_EQ(opt->get_type_size_min(), 0); EXPECT_EQ(opt->get_type_size_max(), 0); run(); @@ -724,35 +609,9 @@ TEST_F(TApp, BoolOnlyFlag) { EXPECT_THROW(run(), CLI::ArgumentMismatch); } -TEST_F(TApp, BoolOption) { - bool bflag{false}; - app.add_option("-b", bflag); - - args = {"-b", "false"}; - run(); - EXPECT_FALSE(bflag); - - args = {"-b", "1"}; - run(); - EXPECT_TRUE(bflag); - - args = {"-b", "-7"}; - run(); - EXPECT_FALSE(bflag); - - // cause an out of bounds error internally - args = {"-b", "751615654161688126132138844896646748852"}; - run(); - EXPECT_TRUE(bflag); - - args = {"-b", "-751615654161688126132138844896646748852"}; - run(); - EXPECT_FALSE(bflag); -} - TEST_F(TApp, ShortOpts) { - unsigned long long funnyint{0}; + std::uint64_t funnyint{0}; std::string someopt; app.add_flag("-z", funnyint); app.add_option("-y", someopt); @@ -765,7 +624,7 @@ TEST_F(TApp, ShortOpts) { EXPECT_EQ(2u, app.count("-z")); EXPECT_EQ(1u, app.count("-y")); - EXPECT_EQ((unsigned long long)2, funnyint); + EXPECT_EQ(std::uint64_t{2}, funnyint); EXPECT_EQ("zyz", someopt); EXPECT_EQ(app.count_all(), 3u); } @@ -797,7 +656,7 @@ TEST_F(TApp, DefaultOpts) { std::string s = "HI"; app.add_option("-i,i", i); - app.add_option("-s,s", s)->capture_default_str(); // Used to be different + app.add_option("-s,s", s)->capture_default_str(); // Used to be different args = {"-i2", "9"}; @@ -892,37 +751,6 @@ TEST_F(TApp, TakeLastOptMulti) { EXPECT_EQ(vals, std::vector<int>({2, 3})); } -TEST_F(TApp, vectorDefaults) { - std::vector<int> vals{4, 5}; - auto opt = app.add_option("--long", vals, "", true); - - args = {"--long", "[1,2,3]"}; - - run(); - - EXPECT_EQ(vals, std::vector<int>({1, 2, 3})); - - args.clear(); - run(); - auto res = app["--long"]->as<std::vector<int>>(); - EXPECT_EQ(res, std::vector<int>({4, 5})); - - app.clear(); - opt->expected(1)->take_last(); - res = app["--long"]->as<std::vector<int>>(); - EXPECT_EQ(res, std::vector<int>({5})); - opt->take_first(); - res = app["--long"]->as<std::vector<int>>(); - EXPECT_EQ(res, std::vector<int>({4})); - - opt->expected(0, 1)->take_last(); - run(); - - EXPECT_EQ(res, std::vector<int>({4})); - res = app["--long"]->as<std::vector<int>>(); - EXPECT_EQ(res, std::vector<int>({5})); -} - TEST_F(TApp, TakeLastOptMulti_alternative_path) { std::vector<int> vals; app.add_option("--long", vals)->expected(2, -1)->take_last(); @@ -1191,7 +1019,7 @@ TEST_F(TApp, RequiredPositionalVector) { // Tests positionals at end TEST_F(TApp, RequiredPositionalValidation) { std::vector<std::string> sources; - int dest; // required + int dest; // required std::string d2; app.add_option("src", sources); app.add_option("dest", dest)->required()->check(CLI::PositiveNumber); @@ -1394,7 +1222,7 @@ TEST_F(TApp, RequireOptPriorityShort) { EXPECT_EQ(remain, std::vector<std::string>({"two", "three"})); } -TEST_F(TApp, NotRequiedExpectedDouble) { +TEST_F(TApp, NotRequiredExpectedDouble) { std::vector<std::string> strs; app.add_option("--str", strs)->expected(2); @@ -1404,7 +1232,7 @@ TEST_F(TApp, NotRequiedExpectedDouble) { EXPECT_THROW(run(), CLI::ArgumentMismatch); } -TEST_F(TApp, NotRequiedExpectedDoubleShort) { +TEST_F(TApp, NotRequiredExpectedDoubleShort) { std::vector<std::string> strs; app.add_option("-s", strs)->expected(2); @@ -1416,7 +1244,7 @@ TEST_F(TApp, NotRequiedExpectedDoubleShort) { TEST_F(TApp, RequiredFlags) { app.add_flag("-a")->required(); - app.add_flag("-b")->mandatory(); // Alternate term + app.add_flag("-b")->mandatory(); // Alternate term EXPECT_THROW(run(), CLI::RequiredError); @@ -1452,27 +1280,6 @@ TEST_F(TApp, CallbackFlags) { EXPECT_THROW(app.add_flag_function("hi", func), CLI::IncorrectConstruction); } -TEST_F(TApp, CallbackBoolFlags) { - - bool value{false}; - - auto func = [&value]() { value = true; }; - - auto cback = app.add_flag_callback("--val", func); - args = {"--val"}; - run(); - EXPECT_TRUE(value); - value = false; - args = {"--val=false"}; - run(); - EXPECT_FALSE(value); - - EXPECT_THROW(app.add_flag_callback("hi", func), CLI::IncorrectConstruction); - cback->multi_option_policy(CLI::MultiOptionPolicy::Throw); - args = {"--val", "--val=false"}; - EXPECT_THROW(run(), CLI::ArgumentMismatch); -} - TEST_F(TApp, CallbackFlagsFalse) { std::int64_t value = 0; @@ -1686,7 +1493,7 @@ TEST_F(TApp, RemoveExcludesLinks) { args = {"--two"}; - run(); // Mostly hoping it does not crash + run(); // Mostly hoping it does not crash } TEST_F(TApp, FileNotExists) { @@ -1700,7 +1507,7 @@ TEST_F(TApp, FileNotExists) { run(); EXPECT_EQ(myfile, filename); - bool ok = static_cast<bool>(std::ofstream(myfile.c_str()).put('a')); // create file + bool ok = static_cast<bool>(std::ofstream(myfile.c_str()).put('a')); // create file EXPECT_TRUE(ok); EXPECT_THROW(run(), CLI::ValidationError); // deactivate the check, so it should run now @@ -1720,7 +1527,7 @@ TEST_F(TApp, FileExists) { EXPECT_THROW(run(), CLI::ValidationError); - bool ok = static_cast<bool>(std::ofstream(myfile.c_str()).put('a')); // create file + bool ok = static_cast<bool>(std::ofstream(myfile.c_str()).put('a')); // create file EXPECT_TRUE(ok); run(); EXPECT_EQ(myfile, filename); @@ -1739,7 +1546,7 @@ TEST_F(TApp, NotFileExists) { EXPECT_NO_THROW(run()); - bool ok = static_cast<bool>(std::ofstream(myfile.c_str()).put('a')); // create file + bool ok = static_cast<bool>(std::ofstream(myfile.c_str()).put('a')); // create file EXPECT_TRUE(ok); EXPECT_THROW(run(), CLI::ValidationError); @@ -1747,107 +1554,6 @@ TEST_F(TApp, NotFileExists) { EXPECT_FALSE(CLI::ExistingFile(myfile).empty()); } -TEST_F(TApp, pair_check) { - std::string myfile{"pair_check_file.txt"}; - bool ok = static_cast<bool>(std::ofstream(myfile.c_str()).put('a')); // create file - EXPECT_TRUE(ok); - - EXPECT_TRUE(CLI::ExistingFile(myfile).empty()); - std::pair<std::string, int> findex; - - auto v0 = CLI::ExistingFile; - v0.application_index(0); - auto v1 = CLI::PositiveNumber; - v1.application_index(1); - app.add_option("--file", findex)->check(v0)->check(v1); - - args = {"--file", myfile, "2"}; - - EXPECT_NO_THROW(run()); - - EXPECT_EQ(findex.first, myfile); - EXPECT_EQ(findex.second, 2); - - args = {"--file", myfile, "-3"}; - - EXPECT_THROW(run(), CLI::ValidationError); - - args = {"--file", myfile, "2"}; - std::remove(myfile.c_str()); - EXPECT_THROW(run(), CLI::ValidationError); -} - -// this will require that modifying the multi-option policy for tuples be allowed which it isn't at present - -TEST_F(TApp, pair_check_take_first) { - std::string myfile{"pair_check_file2.txt"}; - bool ok = static_cast<bool>(std::ofstream(myfile.c_str()).put('a')); // create file - EXPECT_TRUE(ok); - - EXPECT_TRUE(CLI::ExistingFile(myfile).empty()); - std::pair<std::string, int> findex; - - auto opt = app.add_option("--file", findex)->check(CLI::ExistingFile)->check(CLI::PositiveNumber); - EXPECT_THROW(opt->get_validator(3), CLI::OptionNotFound); - opt->get_validator(0)->application_index(0); - opt->get_validator(1)->application_index(1); - opt->multi_option_policy(CLI::MultiOptionPolicy::TakeLast); - args = {"--file", "not_a_file.txt", "-16", "--file", myfile, "2"}; - // should only check the last one - EXPECT_NO_THROW(run()); - - EXPECT_EQ(findex.first, myfile); - EXPECT_EQ(findex.second, 2); - - opt->multi_option_policy(CLI::MultiOptionPolicy::TakeFirst); - - EXPECT_THROW(run(), CLI::ValidationError); -} - -TEST_F(TApp, VectorFixedString) { - std::vector<std::string> strvec; - std::vector<std::string> answer{"mystring", "mystring2", "mystring3"}; - - CLI::Option *opt = app.add_option("-s,--string", strvec)->expected(3); - EXPECT_EQ(3, opt->get_expected()); - - args = {"--string", "mystring", "mystring2", "mystring3"}; - run(); - EXPECT_EQ(3u, app.count("--string")); - EXPECT_EQ(answer, strvec); -} - -TEST_F(TApp, VectorDefaultedFixedString) { - std::vector<std::string> strvec{"one"}; - std::vector<std::string> answer{"mystring", "mystring2", "mystring3"}; - - CLI::Option *opt = app.add_option("-s,--string", strvec, "")->expected(3)->capture_default_str(); - EXPECT_EQ(3, opt->get_expected()); - - args = {"--string", "mystring", "mystring2", "mystring3"}; - run(); - EXPECT_EQ(3u, app.count("--string")); - EXPECT_EQ(answer, strvec); -} - -TEST_F(TApp, VectorIndexedValidator) { - std::vector<int> vvec; - - CLI::Option *opt = app.add_option("-v", vvec); - - args = {"-v", "1", "-1", "-v", "3", "-v", "-976"}; - run(); - EXPECT_EQ(4u, app.count("-v")); - EXPECT_EQ(4u, vvec.size()); - opt->check(CLI::PositiveNumber.application_index(0)); - opt->check((!CLI::PositiveNumber).application_index(1)); - EXPECT_NO_THROW(run()); - EXPECT_EQ(4u, vvec.size()); - // v[3] would be negative - opt->check(CLI::PositiveNumber.application_index(3)); - EXPECT_THROW(run(), CLI::ValidationError); -} - TEST_F(TApp, DefaultedResult) { std::string sval = "NA"; int ival{0}; @@ -1866,93 +1572,6 @@ TEST_F(TApp, DefaultedResult) { EXPECT_EQ(newIval, 442); } -TEST_F(TApp, VectorUnlimString) { - std::vector<std::string> strvec; - std::vector<std::string> answer{"mystring", "mystring2", "mystring3"}; - - CLI::Option *opt = app.add_option("-s,--string", strvec); - EXPECT_EQ(1, opt->get_expected()); - EXPECT_EQ(CLI::detail::expected_max_vector_size, opt->get_expected_max()); - - args = {"--string", "mystring", "mystring2", "mystring3"}; - run(); - EXPECT_EQ(3u, app.count("--string")); - EXPECT_EQ(answer, strvec); - - args = {"-s", "mystring", "mystring2", "mystring3"}; - run(); - EXPECT_EQ(3u, app.count("--string")); - EXPECT_EQ(answer, strvec); -} - -// From https://github.com/CLIUtils/CLI11/issues/420 -TEST_F(TApp, stringLikeTests) { - struct nType { - explicit nType(const std::string &a_value) : m_value{a_value} {} - - explicit operator std::string() const { return std::string{"op str"}; } - - std::string m_value; - }; - - nType m_type{"abc"}; - app.add_option("--type", m_type, "type")->capture_default_str(); - run(); - - EXPECT_EQ(app["--type"]->as<std::string>(), "op str"); - args = {"--type", "bca"}; - run(); - EXPECT_EQ(std::string(m_type), "op str"); - EXPECT_EQ(m_type.m_value, "bca"); -} - -TEST_F(TApp, VectorExpectedRange) { - std::vector<std::string> strvec; - - CLI::Option *opt = app.add_option("--string", strvec); - opt->expected(2, 4)->multi_option_policy(CLI::MultiOptionPolicy::Throw); - - args = {"--string", "mystring", "mystring2", "mystring3"}; - run(); - EXPECT_EQ(3u, app.count("--string")); - - args = {"--string", "mystring"}; - EXPECT_THROW(run(), CLI::ArgumentMismatch); - - args = {"--string", "mystring", "mystring2", "string2", "--string", "string4", "string5"}; - EXPECT_THROW(run(), CLI::ArgumentMismatch); - - EXPECT_EQ(opt->get_expected_max(), 4); - EXPECT_EQ(opt->get_expected_min(), 2); - opt->expected(4, 2); // just test the handling of reversed arguments - EXPECT_EQ(opt->get_expected_max(), 4); - EXPECT_EQ(opt->get_expected_min(), 2); - opt->expected(-5); - EXPECT_EQ(opt->get_expected_max(), 5); - EXPECT_EQ(opt->get_expected_min(), 5); - opt->expected(-5, 7); - EXPECT_EQ(opt->get_expected_max(), 7); - EXPECT_EQ(opt->get_expected_min(), 5); -} - -TEST_F(TApp, VectorFancyOpts) { - std::vector<std::string> strvec; - std::vector<std::string> answer{"mystring", "mystring2", "mystring3"}; - - CLI::Option *opt = app.add_option("-s,--string", strvec)->required()->expected(3); - EXPECT_EQ(3, opt->get_expected()); - - args = {"--string", "mystring", "mystring2", "mystring3"}; - run(); - EXPECT_EQ(3u, app.count("--string")); - EXPECT_EQ(answer, strvec); - - args = {"one", "two"}; - EXPECT_THROW(run(), CLI::RequiredError); - - EXPECT_THROW(run(), CLI::ParseError); -} - TEST_F(TApp, OriginalOrder) { std::vector<int> st1; CLI::Option *op1 = app.add_option("-a", st1); @@ -2131,6 +1750,26 @@ TEST_F(TApp, Env) { EXPECT_THROW(run(), CLI::RequiredError); } +// curiously check if an environmental only option works +TEST_F(TApp, EnvOnly) { + + put_env("CLI11_TEST_ENV_TMP", "2"); + + int val{1}; + CLI::Option *vopt = app.add_option("", val)->envname("CLI11_TEST_ENV_TMP"); + + run(); + + EXPECT_EQ(2, val); + EXPECT_EQ(1u, vopt->count()); + + vopt->required(); + run(); + + unset_env("CLI11_TEST_ENV_TMP"); + EXPECT_THROW(run(), CLI::RequiredError); +} + TEST_F(TApp, RangeInt) { int x{0}; app.add_option("--one", x)->check(CLI::Range(3, 6)); @@ -2338,7 +1977,7 @@ TEST_F(TApp, OptionWithDefaults) { } // Added to test ->transform -TEST_F(TApp, OrderedModifingTransforms) { +TEST_F(TApp, OrderedModifyingTransforms) { std::vector<std::string> val; auto m = app.add_option("-m", val); m->transform([](std::string x) { return x + "1"; }); @@ -2386,170 +2025,6 @@ TEST_F(TApp, EachItem) { EXPECT_EQ(results, dummy); } -// #87 -TEST_F(TApp, CustomDoubleOption) { - - std::pair<int, double> custom_opt; - - auto opt = app.add_option("posit", [&custom_opt](CLI::results_t vals) { - custom_opt = {stol(vals.at(0)), stod(vals.at(1))}; - return true; - }); - opt->type_name("INT FLOAT")->type_size(2); - - args = {"12", "1.5"}; - - run(); - EXPECT_EQ(custom_opt.first, 12); - EXPECT_DOUBLE_EQ(custom_opt.second, 1.5); -} - -// now with tuple support this is possible -TEST_F(TApp, CustomDoubleOptionAlt) { - - std::pair<int, double> custom_opt; - - app.add_option("posit", custom_opt); - - args = {"12", "1.5"}; - - run(); - EXPECT_EQ(custom_opt.first, 12); - EXPECT_DOUBLE_EQ(custom_opt.second, 1.5); -} - -// now with independent type sizes and expected this is possible -TEST_F(TApp, vectorPair) { - - std::vector<std::pair<int, std::string>> custom_opt; - - auto opt = app.add_option("--dict", custom_opt); - - args = {"--dict", "1", "str1", "--dict", "3", "str3"}; - - run(); - EXPECT_EQ(custom_opt.size(), 2u); - EXPECT_EQ(custom_opt[0].first, 1); - EXPECT_EQ(custom_opt[1].second, "str3"); - - args = {"--dict", "1", "str1", "--dict", "3", "str3", "--dict", "-1", "str4"}; - run(); - EXPECT_EQ(custom_opt.size(), 3u); - EXPECT_EQ(custom_opt[2].first, -1); - EXPECT_EQ(custom_opt[2].second, "str4"); - opt->check(CLI::PositiveNumber.application_index(0)); - - EXPECT_THROW(run(), CLI::ValidationError); -} - -TEST_F(TApp, vectorPairFail) { - - std::vector<std::pair<int, std::string>> custom_opt; - - app.add_option("--dict", custom_opt); - - args = {"--dict", "1", "str1", "--dict", "str3", "1"}; - - EXPECT_THROW(run(), CLI::ConversionError); -} - -TEST_F(TApp, vectorPairTypeRange) { - - std::vector<std::pair<int, std::string>> custom_opt; - - auto opt = app.add_option("--dict", custom_opt); - - opt->type_size(2, 1); // just test switched arguments - EXPECT_EQ(opt->get_type_size_min(), 1); - EXPECT_EQ(opt->get_type_size_max(), 2); - - args = {"--dict", "1", "str1", "--dict", "3", "str3"}; - - run(); - EXPECT_EQ(custom_opt.size(), 2u); - EXPECT_EQ(custom_opt[0].first, 1); - EXPECT_EQ(custom_opt[1].second, "str3"); - - args = {"--dict", "1", "str1", "--dict", "3", "--dict", "-1", "str4"}; - run(); - EXPECT_EQ(custom_opt.size(), 3u); - EXPECT_TRUE(custom_opt[1].second.empty()); - EXPECT_EQ(custom_opt[2].first, -1); - EXPECT_EQ(custom_opt[2].second, "str4"); - - opt->type_size(-2, -1); // test negative arguments - EXPECT_EQ(opt->get_type_size_min(), 1); - EXPECT_EQ(opt->get_type_size_max(), 2); - // this type size spec should run exactly as before - run(); - EXPECT_EQ(custom_opt.size(), 3u); - EXPECT_TRUE(custom_opt[1].second.empty()); - EXPECT_EQ(custom_opt[2].first, -1); - EXPECT_EQ(custom_opt[2].second, "str4"); -} - -// now with independent type sizes and expected this is possible -TEST_F(TApp, vectorTuple) { - - std::vector<std::tuple<int, std::string, double>> custom_opt; - - auto opt = app.add_option("--dict", custom_opt); - - args = {"--dict", "1", "str1", "4.3", "--dict", "3", "str3", "2.7"}; - - run(); - EXPECT_EQ(custom_opt.size(), 2u); - EXPECT_EQ(std::get<0>(custom_opt[0]), 1); - EXPECT_EQ(std::get<1>(custom_opt[1]), "str3"); - EXPECT_EQ(std::get<2>(custom_opt[1]), 2.7); - - args = {"--dict", "1", "str1", "4.3", "--dict", "3", "str3", "2.7", "--dict", "-1", "str4", "-1.87"}; - run(); - EXPECT_EQ(custom_opt.size(), 3u); - EXPECT_EQ(std::get<0>(custom_opt[2]), -1); - EXPECT_EQ(std::get<1>(custom_opt[2]), "str4"); - EXPECT_EQ(std::get<2>(custom_opt[2]), -1.87); - opt->check(CLI::PositiveNumber.application_index(0)); - - EXPECT_THROW(run(), CLI::ValidationError); - - args.back() = "haha"; - args[9] = "45"; - EXPECT_THROW(run(), CLI::ConversionError); -} - -// now with independent type sizes and expected this is possible -TEST_F(TApp, vectorVector) { - - std::vector<std::vector<int>> custom_opt; - - auto opt = app.add_option("--dict", custom_opt); - - args = {"--dict", "1", "2", "4", "--dict", "3", "1"}; - - run(); - EXPECT_EQ(custom_opt.size(), 2u); - EXPECT_EQ(custom_opt[0].size(), 3u); - EXPECT_EQ(custom_opt[1].size(), 2u); - - args = {"--dict", "1", "2", "4", "--dict", "3", "1", "--dict", "3", "--dict", - "3", "3", "3", "3", "3", "3", "3", "3", "3", "-3"}; - run(); - EXPECT_EQ(custom_opt.size(), 4u); - EXPECT_EQ(custom_opt[0].size(), 3u); - EXPECT_EQ(custom_opt[1].size(), 2u); - EXPECT_EQ(custom_opt[2].size(), 1u); - EXPECT_EQ(custom_opt[3].size(), 10u); - opt->check(CLI::PositiveNumber.application_index(9)); - - EXPECT_THROW(run(), CLI::ValidationError); - args.pop_back(); - EXPECT_NO_THROW(run()); - - args.back() = "haha"; - EXPECT_THROW(run(), CLI::ConversionError); -} - // #128 TEST_F(TApp, RepeatingMultiArgumentOptions) { std::vector<std::string> entries; diff --git a/packages/CLI11/tests/BoostOptionTypeTest.cpp b/packages/CLI11/tests/BoostOptionTypeTest.cpp new file mode 100644 index 0000000000000000000000000000000000000000..3ddd6ae459bded6af8ee6af1c216891d8c061001 --- /dev/null +++ b/packages/CLI11/tests/BoostOptionTypeTest.cpp @@ -0,0 +1,151 @@ +// Copyright (c) 2017-2020, University of Cincinnati, developed by Henry Schreiner +// under NSF AWARD 1414736 and by the respective contributors. +// All rights reserved. +// +// SPDX-License-Identifier: BSD-3-Clause + +#include "app_helper.hpp" +#include <boost/container/flat_map.hpp> +#include <boost/container/flat_set.hpp> +#include <boost/container/slist.hpp> +#include <boost/container/small_vector.hpp> +#include <boost/container/stable_vector.hpp> +#include <boost/container/static_vector.hpp> +#include <boost/container/vector.hpp> +#include <string> +#include <vector> + +#include "gmock/gmock.h" + +namespace boost { +namespace container { + +template <class T> class TApp_container_single_boost : public TApp { + public: + using container_type = T; + container_type cval{}; + TApp_container_single_boost() : TApp() {} +}; + +using containerTypes_single_boost = + ::testing::Types<small_vector<int, 2>, small_vector<int, 3>, flat_set<int>, stable_vector<int>, slist<int>>; + +TYPED_TEST_SUITE(TApp_container_single_boost, containerTypes_single_boost, ); + +TYPED_TEST(TApp_container_single_boost, containerInt_boost) { + + auto &cv = TApp_container_single_boost<TypeParam>::cval; + CLI::Option *opt = (TApp::app).add_option("-v", cv); + + TApp::args = {"-v", "1", "-1", "-v", "3", "-v", "-976"}; + TApp::run(); + EXPECT_EQ(4u, (TApp::app).count("-v")); + EXPECT_EQ(4u, cv.size()); + opt->check(CLI::PositiveNumber.application_index(0)); + opt->check((!CLI::PositiveNumber).application_index(1)); + EXPECT_NO_THROW(TApp::run()); + EXPECT_EQ(4u, cv.size()); + // v[3] would be negative + opt->check(CLI::PositiveNumber.application_index(3)); + EXPECT_THROW(TApp::run(), CLI::ValidationError); +} + +template <class T> class TApp_container_pair_boost : public TApp { + public: + using container_type = T; + container_type cval{}; + TApp_container_pair_boost() : TApp() {} +}; + +using isp = std::pair<int, std::string>; +using containerTypes_pair_boost = ::testing:: + Types<stable_vector<isp>, small_vector<isp, 2>, flat_set<isp>, slist<isp>, vector<isp>, flat_map<int, std::string>>; + +TYPED_TEST_SUITE(TApp_container_pair_boost, containerTypes_pair_boost, ); + +TYPED_TEST(TApp_container_pair_boost, containerPair_boost) { + + auto &cv = TApp_container_pair_boost<TypeParam>::cval; + (TApp::app).add_option("--dict", cv); + + TApp::args = {"--dict", "1", "str1", "--dict", "3", "str3"}; + + TApp::run(); + EXPECT_EQ(cv.size(), 2u); + + TApp::args = {"--dict", "1", "str1", "--dict", "3", "--dict", "-1", "str4"}; + TApp::run(); + EXPECT_EQ(cv.size(), 3u); +} + +template <class T> class TApp_container_tuple_boost : public TApp { + public: + using container_type = T; + container_type cval{}; + TApp_container_tuple_boost() : TApp() {} +}; + +using tup_obj = std::tuple<int, std::string, double>; +using containerTypes_tuple_boost = + ::testing::Types<small_vector<tup_obj, 3>, stable_vector<tup_obj>, flat_set<tup_obj>, slist<tup_obj>>; + +TYPED_TEST_SUITE(TApp_container_tuple_boost, containerTypes_tuple_boost, ); + +TYPED_TEST(TApp_container_tuple_boost, containerTuple_boost) { + + auto &cv = TApp_container_tuple_boost<TypeParam>::cval; + (TApp::app).add_option("--dict", cv); + + TApp::args = {"--dict", "1", "str1", "4.3", "--dict", "3", "str3", "2.7"}; + + TApp::run(); + EXPECT_EQ(cv.size(), 2u); + + TApp::args = {"--dict", "1", "str1", "4.3", "--dict", "3", "str3", "2.7", "--dict", "-1", "str4", "-1.87"}; + TApp::run(); + EXPECT_EQ(cv.size(), 3u); +} + +using icontainer1 = vector<int>; +using icontainer2 = flat_set<int>; +using icontainer3 = slist<int>; +using containerTypes_container_boost = ::testing::Types<std::vector<icontainer1>, + slist<icontainer1>, + flat_set<icontainer1>, + small_vector<icontainer1, 2>, + std::vector<icontainer2>, + slist<icontainer2>, + flat_set<icontainer2>, + stable_vector<icontainer2>, + static_vector<icontainer3, 10>, + slist<icontainer3>, + flat_set<icontainer3>, + static_vector<icontainer3, 10>>; + +template <class T> class TApp_container_container_boost : public TApp { + public: + using container_type = T; + container_type cval{}; + TApp_container_container_boost() : TApp() {} +}; + +TYPED_TEST_SUITE(TApp_container_container_boost, containerTypes_container_boost, ); + +TYPED_TEST(TApp_container_container_boost, containerContainer_boost) { + + auto &cv = TApp_container_container_boost<TypeParam>::cval; + (TApp::app).add_option("--dict", cv); + + TApp::args = {"--dict", "1", "2", "4", "--dict", "3", "1"}; + + TApp::run(); + EXPECT_EQ(cv.size(), 2u); + + TApp::args = {"--dict", "1", "2", "4", "--dict", "3", "1", "--dict", "3", "--dict", + "3", "3", "3", "3", "3", "3", "3", "3", "3", "-3"}; + TApp::run(); + EXPECT_EQ(cv.size(), 4u); +} + +} // namespace container +} // namespace boost diff --git a/packages/CLI11/tests/CMakeLists.txt b/packages/CLI11/tests/CMakeLists.txt index f697867d7c5b1985f0aaaf3145d7fe0babb4beb2..7e76321e4b0470415e4642f465f756006267d691 100644 --- a/packages/CLI11/tests/CMakeLists.txt +++ b/packages/CLI11/tests/CMakeLists.txt @@ -32,13 +32,19 @@ endif() set(GOOGLE_TEST_INDIVIDUAL OFF) include(AddGoogletest) +# Add boost to test boost::optional if available +find_package(Boost 1.61) + +set(boost-optional-def $<$<BOOL:${Boost_FOUND}>:CLI11_BOOST_OPTIONAL>) + set(CLI11_TESTS HelpersTest ConfigFileTest + OptionTypeTest SimpleTest AppTest SetTest - TransformTest + TransformTest CreationTest SubcommandTest HelpTest @@ -47,6 +53,7 @@ set(CLI11_TESTS OptionalTest DeprecatedTest StringParseTest + ComplexTypeTest TrueFalseTest OptionGroupTest ) @@ -55,6 +62,10 @@ if(WIN32) list(APPEND CLI11_TESTS WindowsTest) endif() +if (Boost_FOUND) + list(APPEND CLI11_TESTS BoostOptionTypeTest) +endif() + set(CLI11_MULTIONLY_TESTS TimerTest) # Only affects current directory, so safe @@ -140,20 +151,29 @@ file(WRITE "${PROJECT_BINARY_DIR}/CTestCustom.cmake" "set(CTEST_CUSTOM_PRE_TEST \"${CMAKE_BINARY_DIR}/informational\")" ) -# Add boost to test boost::optional if available -find_package(Boost 1.61) - -set(boost-optional-def $<$<BOOL:${Boost_FOUND}>:CLI11_BOOST_OPTIONAL>) - target_compile_definitions(informational PRIVATE ${boost-optional-def}) target_compile_definitions(OptionalTest PRIVATE ${boost-optional-def}) +message(STATUS "Boost libs=${Boost_INCLUDE_DIRS}") + if(TARGET Boost::boost) + message(STATUS "including boost target") target_link_libraries(informational PRIVATE Boost::boost) target_link_libraries(OptionalTest PRIVATE Boost::boost) + target_link_libraries(BoostOptionTypeTest PRIVATE Boost::boost) + if(CLI11_SINGLE_FILE AND CLI11_SINGLE_FILE_TESTS) + target_link_libraries(OptionalTest_Single PRIVATE Boost::boost) + target_link_libraries(BoostOptionTypeTest_Single PRIVATE Boost::boost) + endif() elseif(BOOST_FOUND) +message(STATUS "no boost target") target_include_directories(informational PRIVATE ${Boost_INCLUDE_DIRS}) target_include_directories(OptionalTest PRIVATE ${Boost_INCLUDE_DIRS}) + target_include_directories(BoostOptionTypeTest PRIVATE ${Boost_INCLUDE_DIRS}) + if(CLI11_SINGLE_FILE AND CLI11_SINGLE_FILE_TESTS) + target_include_directories(OptionalTest_Single PRIVATE ${Boost_INCLUDE_DIRS}) + target_include_directories(BoostOptionTypeTest_Single PRIVATE ${Boost_INCLUDE_DIRS}) + endif() endif() if(CMAKE_BUILD_TYPE STREQUAL Coverage) diff --git a/packages/CLI11/tests/ComplexTypeTest.cpp b/packages/CLI11/tests/ComplexTypeTest.cpp new file mode 100644 index 0000000000000000000000000000000000000000..43c6cd48bab35cdc2a2ad09e6e59ccbc712a9dd6 --- /dev/null +++ b/packages/CLI11/tests/ComplexTypeTest.cpp @@ -0,0 +1,191 @@ +// Copyright (c) 2017-2020, University of Cincinnati, developed by Henry Schreiner +// under NSF AWARD 1414736 and by the respective contributors. +// All rights reserved. +// +// SPDX-License-Identifier: BSD-3-Clause + +#include "app_helper.hpp" +#include "gmock/gmock.h" +#include <complex> +#include <cstdint> + +using ::testing::HasSubstr; + +using cx = std::complex<double>; + +CLI::Option * +add_option(CLI::App &app, std::string name, cx &variable, std::string description = "", bool defaulted = false) { + CLI::callback_t fun = [&variable](CLI::results_t res) { + double x, y; + bool worked = CLI::detail::lexical_cast(res[0], x) && CLI::detail::lexical_cast(res[1], y); + if(worked) + variable = cx(x, y); + return worked; + }; + + CLI::Option *opt = app.add_option(name, fun, description, defaulted); + opt->type_name("COMPLEX")->type_size(2); + if(defaulted) { + std::stringstream out; + out << variable; + opt->default_str(out.str()); + } + return opt; +} + +TEST_F(TApp, AddingComplexParser) { + + cx comp{0, 0}; + add_option(app, "-c,--complex", comp); + args = {"-c", "1.5", "2.5"}; + + run(); + + EXPECT_DOUBLE_EQ(1.5, comp.real()); + EXPECT_DOUBLE_EQ(2.5, comp.imag()); +} + +TEST_F(TApp, DefaultedComplex) { + + cx comp{1, 2}; + add_option(app, "-c,--complex", comp, "", true); + args = {"-c", "4", "3"}; + + std::string help = app.help(); + EXPECT_THAT(help, HasSubstr("1")); + EXPECT_THAT(help, HasSubstr("2")); + + EXPECT_DOUBLE_EQ(1, comp.real()); + EXPECT_DOUBLE_EQ(2, comp.imag()); + + run(); + + EXPECT_DOUBLE_EQ(4, comp.real()); + EXPECT_DOUBLE_EQ(3, comp.imag()); +} + +// an example of custom complex number converter that can be used to add new parsing options +#if defined(__has_include) +#if __has_include(<regex>) +// an example of custom converter that can be used to add new parsing options +#define HAS_REGEX_INCLUDE +#endif +#endif + +#ifdef HAS_REGEX_INCLUDE +// Gcc 4.8 and older and the corresponding standard libraries have a broken <regex> so this would +// fail. And if a clang compiler is using libstd++ then this will generate an error as well so this is just a check to +// simplify compilation and prevent a much more complicated #if expression +#include <regex> +namespace CLI { +namespace detail { + +// On MSVC and possibly some other new compilers this can be a free standing function without the template +// specialization but this is compiler dependent +template <> bool lexical_cast<std::complex<double>>(const std::string &input, std::complex<double> &output) { + // regular expression to handle complex numbers of various formats + static const std::regex creg( + R"(([+-]?(\d+(\.\d+)?|\.\d+)([eE][+-]?\d+)?)\s*([+-]\s*(\d+(\.\d+)?|\.\d+)([eE][+-]?\d+)?)[ji]*)"); + + std::smatch m; + double x{0.0}, y{0.0}; + bool worked; + std::regex_search(input, m, creg); + if(m.size() == 9) { + worked = CLI::detail::lexical_cast(m[1], x) && CLI::detail::lexical_cast(m[6], y); + if(worked) { + if(*m[5].first == '-') { + y = -y; + } + } + } else { + if((input.back() == 'j') || (input.back() == 'i')) { + auto strval = input.substr(0, input.size() - 1); + CLI::detail::trim(strval); + worked = CLI::detail::lexical_cast(strval, y); + } else { + std::string ival = input; + CLI::detail::trim(ival); + worked = CLI::detail::lexical_cast(ival, x); + } + } + if(worked) { + output = cx{x, y}; + } + return worked; +} +} // namespace detail +} // namespace CLI + +TEST_F(TApp, AddingComplexParserDetail) { + + bool skip_tests = false; + try { // check if the library actually supports regex, it is possible to link against a non working regex in the + // standard library + std::smatch m; + std::string input = "1.5+2.5j"; + static const std::regex creg( + R"(([+-]?(\d+(\.\d+)?|\.\d+)([eE][+-]?\d+)?)\s*([+-]\s*(\d+(\.\d+)?|\.\d+)([eE][+-]?\d+)?)[ji]*)"); + + auto rsearch = std::regex_search(input, m, creg); + if(!rsearch) { + skip_tests = true; + } else { + EXPECT_EQ(m.size(), 9u); + } + + } catch(...) { + skip_tests = true; + } + static_assert(CLI::detail::is_complex<cx>::value, "complex should register as complex in this situation"); + if(!skip_tests) { + cx comp{0, 0}; + + app.add_option("-c,--complex", comp, "add a complex number option"); + args = {"-c", "1.5+2.5j"}; + + run(); + + EXPECT_DOUBLE_EQ(1.5, comp.real()); + EXPECT_DOUBLE_EQ(2.5, comp.imag()); + args = {"-c", "1.5-2.5j"}; + + run(); + + EXPECT_DOUBLE_EQ(1.5, comp.real()); + EXPECT_DOUBLE_EQ(-2.5, comp.imag()); + } +} +#endif +// defining a new complex class +class complex_new { + public: + complex_new() = default; + complex_new(double v1, double v2) : val1_{v1}, val2_{v2} {}; + double real() { return val1_; } + double imag() { return val2_; } + + private: + double val1_{0.0}; + double val2_{0.0}; +}; + +TEST_F(TApp, newComplex) { + complex_new cval; + static_assert(CLI::detail::is_complex<complex_new>::value, "complex new does not register as a complex type"); + static_assert(CLI::detail::classify_object<complex_new>::value == CLI::detail::object_category::complex_number, + "complex new does not result in complex number categorization"); + app.add_option("-c,--complex", cval, "add a complex number option"); + args = {"-c", "1.5+2.5j"}; + + run(); + + EXPECT_DOUBLE_EQ(1.5, cval.real()); + EXPECT_DOUBLE_EQ(2.5, cval.imag()); + args = {"-c", "1.5-2.5j"}; + + run(); + + EXPECT_DOUBLE_EQ(1.5, cval.real()); + EXPECT_DOUBLE_EQ(-2.5, cval.imag()); +} diff --git a/packages/CLI11/tests/ConfigFileTest.cpp b/packages/CLI11/tests/ConfigFileTest.cpp index 855588557ff9fd5811cc7a02adb427a9138a89c0..370ad9769d3418a3ff70053206290858188adfbe 100644 --- a/packages/CLI11/tests/ConfigFileTest.cpp +++ b/packages/CLI11/tests/ConfigFileTest.cpp @@ -1,3 +1,9 @@ +// Copyright (c) 2017-2020, University of Cincinnati, developed by Henry Schreiner +// under NSF AWARD 1414736 and by the respective contributors. +// All rights reserved. +// +// SPDX-License-Identifier: BSD-3-Clause + #include "app_helper.hpp" #include "gmock/gmock.h" @@ -690,12 +696,12 @@ TEST_F(TApp, IniVector) { } TEST_F(TApp, TOMLVector) { - TempFile tmpini{"TestIniTmp.ini"}; + TempFile tmptoml{"TestTomlTmp.toml"}; - app.set_config("--config", tmpini); + app.set_config("--config", tmptoml); { - std::ofstream out{tmpini}; + std::ofstream out{tmptoml}; out << "#this is a comment line\n"; out << "[default]\n"; out << "two=[2,3]\n"; @@ -1045,17 +1051,17 @@ TEST_F(TApp, IniSubcommandMultipleSections) { EXPECT_EQ(parse_c[0], 68U); EXPECT_EQ(parse_c[1], 58U); EXPECT_EQ(subsubcom->count(), 1u); - EXPECT_EQ(subcom2->count(), 0u); // not configurable but value is updated + EXPECT_EQ(subcom2->count(), 0u); // not configurable but value is updated } TEST_F(TApp, DuplicateSubcommandCallbacks) { - TempFile tmpini{"TestIniTmp.ini"}; + TempFile tmptoml{"TesttomlTmp.toml"}; - app.set_config("--config", tmpini); + app.set_config("--config", tmptoml); { - std::ofstream out{tmpini}; + std::ofstream out{tmptoml}; out << "[[foo]]" << std::endl; out << "[[foo]]" << std::endl; out << "[[foo]]" << std::endl; @@ -1195,6 +1201,7 @@ TEST_F(TApp, IniFlagDual) { TempFile tmpini{"TestIniTmp.ini"}; bool boo{false}; + app.config_formatter(std::make_shared<CLI::ConfigINI>()); app.add_flag("--flag", boo); app.set_config("--config", tmpini); @@ -1206,6 +1213,57 @@ TEST_F(TApp, IniFlagDual) { EXPECT_THROW(run(), CLI::ConversionError); } +TEST_F(TApp, IniShort) { + + TempFile tmpini{"TestIniTmp.ini"}; + + int key{0}; + app.add_option("--flag,-f", key); + app.set_config("--config", tmpini); + + { + std::ofstream out{tmpini}; + out << "f=3" << std::endl; + } + + ASSERT_NO_THROW(run()); + EXPECT_EQ(key, 3); +} + +TEST_F(TApp, IniPositional) { + + TempFile tmpini{"TestIniTmp.ini"}; + + int key{0}; + app.add_option("key", key); + app.set_config("--config", tmpini); + + { + std::ofstream out{tmpini}; + out << "key=3" << std::endl; + } + + ASSERT_NO_THROW(run()); + EXPECT_EQ(key, 3); +} + +TEST_F(TApp, IniEnvironmental) { + + TempFile tmpini{"TestIniTmp.ini"}; + + int key{0}; + app.add_option("key", key)->envname("CLI11_TEST_ENV_KEY_TMP"); + app.set_config("--config", tmpini); + + { + std::ofstream out{tmpini}; + out << "CLI11_TEST_ENV_KEY_TMP=3" << std::endl; + } + + ASSERT_NO_THROW(run()); + EXPECT_EQ(key, 3); +} + TEST_F(TApp, IniFlagText) { TempFile tmpini{"TestIniTmp.ini"}; @@ -1362,7 +1420,7 @@ TEST_F(TApp, IniFalseFlagsDefDisableOverrideSuccess) { EXPECT_EQ(15, val); } -TEST_F(TApp, IniOutputSimple) { +TEST_F(TApp, TomlOutputSimple) { int v{0}; app.add_option("--simple", v); @@ -1375,7 +1433,50 @@ TEST_F(TApp, IniOutputSimple) { EXPECT_EQ("simple=3\n", str); } -TEST_F(TApp, IniOutputNoConfigurable) { +TEST_F(TApp, TomlOutputShort) { + + int v{0}; + app.add_option("-s", v); + + args = {"-s3"}; + + run(); + + std::string str = app.config_to_str(); + EXPECT_EQ("s=3\n", str); +} + +TEST_F(TApp, TomlOutputPositional) { + + int v{0}; + app.add_option("pos", v); + + args = {"3"}; + + run(); + + std::string str = app.config_to_str(); + EXPECT_EQ("pos=3\n", str); +} + +// try the output with environmental only arguments +TEST_F(TApp, TomlOutputEnvironmental) { + + put_env("CLI11_TEST_ENV_TMP", "2"); + + int val{1}; + app.add_option(std::string{}, val)->envname("CLI11_TEST_ENV_TMP"); + + run(); + + EXPECT_EQ(2, val); + std::string str = app.config_to_str(); + EXPECT_EQ("CLI11_TEST_ENV_TMP=2\n", str); + + unset_env("CLI11_TEST_ENV_TMP"); +} + +TEST_F(TApp, TomlOutputNoConfigurable) { int v1{0}, v2{0}; app.add_option("--simple", v1); @@ -1389,7 +1490,7 @@ TEST_F(TApp, IniOutputNoConfigurable) { EXPECT_EQ("simple=3\n", str); } -TEST_F(TApp, IniOutputShortSingleDescription) { +TEST_F(TApp, TomlOutputShortSingleDescription) { std::string flag = "some_flag"; const std::string description = "Some short description."; app.add_flag("--" + flag, description); @@ -1397,10 +1498,10 @@ TEST_F(TApp, IniOutputShortSingleDescription) { run(); std::string str = app.config_to_str(true, true); - EXPECT_THAT(str, HasSubstr("; " + description + "\n" + flag + "=false\n")); + EXPECT_THAT(str, HasSubstr("# " + description + "\n" + flag + "=false\n")); } -TEST_F(TApp, IniOutputShortDoubleDescription) { +TEST_F(TApp, TomlOutputShortDoubleDescription) { std::string flag1 = "flagnr1"; std::string flag2 = "flagnr2"; const std::string description1 = "First description."; @@ -1412,10 +1513,10 @@ TEST_F(TApp, IniOutputShortDoubleDescription) { std::string str = app.config_to_str(true, true); EXPECT_THAT( - str, HasSubstr("; " + description1 + "\n" + flag1 + "=false\n\n; " + description2 + "\n" + flag2 + "=false\n")); + str, HasSubstr("# " + description1 + "\n" + flag1 + "=false\n\n# " + description2 + "\n" + flag2 + "=false\n")); } -TEST_F(TApp, IniOutputGroups) { +TEST_F(TApp, TomlOutputGroups) { std::string flag1 = "flagnr1"; std::string flag2 = "flagnr2"; const std::string description1 = "First description."; @@ -1430,7 +1531,7 @@ TEST_F(TApp, IniOutputGroups) { EXPECT_THAT(str, HasSubstr("group2")); } -TEST_F(TApp, IniOutputHiddenOptions) { +TEST_F(TApp, TomlOutputHiddenOptions) { std::string flag1 = "flagnr1"; std::string flag2 = "flagnr2"; double val{12.7}; @@ -1454,7 +1555,7 @@ TEST_F(TApp, IniOutputHiddenOptions) { EXPECT_EQ(loc, std::string::npos); } -TEST_F(TApp, IniOutputMultiLineDescription) { +TEST_F(TApp, TomlOutputMultiLineDescription) { std::string flag = "some_flag"; const std::string description = "Some short description.\nThat has lines."; app.add_flag("--" + flag, description); @@ -1462,12 +1563,12 @@ TEST_F(TApp, IniOutputMultiLineDescription) { run(); std::string str = app.config_to_str(true, true); - EXPECT_THAT(str, HasSubstr("; Some short description.\n")); - EXPECT_THAT(str, HasSubstr("; That has lines.\n")); + EXPECT_THAT(str, HasSubstr("# Some short description.\n")); + EXPECT_THAT(str, HasSubstr("# That has lines.\n")); EXPECT_THAT(str, HasSubstr(flag + "=false\n")); } -TEST_F(TApp, IniOutputOptionGroup) { +TEST_F(TApp, TomlOutputOptionGroup) { std::string flag1 = "flagnr1"; std::string flag2 = "flagnr2"; double val{12.7}; @@ -1496,20 +1597,7 @@ TEST_F(TApp, IniOutputOptionGroup) { EXPECT_GT(locg3, locg1); } -TEST_F(TApp, IniOutputVector) { - - std::vector<int> v; - app.add_option("--vector", v); - - args = {"--vector", "1", "2", "3"}; - - run(); - - std::string str = app.config_to_str(); - EXPECT_EQ("vector=1 2 3\n", str); -} - -TEST_F(TApp, IniOutputVectorTOML) { +TEST_F(TApp, TomlOutputVector) { std::vector<int> v; app.add_option("--vector", v); @@ -1522,7 +1610,7 @@ TEST_F(TApp, IniOutputVectorTOML) { EXPECT_EQ("vector=[1, 2, 3]\n", str); } -TEST_F(TApp, IniOutputVectorCustom) { +TEST_F(TApp, ConfigOutputVectorCustom) { std::vector<int> v; app.add_option("--vector", v); @@ -1537,7 +1625,7 @@ TEST_F(TApp, IniOutputVectorCustom) { EXPECT_EQ("vector:{1; 2; 3}\n", str); } -TEST_F(TApp, IniOutputFlag) { +TEST_F(TApp, TomlOutputFlag) { int v{0}, q{0}; app.add_option("--simple", v); @@ -1553,13 +1641,13 @@ TEST_F(TApp, IniOutputFlag) { EXPECT_THAT(str, HasSubstr("simple=3")); EXPECT_THAT(str, Not(HasSubstr("nothing"))); EXPECT_THAT(str, HasSubstr("onething=true")); - EXPECT_THAT(str, HasSubstr("something=true true")); + EXPECT_THAT(str, HasSubstr("something=[true, true]")); str = app.config_to_str(true); EXPECT_THAT(str, HasSubstr("nothing")); } -TEST_F(TApp, IniOutputSet) { +TEST_F(TApp, TomlOutputSet) { int v{0}; app.add_option("--simple", v)->check(CLI::IsMember({1, 2, 3})); @@ -1572,7 +1660,7 @@ TEST_F(TApp, IniOutputSet) { EXPECT_THAT(str, HasSubstr("simple=2")); } -TEST_F(TApp, IniOutputDefault) { +TEST_F(TApp, TomlOutputDefault) { int v{7}; app.add_option("--simple", v, "", true); @@ -1586,7 +1674,7 @@ TEST_F(TApp, IniOutputDefault) { EXPECT_THAT(str, HasSubstr("simple=7")); } -TEST_F(TApp, IniOutputSubcom) { +TEST_F(TApp, TomlOutputSubcom) { app.add_flag("--simple"); auto subcom = app.add_subcommand("other"); @@ -1600,7 +1688,7 @@ TEST_F(TApp, IniOutputSubcom) { EXPECT_THAT(str, HasSubstr("other.newer=true")); } -TEST_F(TApp, IniOutputSubcomConfigurable) { +TEST_F(TApp, TomlOutputSubcomConfigurable) { app.add_flag("--simple"); auto subcom = app.add_subcommand("other")->configurable(); @@ -1616,7 +1704,7 @@ TEST_F(TApp, IniOutputSubcomConfigurable) { EXPECT_EQ(str.find("other.newer=true"), std::string::npos); } -TEST_F(TApp, IniOutputSubsubcom) { +TEST_F(TApp, TomlOutputSubsubcom) { app.add_flag("--simple"); auto subcom = app.add_subcommand("other"); @@ -1633,7 +1721,7 @@ TEST_F(TApp, IniOutputSubsubcom) { EXPECT_THAT(str, HasSubstr("other.sub2.newest=true")); } -TEST_F(TApp, IniOutputSubsubcomConfigurable) { +TEST_F(TApp, TomlOutputSubsubcomConfigurable) { app.add_flag("--simple"); auto subcom = app.add_subcommand("other")->configurable(); @@ -1654,7 +1742,7 @@ TEST_F(TApp, IniOutputSubsubcomConfigurable) { EXPECT_EQ(str.find("sub2.newest=true"), std::string::npos); } -TEST_F(TApp, IniOutputSubsubcomConfigurableDeep) { +TEST_F(TApp, TomlOutputSubsubcomConfigurableDeep) { app.add_flag("--simple"); auto subcom = app.add_subcommand("other")->configurable(); @@ -1677,7 +1765,7 @@ TEST_F(TApp, IniOutputSubsubcomConfigurableDeep) { EXPECT_EQ(str.find(".absolute_newest=true"), std::string::npos); } -TEST_F(TApp, IniQuotedOutput) { +TEST_F(TApp, TomlOutputQuoted) { std::string val1; app.add_option("--val1", val1); @@ -1697,7 +1785,7 @@ TEST_F(TApp, IniQuotedOutput) { EXPECT_THAT(str, HasSubstr("val2='I am a \"confusing\" string'")); } -TEST_F(TApp, DefaultsIniQuotedOutput) { +TEST_F(TApp, DefaultsTomlOutputQuoted) { std::string val1{"I am a string"}; app.add_option("--val1", val1, "", true); @@ -1718,7 +1806,7 @@ TEST_F(TApp, StopReadingConfigOnClear) { TempFile tmpini{"TestIniTmp.ini"}; app.set_config("--config", tmpini); - auto ptr = app.set_config(); // Should *not* read config file + auto ptr = app.set_config(); // Should *not* read config file EXPECT_EQ(ptr, nullptr); { @@ -1755,3 +1843,327 @@ TEST_F(TApp, ConfigWriteReadWrite) { EXPECT_EQ(config1, config2); } + +/////// INI output tests + +TEST_F(TApp, IniOutputSimple) { + + int v{0}; + app.add_option("--simple", v); + app.config_formatter(std::make_shared<CLI::ConfigINI>()); + args = {"--simple=3"}; + + run(); + + std::string str = app.config_to_str(); + EXPECT_EQ("simple=3\n", str); +} + +TEST_F(TApp, IniOutputNoConfigurable) { + + int v1{0}, v2{0}; + app.add_option("--simple", v1); + app.add_option("--noconf", v2)->configurable(false); + app.config_formatter(std::make_shared<CLI::ConfigINI>()); + args = {"--simple=3", "--noconf=2"}; + + run(); + + std::string str = app.config_to_str(); + EXPECT_EQ("simple=3\n", str); +} + +TEST_F(TApp, IniOutputShortSingleDescription) { + std::string flag = "some_flag"; + const std::string description = "Some short description."; + app.add_flag("--" + flag, description); + app.config_formatter(std::make_shared<CLI::ConfigINI>()); + run(); + + std::string str = app.config_to_str(true, true); + EXPECT_THAT(str, HasSubstr("; " + description + "\n" + flag + "=false\n")); +} + +TEST_F(TApp, IniOutputShortDoubleDescription) { + std::string flag1 = "flagnr1"; + std::string flag2 = "flagnr2"; + const std::string description1 = "First description."; + const std::string description2 = "Second description."; + app.add_flag("--" + flag1, description1); + app.add_flag("--" + flag2, description2); + app.config_formatter(std::make_shared<CLI::ConfigINI>()); + run(); + + std::string str = app.config_to_str(true, true); + EXPECT_THAT( + str, HasSubstr("; " + description1 + "\n" + flag1 + "=false\n\n; " + description2 + "\n" + flag2 + "=false\n")); +} + +TEST_F(TApp, IniOutputGroups) { + std::string flag1 = "flagnr1"; + std::string flag2 = "flagnr2"; + const std::string description1 = "First description."; + const std::string description2 = "Second description."; + app.add_flag("--" + flag1, description1)->group("group1"); + app.add_flag("--" + flag2, description2)->group("group2"); + app.config_formatter(std::make_shared<CLI::ConfigINI>()); + run(); + + std::string str = app.config_to_str(true, true); + EXPECT_THAT(str, HasSubstr("group1")); + EXPECT_THAT(str, HasSubstr("group2")); +} + +TEST_F(TApp, IniOutputHiddenOptions) { + std::string flag1 = "flagnr1"; + std::string flag2 = "flagnr2"; + double val{12.7}; + const std::string description1 = "First description."; + const std::string description2 = "Second description."; + app.add_flag("--" + flag1, description1)->group("group1"); + app.add_flag("--" + flag2, description2)->group("group2"); + app.add_option("--dval", val, "", true)->group(""); + app.config_formatter(std::make_shared<CLI::ConfigINI>()); + run(); + + std::string str = app.config_to_str(true, true); + EXPECT_THAT(str, HasSubstr("group1")); + EXPECT_THAT(str, HasSubstr("group2")); + EXPECT_THAT(str, HasSubstr("dval=12.7")); + auto loc = str.find("dval=12.7"); + auto locg1 = str.find("group1"); + EXPECT_GT(locg1, loc); + // make sure it doesn't come twice + loc = str.find("dval=12.7", loc + 4); + EXPECT_EQ(loc, std::string::npos); +} + +TEST_F(TApp, IniOutputMultiLineDescription) { + std::string flag = "some_flag"; + const std::string description = "Some short description.\nThat has lines."; + app.add_flag("--" + flag, description); + app.config_formatter(std::make_shared<CLI::ConfigINI>()); + run(); + + std::string str = app.config_to_str(true, true); + EXPECT_THAT(str, HasSubstr("; Some short description.\n")); + EXPECT_THAT(str, HasSubstr("; That has lines.\n")); + EXPECT_THAT(str, HasSubstr(flag + "=false\n")); +} + +TEST_F(TApp, IniOutputOptionGroup) { + std::string flag1 = "flagnr1"; + std::string flag2 = "flagnr2"; + double val{12.7}; + const std::string description1 = "First description."; + const std::string description2 = "Second description."; + app.add_flag("--" + flag1, description1)->group("group1"); + app.add_flag("--" + flag2, description2)->group("group2"); + auto og = app.add_option_group("group3", "g3 desc"); + og->add_option("--dval", val, "", true)->group(""); + app.config_formatter(std::make_shared<CLI::ConfigINI>()); + run(); + + std::string str = app.config_to_str(true, true); + EXPECT_THAT(str, HasSubstr("group1")); + EXPECT_THAT(str, HasSubstr("group2")); + EXPECT_THAT(str, HasSubstr("dval=12.7")); + EXPECT_THAT(str, HasSubstr("group3")); + EXPECT_THAT(str, HasSubstr("g3 desc")); + auto loc = str.find("dval=12.7"); + auto locg1 = str.find("group1"); + auto locg3 = str.find("group3"); + EXPECT_LT(locg1, loc); + // make sure it doesn't come twice + loc = str.find("dval=12.7", loc + 4); + EXPECT_EQ(loc, std::string::npos); + EXPECT_GT(locg3, locg1); +} + +TEST_F(TApp, IniOutputVector) { + + std::vector<int> v; + app.add_option("--vector", v); + + args = {"--vector", "1", "2", "3"}; + app.config_formatter(std::make_shared<CLI::ConfigINI>()); + run(); + + std::string str = app.config_to_str(); + EXPECT_EQ("vector=1 2 3\n", str); +} + +TEST_F(TApp, IniOutputFlag) { + + int v{0}, q{0}; + app.add_option("--simple", v); + app.add_flag("--nothing"); + app.add_flag("--onething"); + app.add_flag("--something", q); + + args = {"--simple=3", "--onething", "--something", "--something"}; + app.config_formatter(std::make_shared<CLI::ConfigINI>()); + run(); + + std::string str = app.config_to_str(); + EXPECT_THAT(str, HasSubstr("simple=3")); + EXPECT_THAT(str, Not(HasSubstr("nothing"))); + EXPECT_THAT(str, HasSubstr("onething=true")); + EXPECT_THAT(str, HasSubstr("something=true true")); + + str = app.config_to_str(true); + EXPECT_THAT(str, HasSubstr("nothing")); +} + +TEST_F(TApp, IniOutputSet) { + + int v{0}; + app.add_option("--simple", v)->check(CLI::IsMember({1, 2, 3})); + + args = {"--simple=2"}; + app.config_formatter(std::make_shared<CLI::ConfigINI>()); + run(); + + std::string str = app.config_to_str(); + EXPECT_THAT(str, HasSubstr("simple=2")); +} + +TEST_F(TApp, IniOutputDefault) { + + int v{7}; + app.add_option("--simple", v, "", true); + app.config_formatter(std::make_shared<CLI::ConfigINI>()); + run(); + + std::string str = app.config_to_str(); + EXPECT_THAT(str, Not(HasSubstr("simple=7"))); + + str = app.config_to_str(true); + EXPECT_THAT(str, HasSubstr("simple=7")); +} + +TEST_F(TApp, IniOutputSubcom) { + + app.add_flag("--simple"); + auto subcom = app.add_subcommand("other"); + subcom->add_flag("--newer"); + app.config_formatter(std::make_shared<CLI::ConfigINI>()); + args = {"--simple", "other", "--newer"}; + run(); + + std::string str = app.config_to_str(); + EXPECT_THAT(str, HasSubstr("simple=true")); + EXPECT_THAT(str, HasSubstr("other.newer=true")); +} + +TEST_F(TApp, IniOutputSubcomConfigurable) { + + app.add_flag("--simple"); + auto subcom = app.add_subcommand("other")->configurable(); + subcom->add_flag("--newer"); + app.config_formatter(std::make_shared<CLI::ConfigINI>()); + args = {"--simple", "other", "--newer"}; + run(); + + std::string str = app.config_to_str(); + EXPECT_THAT(str, HasSubstr("simple=true")); + EXPECT_THAT(str, HasSubstr("[other]")); + EXPECT_THAT(str, HasSubstr("newer=true")); + EXPECT_EQ(str.find("other.newer=true"), std::string::npos); +} + +TEST_F(TApp, IniOutputSubsubcom) { + + app.add_flag("--simple"); + auto subcom = app.add_subcommand("other"); + subcom->add_flag("--newer"); + auto subsubcom = subcom->add_subcommand("sub2"); + subsubcom->add_flag("--newest"); + app.config_formatter(std::make_shared<CLI::ConfigINI>()); + args = {"--simple", "other", "--newer", "sub2", "--newest"}; + run(); + + std::string str = app.config_to_str(); + EXPECT_THAT(str, HasSubstr("simple=true")); + EXPECT_THAT(str, HasSubstr("other.newer=true")); + EXPECT_THAT(str, HasSubstr("other.sub2.newest=true")); +} + +TEST_F(TApp, IniOutputSubsubcomConfigurable) { + + app.add_flag("--simple"); + auto subcom = app.add_subcommand("other")->configurable(); + subcom->add_flag("--newer"); + + auto subsubcom = subcom->add_subcommand("sub2"); + subsubcom->add_flag("--newest"); + app.config_formatter(std::make_shared<CLI::ConfigINI>()); + args = {"--simple", "other", "--newer", "sub2", "--newest"}; + run(); + + std::string str = app.config_to_str(); + EXPECT_THAT(str, HasSubstr("simple=true")); + EXPECT_THAT(str, HasSubstr("[other]")); + EXPECT_THAT(str, HasSubstr("newer=true")); + EXPECT_THAT(str, HasSubstr("[other.sub2]")); + EXPECT_THAT(str, HasSubstr("newest=true")); + EXPECT_EQ(str.find("sub2.newest=true"), std::string::npos); +} + +TEST_F(TApp, IniOutputSubsubcomConfigurableDeep) { + + app.add_flag("--simple"); + auto subcom = app.add_subcommand("other")->configurable(); + subcom->add_flag("--newer"); + + auto subsubcom = subcom->add_subcommand("sub2"); + subsubcom->add_flag("--newest"); + auto sssscom = subsubcom->add_subcommand("sub-level2"); + subsubcom->add_flag("--still_newer"); + auto s5com = sssscom->add_subcommand("sub-level3"); + s5com->add_flag("--absolute_newest"); + app.config_formatter(std::make_shared<CLI::ConfigINI>()); + args = {"--simple", "other", "sub2", "sub-level2", "sub-level3", "--absolute_newest"}; + run(); + + std::string str = app.config_to_str(); + EXPECT_THAT(str, HasSubstr("simple=true")); + EXPECT_THAT(str, HasSubstr("[other.sub2.sub-level2.sub-level3]")); + EXPECT_THAT(str, HasSubstr("absolute_newest=true")); + EXPECT_EQ(str.find(".absolute_newest=true"), std::string::npos); +} + +TEST_F(TApp, IniOutputQuoted) { + + std::string val1; + app.add_option("--val1", val1); + + std::string val2; + app.add_option("--val2", val2); + app.config_formatter(std::make_shared<CLI::ConfigINI>()); + args = {"--val1", "I am a string", "--val2", R"(I am a "confusing" string)"}; + + run(); + + EXPECT_EQ("I am a string", val1); + EXPECT_EQ("I am a \"confusing\" string", val2); + + std::string str = app.config_to_str(); + EXPECT_THAT(str, HasSubstr("val1=\"I am a string\"")); + EXPECT_THAT(str, HasSubstr("val2='I am a \"confusing\" string'")); +} + +TEST_F(TApp, DefaultsIniOutputQuoted) { + + std::string val1{"I am a string"}; + app.add_option("--val1", val1, "", true); + + std::string val2{R"(I am a "confusing" string)"}; + app.add_option("--val2", val2, "", true); + app.config_formatter(std::make_shared<CLI::ConfigINI>()); + run(); + + std::string str = app.config_to_str(true); + EXPECT_THAT(str, HasSubstr("val1=\"I am a string\"")); + EXPECT_THAT(str, HasSubstr("val2='I am a \"confusing\" string'")); +} diff --git a/packages/CLI11/tests/CreationTest.cpp b/packages/CLI11/tests/CreationTest.cpp index 7143e3efbc65236fa62b8f881764c19a40b1b9ca..648c4aebba3de45e1e4e85232eafdd54e4c2ce0c 100644 --- a/packages/CLI11/tests/CreationTest.cpp +++ b/packages/CLI11/tests/CreationTest.cpp @@ -1,3 +1,9 @@ +// Copyright (c) 2017-2020, University of Cincinnati, developed by Henry Schreiner +// under NSF AWARD 1414736 and by the respective contributors. +// All rights reserved. +// +// SPDX-License-Identifier: BSD-3-Clause + #include "app_helper.hpp" #include <cstdlib> @@ -527,7 +533,7 @@ TEST_F(TApp, GetOptionList) { auto flag = app.add_flag("--one"); auto opt = app.add_option("--two", two); - const CLI::App &const_app = app; // const alias to force use of const-methods + const CLI::App &const_app = app; // const alias to force use of const-methods std::vector<const CLI::Option *> opt_list = const_app.get_options(); ASSERT_EQ(opt_list.size(), static_cast<std::size_t>(3)); diff --git a/packages/CLI11/tests/DeprecatedTest.cpp b/packages/CLI11/tests/DeprecatedTest.cpp index 367e6fd883805dda4bfea88615c8863233080661..a8f41971270021c57f50ff64dcb5f97387175791 100644 --- a/packages/CLI11/tests/DeprecatedTest.cpp +++ b/packages/CLI11/tests/DeprecatedTest.cpp @@ -1,3 +1,9 @@ +// Copyright (c) 2017-2020, University of Cincinnati, developed by Henry Schreiner +// under NSF AWARD 1414736 and by the respective contributors. +// All rights reserved. +// +// SPDX-License-Identifier: BSD-3-Clause + #include "app_helper.hpp" #include "gmock/gmock.h" @@ -5,7 +11,7 @@ using ::testing::HasSubstr; using ::testing::Not; -TEST(Deprecated, Emtpy) { +TEST(Deprecated, Empty) { // No deprecated features at this time. EXPECT_TRUE(true); } diff --git a/packages/CLI11/tests/FormatterTest.cpp b/packages/CLI11/tests/FormatterTest.cpp index 47766a828a6dd2ec00c7160cbc17ec4bc3ac8a3b..fd2547835733b00ea54ef5d1ffeb3b33fde5f48a 100644 --- a/packages/CLI11/tests/FormatterTest.cpp +++ b/packages/CLI11/tests/FormatterTest.cpp @@ -1,3 +1,9 @@ +// Copyright (c) 2017-2020, University of Cincinnati, developed by Henry Schreiner +// under NSF AWARD 1414736 and by the respective contributors. +// All rights reserved. +// +// SPDX-License-Identifier: BSD-3-Clause + #ifdef CLI11_SINGLE_FILE #include "CLI11.hpp" #else diff --git a/packages/CLI11/tests/HelpTest.cpp b/packages/CLI11/tests/HelpTest.cpp index 44f0f5318c50f3ebf72e91a34fef78d63c64fdf5..68d96d13b5ee87432363b39039719b2d1c959458 100644 --- a/packages/CLI11/tests/HelpTest.cpp +++ b/packages/CLI11/tests/HelpTest.cpp @@ -1,3 +1,9 @@ +// Copyright (c) 2017-2020, University of Cincinnati, developed by Henry Schreiner +// under NSF AWARD 1414736 and by the respective contributors. +// All rights reserved. +// +// SPDX-License-Identifier: BSD-3-Clause + #ifdef CLI11_SINGLE_FILE #include "CLI11.hpp" #else @@ -455,7 +461,7 @@ TEST(THelp, Subcom) { char y[] = "sub2"; std::vector<char *> args = {x, y}; - app.parse((int)args.size(), args.data()); + app.parse(static_cast<int>(args.size()), args.data()); help = app.help(); EXPECT_THAT(help, HasSubstr("Usage: ./myprogram sub2")); @@ -467,7 +473,7 @@ TEST(THelp, MasterName) { char x[] = "./myprogram"; std::vector<char *> args = {x}; - app.parse((int)args.size(), args.data()); + app.parse(static_cast<int>(args.size()), args.data()); EXPECT_THAT(app.help(), HasSubstr("Usage: MyRealName")); } @@ -715,7 +721,7 @@ TEST(Exit, ExitCodes) { EXPECT_EQ(0, app.exit(CLI::CallForHelp())); EXPECT_EQ(i, app.exit(CLI::ExtrasError({"Thing"}))); EXPECT_EQ(42, app.exit(CLI::RuntimeError(42))); - EXPECT_EQ(1, app.exit(CLI::RuntimeError())); // Not sure if a default here is a good thing + EXPECT_EQ(1, app.exit(CLI::RuntimeError())); // Not sure if a default here is a good thing } struct CapturedHelp : public ::testing::Test { @@ -731,7 +737,7 @@ struct CapturedHelp : public ::testing::Test { } }; -TEST_F(CapturedHelp, Sucessful) { +TEST_F(CapturedHelp, Successful) { EXPECT_EQ(run(CLI::Success()), 0); EXPECT_EQ(out.str(), ""); EXPECT_EQ(err.str(), ""); @@ -825,7 +831,7 @@ TEST_F(CapturedHelp, AllOnlyError) { EXPECT_THAT(err.str(), Not(HasSubstr("Usage"))); } -TEST_F(CapturedHelp, RepacedError) { +TEST_F(CapturedHelp, ReplacedError) { app.failure_message(CLI::FailureMessage::help); EXPECT_EQ(run(CLI::ExtrasError({"Thing"})), static_cast<int>(CLI::ExitCodes::ExtrasError)); @@ -945,7 +951,7 @@ TEST(THelp, ValidatorsText) { std::string help = app.help(); EXPECT_THAT(help, HasSubstr("TEXT:FILE")); EXPECT_THAT(help, HasSubstr("INT in [1 - 4]")); - EXPECT_THAT(help, HasSubstr("UINT:INT in [0 - 12]")); // Loses UINT + EXPECT_THAT(help, HasSubstr("UINT:INT in [0 - 12]")); // Loses UINT } TEST(THelp, ValidatorsTextCustom) { @@ -995,7 +1001,7 @@ TEST(THelp, CombinedValidatorsText) { app.add_option("--f1", filename)->check(CLI::ExistingFile | CLI::ExistingDirectory); // This would be nice if it put something other than string, but would it be path or file? - // Can't programatically tell! + // Can't programmatically tell! // (Users can use ExistingPath, by the way) std::string help = app.help(); EXPECT_THAT(help, HasSubstr("TEXT:(FILE) OR (DIR)")); diff --git a/packages/CLI11/tests/HelpersTest.cpp b/packages/CLI11/tests/HelpersTest.cpp index 9b4c288337ed5008fabca70a587743912d6bd5b9..a60076261a2925ef4fbddab861c5f69be252e945 100644 --- a/packages/CLI11/tests/HelpersTest.cpp +++ b/packages/CLI11/tests/HelpersTest.cpp @@ -1,3 +1,9 @@ +// Copyright (c) 2017-2020, University of Cincinnati, developed by Henry Schreiner +// under NSF AWARD 1414736 and by the respective contributors. +// All rights reserved. +// +// SPDX-License-Identifier: BSD-3-Clause + #include "app_helper.hpp" #include <array> @@ -6,8 +12,10 @@ #include <cstdint> #include <cstdio> #include <fstream> +#include <map> #include <string> #include <tuple> +#include <unordered_map> #include <utility> class NotStreamable {}; @@ -52,6 +60,57 @@ TEST(TypeTools, type_size) { EXPECT_EQ(V, 5); V = CLI::detail::type_count<std::vector<std::pair<std::string, double>>>::value; EXPECT_EQ(V, 2); + V = CLI::detail::type_count<std::tuple<std::pair<std::string, double>>>::value; + EXPECT_EQ(V, 2); + V = CLI::detail::type_count<std::tuple<int, std::pair<std::string, double>>>::value; + EXPECT_EQ(V, 3); + V = CLI::detail::type_count<std::tuple<std::pair<int, double>, std::pair<std::string, double>>>::value; + EXPECT_EQ(V, 4); + // maps + V = CLI::detail::type_count<std::map<int, std::pair<int, double>>>::value; + EXPECT_EQ(V, 3); + // three level tuples + V = CLI::detail::type_count<std::tuple<int, std::pair<int, std::tuple<int, double, std::string>>>>::value; + EXPECT_EQ(V, 5); + V = CLI::detail::type_count<std::pair<int, std::vector<int>>>::value; + EXPECT_GE(V, CLI::detail::expected_max_vector_size); + V = CLI::detail::type_count<std::vector<std::vector<int>>>::value; + EXPECT_EQ(V, CLI::detail::expected_max_vector_size); +} + +TEST(TypeTools, type_size_min) { + auto V = CLI::detail::type_count_min<int>::value; + EXPECT_EQ(V, 1); + V = CLI::detail::type_count_min<void>::value; + EXPECT_EQ(V, 0); + V = CLI::detail::type_count_min<std::vector<double>>::value; + EXPECT_EQ(V, 1); + V = CLI::detail::type_count_min<std::tuple<double, int>>::value; + EXPECT_EQ(V, 2); + V = CLI::detail::type_count_min<std::tuple<std::string, double, int>>::value; + EXPECT_EQ(V, 3); + V = CLI::detail::type_count_min<std::array<std::string, 5>>::value; + EXPECT_EQ(V, 5); + V = CLI::detail::type_count_min<std::vector<std::pair<std::string, double>>>::value; + EXPECT_EQ(V, 2); + V = CLI::detail::type_count_min<std::tuple<std::pair<std::string, double>>>::value; + EXPECT_EQ(V, 2); + V = CLI::detail::type_count_min<std::tuple<int, std::pair<std::string, double>>>::value; + EXPECT_EQ(V, 3); + V = CLI::detail::type_count_min<std::tuple<std::pair<int, double>, std::pair<std::string, double>>>::value; + EXPECT_EQ(V, 4); + // maps + V = CLI::detail::type_count_min<std::map<int, std::pair<int, double>>>::value; + EXPECT_EQ(V, 3); + // three level tuples + V = CLI::detail::type_count_min<std::tuple<int, std::pair<int, std::tuple<int, double, std::string>>>>::value; + EXPECT_EQ(V, 5); + V = CLI::detail::type_count_min<std::pair<int, std::vector<int>>>::value; + EXPECT_EQ(V, 2); + V = CLI::detail::type_count_min<std::vector<std::vector<int>>>::value; + EXPECT_EQ(V, 1); + V = CLI::detail::type_count_min<std::vector<std::vector<std::pair<int, int>>>>::value; + EXPECT_EQ(V, 2); } TEST(TypeTools, expected_count) { @@ -218,7 +277,7 @@ TEST(Trim, TrimCopy) { TEST(Validators, FileExists) { std::string myfile{"TestFileNotUsed.txt"}; EXPECT_FALSE(CLI::ExistingFile(myfile).empty()); - bool ok = static_cast<bool>(std::ofstream(myfile.c_str()).put('a')); // create file + bool ok = static_cast<bool>(std::ofstream(myfile.c_str()).put('a')); // create file EXPECT_TRUE(ok); EXPECT_TRUE(CLI::ExistingFile(myfile).empty()); @@ -229,7 +288,7 @@ TEST(Validators, FileExists) { TEST(Validators, FileNotExists) { std::string myfile{"TestFileNotUsed.txt"}; EXPECT_TRUE(CLI::NonexistentPath(myfile).empty()); - bool ok = static_cast<bool>(std::ofstream(myfile.c_str()).put('a')); // create file + bool ok = static_cast<bool>(std::ofstream(myfile.c_str()).put('a')); // create file EXPECT_TRUE(ok); EXPECT_FALSE(CLI::NonexistentPath(myfile).empty()); @@ -255,7 +314,7 @@ TEST(Validators, DirectoryNotExists) { TEST(Validators, DirectoryIsFile) { std::string myfile{"TestFileNotUsed.txt"}; EXPECT_TRUE(CLI::NonexistentPath(myfile).empty()); - bool ok = static_cast<bool>(std::ofstream(myfile.c_str()).put('a')); // create file + bool ok = static_cast<bool>(std::ofstream(myfile.c_str()).put('a')); // create file EXPECT_TRUE(ok); EXPECT_FALSE(CLI::ExistingDirectory(myfile).empty()); @@ -271,7 +330,7 @@ TEST(Validators, PathExistsDir) { TEST(Validators, PathExistsFile) { std::string myfile{"TestFileNotUsed.txt"}; EXPECT_FALSE(CLI::ExistingPath(myfile).empty()); - bool ok = static_cast<bool>(std::ofstream(myfile.c_str()).put('a')); // create file + bool ok = static_cast<bool>(std::ofstream(myfile.c_str()).put('a')); // create file EXPECT_TRUE(ok); EXPECT_TRUE(CLI::ExistingPath(myfile).empty()); @@ -383,7 +442,7 @@ TEST(Validators, CombinedOrRange) { TEST(Validators, CombinedPaths) { std::string myfile{"TestFileNotUsed.txt"}; EXPECT_FALSE(CLI::ExistingFile(myfile).empty()); - bool ok = static_cast<bool>(std::ofstream(myfile.c_str()).put('a')); // create file + bool ok = static_cast<bool>(std::ofstream(myfile.c_str()).put('a')); // create file EXPECT_TRUE(ok); std::string dir{"../tests"}; @@ -435,7 +494,7 @@ TEST(Validators, ProgramNameSplit) { EXPECT_EQ(res.second, "this is a bunch of extra stuff"); res = CLI::detail::split_program_name("./program_name this is a bunch of extra stuff "); - EXPECT_EQ(res.first, "./program_name"); // test sectioning of first argument even if it can't detect the file + EXPECT_EQ(res.first, "./program_name"); // test sectioning of first argument even if it can't detect the file EXPECT_EQ(res.second, "this is a bunch of extra stuff"); res = CLI::detail::split_program_name(std::string(" ./") + std::string(myfile) + " "); @@ -663,7 +722,7 @@ TEST(AppHelper, TempfileCreated) { EXPECT_FALSE(CLI::ExistingFile(myfile).empty()); - bool ok = static_cast<bool>(std::ofstream(myfile.c_str()).put('a')); // create file + bool ok = static_cast<bool>(std::ofstream(myfile.c_str()).put('a')); // create file EXPECT_TRUE(ok); EXPECT_TRUE(CLI::ExistingFile(name).empty()); EXPECT_THROW({ TempFile otherfile(name); }, std::runtime_error); @@ -843,7 +902,7 @@ TEST(Types, TypeName) { std::string int_name = CLI::detail::type_name<int>(); EXPECT_EQ("INT", int_name); - std::string int2_name = CLI::detail::type_name<short>(); + std::string int2_name = CLI::detail::type_name<std::int16_t>(); EXPECT_EQ("INT", int2_name); std::string uint_name = CLI::detail::type_name<unsigned char>(); @@ -862,6 +921,10 @@ TEST(Types, TypeName) { CLI::detail::object_category::tuple_value, "pair<int,string> does not read like a tuple"); + static_assert(CLI::detail::classify_object<std::tuple<std::string, double>>::value == + CLI::detail::object_category::tuple_value, + "tuple<string,double> does not read like a tuple"); + std::string pair_name = CLI::detail::type_name<std::vector<std::pair<int, std::string>>>(); EXPECT_EQ("[INT,TEXT]", pair_name); @@ -869,7 +932,7 @@ TEST(Types, TypeName) { EXPECT_EQ("UINT", vector_name); auto vclass = CLI::detail::classify_object<std::vector<std::vector<unsigned char>>>::value; - EXPECT_EQ(vclass, CLI::detail::object_category::vector_value); + EXPECT_EQ(vclass, CLI::detail::object_category::container_value); auto tclass = CLI::detail::classify_object<std::tuple<double>>::value; EXPECT_EQ(tclass, CLI::detail::object_category::number_constructible); @@ -883,6 +946,18 @@ TEST(Types, TypeName) { tuple_name = CLI::detail::type_name<std::tuple<int, std::string>>(); EXPECT_EQ("[INT,TEXT]", tuple_name); + tuple_name = CLI::detail::type_name<std::tuple<const int, std::string>>(); + EXPECT_EQ("[INT,TEXT]", tuple_name); + + tuple_name = CLI::detail::type_name<const std::tuple<int, std::string>>(); + EXPECT_EQ("[INT,TEXT]", tuple_name); + + tuple_name = CLI::detail::type_name<std::tuple<std::string, double>>(); + EXPECT_EQ("[TEXT,FLOAT]", tuple_name); + + tuple_name = CLI::detail::type_name<const std::tuple<std::string, double>>(); + EXPECT_EQ("[TEXT,FLOAT]", tuple_name); + tuple_name = CLI::detail::type_name<std::tuple<int, std::string, double>>(); EXPECT_EQ("[INT,TEXT,FLOAT]", tuple_name); @@ -911,6 +986,8 @@ TEST(Types, TypeName) { "tuple<test> does not classify as a tuple"); std::string enum_name2 = CLI::detail::type_name<std::tuple<test>>(); EXPECT_EQ("ENUM", enum_name2); + std::string umapName = CLI::detail::type_name<std::unordered_map<int, std::tuple<std::string, double>>>(); + EXPECT_EQ("[INT,[TEXT,FLOAT]]", umapName); } TEST(Types, OverflowSmall) { @@ -994,12 +1071,12 @@ TEST(Types, LexicalCastParsable) { std::complex<double> output; EXPECT_TRUE(CLI::detail::lexical_cast(input, output)); - EXPECT_DOUBLE_EQ(output.real(), 4.2); // Doing this in one go sometimes has trouble - EXPECT_DOUBLE_EQ(output.imag(), 7.3); // on clang + c++4.8 due to missing const + EXPECT_DOUBLE_EQ(output.real(), 4.2); // Doing this in one go sometimes has trouble + EXPECT_DOUBLE_EQ(output.imag(), 7.3); // on clang + gcc 4.8 due to missing const EXPECT_TRUE(CLI::detail::lexical_cast("2.456", output)); - EXPECT_DOUBLE_EQ(output.real(), 2.456); // Doing this in one go sometimes has trouble - EXPECT_DOUBLE_EQ(output.imag(), 0.0); // on clang + c++4.8 due to missing const + EXPECT_DOUBLE_EQ(output.real(), 2.456); // Doing this in one go sometimes has trouble + EXPECT_DOUBLE_EQ(output.imag(), 0.0); // on clang + gcc 4.8 due to missing const EXPECT_FALSE(CLI::detail::lexical_cast(fail_input, output)); EXPECT_FALSE(CLI::detail::lexical_cast(extra_input, output)); @@ -1173,6 +1250,26 @@ TEST(Types, LexicalConversionComplex) { EXPECT_EQ(x.imag(), 3.5); } +static_assert(CLI::detail::is_wrapper<std::vector<double>>::value, "vector double should be a wrapper"); +static_assert(CLI::detail::is_wrapper<std::vector<std::string>>::value, "vector string should be a wrapper"); +static_assert(CLI::detail::is_wrapper<std::string>::value, "string should be a wrapper"); +static_assert(!CLI::detail::is_wrapper<double>::value, "double should not be a wrapper"); + +static_assert(CLI::detail::is_mutable_container<std::vector<double>>::value, "vector class should be a container"); +static_assert(CLI::detail::is_mutable_container<std::vector<std::string>>::value, "vector class should be a container"); +static_assert(!CLI::detail::is_mutable_container<std::string>::value, "string should be a container"); +static_assert(!CLI::detail::is_mutable_container<double>::value, "double should not be a container"); +static_assert(!CLI::detail::is_mutable_container<std::array<double, 5>>::value, "array should not be a container"); + +static_assert(CLI::detail::is_mutable_container<std::vector<int>>::value, "vector int should be a container"); + +static_assert(CLI::detail::is_readable_container<std::vector<int> &>::value, + "vector int & should be a readable container"); +static_assert(CLI::detail::is_readable_container<const std::vector<int>>::value, + "const vector int should be a readable container"); +static_assert(CLI::detail::is_readable_container<const std::vector<int> &>::value, + "const vector int & should be a readable container"); + TEST(FixNewLines, BasicCheck) { std::string input = "one\ntwo"; std::string output = "one\n; two"; diff --git a/packages/CLI11/tests/NewParseTest.cpp b/packages/CLI11/tests/NewParseTest.cpp index 6d7a4873307cba16054369e0c4a29d966b8f3b58..cf0adc384726c3b51fbdcfb0244ff105914701c0 100644 --- a/packages/CLI11/tests/NewParseTest.cpp +++ b/packages/CLI11/tests/NewParseTest.cpp @@ -1,3 +1,9 @@ +// Copyright (c) 2017-2020, University of Cincinnati, developed by Henry Schreiner +// under NSF AWARD 1414736 and by the respective contributors. +// All rights reserved. +// +// SPDX-License-Identifier: BSD-3-Clause + #include "app_helper.hpp" #include "gmock/gmock.h" #include <complex> @@ -7,47 +13,36 @@ using ::testing::HasSubstr; using cx = std::complex<double>; -CLI::Option * -add_option(CLI::App &app, std::string name, cx &variable, std::string description = "", bool defaulted = false) { - CLI::callback_t fun = [&variable](CLI::results_t res) { - double x, y; - bool worked = CLI::detail::lexical_cast(res[0], x) && CLI::detail::lexical_cast(res[1], y); - if(worked) - variable = cx(x, y); - return worked; - }; - - CLI::Option *opt = app.add_option(name, fun, description, defaulted); - opt->type_name("COMPLEX")->type_size(2); - if(defaulted) { - std::stringstream out; - out << variable; - opt->default_str(out.str()); - } - return opt; -} +TEST_F(TApp, Complex) { + cx comp{1, 2}; + app.add_complex("-c,--complex", comp, "", true); + + args = {"-c", "4", "3"}; -TEST_F(TApp, AddingComplexParser) { + std::string help = app.help(); + EXPECT_THAT(help, HasSubstr("1")); + EXPECT_THAT(help, HasSubstr("2")); + EXPECT_THAT(help, HasSubstr("COMPLEX")); - cx comp{0, 0}; - add_option(app, "-c,--complex", comp); - args = {"-c", "1.5", "2.5"}; + EXPECT_DOUBLE_EQ(1, comp.real()); + EXPECT_DOUBLE_EQ(2, comp.imag()); run(); - EXPECT_DOUBLE_EQ(1.5, comp.real()); - EXPECT_DOUBLE_EQ(2.5, comp.imag()); + EXPECT_DOUBLE_EQ(4, comp.real()); + EXPECT_DOUBLE_EQ(3, comp.imag()); } -TEST_F(TApp, DefaultComplex) { - +TEST_F(TApp, ComplexOption) { cx comp{1, 2}; - add_option(app, "-c,--complex", comp, "", true); + app.add_option("-c,--complex", comp, "", true); + args = {"-c", "4", "3"}; std::string help = app.help(); EXPECT_THAT(help, HasSubstr("1")); EXPECT_THAT(help, HasSubstr("2")); + EXPECT_THAT(help, HasSubstr("COMPLEX")); EXPECT_DOUBLE_EQ(1, comp.real()); EXPECT_DOUBLE_EQ(2, comp.imag()); @@ -58,9 +53,9 @@ TEST_F(TApp, DefaultComplex) { EXPECT_DOUBLE_EQ(3, comp.imag()); } -TEST_F(TApp, BuiltinComplex) { - cx comp{1, 2}; - app.add_complex("-c,--complex", comp, "", true); +TEST_F(TApp, ComplexFloat) { + std::complex<float> comp{1, 2}; + app.add_complex<std::complex<float>, float>("-c,--complex", comp, "", true); args = {"-c", "4", "3"}; @@ -69,18 +64,18 @@ TEST_F(TApp, BuiltinComplex) { EXPECT_THAT(help, HasSubstr("2")); EXPECT_THAT(help, HasSubstr("COMPLEX")); - EXPECT_DOUBLE_EQ(1, comp.real()); - EXPECT_DOUBLE_EQ(2, comp.imag()); + EXPECT_FLOAT_EQ(1, comp.real()); + EXPECT_FLOAT_EQ(2, comp.imag()); run(); - EXPECT_DOUBLE_EQ(4, comp.real()); - EXPECT_DOUBLE_EQ(3, comp.imag()); + EXPECT_FLOAT_EQ(4, comp.real()); + EXPECT_FLOAT_EQ(3, comp.imag()); } -TEST_F(TApp, BuiltinComplexFloat) { +TEST_F(TApp, ComplexFloatOption) { std::complex<float> comp{1, 2}; - app.add_complex<std::complex<float>, float>("-c,--complex", comp, "", true); + app.add_option("-c,--complex", comp, "", true); args = {"-c", "4", "3"}; @@ -98,7 +93,7 @@ TEST_F(TApp, BuiltinComplexFloat) { EXPECT_FLOAT_EQ(3, comp.imag()); } -TEST_F(TApp, BuiltinComplexWithDelimiter) { +TEST_F(TApp, ComplexWithDelimiter) { cx comp{1, 2}; app.add_complex("-c,--complex", comp, "", true)->delimiter('+'); @@ -130,7 +125,39 @@ TEST_F(TApp, BuiltinComplexWithDelimiter) { EXPECT_DOUBLE_EQ(-4, comp.imag()); } -TEST_F(TApp, BuiltinComplexIgnoreI) { +TEST_F(TApp, ComplexWithDelimiterOption) { + cx comp{1, 2}; + app.add_option("-c,--complex", comp, "", true)->delimiter('+'); + + args = {"-c", "4+3i"}; + + std::string help = app.help(); + EXPECT_THAT(help, HasSubstr("1")); + EXPECT_THAT(help, HasSubstr("2")); + EXPECT_THAT(help, HasSubstr("COMPLEX")); + + EXPECT_DOUBLE_EQ(1, comp.real()); + EXPECT_DOUBLE_EQ(2, comp.imag()); + + run(); + + EXPECT_DOUBLE_EQ(4, comp.real()); + EXPECT_DOUBLE_EQ(3, comp.imag()); + + args = {"-c", "5+-3i"}; + run(); + + EXPECT_DOUBLE_EQ(5, comp.real()); + EXPECT_DOUBLE_EQ(-3, comp.imag()); + + args = {"-c", "6", "-4i"}; + run(); + + EXPECT_DOUBLE_EQ(6, comp.real()); + EXPECT_DOUBLE_EQ(-4, comp.imag()); +} + +TEST_F(TApp, ComplexIgnoreI) { cx comp{1, 2}; app.add_complex("-c,--complex", comp); @@ -142,7 +169,19 @@ TEST_F(TApp, BuiltinComplexIgnoreI) { EXPECT_DOUBLE_EQ(3, comp.imag()); } -TEST_F(TApp, BuiltinComplexSingleArg) { +TEST_F(TApp, ComplexIgnoreIOption) { + cx comp{1, 2}; + app.add_option("-c,--complex", comp); + + args = {"-c", "4", "3i"}; + + run(); + + EXPECT_DOUBLE_EQ(4, comp.real()); + EXPECT_DOUBLE_EQ(3, comp.imag()); +} + +TEST_F(TApp, ComplexSingleArg) { cx comp{1, 2}; app.add_complex("-c,--complex", comp); @@ -176,7 +215,41 @@ TEST_F(TApp, BuiltinComplexSingleArg) { EXPECT_DOUBLE_EQ(-2.7, comp.imag()); } -TEST_F(TApp, BuiltinComplexSingleImag) { +TEST_F(TApp, ComplexSingleArgOption) { + cx comp{1, 2}; + app.add_option("-c,--complex", comp); + + args = {"-c", "4"}; + run(); + EXPECT_DOUBLE_EQ(4, comp.real()); + EXPECT_DOUBLE_EQ(0, comp.imag()); + + args = {"-c", "4-2i"}; + run(); + EXPECT_DOUBLE_EQ(4, comp.real()); + EXPECT_DOUBLE_EQ(-2, comp.imag()); + args = {"-c", "4+2i"}; + run(); + EXPECT_DOUBLE_EQ(4, comp.real()); + EXPECT_DOUBLE_EQ(2, comp.imag()); + + args = {"-c", "-4+2j"}; + run(); + EXPECT_DOUBLE_EQ(-4, comp.real()); + EXPECT_DOUBLE_EQ(2, comp.imag()); + + args = {"-c", "-4.2-2j"}; + run(); + EXPECT_DOUBLE_EQ(-4.2, comp.real()); + EXPECT_DOUBLE_EQ(-2, comp.imag()); + + args = {"-c", "-4.2-2.7i"}; + run(); + EXPECT_DOUBLE_EQ(-4.2, comp.real()); + EXPECT_DOUBLE_EQ(-2.7, comp.imag()); +} + +TEST_F(TApp, ComplexSingleImag) { cx comp{1, 2}; app.add_complex("-c,--complex", comp); @@ -199,6 +272,29 @@ TEST_F(TApp, BuiltinComplexSingleImag) { EXPECT_DOUBLE_EQ(0, comp.imag()); } +TEST_F(TApp, ComplexSingleImagOption) { + cx comp{1, 2}; + app.add_option("-c,--complex", comp); + + args = {"-c", "4j"}; + run(); + EXPECT_DOUBLE_EQ(0, comp.real()); + EXPECT_DOUBLE_EQ(4, comp.imag()); + + args = {"-c", "-4j"}; + run(); + EXPECT_DOUBLE_EQ(0, comp.real()); + EXPECT_DOUBLE_EQ(-4, comp.imag()); + args = {"-c", "-4"}; + run(); + EXPECT_DOUBLE_EQ(-4, comp.real()); + EXPECT_DOUBLE_EQ(0, comp.imag()); + args = {"-c", "+4"}; + run(); + EXPECT_DOUBLE_EQ(4, comp.real()); + EXPECT_DOUBLE_EQ(0, comp.imag()); +} + /// Simple class containing two strings useful for testing lexical cast and conversions class spair { public: @@ -222,8 +318,8 @@ template <> bool lexical_cast<spair>(const std::string &input, spair &output) { output = {input.substr(0, sep), input.substr(sep + 1)}; return true; } -} // namespace detail -} // namespace CLI +} // namespace detail +} // namespace CLI TEST_F(TApp, custom_string_converter) { spair val; @@ -245,98 +341,6 @@ TEST_F(TApp, custom_string_converterFail) { EXPECT_THROW(run(), CLI::ConversionError); } -// an example of custom complex number converter that can be used to add new parsing options -#if defined(__has_include) -#if __has_include(<regex>) -// an example of custom converter that can be used to add new parsing options -#define HAS_REGEX_INCLUDE -#endif -#endif - -#ifdef HAS_REGEX_INCLUDE -// Gcc 4.8 and older and the corresponding standard libraries have a broken <regex> so this would -// fail. And if a clang compiler is using libstd++ then this will generate an error as well so this is just a check to -// simplify compilation and prevent a much more complicated #if expression -#include <regex> -namespace CLI { -namespace detail { - -// On MSVC and possibly some other new compilers this can be a free standing function without the template -// specialization but this is compiler dependent -template <> bool lexical_cast<std::complex<double>>(const std::string &input, std::complex<double> &output) { - // regular expression to handle complex numbers of various formats - static const std::regex creg( - R"(([+-]?(\d+(\.\d+)?|\.\d+)([eE][+-]?\d+)?)\s*([+-]\s*(\d+(\.\d+)?|\.\d+)([eE][+-]?\d+)?)[ji]*)"); - - std::smatch m; - double x{0.0}, y{0.0}; - bool worked; - std::regex_search(input, m, creg); - if(m.size() == 9) { - worked = CLI::detail::lexical_cast(m[1], x) && CLI::detail::lexical_cast(m[6], y); - if(worked) { - if(*m[5].first == '-') { - y = -y; - } - } - } else { - if((input.back() == 'j') || (input.back() == 'i')) { - auto strval = input.substr(0, input.size() - 1); - CLI::detail::trim(strval); - worked = CLI::detail::lexical_cast(strval, y); - } else { - std::string ival = input; - CLI::detail::trim(ival); - worked = CLI::detail::lexical_cast(ival, x); - } - } - if(worked) { - output = cx{x, y}; - } - return worked; -} -} // namespace detail -} // namespace CLI - -TEST_F(TApp, AddingComplexParserDetail) { - - bool skip_tests = false; - try { // check if the library actually supports regex, it is possible to link against a non working regex in the - // standard library - std::smatch m; - std::string input = "1.5+2.5j"; - static const std::regex creg( - R"(([+-]?(\d+(\.\d+)?|\.\d+)([eE][+-]?\d+)?)\s*([+-]\s*(\d+(\.\d+)?|\.\d+)([eE][+-]?\d+)?)[ji]*)"); - - auto rsearch = std::regex_search(input, m, creg); - if(!rsearch) { - skip_tests = true; - } else { - EXPECT_EQ(m.size(), 9u); - } - - } catch(...) { - skip_tests = true; - } - if(!skip_tests) { - cx comp{0, 0}; - app.add_option("-c,--complex", comp, "add a complex number option"); - args = {"-c", "1.5+2.5j"}; - - run(); - - EXPECT_DOUBLE_EQ(1.5, comp.real()); - EXPECT_DOUBLE_EQ(2.5, comp.imag()); - args = {"-c", "1.5-2.5j"}; - - run(); - - EXPECT_DOUBLE_EQ(1.5, comp.real()); - EXPECT_DOUBLE_EQ(-2.5, comp.imag()); - } -} -#endif - /// simple class to wrap another with a very specific type constructor and assignment operators to test out some of the /// option assignments template <class X> class objWrapper { @@ -523,3 +527,107 @@ TEST_F(TApp, uint16Wrapper) { EXPECT_THROW(run(), CLI::ConversionError); } + +template <class T> class SimpleWrapper { + public: + SimpleWrapper() : val_{} {}; + explicit SimpleWrapper(const T &initial) : val_{initial} {}; + T &getRef() { return val_; } + using value_type = T; + + private: + T val_; +}; + +TEST_F(TApp, wrapperInt) { + SimpleWrapper<int> wrap; + app.add_option("--val", wrap); + args = {"--val", "2"}; + + run(); + EXPECT_EQ(wrap.getRef(), 2); +} + +TEST_F(TApp, wrapperString) { + SimpleWrapper<std::string> wrap; + app.add_option("--val", wrap); + args = {"--val", "str"}; + + run(); + EXPECT_EQ(wrap.getRef(), "str"); +} + +TEST_F(TApp, wrapperVector) { + SimpleWrapper<std::vector<int>> wrap; + app.add_option("--val", wrap); + args = {"--val", "1", "2", "3", "4"}; + + run(); + auto v1 = wrap.getRef(); + auto v2 = std::vector<int>{1, 2, 3, 4}; + EXPECT_EQ(v1, v2); +} + +TEST_F(TApp, wrapperwrapperString) { + SimpleWrapper<SimpleWrapper<std::string>> wrap; + app.add_option("--val", wrap); + args = {"--val", "arg"}; + + run(); + auto v1 = wrap.getRef().getRef(); + auto v2 = "arg"; + EXPECT_EQ(v1, v2); +} + +TEST_F(TApp, wrapperwrapperVector) { + SimpleWrapper<SimpleWrapper<std::vector<int>>> wrap; + auto opt = app.add_option("--val", wrap); + args = {"--val", "1", "2", "3", "4"}; + + run(); + auto v1 = wrap.getRef().getRef(); + auto v2 = std::vector<int>{1, 2, 3, 4}; + EXPECT_EQ(v1, v2); + opt->type_size(0, 5); + + args = {"--val"}; + + run(); + EXPECT_TRUE(wrap.getRef().getRef().empty()); + + args = {"--val", "happy", "sad"}; + + EXPECT_THROW(run(), CLI::ConversionError); +} + +TEST_F(TApp, wrapperComplex) { + SimpleWrapper<std::complex<double>> wrap; + app.add_option("--val", wrap); + args = {"--val", "1", "2"}; + + run(); + auto &v1 = wrap.getRef(); + auto v2 = std::complex<double>{1, 2}; + EXPECT_EQ(v1.real(), v2.real()); + EXPECT_EQ(v1.imag(), v2.imag()); + args = {"--val", "1.4-4j"}; + + run(); + v2 = std::complex<double>{1.4, -4}; + EXPECT_EQ(v1.real(), v2.real()); + EXPECT_EQ(v1.imag(), v2.imag()); +} + +TEST_F(TApp, vectorComplex) { + std::vector<std::complex<double>> vcomplex; + app.add_option("--val", vcomplex); + args = {"--val", "1", "2", "--val", "1.4-4j"}; + + run(); + + ASSERT_EQ(vcomplex.size(), 2U); + EXPECT_EQ(vcomplex[0].real(), 1.0); + EXPECT_EQ(vcomplex[0].imag(), 2.0); + EXPECT_EQ(vcomplex[1].real(), 1.4); + EXPECT_EQ(vcomplex[1].imag(), -4.0); +} diff --git a/packages/CLI11/tests/OptionGroupTest.cpp b/packages/CLI11/tests/OptionGroupTest.cpp index 91f954dfacf8fb77f0947b6d08e8487e93607cb7..51bb9fd260cdb1fb11cf760e4bb5b0ba6d6ae353 100644 --- a/packages/CLI11/tests/OptionGroupTest.cpp +++ b/packages/CLI11/tests/OptionGroupTest.cpp @@ -1,3 +1,9 @@ +// Copyright (c) 2017-2020, University of Cincinnati, developed by Henry Schreiner +// under NSF AWARD 1414736 and by the respective contributors. +// All rights reserved. +// +// SPDX-License-Identifier: BSD-3-Clause + #include "app_helper.hpp" #include "gmock/gmock.h" @@ -768,6 +774,6 @@ TEST_F(ManyGroupsPreTrigger, PreTriggerTestsSubcommand) { EXPECT_EQ(triggerMain, 4u); EXPECT_EQ(trigger1, 1u); EXPECT_EQ(trigger2, 3u); - EXPECT_EQ(trigger3, 1u); // processes the first argument in group3 which includes the entire subcommand, which will - // go until the sub1 command is given + EXPECT_EQ(trigger3, 1u); // processes the first argument in group3 which includes the entire subcommand, which will + // go until the sub1 command is given } diff --git a/packages/CLI11/tests/OptionTypeTest.cpp b/packages/CLI11/tests/OptionTypeTest.cpp new file mode 100644 index 0000000000000000000000000000000000000000..4ee7deb818300863fe6636c55999b58deed24f2c --- /dev/null +++ b/packages/CLI11/tests/OptionTypeTest.cpp @@ -0,0 +1,840 @@ +// Copyright (c) 2017-2020, University of Cincinnati, developed by Henry Schreiner +// under NSF AWARD 1414736 and by the respective contributors. +// All rights reserved. +// +// SPDX-License-Identifier: BSD-3-Clause + +#include "app_helper.hpp" +#include <complex> +#include <cstdint> +#include <cstdlib> +#include <deque> +#include <forward_list> +#include <list> +#include <map> +#include <queue> +#include <set> +#include <unordered_map> +#include <unordered_set> +#include <vector> + +#include "gmock/gmock.h" + +TEST_F(TApp, OneStringAgain) { + std::string str; + app.add_option("-s,--string", str); + args = {"--string", "mystring"}; + run(); + EXPECT_EQ(1u, app.count("-s")); + EXPECT_EQ(1u, app.count("--string")); + EXPECT_EQ(str, "mystring"); +} + +TEST_F(TApp, OneStringFunction) { + std::string str; + app.add_option_function<std::string>("-s,--string", [&str](const std::string &val) { str = val; }); + args = {"--string", "mystring"}; + run(); + EXPECT_EQ(1u, app.count("-s")); + EXPECT_EQ(1u, app.count("--string")); + EXPECT_EQ(str, "mystring"); +} + +TEST_F(TApp, doubleFunction) { + double res{0.0}; + app.add_option_function<double>("--val", [&res](double val) { res = std::abs(val + 54); }); + args = {"--val", "-354.356"}; + run(); + EXPECT_EQ(res, 300.356); + // get the original value as entered as an integer + EXPECT_EQ(app["--val"]->as<float>(), -354.356f); +} + +TEST_F(TApp, doubleFunctionFail) { + double res; + app.add_option_function<double>("--val", [&res](double val) { res = std::abs(val + 54); }); + args = {"--val", "not_double"}; + EXPECT_THROW(run(), CLI::ConversionError); +} + +TEST_F(TApp, doubleVectorFunction) { + std::vector<double> res; + app.add_option_function<std::vector<double>>("--val", [&res](const std::vector<double> &val) { + res = val; + std::transform(res.begin(), res.end(), res.begin(), [](double v) { return v + 5.0; }); + }); + args = {"--val", "5", "--val", "6", "--val", "7"}; + run(); + EXPECT_EQ(res.size(), 3u); + EXPECT_EQ(res[0], 10.0); + EXPECT_EQ(res[2], 12.0); +} + +TEST_F(TApp, doubleVectorFunctionFail) { + std::vector<double> res; + std::string vstring = "--val"; + app.add_option_function<std::vector<double>>(vstring, [&res](const std::vector<double> &val) { + res = val; + std::transform(res.begin(), res.end(), res.begin(), [](double v) { return v + 5.0; }); + }); + args = {"--val", "five", "--val", "nine", "--val", "7"}; + EXPECT_THROW(run(), CLI::ConversionError); + // check that getting the results through the results function generates the same error + EXPECT_THROW(app[vstring]->results(res), CLI::ConversionError); + auto strvec = app[vstring]->as<std::vector<std::string>>(); + EXPECT_EQ(strvec.size(), 3u); +} + +TEST_F(TApp, doubleVectorFunctionRunCallbackOnDefault) { + std::vector<double> res; + auto opt = app.add_option_function<std::vector<double>>("--val", [&res](const std::vector<double> &val) { + res = val; + std::transform(res.begin(), res.end(), res.begin(), [](double v) { return v + 5.0; }); + }); + args = {"--val", "5", "--val", "6", "--val", "7"}; + run(); + EXPECT_EQ(res.size(), 3u); + EXPECT_EQ(res[0], 10.0); + EXPECT_EQ(res[2], 12.0); + EXPECT_FALSE(opt->get_run_callback_for_default()); + opt->run_callback_for_default(); + opt->default_val(std::vector<int>{2, 1, -2}); + EXPECT_EQ(res[0], 7.0); + EXPECT_EQ(res[2], 3.0); + + EXPECT_THROW(opt->default_val("this is a string"), CLI::ConversionError); + auto vec = opt->as<std::vector<double>>(); + ASSERT_EQ(vec.size(), 3U); + EXPECT_EQ(vec[0], 5.0); + EXPECT_EQ(vec[2], 7.0); + opt->check(CLI::Number); + opt->run_callback_for_default(false); + EXPECT_THROW(opt->default_val("this is a string"), CLI::ValidationError); +} + +TEST_F(TApp, BoolAndIntFlags) { + + bool bflag{false}; + int iflag{0}; + unsigned int uflag{0}; + + app.add_flag("-b", bflag); + app.add_flag("-i", iflag); + app.add_flag("-u", uflag); + + args = {"-b", "-i", "-u"}; + run(); + EXPECT_TRUE(bflag); + EXPECT_EQ(1, iflag); + EXPECT_EQ((unsigned int)1, uflag); + + args = {"-b", "-b"}; + ASSERT_NO_THROW(run()); + EXPECT_TRUE(bflag); + + bflag = false; + + args = {"-iiiuu"}; + run(); + EXPECT_FALSE(bflag); + EXPECT_EQ(3, iflag); + EXPECT_EQ((unsigned int)2, uflag); +} + +TEST_F(TApp, BoolOption) { + bool bflag{false}; + app.add_option("-b", bflag); + + args = {"-b", "false"}; + run(); + EXPECT_FALSE(bflag); + + args = {"-b", "1"}; + run(); + EXPECT_TRUE(bflag); + + args = {"-b", "-7"}; + run(); + EXPECT_FALSE(bflag); + + // cause an out of bounds error internally + args = {"-b", "751615654161688126132138844896646748852"}; + run(); + EXPECT_TRUE(bflag); + + args = {"-b", "-751615654161688126132138844896646748852"}; + run(); + EXPECT_FALSE(bflag); +} + +TEST_F(TApp, vectorDefaults) { + std::vector<int> vals{4, 5}; + auto opt = app.add_option("--long", vals, "", true); + + args = {"--long", "[1,2,3]"}; + + run(); + + EXPECT_EQ(vals, std::vector<int>({1, 2, 3})); + + args.clear(); + run(); + auto res = app["--long"]->as<std::vector<int>>(); + EXPECT_EQ(res, std::vector<int>({4, 5})); + + app.clear(); + opt->expected(1)->take_last(); + res = app["--long"]->as<std::vector<int>>(); + EXPECT_EQ(res, std::vector<int>({5})); + opt->take_first(); + res = app["--long"]->as<std::vector<int>>(); + EXPECT_EQ(res, std::vector<int>({4})); + + opt->expected(0, 1)->take_last(); + run(); + + EXPECT_EQ(res, std::vector<int>({4})); + res = app["--long"]->as<std::vector<int>>(); + EXPECT_EQ(res, std::vector<int>({5})); +} + +TEST_F(TApp, CallbackBoolFlags) { + + bool value{false}; + + auto func = [&value]() { value = true; }; + + auto cback = app.add_flag_callback("--val", func); + args = {"--val"}; + run(); + EXPECT_TRUE(value); + value = false; + args = {"--val=false"}; + run(); + EXPECT_FALSE(value); + + EXPECT_THROW(app.add_flag_callback("hi", func), CLI::IncorrectConstruction); + cback->multi_option_policy(CLI::MultiOptionPolicy::Throw); + args = {"--val", "--val=false"}; + EXPECT_THROW(run(), CLI::ArgumentMismatch); +} + +TEST_F(TApp, pair_check) { + std::string myfile{"pair_check_file.txt"}; + bool ok = static_cast<bool>(std::ofstream(myfile.c_str()).put('a')); // create file + EXPECT_TRUE(ok); + + EXPECT_TRUE(CLI::ExistingFile(myfile).empty()); + std::pair<std::string, int> findex; + + auto v0 = CLI::ExistingFile; + v0.application_index(0); + auto v1 = CLI::PositiveNumber; + v1.application_index(1); + app.add_option("--file", findex)->check(v0)->check(v1); + + args = {"--file", myfile, "2"}; + + EXPECT_NO_THROW(run()); + + EXPECT_EQ(findex.first, myfile); + EXPECT_EQ(findex.second, 2); + + args = {"--file", myfile, "-3"}; + + EXPECT_THROW(run(), CLI::ValidationError); + + args = {"--file", myfile, "2"}; + std::remove(myfile.c_str()); + EXPECT_THROW(run(), CLI::ValidationError); +} + +// this will require that modifying the multi-option policy for tuples be allowed which it isn't at present + +TEST_F(TApp, pair_check_take_first) { + std::string myfile{"pair_check_file2.txt"}; + bool ok = static_cast<bool>(std::ofstream(myfile.c_str()).put('a')); // create file + EXPECT_TRUE(ok); + + EXPECT_TRUE(CLI::ExistingFile(myfile).empty()); + std::pair<std::string, int> findex; + + auto opt = app.add_option("--file", findex)->check(CLI::ExistingFile)->check(CLI::PositiveNumber); + EXPECT_THROW(opt->get_validator(3), CLI::OptionNotFound); + opt->get_validator(0)->application_index(0); + opt->get_validator(1)->application_index(1); + opt->multi_option_policy(CLI::MultiOptionPolicy::TakeLast); + args = {"--file", "not_a_file.txt", "-16", "--file", myfile, "2"}; + // should only check the last one + EXPECT_NO_THROW(run()); + + EXPECT_EQ(findex.first, myfile); + EXPECT_EQ(findex.second, 2); + + opt->multi_option_policy(CLI::MultiOptionPolicy::TakeFirst); + + EXPECT_THROW(run(), CLI::ValidationError); +} + +TEST_F(TApp, VectorFixedString) { + std::vector<std::string> strvec; + std::vector<std::string> answer{"mystring", "mystring2", "mystring3"}; + + CLI::Option *opt = app.add_option("-s,--string", strvec)->expected(3); + EXPECT_EQ(3, opt->get_expected()); + + args = {"--string", "mystring", "mystring2", "mystring3"}; + run(); + EXPECT_EQ(3u, app.count("--string")); + EXPECT_EQ(answer, strvec); +} + +TEST_F(TApp, VectorDefaultedFixedString) { + std::vector<std::string> strvec{"one"}; + std::vector<std::string> answer{"mystring", "mystring2", "mystring3"}; + + CLI::Option *opt = app.add_option("-s,--string", strvec, "")->expected(3)->capture_default_str(); + EXPECT_EQ(3, opt->get_expected()); + + args = {"--string", "mystring", "mystring2", "mystring3"}; + run(); + EXPECT_EQ(3u, app.count("--string")); + EXPECT_EQ(answer, strvec); +} + +TEST_F(TApp, VectorIndexedValidator) { + std::vector<int> vvec; + + CLI::Option *opt = app.add_option("-v", vvec); + + args = {"-v", "1", "-1", "-v", "3", "-v", "-976"}; + run(); + EXPECT_EQ(4u, app.count("-v")); + EXPECT_EQ(4u, vvec.size()); + opt->check(CLI::PositiveNumber.application_index(0)); + opt->check((!CLI::PositiveNumber).application_index(1)); + EXPECT_NO_THROW(run()); + EXPECT_EQ(4u, vvec.size()); + // v[3] would be negative + opt->check(CLI::PositiveNumber.application_index(3)); + EXPECT_THROW(run(), CLI::ValidationError); +} + +TEST_F(TApp, VectorUnlimString) { + std::vector<std::string> strvec; + std::vector<std::string> answer{"mystring", "mystring2", "mystring3"}; + + CLI::Option *opt = app.add_option("-s,--string", strvec); + EXPECT_EQ(1, opt->get_expected()); + EXPECT_EQ(CLI::detail::expected_max_vector_size, opt->get_expected_max()); + + args = {"--string", "mystring", "mystring2", "mystring3"}; + run(); + EXPECT_EQ(3u, app.count("--string")); + EXPECT_EQ(answer, strvec); + + args = {"-s", "mystring", "mystring2", "mystring3"}; + run(); + EXPECT_EQ(3u, app.count("--string")); + EXPECT_EQ(answer, strvec); +} + +// From https://github.com/CLIUtils/CLI11/issues/420 +TEST_F(TApp, stringLikeTests) { + struct nType { + explicit nType(const std::string &a_value) : m_value{a_value} {} + + explicit operator std::string() const { return std::string{"op str"}; } + + std::string m_value; + }; + + nType m_type{"abc"}; + app.add_option("--type", m_type, "type")->capture_default_str(); + run(); + + EXPECT_EQ(app["--type"]->as<std::string>(), "op str"); + args = {"--type", "bca"}; + run(); + EXPECT_EQ(std::string(m_type), "op str"); + EXPECT_EQ(m_type.m_value, "bca"); +} + +TEST_F(TApp, VectorExpectedRange) { + std::vector<std::string> strvec; + + CLI::Option *opt = app.add_option("--string", strvec); + opt->expected(2, 4)->multi_option_policy(CLI::MultiOptionPolicy::Throw); + + args = {"--string", "mystring", "mystring2", "mystring3"}; + run(); + EXPECT_EQ(3u, app.count("--string")); + + args = {"--string", "mystring"}; + EXPECT_THROW(run(), CLI::ArgumentMismatch); + + args = {"--string", "mystring", "mystring2", "string2", "--string", "string4", "string5"}; + EXPECT_THROW(run(), CLI::ArgumentMismatch); + + EXPECT_EQ(opt->get_expected_max(), 4); + EXPECT_EQ(opt->get_expected_min(), 2); + opt->expected(4, 2); // just test the handling of reversed arguments + EXPECT_EQ(opt->get_expected_max(), 4); + EXPECT_EQ(opt->get_expected_min(), 2); + opt->expected(-5); + EXPECT_EQ(opt->get_expected_max(), 5); + EXPECT_EQ(opt->get_expected_min(), 5); + opt->expected(-5, 7); + EXPECT_EQ(opt->get_expected_max(), 7); + EXPECT_EQ(opt->get_expected_min(), 5); +} + +TEST_F(TApp, VectorFancyOpts) { + std::vector<std::string> strvec; + std::vector<std::string> answer{"mystring", "mystring2", "mystring3"}; + + CLI::Option *opt = app.add_option("-s,--string", strvec)->required()->expected(3); + EXPECT_EQ(3, opt->get_expected()); + + args = {"--string", "mystring", "mystring2", "mystring3"}; + run(); + EXPECT_EQ(3u, app.count("--string")); + EXPECT_EQ(answer, strvec); + + args = {"one", "two"}; + EXPECT_THROW(run(), CLI::RequiredError); + + EXPECT_THROW(run(), CLI::ParseError); +} + +// #87 +TEST_F(TApp, CustomDoubleOption) { + + std::pair<int, double> custom_opt; + + auto opt = app.add_option("posit", [&custom_opt](CLI::results_t vals) { + custom_opt = {stol(vals.at(0)), stod(vals.at(1))}; + return true; + }); + opt->type_name("INT FLOAT")->type_size(2); + + args = {"12", "1.5"}; + + run(); + EXPECT_EQ(custom_opt.first, 12); + EXPECT_DOUBLE_EQ(custom_opt.second, 1.5); +} + +// now with tuple support this is possible +TEST_F(TApp, CustomDoubleOptionAlt) { + + std::pair<int, double> custom_opt; + + app.add_option("posit", custom_opt); + + args = {"12", "1.5"}; + + run(); + EXPECT_EQ(custom_opt.first, 12); + EXPECT_DOUBLE_EQ(custom_opt.second, 1.5); +} + +// now with independent type sizes and expected this is possible +TEST_F(TApp, vectorPair) { + + std::vector<std::pair<int, std::string>> custom_opt; + + auto opt = app.add_option("--dict", custom_opt); + + args = {"--dict", "1", "str1", "--dict", "3", "str3"}; + + run(); + ASSERT_EQ(custom_opt.size(), 2u); + EXPECT_EQ(custom_opt[0].first, 1); + EXPECT_EQ(custom_opt[1].second, "str3"); + + args = {"--dict", "1", "str1", "--dict", "3", "str3", "--dict", "-1", "str4"}; + run(); + ASSERT_EQ(custom_opt.size(), 3u); + EXPECT_EQ(custom_opt[2].first, -1); + EXPECT_EQ(custom_opt[2].second, "str4"); + opt->check(CLI::PositiveNumber.application_index(0)); + + EXPECT_THROW(run(), CLI::ValidationError); +} + +TEST_F(TApp, vectorPairFail) { + + std::vector<std::pair<int, std::string>> custom_opt; + + app.add_option("--dict", custom_opt); + + args = {"--dict", "1", "str1", "--dict", "str3", "1"}; + + EXPECT_THROW(run(), CLI::ConversionError); +} + +TEST_F(TApp, vectorPairTypeRange) { + + std::vector<std::pair<int, std::string>> custom_opt; + + auto opt = app.add_option("--dict", custom_opt); + + opt->type_size(2, 1); // just test switched arguments + EXPECT_EQ(opt->get_type_size_min(), 1); + EXPECT_EQ(opt->get_type_size_max(), 2); + + args = {"--dict", "1", "str1", "--dict", "3", "str3"}; + + run(); + ASSERT_EQ(custom_opt.size(), 2u); + EXPECT_EQ(custom_opt[0].first, 1); + EXPECT_EQ(custom_opt[1].second, "str3"); + + args = {"--dict", "1", "str1", "--dict", "3", "--dict", "-1", "str4"}; + run(); + ASSERT_EQ(custom_opt.size(), 3u); + EXPECT_TRUE(custom_opt[1].second.empty()); + EXPECT_EQ(custom_opt[2].first, -1); + EXPECT_EQ(custom_opt[2].second, "str4"); + + opt->type_size(-2, -1); // test negative arguments + EXPECT_EQ(opt->get_type_size_min(), 1); + EXPECT_EQ(opt->get_type_size_max(), 2); + // this type size spec should run exactly as before + run(); + ASSERT_EQ(custom_opt.size(), 3u); + EXPECT_TRUE(custom_opt[1].second.empty()); + EXPECT_EQ(custom_opt[2].first, -1); + EXPECT_EQ(custom_opt[2].second, "str4"); +} + +// now with independent type sizes and expected this is possible +TEST_F(TApp, vectorTuple) { + + std::vector<std::tuple<int, std::string, double>> custom_opt; + + auto opt = app.add_option("--dict", custom_opt); + + args = {"--dict", "1", "str1", "4.3", "--dict", "3", "str3", "2.7"}; + + run(); + ASSERT_EQ(custom_opt.size(), 2u); + EXPECT_EQ(std::get<0>(custom_opt[0]), 1); + EXPECT_EQ(std::get<1>(custom_opt[1]), "str3"); + EXPECT_EQ(std::get<2>(custom_opt[1]), 2.7); + + args = {"--dict", "1", "str1", "4.3", "--dict", "3", "str3", "2.7", "--dict", "-1", "str4", "-1.87"}; + run(); + ASSERT_EQ(custom_opt.size(), 3u); + EXPECT_EQ(std::get<0>(custom_opt[2]), -1); + EXPECT_EQ(std::get<1>(custom_opt[2]), "str4"); + EXPECT_EQ(std::get<2>(custom_opt[2]), -1.87); + opt->check(CLI::PositiveNumber.application_index(0)); + + EXPECT_THROW(run(), CLI::ValidationError); + + args.back() = "haha"; + args[9] = "45"; + EXPECT_THROW(run(), CLI::ConversionError); +} + +// now with independent type sizes and expected this is possible +TEST_F(TApp, vectorVector) { + + std::vector<std::vector<int>> custom_opt; + + auto opt = app.add_option("--dict", custom_opt); + + args = {"--dict", "1", "2", "4", "--dict", "3", "1"}; + + run(); + ASSERT_EQ(custom_opt.size(), 2u); + EXPECT_EQ(custom_opt[0].size(), 3u); + EXPECT_EQ(custom_opt[1].size(), 2u); + + args = {"--dict", "1", "2", "4", "--dict", "3", "1", "--dict", "3", "--dict", + "3", "3", "3", "3", "3", "3", "3", "3", "3", "-3"}; + run(); + ASSERT_EQ(custom_opt.size(), 4u); + EXPECT_EQ(custom_opt[0].size(), 3u); + EXPECT_EQ(custom_opt[1].size(), 2u); + EXPECT_EQ(custom_opt[2].size(), 1u); + EXPECT_EQ(custom_opt[3].size(), 10u); + opt->check(CLI::PositiveNumber.application_index(9)); + + EXPECT_THROW(run(), CLI::ValidationError); + args.pop_back(); + EXPECT_NO_THROW(run()); + + args.back() = "haha"; + EXPECT_THROW(run(), CLI::ConversionError); + + args = {"--dict", "1", "2", "4", "%%", "3", "1", "%%", "3", "%%", "3", "3", "3", "3", "3", "3", "3", "3", "3", "3"}; + run(); + ASSERT_EQ(custom_opt.size(), 4u); +} + +// now with independent type sizes and expected this is possible +TEST_F(TApp, vectorVectorFixedSize) { + + std::vector<std::vector<int>> custom_opt; + + auto opt = app.add_option("--dict", custom_opt)->type_size(4); + + args = {"--dict", "1", "2", "4", "3", "--dict", "3", "1", "2", "8"}; + + run(); + ASSERT_EQ(custom_opt.size(), 2u); + EXPECT_EQ(custom_opt[0].size(), 4u); + EXPECT_EQ(custom_opt[1].size(), 4u); + + args = {"--dict", "1", "2", "4", "--dict", "3", "1", "7", "6"}; + EXPECT_THROW(run(), CLI::ConversionError); + // this should reset it + opt->type_size(CLI::detail::expected_max_vector_size); + opt->type_size(1, CLI::detail::expected_max_vector_size); + EXPECT_NO_THROW(run()); + ASSERT_EQ(custom_opt.size(), 2U); +} + +// now with independent type sizes and expected this is possible +TEST_F(TApp, tuplePair) { + std::tuple<std::pair<int, double>> custom_opt; + + app.add_option("--pr", custom_opt); + + args = {"--pr", "1", "2"}; + + run(); + EXPECT_EQ(std::get<0>(custom_opt).first, 1); + EXPECT_EQ(std::get<0>(custom_opt).second, 2.0); +} +// now with independent type sizes and expected this is possible +TEST_F(TApp, tupleintPair) { + std::tuple<int, std::pair<int, double>> custom_opt; + + app.add_option("--pr", custom_opt); + + args = {"--pr", "3", "1", "2"}; + + run(); + EXPECT_EQ(std::get<0>(custom_opt), 3); + EXPECT_EQ(std::get<1>(custom_opt).first, 1); + EXPECT_EQ(std::get<1>(custom_opt).second, 2.0); +} + +static_assert(CLI::detail::is_mutable_container<std::set<std::string>>::value, "set should be a container"); +static_assert(CLI::detail::is_mutable_container<std::map<std::string, std::string>>::value, + "map should be a container"); +static_assert(CLI::detail::is_mutable_container<std::unordered_map<std::string, double>>::value, + "unordered_map should be a container"); + +static_assert(CLI::detail::is_mutable_container<std::list<std::pair<int, std::string>>>::value, + "list should be a container"); + +static_assert(CLI::detail::type_count<std::set<std::string>>::value == 1, "set should have a type size of 1"); +static_assert(CLI::detail::type_count<std::set<std::tuple<std::string, int, int>>>::value == 3, + "tuple set should have size of 3"); +static_assert(CLI::detail::type_count<std::map<std::string, std::string>>::value == 2, + "map should have a type size of 2"); +static_assert(CLI::detail::type_count<std::unordered_map<std::string, double>>::value == 2, + "unordered_map should have a type size of 2"); + +static_assert(CLI::detail::type_count<std::list<std::pair<int, std::string>>>::value == 2, + "list<int,string> should have a type size of 2"); +static_assert(CLI::detail::type_count<std::map<std::string, std::pair<int, std::string>>>::value == 3, + "map<string,pair<int,string>> should have a type size of 3"); + +template <class T> class TApp_container_single : public TApp { + public: + using container_type = T; + container_type cval{}; + TApp_container_single() : TApp() {} +}; + +using containerTypes_single = + ::testing::Types<std::vector<int>, std::deque<int>, std::set<int>, std::list<int>, std::unordered_set<int>>; + +TYPED_TEST_SUITE(TApp_container_single, containerTypes_single, ); + +TYPED_TEST(TApp_container_single, containerInt) { + + auto &cv = TApp_container_single<TypeParam>::cval; + CLI::Option *opt = (TApp::app).add_option("-v", cv); + + TApp::args = {"-v", "1", "-1", "-v", "3", "-v", "-976"}; + TApp::run(); + EXPECT_EQ(4u, (TApp::app).count("-v")); + EXPECT_EQ(4u, cv.size()); + opt->check(CLI::PositiveNumber.application_index(0)); + opt->check((!CLI::PositiveNumber).application_index(1)); + EXPECT_NO_THROW(TApp::run()); + EXPECT_EQ(4u, cv.size()); + // v[3] would be negative + opt->check(CLI::PositiveNumber.application_index(3)); + EXPECT_THROW(TApp::run(), CLI::ValidationError); +} + +template <class T> class TApp_container_pair : public TApp { + public: + using container_type = T; + container_type cval{}; + TApp_container_pair() : TApp() {} +}; + +using isp = std::pair<int, std::string>; +using containerTypes_pair = ::testing::Types<std::vector<isp>, + std::deque<isp>, + std::set<isp>, + std::list<isp>, + std::map<int, std::string>, + std::unordered_map<int, std::string>>; + +TYPED_TEST_SUITE(TApp_container_pair, containerTypes_pair, ); + +TYPED_TEST(TApp_container_pair, containerPair) { + + auto &cv = TApp_container_pair<TypeParam>::cval; + (TApp::app).add_option("--dict", cv); + + TApp::args = {"--dict", "1", "str1", "--dict", "3", "str3"}; + + TApp::run(); + EXPECT_EQ(cv.size(), 2u); + + TApp::args = {"--dict", "1", "str1", "--dict", "3", "--dict", "-1", "str4"}; + TApp::run(); + EXPECT_EQ(cv.size(), 3u); +} + +template <class T> class TApp_container_tuple : public TApp { + public: + using container_type = T; + container_type cval{}; + TApp_container_tuple() : TApp() {} +}; + +using tup_obj = std::tuple<int, std::string, double>; +using containerTypes_tuple = ::testing::Types<std::vector<tup_obj>, + std::deque<tup_obj>, + std::set<tup_obj>, + std::list<tup_obj>, + std::map<int, std::pair<std::string, double>>, + std::unordered_map<int, std::tuple<std::string, double>>>; + +TYPED_TEST_SUITE(TApp_container_tuple, containerTypes_tuple, ); + +TYPED_TEST(TApp_container_tuple, containerTuple) { + + auto &cv = TApp_container_tuple<TypeParam>::cval; + (TApp::app).add_option("--dict", cv); + + TApp::args = {"--dict", "1", "str1", "4.3", "--dict", "3", "str3", "2.7"}; + + TApp::run(); + EXPECT_EQ(cv.size(), 2u); + + TApp::args = {"--dict", "1", "str1", "4.3", "--dict", "3", "str3", "2.7", "--dict", "-1", "str4", "-1.87"}; + TApp::run(); + EXPECT_EQ(cv.size(), 3u); +} + +using icontainer1 = std::vector<int>; +using icontainer2 = std::list<int>; +using icontainer3 = std::set<int>; +using icontainer4 = std::pair<int, std::vector<int>>; + +using containerTypes_container = ::testing::Types<std::vector<icontainer1>, + std::list<icontainer1>, + std::set<icontainer1>, + std::deque<icontainer1>, + std::vector<icontainer2>, + std::list<icontainer2>, + std::set<icontainer2>, + std::deque<icontainer2>, + std::vector<icontainer3>, + std::list<icontainer3>, + std::set<icontainer3>, + std::deque<icontainer3>>; + +template <class T> class TApp_container_container : public TApp { + public: + using container_type = T; + container_type cval{}; + TApp_container_container() : TApp() {} +}; + +TYPED_TEST_SUITE(TApp_container_container, containerTypes_container, ); + +TYPED_TEST(TApp_container_container, containerContainer) { + + auto &cv = TApp_container_container<TypeParam>::cval; + (TApp::app).add_option("--dict", cv); + + TApp::args = {"--dict", "1", "2", "4", "--dict", "3", "1"}; + + TApp::run(); + EXPECT_EQ(cv.size(), 2u); + + TApp::args = {"--dict", "1", "2", "4", "--dict", "3", "1", "--dict", "3", "--dict", + "3", "3", "3", "3", "3", "3", "3", "3", "3", "-3"}; + TApp::run(); + EXPECT_EQ(cv.size(), 4u); +} + +TEST_F(TApp, containerContainer) { + + std::vector<icontainer4> cv; + app.add_option("--dict", cv); + + args = {"--dict", "1", "2", "4", "--dict", "3", "1"}; + + run(); + EXPECT_EQ(cv.size(), 2u); + + args = {"--dict", "1", "2", "4", "--dict", "3", "1", "--dict", "3", "", "--dict", + "3", "3", "3", "3", "3", "3", "3", "3", "3", "-3"}; + run(); + EXPECT_EQ(cv.size(), 4u); +} + +TEST_F(TApp, unknownContainerWrapper) { + + class vopt { + public: + vopt() = default; + explicit vopt(const std::vector<double> &vdub) : val_{vdub} {}; + std::vector<double> val_{}; + }; + + vopt cv; + app.add_option<vopt, std::vector<double>>("--vv", cv); + + args = {"--vv", "1", "2", "4"}; + + run(); + EXPECT_EQ(cv.val_.size(), 3u); + args = {"--vv", ""}; + + run(); + EXPECT_TRUE(cv.val_.empty()); +} + +TEST_F(TApp, tupleTwoVectors) { + + std::tuple<std::vector<int>, std::vector<int>> cv; + app.add_option("--vv", cv); + + args = {"--vv", "1", "2", "4"}; + + run(); + EXPECT_EQ(std::get<0>(cv).size(), 3U); + EXPECT_TRUE(std::get<1>(cv).empty()); + + args = {"--vv", "1", "2", "%%", "4", "4", "5"}; + + run(); + EXPECT_EQ(std::get<0>(cv).size(), 2U); + EXPECT_EQ(std::get<1>(cv).size(), 3U); +} diff --git a/packages/CLI11/tests/OptionalTest.cpp b/packages/CLI11/tests/OptionalTest.cpp index 598d16c55886e1da6527bff83ebdf3251460f380..a52c389039580a0c13c446b36d048a1bbc09542d 100644 --- a/packages/CLI11/tests/OptionalTest.cpp +++ b/packages/CLI11/tests/OptionalTest.cpp @@ -1,3 +1,10 @@ +// Copyright (c) 2017-2020, University of Cincinnati, developed by Henry Schreiner +// under NSF AWARD 1414736 and by the respective contributors. +// All rights reserved. +// +// SPDX-License-Identifier: BSD-3-Clause + +#include <complex> #include <cstdint> #include <cstdlib> #include <iostream> @@ -72,6 +79,44 @@ TEST_F(TApp, StdOptionalTest) { EXPECT_EQ(*opt, 3); } +TEST_F(TApp, StdOptionalVectorEmptyDirect) { + std::optional<std::vector<int>> opt; + app.add_option("-v,--vec", opt)->expected(0, 3)->allow_extra_args(); + // app.add_option("-v,--vec", opt)->expected(0, 3)->allow_extra_args(); + run(); + EXPECT_FALSE(opt); + args = {"-v"}; + opt = std::vector<int>{4, 3}; + run(); + EXPECT_FALSE(opt); + args = {"-v", "1", "4", "5"}; + run(); + EXPECT_TRUE(opt); + std::vector<int> expV{1, 4, 5}; + EXPECT_EQ(*opt, expV); +} + +TEST_F(TApp, StdOptionalComplexDirect) { + std::optional<std::complex<double>> opt; + app.add_option("-c,--complex", opt)->type_size(0, 2); + run(); + EXPECT_FALSE(opt); + args = {"-c"}; + opt = std::complex<double>{4.0, 3.0}; + run(); + EXPECT_FALSE(opt); + args = {"-c", "1+2j"}; + run(); + EXPECT_TRUE(opt); + std::complex<double> val{1, 2}; + EXPECT_EQ(*opt, val); + args = {"-c", "3", "-4"}; + run(); + EXPECT_TRUE(opt); + std::complex<double> val2{3, -4}; + EXPECT_EQ(*opt, val2); +} + #ifdef _MSC_VER #pragma warning(default : 4244) #endif @@ -165,11 +210,16 @@ TEST_F(TApp, BoostOptionalStringTest) { EXPECT_TRUE(opt); EXPECT_EQ(*opt, "strv"); } +namespace boost { +using CLI::enums::operator<<; +} TEST_F(TApp, BoostOptionalEnumTest) { + enum class eval : char { val0 = 0, val1 = 1, val2 = 2, val3 = 3, val4 = 4 }; - boost::optional<eval> opt; + boost::optional<eval> opt, opt2; auto optptr = app.add_option<decltype(opt), eval>("-v,--val", opt); + app.add_option_no_stream("-e,--eval", opt2); optptr->capture_default_str(); auto dstring = optptr->get_default_str(); @@ -206,6 +256,7 @@ TEST_F(TApp, BoostOptionalVector) { TEST_F(TApp, BoostOptionalVectorEmpty) { boost::optional<std::vector<int>> opt; app.add_option<decltype(opt), std::vector<int>>("-v,--vec", opt)->expected(0, 3)->allow_extra_args(); + // app.add_option("-v,--vec", opt)->expected(0, 3)->allow_extra_args(); run(); EXPECT_FALSE(opt); args = {"-v"}; @@ -219,6 +270,44 @@ TEST_F(TApp, BoostOptionalVectorEmpty) { EXPECT_EQ(*opt, expV); } +TEST_F(TApp, BoostOptionalVectorEmptyDirect) { + boost::optional<std::vector<int>> opt; + app.add_option_no_stream("-v,--vec", opt)->expected(0, 3)->allow_extra_args(); + // app.add_option("-v,--vec", opt)->expected(0, 3)->allow_extra_args(); + run(); + EXPECT_FALSE(opt); + args = {"-v"}; + opt = std::vector<int>{4, 3}; + run(); + EXPECT_FALSE(opt); + args = {"-v", "1", "4", "5"}; + run(); + EXPECT_TRUE(opt); + std::vector<int> expV{1, 4, 5}; + EXPECT_EQ(*opt, expV); +} + +TEST_F(TApp, BoostOptionalComplexDirect) { + boost::optional<std::complex<double>> opt; + app.add_option("-c,--complex", opt)->type_size(0, 2); + run(); + EXPECT_FALSE(opt); + args = {"-c"}; + opt = std::complex<double>{4.0, 3.0}; + run(); + EXPECT_FALSE(opt); + args = {"-c", "1+2j"}; + run(); + EXPECT_TRUE(opt); + std::complex<double> val{1, 2}; + EXPECT_EQ(*opt, val); + args = {"-c", "3", "-4"}; + run(); + EXPECT_TRUE(opt); + std::complex<double> val2{3, -4}; + EXPECT_EQ(*opt, val2); +} + #endif #if !CLI11_OPTIONAL diff --git a/packages/CLI11/tests/SetTest.cpp b/packages/CLI11/tests/SetTest.cpp index e86bdc6ff6107ef565b605fb1dfd4c93074ce29e..7b6236edcbb13dfe0eab73200e11fd223daf57bf 100644 --- a/packages/CLI11/tests/SetTest.cpp +++ b/packages/CLI11/tests/SetTest.cpp @@ -1,3 +1,9 @@ +// Copyright (c) 2017-2020, University of Cincinnati, developed by Henry Schreiner +// under NSF AWARD 1414736 and by the respective contributors. +// All rights reserved. +// +// SPDX-License-Identifier: BSD-3-Clause + #include "app_helper.hpp" #include <map> @@ -508,11 +514,11 @@ TEST_F(TApp, InSetIgnoreCase) { args = {"--quick", "two"}; run(); - EXPECT_EQ("Two", choice); // Keeps caps from set + EXPECT_EQ("Two", choice); // Keeps caps from set args = {"--quick", "ThrEE"}; run(); - EXPECT_EQ("THREE", choice); // Keeps caps from set + EXPECT_EQ("THREE", choice); // Keeps caps from set args = {"--quick", "four"}; EXPECT_THROW(run(), CLI::ValidationError); @@ -533,11 +539,11 @@ TEST_F(TApp, InSetIgnoreCaseMutableValue) { args = {"--quick", "two"}; run(); - EXPECT_EQ("Two", choice); // Keeps caps from set + EXPECT_EQ("Two", choice); // Keeps caps from set args = {"--quick", "ThrEE"}; run(); - EXPECT_EQ("THREE", choice); // Keeps caps from set + EXPECT_EQ("THREE", choice); // Keeps caps from set options.clear(); args = {"--quick", "ThrEE"}; @@ -556,16 +562,16 @@ TEST_F(TApp, InSetIgnoreCasePointer) { args = {"--quick", "two"}; run(); - EXPECT_EQ("Two", choice); // Keeps caps from set + EXPECT_EQ("Two", choice); // Keeps caps from set args = {"--quick", "ThrEE"}; run(); - EXPECT_EQ("THREE", choice); // Keeps caps from set + EXPECT_EQ("THREE", choice); // Keeps caps from set delete options; args = {"--quick", "ThrEE"}; run(); - EXPECT_EQ("THREE", choice); // this does not throw a segfault + EXPECT_EQ("THREE", choice); // this does not throw a segfault args = {"--quick", "four"}; EXPECT_THROW(run(), CLI::ValidationError); @@ -600,11 +606,11 @@ TEST_F(TApp, InSetIgnoreUnderscore) { args = {"--quick", "optiontwo"}; run(); - EXPECT_EQ("option_two", choice); // Keeps underscore from set + EXPECT_EQ("option_two", choice); // Keeps underscore from set args = {"--quick", "_option_thr_ee"}; run(); - EXPECT_EQ("optionthree", choice); // no underscore + EXPECT_EQ("optionthree", choice); // no underscore args = {"--quick", "Option4"}; EXPECT_THROW(run(), CLI::ValidationError); @@ -626,11 +632,11 @@ TEST_F(TApp, InSetIgnoreCaseUnderscore) { args = {"--quick", "OptionTwo"}; run(); - EXPECT_EQ("option_two", choice); // Keeps underscore and case from set + EXPECT_EQ("option_two", choice); // Keeps underscore and case from set args = {"--quick", "_OPTION_thr_ee"}; run(); - EXPECT_EQ("OptionThree", choice); // no underscore + EXPECT_EQ("OptionThree", choice); // no underscore args = {"--quick", "Option4"}; EXPECT_THROW(run(), CLI::ValidationError); diff --git a/packages/CLI11/tests/SimpleTest.cpp b/packages/CLI11/tests/SimpleTest.cpp index d87495f3e5e91fa7eeaea291292129e266976a71..dfcd5579127bf363a04893de6826fbb36ed345bd 100644 --- a/packages/CLI11/tests/SimpleTest.cpp +++ b/packages/CLI11/tests/SimpleTest.cpp @@ -1,3 +1,9 @@ +// Copyright (c) 2017-2020, University of Cincinnati, developed by Henry Schreiner +// under NSF AWARD 1414736 and by the respective contributors. +// All rights reserved. +// +// SPDX-License-Identifier: BSD-3-Clause + #ifdef CLI11_SINGLE_FILE #include "CLI11.hpp" #else diff --git a/packages/CLI11/tests/StringParseTest.cpp b/packages/CLI11/tests/StringParseTest.cpp index a457403c1e2964aafc6436c241982eb93f89cc42..568bb4ecc45105db27fd51df415c70c379016cac 100644 --- a/packages/CLI11/tests/StringParseTest.cpp +++ b/packages/CLI11/tests/StringParseTest.cpp @@ -1,3 +1,9 @@ +// Copyright (c) 2017-2020, University of Cincinnati, developed by Henry Schreiner +// under NSF AWARD 1414736 and by the respective contributors. +// All rights reserved. +// +// SPDX-License-Identifier: BSD-3-Clause + #include "app_helper.hpp" #include "gmock/gmock.h" diff --git a/packages/CLI11/tests/SubcommandTest.cpp b/packages/CLI11/tests/SubcommandTest.cpp index b9994a56ae825bbee6526d99564f0e2477129a34..da6d3628c45b5369abdffac090e4c4be3ead85da 100644 --- a/packages/CLI11/tests/SubcommandTest.cpp +++ b/packages/CLI11/tests/SubcommandTest.cpp @@ -1,3 +1,9 @@ +// Copyright (c) 2017-2020, University of Cincinnati, developed by Henry Schreiner +// under NSF AWARD 1414736 and by the respective contributors. +// All rights reserved. +// +// SPDX-License-Identifier: BSD-3-Clause + #include "app_helper.hpp" #include "gmock/gmock.h" @@ -104,7 +110,7 @@ TEST_F(TApp, CrazyNameSubcommand) { EXPECT_EQ(sub1->count(), 1u); } -TEST_F(TApp, RequiredAndSubcoms) { // #23 +TEST_F(TApp, RequiredAndSubcommands) { // #23 std::string baz; app.add_option("baz", baz, "Baz Description", true)->required(); @@ -631,13 +637,13 @@ TEST_F(TApp, CallbackOrderingImmediateMain) { EXPECT_EQ(0, sub_val); // the main app callback should run before the subcommand callbacks app.immediate_callback(); - val = 0; // reset value + val = 0; // reset value run(); EXPECT_EQ(2, val); EXPECT_EQ(1, sub_val); // the subcommand callback now runs immediately after processing and before the main app callback again sub->immediate_callback(); - val = 0; // reset value + val = 0; // reset value run(); EXPECT_EQ(1, val); EXPECT_EQ(0, sub_val); @@ -719,7 +725,7 @@ TEST_F(TApp, Required1SubCom) { EXPECT_THROW(run(), CLI::ExtrasError); } -TEST_F(TApp, BadSubcomSearch) { +TEST_F(TApp, BadSubcommandSearch) { auto one = app.add_subcommand("one"); auto two = one->add_subcommand("two"); @@ -1213,7 +1219,7 @@ TEST_F(ManySubcommands, Unlimited) { run(); EXPECT_EQ(app.remaining(true), vs_t()); - app.require_subcommand(2, 0); // 2 or more + app.require_subcommand(2, 0); // 2 or more run(); EXPECT_EQ(app.remaining(true), vs_t()); diff --git a/packages/CLI11/tests/TimerTest.cpp b/packages/CLI11/tests/TimerTest.cpp index 8051c9ec6b6f51d27564f26ba856ab430ce02ca8..51d8bb09b50c79b79ff62a37224ef79253c4dbed 100644 --- a/packages/CLI11/tests/TimerTest.cpp +++ b/packages/CLI11/tests/TimerTest.cpp @@ -1,3 +1,9 @@ +// Copyright (c) 2017-2020, University of Cincinnati, developed by Henry Schreiner +// under NSF AWARD 1414736 and by the respective contributors. +// All rights reserved. +// +// SPDX-License-Identifier: BSD-3-Clause + #include "CLI/Timer.hpp" #include "gmock/gmock.h" #include "gtest/gtest.h" diff --git a/packages/CLI11/tests/TransformTest.cpp b/packages/CLI11/tests/TransformTest.cpp index 33bd9ce39d82e6bf0878982d24a024b2e57ee726..ae802af079a8712bd4c64e16ab3f6dbd5e8f6434 100644 --- a/packages/CLI11/tests/TransformTest.cpp +++ b/packages/CLI11/tests/TransformTest.cpp @@ -1,3 +1,9 @@ +// Copyright (c) 2017-2020, University of Cincinnati, developed by Henry Schreiner +// under NSF AWARD 1414736 and by the respective contributors. +// All rights reserved. +// +// SPDX-License-Identifier: BSD-3-Clause + #include "app_helper.hpp" #include <array> @@ -104,7 +110,7 @@ TEST_F(TApp, EnumCheckedTransform) { } // from jzakrzewski Issue #330 -TEST_F(TApp, EnumCheckedDefualtTransform) { +TEST_F(TApp, EnumCheckedDefaultTransform) { enum class existing : std::int16_t { abort, overwrite, remove }; app.add_option("--existing", "What to do if file already exists in the destination") ->transform( @@ -541,7 +547,7 @@ TEST_F(TApp, BoundTests) { EXPECT_TRUE(help.find("[3.4 - 5.9]") != std::string::npos); } -TEST_F(TApp, NumberWithUnitCorrecltySplitNumber) { +TEST_F(TApp, NumberWithUnitCorrectlySplitNumber) { std::map<std::string, int> mapping{{"a", 10}, {"b", 100}, {"cc", 1000}}; int value = 0; diff --git a/packages/CLI11/tests/TrueFalseTest.cpp b/packages/CLI11/tests/TrueFalseTest.cpp index 546c3d24a5a5d484935c6383b995608a9461de9e..7c37d456dc79c9cacb409573a3ce184e02a90a8c 100644 --- a/packages/CLI11/tests/TrueFalseTest.cpp +++ b/packages/CLI11/tests/TrueFalseTest.cpp @@ -1,10 +1,16 @@ +// Copyright (c) 2017-2020, University of Cincinnati, developed by Henry Schreiner +// under NSF AWARD 1414736 and by the respective contributors. +// All rights reserved. +// +// SPDX-License-Identifier: BSD-3-Clause + #include "app_helper.hpp" /// This allows a set of strings to be run over by a test -struct TApp_TBO : public TApp, public ::testing::WithParamInterface<const char *> {}; +struct TApp_TBO : public TApp_base, testing::TestWithParam<const char *> {}; TEST_P(TApp_TBO, TrueBoolOption) { - bool value{false}; // Not used, but set just in case + bool value{false}; // Not used, but set just in case app.add_option("-b,--bool", value); args = {"--bool", GetParam()}; run(); @@ -13,13 +19,13 @@ TEST_P(TApp_TBO, TrueBoolOption) { } // Change to INSTANTIATE_TEST_SUITE_P in GTest master -INSTANTIATE_TEST_CASE_P(TrueBoolOptions, TApp_TBO, ::testing::Values("true", "on", "True", "ON"), ); +INSTANTIATE_TEST_SUITE_P(TrueBoolOptions_test, TApp_TBO, testing::Values("true", "on", "True", "ON")); /// This allows a set of strings to be run over by a test -struct TApp_FBO : public TApp, public ::testing::WithParamInterface<const char *> {}; +struct TApp_FBO : public TApp_base, public ::testing::TestWithParam<const char *> {}; TEST_P(TApp_FBO, FalseBoolOptions) { - bool value{true}; // Not used, but set just in case + bool value{true}; // Not used, but set just in case app.add_option("-b,--bool", value); args = {"--bool", GetParam()}; run(); @@ -27,4 +33,4 @@ TEST_P(TApp_FBO, FalseBoolOptions) { EXPECT_FALSE(value); } -INSTANTIATE_TEST_CASE_P(FalseBoolOptions, TApp_FBO, ::testing::Values("false", "off", "False", "OFF"), ); +INSTANTIATE_TEST_SUITE_P(FalseBoolOptions_test, TApp_FBO, ::testing::Values("false", "off", "False", "OFF")); diff --git a/packages/CLI11/tests/WindowsTest.cpp b/packages/CLI11/tests/WindowsTest.cpp index 58a1faf732d0cd5ff0812fd4a7ac2f0fb50fd47c..41053bbe9212c6c1f6134c9e230d22c77a04cc3f 100644 --- a/packages/CLI11/tests/WindowsTest.cpp +++ b/packages/CLI11/tests/WindowsTest.cpp @@ -1,3 +1,9 @@ +// Copyright (c) 2017-2020, University of Cincinnati, developed by Henry Schreiner +// under NSF AWARD 1414736 and by the respective contributors. +// All rights reserved. +// +// SPDX-License-Identifier: BSD-3-Clause + #include "app_helper.hpp" #include <Windows.h> diff --git a/packages/CLI11/tests/app_helper.hpp b/packages/CLI11/tests/app_helper.hpp index eb8eb2b7d568772db1a5185edec55b616b0ca7e2..6b250a422cb5f13eb549d9a5bf9d91f58f029953 100644 --- a/packages/CLI11/tests/app_helper.hpp +++ b/packages/CLI11/tests/app_helper.hpp @@ -1,3 +1,9 @@ +// Copyright (c) 2017-2020, University of Cincinnati, developed by Henry Schreiner +// under NSF AWARD 1414736 and by the respective contributors. +// All rights reserved. +// +// SPDX-License-Identifier: BSD-3-Clause + #pragma once #ifdef CLI11_SINGLE_FILE @@ -8,13 +14,17 @@ #include "gtest/gtest.h" #include <iostream> +#include <string> +#include <utility> +#include <vector> using input_t = std::vector<std::string>; -struct TApp : public ::testing::Test { +class TApp_base { + public: CLI::App app{"My Test Program"}; input_t args{}; - + virtual ~TApp_base() = default; void run() { // It is okay to re-parse - clear is called automatically before a parse. input_t newargs = args; @@ -23,6 +33,8 @@ struct TApp : public ::testing::Test { } }; +class TApp : public TApp_base, public ::testing::Test {}; + class TempFile { std::string _name{}; @@ -33,7 +45,7 @@ class TempFile { } ~TempFile() { - std::remove(_name.c_str()); // Doesn't matter if returns 0 or not + std::remove(_name.c_str()); // Doesn't matter if returns 0 or not } operator const std::string &() const { return _name; } diff --git a/packages/CLI11/tests/informational.cpp b/packages/CLI11/tests/informational.cpp index b5a0098c7566e6813d667e9917c69a625d737f55..92f7dc4c8d597e729ae62efadf0eb226aa66c31f 100644 --- a/packages/CLI11/tests/informational.cpp +++ b/packages/CLI11/tests/informational.cpp @@ -1,3 +1,9 @@ +// Copyright (c) 2017-2020, University of Cincinnati, developed by Henry Schreiner +// under NSF AWARD 1414736 and by the respective contributors. +// All rights reserved. +// +// SPDX-License-Identifier: BSD-3-Clause + #ifdef CLI11_SINGLE_FILE #include "CLI11.hpp" #else diff --git a/packages/CLI11/tests/link_test_1.cpp b/packages/CLI11/tests/link_test_1.cpp index 6be3bfae5198c7fc0703f3fa4a5115da2abdfd0a..be91037607cc1d090ca572a93803c527a8242975 100644 --- a/packages/CLI11/tests/link_test_1.cpp +++ b/packages/CLI11/tests/link_test_1.cpp @@ -1,3 +1,9 @@ +// Copyright (c) 2017-2020, University of Cincinnati, developed by Henry Schreiner +// under NSF AWARD 1414736 and by the respective contributors. +// All rights reserved. +// +// SPDX-License-Identifier: BSD-3-Clause + #include "CLI/CLI.hpp" #include "CLI/Timer.hpp" diff --git a/packages/CLI11/tests/link_test_2.cpp b/packages/CLI11/tests/link_test_2.cpp index 85c5a74fc6620da7410bd05d414365e8718d60d4..ba4cc8fe1d01be7bce5276c8b3fcd5349afb7790 100644 --- a/packages/CLI11/tests/link_test_2.cpp +++ b/packages/CLI11/tests/link_test_2.cpp @@ -1,3 +1,9 @@ +// Copyright (c) 2017-2020, University of Cincinnati, developed by Henry Schreiner +// under NSF AWARD 1414736 and by the respective contributors. +// All rights reserved. +// +// SPDX-License-Identifier: BSD-3-Clause + #include "CLI/CLI.hpp" #include "CLI/Timer.hpp" #include <gtest/gtest.h> diff --git a/packages/CLI11/tests/mesonTest/main.cpp b/packages/CLI11/tests/mesonTest/main.cpp index 24647ba11771093c68236e31a71f2f787cf5d00c..69813782ad87dd6c6d4b4725cc6700ad79a4da14 100644 --- a/packages/CLI11/tests/mesonTest/main.cpp +++ b/packages/CLI11/tests/mesonTest/main.cpp @@ -1,3 +1,9 @@ +// Copyright (c) 2017-2020, University of Cincinnati, developed by Henry Schreiner +// under NSF AWARD 1414736 and by the respective contributors. +// All rights reserved. +// +// SPDX-License-Identifier: BSD-3-Clause + #include <CLI/CLI.hpp> int main(int argc, char **argv) {