diff --git a/packages/CLI11/.all-contributorsrc b/packages/CLI11/.all-contributorsrc index b95934e25458f31722995ac160ee685dd0d2c11e..f699ad2b03d93291886734c05d338cadd23269b0 100644 --- a/packages/CLI11/.all-contributorsrc +++ b/packages/CLI11/.all-contributorsrc @@ -377,6 +377,69 @@ "contributions": [ "code" ] + }, + { + "login": "rcurtin", + "name": "Ryan Curtin", + "avatar_url": "https://avatars0.githubusercontent.com/u/1845039?v=4", + "profile": "http://www.ratml.org/", + "contributions": [ + "doc" + ] + }, + { + "login": "mbhall88", + "name": "Michael Hall", + "avatar_url": "https://avatars3.githubusercontent.com/u/20403931?v=4", + "profile": "https://mbh.sh", + "contributions": [ + "doc" + ] + }, + { + "login": "ferdymercury", + "name": "ferdymercury", + "avatar_url": "https://avatars3.githubusercontent.com/u/10653970?v=4", + "profile": "https://github.com/ferdymercury", + "contributions": [ + "doc" + ] + }, + { + "login": "jakoblover", + "name": "Jakob Lover", + "avatar_url": "https://avatars0.githubusercontent.com/u/14160441?v=4", + "profile": "https://github.com/jakoblover", + "contributions": [ + "code" + ] + }, + { + "login": "ZeeD26", + "name": "Dominik Steinberger", + "avatar_url": "https://avatars2.githubusercontent.com/u/2487468?v=4", + "profile": "https://github.com/ZeeD26", + "contributions": [ + "code" + ] + }, + { + "login": "dfleury2", + "name": "D. Fleury", + "avatar_url": "https://avatars1.githubusercontent.com/u/4805384?v=4", + "profile": "https://github.com/dfleury2", + "contributions": [ + "code" + ] + }, + { + "login": "dbarowy", + "name": "Dan Barowy", + "avatar_url": "https://avatars3.githubusercontent.com/u/573142?v=4", + "profile": "https://github.com/dbarowy", + "contributions": [ + "doc" + ] } ], "contributorsPerLine": 7, diff --git a/packages/CLI11/.appveyor.yml b/packages/CLI11/.appveyor.yml index 9f3365cc4f5d310da21e21648855e0dc4abcf3fa..a4ae11883dc71dfee74dd77f83747897ba4d01d3 100644 --- a/packages/CLI11/.appveyor.yml +++ b/packages/CLI11/.appveyor.yml @@ -1,8 +1,9 @@ -version: 1.9.0.{build} +version: 1.9.1.{build} branches: only: - master + - v1 install: - git submodule update --init --recursive diff --git a/packages/CLI11/.github/dependabot.yml b/packages/CLI11/.github/dependabot.yml new file mode 100644 index 0000000000000000000000000000000000000000..73273365c0dd0de34754b26f08c0f4d16a5a535c --- /dev/null +++ b/packages/CLI11/.github/dependabot.yml @@ -0,0 +1,16 @@ +version: 2 +updates: + # Maintain dependencies for GitHub Actions + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "daily" + ignore: + # Official actions have moving tags like v1 + # that are used, so they don't need updates here + - dependency-name: "actions/checkout" + - dependency-name: "actions/setup-python" + - dependency-name: "actions/cache" + - dependency-name: "actions/upload-artifact" + - dependency-name: "actions/download-artifact" + - dependency-name: "actions/labeler" diff --git a/packages/CLI11/.github/labeler_merged.yml b/packages/CLI11/.github/labeler_merged.yml new file mode 100644 index 0000000000000000000000000000000000000000..434ab5839ba92ff1a244b0edca9d6effea620c0b --- /dev/null +++ b/packages/CLI11/.github/labeler_merged.yml @@ -0,0 +1,4 @@ +needs changelog: + - all: ['!CHANGELOG.md'] +needs README: + - all: ['!README.md'] diff --git a/packages/CLI11/.github/workflows/build.yml b/packages/CLI11/.github/workflows/build.yml index 35864c098b830f102315b20d45932e28f892a699..d3f8043a5440de0aee244565809397a5950a50e4 100644 --- a/packages/CLI11/.github/workflows/build.yml +++ b/packages/CLI11/.github/workflows/build.yml @@ -16,17 +16,14 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v2 with: submodules: true - - uses: actions/setup-python@v1 - - - name: Make header - run: python ./scripts/MakeSingleHeader.py CLI11.hpp + - uses: actions/setup-python@v2 - name: Prepare CMake config - run: cmake -S . -B build + run: cmake -S . -B build -DCLI11_SINGLE_FILE=ON - name: Make package run: cmake --build build --target package_source @@ -37,12 +34,15 @@ jobs: cp build/CLI11-*-Source.* CLI11-Source cp build/CLI11-*-Source.* . - - uses: actions/upload-artifact@v1 + - name: Make header + run: cmake --build build --target CLI11-generate-single-file + + - uses: actions/upload-artifact@v2 with: name: CLI11.hpp - path: CLI11.hpp + path: build/include/CLI11.hpp - - uses: actions/upload-artifact@v1 + - uses: actions/upload-artifact@v2 with: name: CLI11-Source path: CLI11-Source diff --git a/packages/CLI11/.github/workflows/pr_merged.yml b/packages/CLI11/.github/workflows/pr_merged.yml new file mode 100644 index 0000000000000000000000000000000000000000..6fadc0fa80865e4cc77feb27af49d37d4c695a94 --- /dev/null +++ b/packages/CLI11/.github/workflows/pr_merged.yml @@ -0,0 +1,15 @@ +name: PR merged +on: + pull_request_target: + types: [closed] + +jobs: + label-merged: + name: Changelog needed + runs-on: ubuntu-latest + if: github.event.pull_request.merged == true + steps: + - uses: actions/labeler@main + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + configuration-path: .github/labeler_merged.yml diff --git a/packages/CLI11/.github/workflows/tests.yml b/packages/CLI11/.github/workflows/tests.yml index 86cc78ec2eca21d575c05154af554d47325e9d09..9fcfc705b2860f8ff85464eae2562ae90229f123 100644 --- a/packages/CLI11/.github/workflows/tests.yml +++ b/packages/CLI11/.github/workflows/tests.yml @@ -15,13 +15,7 @@ jobs: steps: - uses: actions/checkout@v2 - uses: actions/setup-python@v2 - - name: set PY - run: echo "::set-env name=PY::$(python -c 'import hashlib, sys;print(hashlib.sha256(sys.version.encode()+sys.executable.encode()).hexdigest())')" - - uses: actions/cache@v1 - with: - path: ~/.cache/pre-commit - key: pre-commit|${{ env.PY }}|${{ hashFiles('.pre-commit-config.yaml') }} - - uses: pre-commit/action@v1.1.0 + - uses: pre-commit/action@v2.0.0 cuda-build: name: CUDA build only @@ -33,8 +27,8 @@ jobs: submodules: true - name: Add wget run: apt-get update && apt-get install -y wget - - name: Install Modern CMake - run: wget -qO- "https://cmake.org/files/v3.16/cmake-3.16.0-Linux-x86_64.tar.gz" | tar --strip-components=1 -xz -C /usr/local + - name: Setup cmake + uses: jwlawson/actions-setup-cmake@v1.7 - name: Configure run: cmake -S . -B build -DCLI11_CUDA_TESTS=ON - name: Build @@ -44,7 +38,7 @@ jobs: name: CMake config check runs-on: ubuntu-latest steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v2 with: submodules: true - name: CMake 3.4 @@ -112,9 +106,14 @@ jobs: with: version: 3.16.8 if: success() || failure() - - name: CMake 3.16 (full) + - name: CMake 3.17 uses: ./.github/actions/cmake_config with: version: 3.17.3 + if: success() || failure() + - name: CMake 3.18 (full) + uses: ./.github/actions/cmake_config + with: + version: 3.18.0 options: -DCLI11_SANITIZERS=ON -DCLI11_BUILD_EXAMPLES_JSON=ON if: success() || failure() diff --git a/packages/CLI11/.gitrepo b/packages/CLI11/.gitrepo index 4b4e25a2b1b976c20f3ad37c88290ccfc63e5fc5..e423eb3eead27d0875cc8ea21a30bf8ceec6ed8c 100644 --- a/packages/CLI11/.gitrepo +++ b/packages/CLI11/.gitrepo @@ -6,7 +6,7 @@ [subrepo] remote = git@github.com:CLIUtils/CLI11.git branch = master - commit = 2b059cbdbe844450e1675a5dda3cb8acb1147631 - parent = fb9f84797173b01c7277657b883e954800be1fc9 - cmdver = 0.4.1 + commit = 639a8add1e248c7337b420ff68572ddb3893e080 + parent = b7068f18e2c214064a81a5b561d5f04a80d2a847 + cmdver = 0.4.3 method = merge diff --git a/packages/CLI11/.pre-commit-config.yaml b/packages/CLI11/.pre-commit-config.yaml index 937cce329034da202d16066ee915f94780d7bc25..f48801a6e65cb8c7d64f86de091d41f348f19a00 100644 --- a/packages/CLI11/.pre-commit-config.yaml +++ b/packages/CLI11/.pre-commit-config.yaml @@ -1,11 +1,12 @@ repos: - repo: https://github.com/psf/black - rev: 19.3b0 + rev: 20.8b1 hooks: - id: black + - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v2.3.0 + rev: v3.4.0 hooks: - id: check-added-large-files - id: mixed-line-ending @@ -14,6 +15,7 @@ repos: - id: check-case-conflict - id: check-symlinks - id: check-yaml + - repo: local hooks: - id: docker-clang-format diff --git a/packages/CLI11/CLI11.hpp.in b/packages/CLI11/CLI11.hpp.in new file mode 100644 index 0000000000000000000000000000000000000000..8de9c532e300e14edf6c66550eea60431d724b1f --- /dev/null +++ b/packages/CLI11/CLI11.hpp.in @@ -0,0 +1,67 @@ +// CLI11: Version {version} +// Originally designed by Henry Schreiner +// https://github.com/CLIUtils/CLI11 +// +// This is a standalone header file generated by MakeSingleHeader.py in CLI11/scripts +// from: {git} +// +// CLI11 {version} Copyright (c) 2017-2020 University of Cincinnati, developed by Henry +// Schreiner under NSF AWARD 1414736. All rights reserved. +// +// Redistribution and use in source and binary forms of CLI11, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// 3. Neither the name of the copyright holder nor the names of its contributors +// may be used to endorse or promote products derived from this software without +// specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR +// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +// ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// Standard combined includes: +{public_includes} + +{version_hpp} + +{macros_hpp} + +{validators_hpp_filesystem} + +namespace {namespace} {{ + +{string_tools_hpp} + +{error_hpp} + +{type_tools_hpp} + +{split_hpp} + +{config_fwd_hpp} + +{validators_hpp} + +{formatter_fwd_hpp} + +{option_hpp} + +{app_hpp} + +{config_hpp} + +{formatter_hpp} + +}} // namespace {namespace} diff --git a/packages/CLI11/CMakeLists.txt b/packages/CLI11/CMakeLists.txt index 574ca1ee824c68e165fa9c013afd39db66bd658a..ee0228ee5b20206c523c09325e1f8a3921e397b8 100644 --- a/packages/CLI11/CMakeLists.txt +++ b/packages/CLI11/CMakeLists.txt @@ -224,6 +224,8 @@ if(CLI11_INSTALL) NAMESPACE CLI11:: FILE CLI11Targets.cmake) + include(cmake/CLI11GeneratePkgConfig.cmake) + # Register in the user cmake package registry export(PACKAGE CLI11) endif() @@ -245,7 +247,10 @@ if(CLI11_SINGLE_FILE) add_custom_command(OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/include/CLI11.hpp" COMMAND Python::Interpreter "${CMAKE_CURRENT_SOURCE_DIR}/scripts/MakeSingleHeader.py" - "${CMAKE_CURRENT_BINARY_DIR}/include/CLI11.hpp" + ${CLI11_headers} + --main "${CMAKE_CURRENT_SOURCE_DIR}/CLI11.hpp.in" + --output "${CMAKE_CURRENT_BINARY_DIR}/include/CLI11.hpp" + --version "${CLI11_VERSION}" DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/include/CLI/CLI.hpp" ${CLI11_headers}) diff --git a/packages/CLI11/README.md b/packages/CLI11/README.md index d3552752b3135f5d23213173dc9a276b9cf540b8..8ffb6b25ddd02583763cb18dcd4902ca221a8b63 100644 --- a/packages/CLI11/README.md +++ b/packages/CLI11/README.md @@ -133,11 +133,25 @@ There are some other possible "features" that are intentionally not supported by ## Install -To use, there are two methods: +To use, there are several methods: -1. Copy `CLI11.hpp` from the [most recent release][github releases] into your include directory, and you are set. This is combined from the source files for every release. This includes the entire command parser library, but does not include separate utilities (like `Timer`, `AutoTimer`). The utilities are completely self contained and can be copied separately. -2. Use `CLI/*.hpp` files. You could check out the repository as a submodule, for example. You can use the `CLI11::CLI11` interface target when linking from `add_subdirectory`. - You can also configure and optionally install the project, and then use `find_package(CLI11 CONFIG)` to get the `CLI11::CLI11` target. You can also use [Conan.io][conan-link] or [Hunter][]. +1. All-in-one local header: Copy `CLI11.hpp` from the [most recent release][github releases] into your include directory, and you are set. This is combined from the source files for every release. This includes the entire command parser library, but does not include separate utilities (like `Timer`, `AutoTimer`). The utilities are completely self contained and can be copied separately. +2. All-in-one global header: Like above, but copying the file to a shared folder location like `/opt/CLI11`. Then, the C++ include path has to be extended to point at this folder. With CMake, use `include_directories(/opt/CLI11)` +3. Local headers and target: Use `CLI/*.hpp` files. You could check out the repository as a git submodule, for example. With CMake, you can use `add_subdirectory` and the `CLI11::CLI11` interface target when linking. If not using a submodule, you must ensure that the copied files are located inside the same tree directory than your current project, to prevent an error with CMake and `add_subdirectory`. +4. Global headers: Use `CLI/*.hpp` files stored in a shared folder. You could check out the git repository in a system-wide folder, for example `/opt/`. With CMake, you could add to the include path via: +```bash +if(NOT DEFINED CLI11_DIR) +set (CLI11_DIR "/opt/CLI11" CACHE STRING "CLI11 git repository") +endif() +include_directories(${CLI11_DIR}/include) +``` +And then in the source code (adding several headers might be needed to prevent linker errors): +```cpp +#include "CLI/App.hpp" +#include "CLI/Formatter.hpp" +#include "CLI/Config.hpp" +``` +5. Global headers and target: configuring and installing the project is required for linking CLI11 to your project in the same way as you would do with any other external library. With CMake, this step allows using `find_package(CLI11 CONFIG REQUIRED)` and then using the `CLI11::CLI11` target when linking. If `CMAKE_INSTALL_PREFIX` was changed during install to a specific folder like `/opt/CLI11`, then you have to pass `-DCLI11_DIR=/opt/CLI11` when building your current project. You can also use [Conan.io][conan-link] or [Hunter][]. (These are just conveniences to allow you to use your favorite method of managing packages; it's just header only so including the correct path and using C++11 is all you really need.) @@ -210,7 +224,7 @@ 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 π. Also supported are complex numbersπ§, wrapper typesπ§, and containers besides vectorπ§ of any other supported type. + variable_to_bind_to, // bool, char(see note)π§, 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, @@ -219,6 +233,8 @@ app.add_option_function<type>(option_name, app.add_complex(... // Special case: support for complex numbers β οΈ. Complex numbers are now fully supported in the add_option so this function is redundant. +// char as an option type is supported before 2.0 but in 2.0 it defaulted to allowing single non numerical characters in addition to the numeric values. + // π 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 @@ -322,6 +338,7 @@ Before parsing, you can set the following options: - `->ignore_case()`: Ignore the case on the command line (also works on subcommands, does not affect arguments). - `->ignore_underscore()`: Ignore any underscores in the options names (also works on subcommands, does not affect arguments). For example "option_one" will match with "optionone". This does not apply to short form options since they only have one character - `->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. +- `->allow_extra_args(true/false)`: π§ If set to true the option will take an unlimited number of arguments like a vector, if false it will limit the number of arguments to the size of the type used in the option. Default value depends on the nature of the type use, containers default to true, others default to false. - `->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()`,`->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). @@ -336,6 +353,7 @@ Before parsing, you can set the following options: - `->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 have a stream operator). +- `->option_text(string)`: Sets the text between the option name and description. 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. @@ -391,6 +409,7 @@ CLI11 has several Validators built-in that perform some common checks - `CLI::NonNegativeNumber`: Requires the number be greater or equal to 0 - `CLI::Number`: Requires the input be a number. - `CLI::ValidIPV4`: Requires that the option be a valid IPv4 string e.g. `'255.255.255.255'`, `'10.1.1.7'`. +- `CLI::TypeValidator<TYPE>`:π§ Requires that the option be convertible to the specified type e.g. `CLI::TypeValidator<unsigned int>()` would require that the input be convertible to an `unsigned int` regardless of the end conversion. These Validators can be used by simply passing the name into the `check` or `transform` methods on an option @@ -541,6 +560,7 @@ There are several options that are supported on the main app and subcommands and - `.disable()`: Specify that the subcommand is disabled, if given with a bool value it will enable or disable the subcommand or option group. - `.disabled_by_default()`: Specify that at the start of parsing the subcommand/option_group should be disabled. This is useful for allowing some Subcommands to trigger others. - `.enabled_by_default()`: Specify that at the start of each parse the subcommand/option_group should be enabled. This is useful for allowing some Subcommands to disable others. +- `.silent()`: π§ Specify that the subcommand is silent meaning that if used it won't show up in the subcommand list. This allows the use of subcommands as modifiers - `.validate_positionals()`: Specify that positionals should pass validation before matching. Validation is specified through `transform`, `check`, and `each` for an option. If an argument fails validation it is not an error and matching proceeds to the next available positional or extra arguments. - `.excludes(option_or_subcommand)`: If given an option pointer or pointer to another subcommand, these subcommands cannot be given together. In the case of options, if the option is passed the subcommand cannot be used and will generate an error. - `.needs(option_or_subcommand)`: π If given an option pointer or pointer to another subcommand, the subcommands will require the given option to have been given before this subcommand is validated which occurs prior to execution of any callback or after parsing is completed. @@ -697,7 +717,7 @@ app.set_config(option_name="", 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 +```toml # Comments are supported, using a # # The default section is [default], case insensitive @@ -732,6 +752,14 @@ Spaces before and after the name and argument are ignored. Multiple arguments ar 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 the app and option descriptions. See [Config files](https://cliutils.github.io/CLI11/book/chapters/config.html) for some additional details. +If it is desired that multiple configuration be allowed. Use + +```cpp +app.set_config("--config")->expected(1, X); +``` + +Where X is some positive number and will allow up to `X` configuration files to be specified by separate `--config` arguments. + ### Inheriting defaults Many of the defaults for subcommands and even options are inherited from their creators. The inherited default values for subcommands are `allow_extras`, `prefix_command`, `ignore_case`, `ignore_underscore`, `fallthrough`, `group`, `footer`,`immediate_callback` and maximum number of required subcommands. The help flag existence, name, and description are inherited, as well. @@ -928,6 +956,15 @@ This project was created by [Henry Schreiner](https://github.com/henryiii) and m <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> <td align="center"><a href="https://ondrejcertik.com/"><img src="https://avatars3.githubusercontent.com/u/20568?v=4" width="100px;" alt=""/><br /><sub><b>OndΕej ΔertΓk</b></sub></a><br /><a href="https://github.com/CLIUtils/CLI11/issues?q=author%3Acertik" title="Bug reports">π</a></td> <td align="center"><a href="http://sam.hocevar.net/"><img src="https://avatars2.githubusercontent.com/u/245089?v=4" width="100px;" alt=""/><br /><sub><b>Sam Hocevar</b></sub></a><br /><a href="https://github.com/CLIUtils/CLI11/commits?author=samhocevar" title="Code">π»</a></td> + <td align="center"><a href="http://www.ratml.org/"><img src="https://avatars0.githubusercontent.com/u/1845039?v=4" width="100px;" alt=""/><br /><sub><b>Ryan Curtin</b></sub></a><br /><a href="https://github.com/CLIUtils/CLI11/commits?author=rcurtin" title="Documentation">π</a></td> + <td align="center"><a href="https://mbh.sh"><img src="https://avatars3.githubusercontent.com/u/20403931?v=4" width="100px;" alt=""/><br /><sub><b>Michael Hall</b></sub></a><br /><a href="https://github.com/CLIUtils/CLI11/commits?author=mbhall88" title="Documentation">π</a></td> + <td align="center"><a href="https://github.com/ferdymercury"><img src="https://avatars3.githubusercontent.com/u/10653970?v=4" width="100px;" alt=""/><br /><sub><b>ferdymercury</b></sub></a><br /><a href="https://github.com/CLIUtils/CLI11/commits?author=ferdymercury" title="Documentation">π</a></td> + </tr> + <tr> + <td align="center"><a href="https://github.com/jakoblover"><img src="https://avatars0.githubusercontent.com/u/14160441?v=4" width="100px;" alt=""/><br /><sub><b>Jakob Lover</b></sub></a><br /><a href="https://github.com/CLIUtils/CLI11/commits?author=jakoblover" title="Code">π»</a></td> + <td align="center"><a href="https://github.com/ZeeD26"><img src="https://avatars2.githubusercontent.com/u/2487468?v=4" width="100px;" alt=""/><br /><sub><b>Dominik Steinberger</b></sub></a><br /><a href="https://github.com/CLIUtils/CLI11/commits?author=ZeeD26" title="Code">π»</a></td> + <td align="center"><a href="https://github.com/dfleury2"><img src="https://avatars1.githubusercontent.com/u/4805384?v=4" width="100px;" alt=""/><br /><sub><b>D. Fleury</b></sub></a><br /><a href="https://github.com/CLIUtils/CLI11/commits?author=dfleury2" title="Code">π»</a></td> + <td align="center"><a href="https://github.com/dbarowy"><img src="https://avatars3.githubusercontent.com/u/573142?v=4" width="100px;" alt=""/><br /><sub><b>Dan Barowy</b></sub></a><br /><a href="https://github.com/CLIUtils/CLI11/commits?author=dbarowy" title="Documentation">π</a></td> </tr> </table> diff --git a/packages/CLI11/book/README.md b/packages/CLI11/book/README.md index 8af5a7ecc12c8196796dd8ea4915287a9c2d43d3..79a061fb1c6514e8f46f2829bcb49185f7d3706e 100644 --- a/packages/CLI11/book/README.md +++ b/packages/CLI11/book/README.md @@ -39,7 +39,7 @@ gitbook:examples $ c++ -std=c++11 intro.cpp Much more complicated options are handled elegantly: ```cpp -std::string req_real_file; +std::string file; app.add_option("-f,--file", file, "Require an existing file") ->required() ->check(CLI::ExistingFile); diff --git a/packages/CLI11/book/chapters/config.md b/packages/CLI11/book/chapters/config.md index fd2dbd7d95c0c4d29c6d080051641cbc3b7a13a1..219c3d9dcde4cff4fbbcfe41a838446ddd0a7112 100644 --- a/packages/CLI11/book/chapters/config.md +++ b/packages/CLI11/book/chapters/config.md @@ -78,6 +78,16 @@ 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. +## Multiple configuration files + +If it is desired that multiple configuration be allowed. Use + +```cpp +app.set_config("--config")->expected(1, X); +``` + +Where X is some positive integer and will allow up to `X` configuration files to be specified by separate `--config` arguments. + ## Writing out a configure file 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 diff --git a/packages/CLI11/book/chapters/formatting.md b/packages/CLI11/book/chapters/formatting.md index 2af98fae7f31390025e89e58762ec9e87416b477..66dd228da893faa11fca64c3524b7e137bb3022a 100644 --- a/packages/CLI11/book/chapters/formatting.md +++ b/packages/CLI11/book/chapters/formatting.md @@ -30,7 +30,7 @@ You can further configure pieces of the code while still keeping most of the for ```cpp class MyFormatter : public CLI::Formatter { public: - std::string make_opts(const CLI::Option *) const override {return "";} + std::string make_option_opts(const CLI::Option *) const override {return "";} }; app.formatter(std::make_shared<MyFormatter>()); ``` diff --git a/packages/CLI11/book/chapters/options.md b/packages/CLI11/book/chapters/options.md index 149cd654bd93522ee0662cd4a5fa1a01f6b0402e..04bfd0bbd2f0a04e683d5f31a9df9f1bf2e79e00 100644 --- a/packages/CLI11/book/chapters/options.md +++ b/packages/CLI11/book/chapters/options.md @@ -22,6 +22,7 @@ You can use any C++ int-like type, not just `int`. CLI11 understands the followi |-------------|-------| | 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 | +| char | For a single char, single string values are accepted, otherwise longer strings are treated as integral values and a conversion is attempted | | 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 | @@ -128,7 +129,7 @@ When you call `add_option`, you get a pointer to the added option. You can use t | `->allow_extra_args()` | Allow extra argument values to be included when an option is passed. Enabled by default for vector options. | | `->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. | +| `->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)` | diff --git a/packages/CLI11/book/chapters/subcommands.md b/packages/CLI11/book/chapters/subcommands.md index ee2b551081c1cb9f0a758be45d2e237e32028951..230cca880040931ee41a590832f58908cd6b5697 100644 --- a/packages/CLI11/book/chapters/subcommands.md +++ b/packages/CLI11/book/chapters/subcommands.md @@ -112,3 +112,18 @@ Here, `--shared_flag` was set on the main app, and on the command line it "falls This is a special mode that allows "prefix" commands, where the parsing completely stops when it gets to an unknown option. Further unknown options are ignored, even if they could match. Git is the traditional example for prefix commands; if you run git with an unknown subcommand, like "`git thing`", it then calls another command called "`git-thing`" with the remaining options intact. +### Silent subcommands + +Subcommands can be modified by using the `silent` option. This will prevent the subcommand from showing up in the get_subcommands list. This can be used to make subcommands into modifiers. For example, a help subcommand might look like + +```c++ + auto sub1 = app.add_subcommand("help")->silent(); + sub1->parse_complete_callback([]() { throw CLI::CallForHelp(); }); +``` + +This would allow calling help such as: + +```bash +./app help +./app help sub1 +``` diff --git a/packages/CLI11/book/chapters/validators.md b/packages/CLI11/book/chapters/validators.md index 4d5ebf0fc53c633b49ed54090644de3919efc67c..6266a9cae4e8e7394370b8877fde2679a4f05548 100644 --- a/packages/CLI11/book/chapters/validators.md +++ b/packages/CLI11/book/chapters/validators.md @@ -1,9 +1,5 @@ # Validators -{% hint style='info' %} -Improved in CLI11 1.6 -{% endhint %} - There are two forms of validators: * `transform` validators: mutating @@ -15,9 +11,9 @@ the function should throw a `CLI::ValidationError` with the appropriate reason a However, `check` validators come in two forms; either a simple function with the const version of the above signature, `std::string(const std::string &)`, or a subclass of `struct CLI::Validator`. This -structure has two members that a user should set; one (`func`) is the function to add to the Option +structure has two members that a user should set; one (`func_`) is the function to add to the Option (exactly matching the above function signature, since it will become that function), and the other is -`tname`, and is the type name to set on the Option (unless empty, in which case the typename will be +`name_`, and is the type name to set on the Option (unless empty, in which case the typename will be left unchanged). Validators can be combined with `&` and `|`, and they have an `operator()` so that you can call them @@ -29,8 +25,8 @@ An example of a custom validator: ```cpp struct LowerCaseValidator : public Validator { LowerCaseValidator() { - tname = "LOWER"; - func = [](const std::string &str) { + name_ = "LOWER"; + func_ = [](const std::string &str) { if(CLI::detail::to_lower(str) != str) return std::string("String is not lower case"); else @@ -54,3 +50,15 @@ The built-in validators for CLI11 are: | `Range(min=0, max)` | Produce a range (factory). Min and max are inclusive. | +And, the protected members that you can set when you make your own are: + +| Type | Member | Description | +|------|--------|-------------| +| `std::function<std::string(std::string &)>` | `func_` | Core validation function - modifies input and returns "" if successful | +| `std::function<std::string()>` | `desc_function` | Optional description function (uses `description_` instead if not set) | +| `std::string` | `name_` | The name for search purposes | +| `int` (`-1`) | `application_index_` | The element this validator applies to (-1 for all) | +| `bool` (`true`) | `active_` | This can be disabled | +| `bool` (`false`) | `non_modifying_` | Specify that this is a Validator instead of a Transformer | + + diff --git a/packages/CLI11/cmake/CLI11.pc.in b/packages/CLI11/cmake/CLI11.pc.in new file mode 100644 index 0000000000000000000000000000000000000000..8d418739dee248662bfb1b20219136dae2fde995 --- /dev/null +++ b/packages/CLI11/cmake/CLI11.pc.in @@ -0,0 +1,9 @@ +prefix=@CMAKE_INSTALL_PREFIX@ +exec_prefix=${prefix} +includedir=${prefix}/include + +Name: CLI11 +Description: C++ command line parser +Version: @PROJECT_VERSION@ + +Cflags: -I${includedir} diff --git a/packages/CLI11/cmake/CLI11GeneratePkgConfig.cmake b/packages/CLI11/cmake/CLI11GeneratePkgConfig.cmake new file mode 100644 index 0000000000000000000000000000000000000000..667705d2a7a53d6b78ae6d17413cc77a90b8a3cd --- /dev/null +++ b/packages/CLI11/cmake/CLI11GeneratePkgConfig.cmake @@ -0,0 +1,6 @@ +configure_file("cmake/CLI11.pc.in" "CLI11.pc" @ONLY) + +install(FILES "${PROJECT_BINARY_DIR}/CLI11.pc" + DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig") + + diff --git a/packages/CLI11/include/CLI/App.hpp b/packages/CLI11/include/CLI/App.hpp index 8d47976e56c39aeaf88277ea27a27cf93c4df442..d3c513bd03de33141eafa41ea176fe742992e69d 100644 --- a/packages/CLI11/include/CLI/App.hpp +++ b/packages/CLI11/include/CLI/App.hpp @@ -6,6 +6,7 @@ #pragma once +// [CLI11:public_includes:set] #include <algorithm> #include <cstdint> #include <functional> @@ -18,6 +19,7 @@ #include <string> #include <utility> #include <vector> +// [CLI11:public_includes:end] // CLI Library includes #include "ConfigFwd.hpp" @@ -30,6 +32,7 @@ #include "TypeTools.hpp" namespace CLI { +// [CLI11:app_hpp:verbatim] #ifndef CLI11_PARSE #define CLI11_PARSE(app, argc, argv) \ @@ -218,11 +221,12 @@ class App { /// If set to true positional options are validated before assigning INHERITABLE bool validate_positionals_{false}; - /// A pointer to the parent if this is a subcommand - App *parent_{nullptr}; + /// indicator that the subcommand is silent and won't show up in subcommands list + /// This is potentially useful as a modifier subcommand + bool silent_{false}; /// Counts the number of times this command/subcommand was parsed - std::size_t parsed_{0}; + std::uint32_t parsed_{0U}; /// Minimum required subcommands (not inheritable!) std::size_t require_subcommand_min_{0}; @@ -236,6 +240,9 @@ class App { /// Max number of options allowed. 0 is unlimited (not inheritable) std::size_t require_option_max_{0}; + /// A pointer to the parent if this is a subcommand + App *parent_{nullptr}; + /// The group membership INHERITABLE std::string group_{"Subcommands"}; @@ -396,6 +403,12 @@ class App { return this; } + /// silence the subcommand from showing up in the processed list + App *silent(bool silence = true) { + silent_ = silence; + return this; + } + /// Set the subcommand to be disabled by default, so on clear(), at the start of each parse it is disabled App *disabled_by_default(bool disable = true) { if(disable) { @@ -789,7 +802,8 @@ class App { /// Add option for flag with integer result - defaults to allowing multiple passings, but can be forced to one /// if `multi_option_policy(CLI::MultiOptionPolicy::Throw)` is used. template <typename T, - enable_if_t<std::is_integral<T>::value && !is_bool<T>::value, detail::enabler> = detail::dummy> + enable_if_t<std::is_constructible<T, std::int64_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 std::string flag_description = "") { @@ -810,7 +824,7 @@ class App { /// that can be converted from a string template <typename T, 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<T, std::int64_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, @@ -824,9 +838,9 @@ class App { } /// Vector version to capture multiple flags. - template < - typename T, - enable_if_t<!std::is_assignable<std::function<void(std::int64_t)>, T>::value, detail::enabler> = detail::dummy> + template <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::string flag_description = "") { @@ -1766,6 +1780,9 @@ class App { /// Get the status of disabled bool get_disabled() const { return disabled_; } + /// Get the status of silence + bool get_silent() const { return silent_; } + /// Get the status of disabled bool get_immediate_callback() const { return immediate_callback_; } @@ -1820,7 +1837,21 @@ class App { } /// Get a display name for an app - std::string get_display_name() const { return (!name_.empty()) ? name_ : "[Option Group: " + get_group() + "]"; } + std::string get_display_name(bool with_aliases = false) const { + if(name_.empty()) { + return std::string("[Option Group: ") + get_group() + "]"; + } + if(aliases_.empty() || !with_aliases || aliases_.empty()) { + return name_; + } + std::string dispname = name_; + for(const auto &lalias : aliases_) { + dispname.push_back(','); + dispname.push_back(' '); + dispname.append(lalias); + } + return dispname; + } /// Check the name, case insensitive and underscore insensitive if set bool check_name(std::string name_to_check) const { @@ -2053,29 +2084,31 @@ class App { void _process_config_file() { if(config_ptr_ != nullptr) { bool config_required = config_ptr_->get_required(); - bool file_given = config_ptr_->count() > 0; - auto config_file = config_ptr_->as<std::string>(); - if(config_file.empty()) { + auto file_given = config_ptr_->count() > 0; + auto config_files = config_ptr_->as<std::vector<std::string>>(); + if(config_files.empty() || config_files.front().empty()) { if(config_required) { throw FileError::Missing("no specified config file"); } return; } - - auto path_result = detail::check_path(config_file.c_str()); - if(path_result == detail::path_type::file) { - try { - std::vector<ConfigItem> values = config_formatter_->from_file(config_file); - _parse_config(values); - if(!file_given) { - config_ptr_->add_result(config_file); + for(auto rit = config_files.rbegin(); rit != config_files.rend(); ++rit) { + const auto &config_file = *rit; + auto path_result = detail::check_path(config_file.c_str()); + if(path_result == detail::path_type::file) { + try { + std::vector<ConfigItem> values = config_formatter_->from_file(config_file); + _parse_config(values); + if(!file_given) { + config_ptr_->add_result(config_file); + } + } catch(const FileError &) { + if(config_required || file_given) + throw; } - } catch(const FileError &) { - if(config_required || file_given) - throw; + } else if(config_required || file_given) { + throw FileError::Missing(config_file); } - } else if(config_required || file_given) { - throw FileError::Missing(config_file); } } } @@ -2250,10 +2283,13 @@ class App { } if(require_option_min_ > used_options || (require_option_max_ > 0 && require_option_max_ < used_options)) { - auto option_list = detail::join(options_, [](const Option_p &ptr) { return ptr->get_name(false, true); }); - if(option_list.compare(0, 10, "-h,--help,") == 0) { - option_list.erase(0, 10); - } + auto option_list = detail::join(options_, [this](const Option_p &ptr) { + if(ptr.get() == help_ptr_ || ptr.get() == help_all_ptr_) { + return std::string{}; + } + return ptr->get_name(false, true); + }); + auto subc_list = get_subcommands([](App *app) { return ((app->get_name().empty()) && (!app->disabled_)); }); if(!subc_list.empty()) { option_list += "," + detail::join(subc_list, [](const App *app) { return app->get_display_name(); }); @@ -2388,8 +2424,8 @@ class App { /// /// If this has more than one dot.separated.name, go into the subcommand matching it /// Returns true if it managed to find the option, if false you'll need to remove the arg manually. - void _parse_config(std::vector<ConfigItem> &args) { - for(ConfigItem item : args) { + void _parse_config(const std::vector<ConfigItem> &args) { + for(const ConfigItem &item : args) { if(!_parse_single_config(item) && allow_config_extras_ == config_extras_mode::error) throw ConfigError::Extras(item.fullname()); } @@ -2667,12 +2703,16 @@ class App { auto com = _find_subcommand(args.back(), true, true); if(com != nullptr) { args.pop_back(); - parsed_subcommands_.push_back(com); + if(!com->silent_) { + parsed_subcommands_.push_back(com); + } com->_parse(args); auto parent_app = com->parent_; while(parent_app != this) { parent_app->_trigger_pre_parse(args.size()); - parent_app->parsed_subcommands_.push_back(com); + if(!com->silent_) { + parent_app->parsed_subcommands_.push_back(com); + } parent_app = parent_app->parent_; } return true; @@ -2761,7 +2801,12 @@ class App { } int min_num = (std::min)(op->get_type_size_min(), op->get_items_expected_min()); int max_num = op->get_items_expected_max(); - + // check container like options to limit the argument size to a single type if the allow_extra_flags argument is + // set. 16 is somewhat arbitrary (needs to be at least 4) + if(max_num >= detail::expected_max_vector_size / 16 && !op->get_allow_extra_args()) { + auto tmax = op->get_type_size_max(); + max_num = detail::checked_multiply(tmax, op->get_expected_min()) ? tmax : detail::expected_max_vector_size; + } // 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 @@ -3192,4 +3237,5 @@ struct AppFriend { }; } // namespace detail +// [CLI11:app_hpp:end] } // namespace CLI diff --git a/packages/CLI11/include/CLI/Config.hpp b/packages/CLI11/include/CLI/Config.hpp index 5a982236cc16aa5bf2cd48c45b42748591cdb334..725872dcaf786b5787e8826f78112292f3e1ad5f 100644 --- a/packages/CLI11/include/CLI/Config.hpp +++ b/packages/CLI11/include/CLI/Config.hpp @@ -6,19 +6,21 @@ #pragma once +// [CLI11:public_includes:set] #include <algorithm> #include <fstream> #include <iostream> #include <string> #include <utility> #include <vector> +// [CLI11:public_includes:set] #include "App.hpp" #include "ConfigFwd.hpp" #include "StringTools.hpp" namespace CLI { - +// [CLI11:config_hpp:verbatim] namespace detail { inline std::string convert_arg_for_ini(const std::string &arg) { @@ -211,7 +213,11 @@ inline std::vector<ConfigItem> ConfigBase::from_config(std::istream &input) cons if(pos != std::string::npos) { name = detail::trim_copy(line.substr(0, pos)); std::string item = detail::trim_copy(line.substr(pos + 1)); - if(item.size() > 1 && item.front() == aStart && item.back() == aEnd) { + if(item.size() > 1 && item.front() == aStart) { + for(std::string multiline; item.back() != aEnd && std::getline(input, multiline);) { + detail::trim(multiline); + item += multiline; + } items_buffer = detail::split_up(item.substr(1, item.length() - 2), aSep); } else if((isDefaultArray || isINIArray) && item.find_first_of(aSep) != std::string::npos) { items_buffer = detail::split_up(item, aSep); @@ -344,4 +350,5 @@ ConfigBase::to_config(const App *app, bool default_also, bool write_description, return out.str(); } +// [CLI11:config_hpp:end] } // namespace CLI diff --git a/packages/CLI11/include/CLI/ConfigFwd.hpp b/packages/CLI11/include/CLI/ConfigFwd.hpp index 2546d1352022147614cdc276acd1cef18ab9fb65..2d9f9a39c7d0609ee524bb3d881a6c836f8c0ff9 100644 --- a/packages/CLI11/include/CLI/ConfigFwd.hpp +++ b/packages/CLI11/include/CLI/ConfigFwd.hpp @@ -6,16 +6,19 @@ #pragma once +// [CLI11:public_includes:set] #include <algorithm> #include <fstream> #include <iostream> #include <string> #include <vector> +// [CLI11:public_includes:end] #include "Error.hpp" #include "StringTools.hpp" namespace CLI { +// [CLI11:config_fwd_hpp:verbatim] class App; @@ -128,4 +131,5 @@ class ConfigINI : public ConfigTOML { valueDelimiter = '='; } }; +// [CLI11:config_fwd_hpp:end] } // namespace CLI diff --git a/packages/CLI11/include/CLI/Error.hpp b/packages/CLI11/include/CLI/Error.hpp index 11feaedbcdf2d6c13faf8519abd588ac1cde31f0..e6a8325730deb119e7c37989cdf469e78d951f57 100644 --- a/packages/CLI11/include/CLI/Error.hpp +++ b/packages/CLI11/include/CLI/Error.hpp @@ -6,16 +6,19 @@ #pragma once +// [CLI11:public_includes:set] #include <exception> #include <stdexcept> #include <string> #include <utility> #include <vector> +// [CLI11:public_includes:end] // CLI library includes #include "StringTools.hpp" namespace CLI { +// [CLI11:error_hpp:verbatim] // Use one of these on all error classes. // These are temporary and are undef'd at the end of this file. @@ -176,7 +179,7 @@ class CallForVersion : public Success { : CallForVersion("This should be caught in your main function, see examples", ExitCodes::Success) {} }; -/// Does not output a diagnostic in CLI11_PARSE, but allows to return from main() with a specific error code. +/// Does not output a diagnostic in CLI11_PARSE, but allows main() to return with a specific error code. class RuntimeError : public ParseError { CLI11_ERROR_DEF(ParseError, RuntimeError) explicit RuntimeError(int exit_code = 1) : RuntimeError("Runtime error", exit_code) {} @@ -344,4 +347,5 @@ class OptionNotFound : public Error { /// @} +// [CLI11:error_hpp:end] } // namespace CLI diff --git a/packages/CLI11/include/CLI/Formatter.hpp b/packages/CLI11/include/CLI/Formatter.hpp index 8a8ba3526e15da81b08c9a3c722c07797f9214d1..cb9e92a57ab5a9bc68a1b156752a9549c28af9cc 100644 --- a/packages/CLI11/include/CLI/Formatter.hpp +++ b/packages/CLI11/include/CLI/Formatter.hpp @@ -6,14 +6,17 @@ #pragma once +// [CLI11:public_includes:set] #include <algorithm> #include <string> #include <vector> +// [CLI11:public_includes:end] #include "App.hpp" #include "FormatterFwd.hpp" namespace CLI { +// [CLI11:formatter_hpp:verbatim] inline std::string Formatter::make_group(std::string group, bool is_positional, std::vector<const Option *> opts) const { @@ -205,15 +208,18 @@ inline std::string Formatter::make_subcommands(const App *app, AppFormatMode mod inline std::string Formatter::make_subcommand(const App *sub) const { std::stringstream out; - detail::format_help(out, sub->get_name(), sub->get_description(), column_width_); + detail::format_help(out, sub->get_display_name(true), sub->get_description(), column_width_); return out.str(); } inline std::string Formatter::make_expanded(const App *sub) const { std::stringstream out; - out << sub->get_display_name() << "\n"; + out << sub->get_display_name(true) << "\n"; out << make_description(sub); + if(sub->get_name().empty() && !sub->get_aliases().empty()) { + detail::format_aliases(out, sub->get_aliases(), column_width_ + 2); + } out << make_positionals(sub); out << make_groups(sub, AppFormatMode::Sub); out << make_subcommands(sub, AppFormatMode::Sub); @@ -236,30 +242,34 @@ inline std::string Formatter::make_option_name(const Option *opt, bool is_positi inline std::string Formatter::make_option_opts(const Option *opt) const { std::stringstream out; - if(opt->get_type_size() != 0) { - if(!opt->get_type_name().empty()) - out << " " << get_label(opt->get_type_name()); - if(!opt->get_default_str().empty()) - out << "=" << opt->get_default_str(); - if(opt->get_expected_max() == detail::expected_max_vector_size) - out << " ..."; - else if(opt->get_expected_min() > 1) - out << " x " << opt->get_expected(); - - if(opt->get_required()) - out << " " << get_label("REQUIRED"); - } - if(!opt->get_envname().empty()) - out << " (" << get_label("Env") << ":" << opt->get_envname() << ")"; - if(!opt->get_needs().empty()) { - out << " " << get_label("Needs") << ":"; - for(const Option *op : opt->get_needs()) - out << " " << op->get_name(); - } - if(!opt->get_excludes().empty()) { - out << " " << get_label("Excludes") << ":"; - for(const Option *op : opt->get_excludes()) - out << " " << op->get_name(); + if(!opt->get_option_text().empty()) { + out << " " << opt->get_option_text(); + } else { + if(opt->get_type_size() != 0) { + if(!opt->get_type_name().empty()) + out << " " << get_label(opt->get_type_name()); + if(!opt->get_default_str().empty()) + out << "=" << opt->get_default_str(); + if(opt->get_expected_max() == detail::expected_max_vector_size) + out << " ..."; + else if(opt->get_expected_min() > 1) + out << " x " << opt->get_expected(); + + if(opt->get_required()) + out << " " << get_label("REQUIRED"); + } + if(!opt->get_envname().empty()) + out << " (" << get_label("Env") << ":" << opt->get_envname() << ")"; + if(!opt->get_needs().empty()) { + out << " " << get_label("Needs") << ":"; + for(const Option *op : opt->get_needs()) + out << " " << op->get_name(); + } + if(!opt->get_excludes().empty()) { + out << " " << get_label("Excludes") << ":"; + for(const Option *op : opt->get_excludes()) + out << " " << op->get_name(); + } } return out.str(); } @@ -278,4 +288,5 @@ inline std::string Formatter::make_option_usage(const Option *opt) const { return opt->get_required() ? out.str() : "[" + out.str() + "]"; } +// [CLI11:formatter_hpp:end] } // namespace CLI diff --git a/packages/CLI11/include/CLI/FormatterFwd.hpp b/packages/CLI11/include/CLI/FormatterFwd.hpp index 362219e24e817aa42b682d06a0204ac4ca87b26a..6908d464447ae76081eb6240b7ac9bf86eadfcd2 100644 --- a/packages/CLI11/include/CLI/FormatterFwd.hpp +++ b/packages/CLI11/include/CLI/FormatterFwd.hpp @@ -6,14 +6,17 @@ #pragma once +// [CLI11:public_includes:set] #include <map> #include <string> #include <utility> #include <vector> +// [CLI11:public_includes:end] #include "StringTools.hpp" namespace CLI { +// [CLI11:formatter_fwd_hpp:verbatim] class Option; class App; @@ -177,4 +180,5 @@ class Formatter : public FormatterBase { ///@} }; +// [CLI11:formatter_fwd_hpp:end] } // namespace CLI diff --git a/packages/CLI11/include/CLI/Macros.hpp b/packages/CLI11/include/CLI/Macros.hpp index 44e7098a0f6e990d479a337d35bdc07085403041..778fec7dcbdace22952977e0feb851e5be8cf9c5 100644 --- a/packages/CLI11/include/CLI/Macros.hpp +++ b/packages/CLI11/include/CLI/Macros.hpp @@ -6,7 +6,7 @@ #pragma once -// [CLI11:verbatim] +// [CLI11:macros_hpp:verbatim] // The following version macro is very similar to the one in PyBind11 #if !(defined(_MSC_VER) && __cplusplus == 199711L) && !defined(__INTEL_COMPILER) @@ -41,4 +41,4 @@ #define CLI11_DEPRECATED(reason) __attribute__((deprecated(reason))) #endif -// [CLI11:verbatim] +// [CLI11:macros_hpp:end] diff --git a/packages/CLI11/include/CLI/Option.hpp b/packages/CLI11/include/CLI/Option.hpp index e980002847881b5c7eabe3f8b7573c25351afeec..f09f61f9730f105383552b92a245df786a3c48c6 100644 --- a/packages/CLI11/include/CLI/Option.hpp +++ b/packages/CLI11/include/CLI/Option.hpp @@ -6,6 +6,7 @@ #pragma once +// [CLI11:public_includes:set] #include <algorithm> #include <functional> #include <memory> @@ -14,6 +15,7 @@ #include <tuple> #include <utility> #include <vector> +// [CLI11:public_includes:end] #include "Error.hpp" #include "Macros.hpp" @@ -22,6 +24,7 @@ #include "Validators.hpp" namespace CLI { +// [CLI11:option_hpp:verbatim] using results_t = std::vector<std::string>; /// callback function definition @@ -264,6 +267,9 @@ class Option : public OptionBase<Option> { /// A human readable default value, either manually set, captured, or captured by default std::string default_str_{}; + /// If given, replace the text that describes the option type and usage in the help text + std::string option_text_{}; + /// A human readable type value, set when App creates this /// /// This is a lambda function so "types" can be dynamic, such as when a set prints its contents. @@ -736,6 +742,13 @@ class Option : public OptionBase<Option> { return this; } + Option *option_text(std::string text) { + option_text_ = std::move(text); + return this; + } + + const std::string &get_option_text() const { return option_text_; } + ///@} /// @name Help tools ///@{ @@ -1306,4 +1319,5 @@ class Option : public OptionBase<Option> { } }; // namespace CLI +// [CLI11:option_hpp:end] } // namespace CLI diff --git a/packages/CLI11/include/CLI/Split.hpp b/packages/CLI11/include/CLI/Split.hpp index 6306404bcf86f8093f36d34ec5898063de3c8518..36c953de8471e2b4f414c3c877ff087b494638c2 100644 --- a/packages/CLI11/include/CLI/Split.hpp +++ b/packages/CLI11/include/CLI/Split.hpp @@ -6,15 +6,19 @@ #pragma once +// [CLI11:public_includes:set] #include <string> #include <tuple> #include <utility> #include <vector> +// [CLI11:public_includes:end] #include "Error.hpp" #include "StringTools.hpp" namespace CLI { +// [CLI11:split_hpp:verbatim] + namespace detail { // Returns false if not a short option. Otherwise, sets opt name and rest and returns true @@ -135,4 +139,5 @@ get_names(const std::vector<std::string> &input) { } } // namespace detail +// [CLI11:split_hpp:end] } // namespace CLI diff --git a/packages/CLI11/include/CLI/StringTools.hpp b/packages/CLI11/include/CLI/StringTools.hpp index 70f202aa71901720370e7daf4c50115c49e6be48..090445e5b1236ce92f3b009bd1d55cde9a5d0ebd 100644 --- a/packages/CLI11/include/CLI/StringTools.hpp +++ b/packages/CLI11/include/CLI/StringTools.hpp @@ -6,6 +6,7 @@ #pragma once +// [CLI11:public_includes:set] #include <algorithm> #include <iomanip> #include <locale> @@ -14,9 +15,12 @@ #include <string> #include <type_traits> #include <vector> +// [CLI11:public_includes:end] namespace CLI { +// [CLI11:string_tools_hpp:verbatim] + /// Include the items in this namespace to get free conversion of enums to/from streams. /// (This is available inside CLI as well, so CLI11 will use this without a using statement). namespace enums { @@ -76,10 +80,14 @@ std::string join(const T &v, Callable func, std::string delim = ",") { std::ostringstream s; auto beg = std::begin(v); auto end = std::end(v); - if(beg != end) - s << func(*beg++); + auto loc = s.tellp(); while(beg != end) { - s << delim << func(*beg++); + auto nloc = s.tellp(); + if(nloc > loc) { + s << delim; + loc = nloc; + } + s << func(*beg++); } return s.str(); } @@ -155,7 +163,7 @@ inline std::string trim_copy(const std::string &str, const std::string &filter) return trim(s, filter); } /// Print a two part "help" string -inline std::ostream &format_help(std::ostream &out, std::string name, std::string description, std::size_t wid) { +inline std::ostream &format_help(std::ostream &out, std::string name, const std::string &description, std::size_t wid) { name = " " + name; out << std::setw(static_cast<int>(wid)) << std::left << name; if(!description.empty()) { @@ -172,6 +180,24 @@ inline std::ostream &format_help(std::ostream &out, std::string name, std::strin return out; } +/// Print subcommand aliases +inline std::ostream &format_aliases(std::ostream &out, const std::vector<std::string> &aliases, std::size_t wid) { + if(!aliases.empty()) { + out << std::setw(static_cast<int>(wid)) << " aliases: "; + bool front = true; + for(const auto &alias : aliases) { + if(!front) { + out << ", "; + } else { + front = false; + } + out << alias; + } + out << "\n"; + } + return out; +} + /// Verify the first character of an option template <typename T> bool valid_first_char(T c) { return std::isalnum(c, std::locale()) || c == '_' || c == '?' || c == '@'; @@ -310,7 +336,12 @@ inline std::vector<std::string> split_up(std::string str, char delimiter = '\0') } if(end != std::string::npos) { output.push_back(str.substr(1, end - 1)); - str = str.substr(end + 1); + if(end + 2 < str.size()) { + str = str.substr(end + 2); + } else { + str.clear(); + } + } else { output.push_back(str.substr(1)); str = ""; @@ -382,4 +413,6 @@ inline std::string &add_quotes_if_needed(std::string &str) { } // namespace detail +// [CLI11:string_tools_hpp:end] + } // namespace CLI diff --git a/packages/CLI11/include/CLI/TypeTools.hpp b/packages/CLI11/include/CLI/TypeTools.hpp index 4b85f05c51aa8c5b93dba3257e66629403fa6f70..be0e89ab6f55a304e7171e26010fcb1b0b4dd983 100644 --- a/packages/CLI11/include/CLI/TypeTools.hpp +++ b/packages/CLI11/include/CLI/TypeTools.hpp @@ -6,7 +6,7 @@ #pragma once -#include "StringTools.hpp" +// [CLI11:public_includes:set] #include <cstdint> #include <exception> #include <memory> @@ -14,8 +14,12 @@ #include <type_traits> #include <utility> #include <vector> +// [CLI11:public_includes:end] + +#include "StringTools.hpp" namespace CLI { +// [CLI11:type_tools_hpp:verbatim] // Type tools @@ -505,6 +509,7 @@ struct expected_count<T, typename std::enable_if<!is_mutable_container<T>::value // Enumeration of the different supported categorizations of objects enum class object_category : int { + char_value = 1, integral_value = 2, unsigned_integral = 4, enumeration = 6, @@ -525,27 +530,36 @@ enum class object_category : int { }; +/// Set of overloads to classify an object according to type + /// some type that is not otherwise recognized template <typename T, typename Enable = void> struct classify_object { static constexpr object_category value{object_category::other}; }; -/// Set of overloads to classify an object according to type +/// Signed integers template <typename T> -struct classify_object<T, - typename std::enable_if<std::is_integral<T>::value && std::is_signed<T>::value && - !is_bool<T>::value && !std::is_enum<T>::value>::type> { +struct classify_object< + T, + typename std::enable_if<std::is_integral<T>::value && !std::is_same<T, char>::value && std::is_signed<T>::value && + !is_bool<T>::value && !std::is_enum<T>::value>::type> { static constexpr object_category value{object_category::integral_value}; }; /// Unsigned integers template <typename T> -struct classify_object< - T, - typename std::enable_if<std::is_integral<T>::value && std::is_unsigned<T>::value && !is_bool<T>::value>::type> { +struct classify_object<T, + typename std::enable_if<std::is_integral<T>::value && std::is_unsigned<T>::value && + !std::is_same<T, char>::value && !is_bool<T>::value>::type> { static constexpr object_category value{object_category::unsigned_integral}; }; +/// single character values +template <typename T> +struct classify_object<T, typename std::enable_if<std::is_same<T, char>::value && !std::is_enum<T>::value>::type> { + static constexpr object_category value{object_category::char_value}; +}; + /// Boolean values template <typename T> struct classify_object<T, typename std::enable_if<is_bool<T>::value>::type> { static constexpr object_category value{object_category::boolean_value}; @@ -657,6 +671,12 @@ template <typename T> struct classify_object<T, typename std::enable_if<is_mutab /// http://stackoverflow.com/questions/1055452/c-get-name-of-type-in-template /// But this is cleaner and works better in this case +template <typename T, + enable_if_t<classify_object<T>::value == object_category::char_value, detail::enabler> = detail::dummy> +constexpr const char *type_name() { + return "CHAR"; +} + template <typename T, enable_if_t<classify_object<T>::value == object_category::integral_value || classify_object<T>::value == object_category::integer_constructible, @@ -767,6 +787,30 @@ inline std::string type_name() { // Lexical cast +/// Convert to an unsigned integral +template <typename T, enable_if_t<std::is_unsigned<T>::value, detail::enabler> = detail::dummy> +bool integral_conversion(const std::string &input, T &output) noexcept { + if(input.empty()) { + return false; + } + char *val = nullptr; + std::uint64_t output_ll = std::strtoull(input.c_str(), &val, 0); + output = static_cast<T>(output_ll); + return val == (input.c_str() + input.size()) && static_cast<std::uint64_t>(output) == output_ll; +} + +/// Convert to a signed integral +template <typename T, enable_if_t<std::is_signed<T>::value, detail::enabler> = detail::dummy> +bool integral_conversion(const std::string &input, T &output) noexcept { + if(input.empty()) { + return false; + } + char *val = nullptr; + std::int64_t output_ll = std::strtoll(input.c_str(), &val, 0); + output = static_cast<T>(output_ll); + return val == (input.c_str() + input.size()) && static_cast<std::int64_t>(output) == output_ll; +} + /// Convert a flag into an integer value typically binary flags inline std::int64_t to_flag_value(std::string val) { static const std::string trueString("true"); @@ -810,39 +854,24 @@ inline std::int64_t to_flag_value(std::string val) { return ret; } -/// Signed integers +/// Integer conversion template <typename T, - enable_if_t<classify_object<T>::value == object_category::integral_value, detail::enabler> = detail::dummy> + enable_if_t<classify_object<T>::value == object_category::integral_value || + classify_object<T>::value == object_category::unsigned_integral, + detail::enabler> = detail::dummy> bool lexical_cast(const std::string &input, T &output) { - try { - std::size_t n = 0; - std::int64_t output_ll = std::stoll(input, &n, 0); - output = static_cast<T>(output_ll); - return n == input.size() && static_cast<std::int64_t>(output) == output_ll; - } catch(const std::invalid_argument &) { - return false; - } catch(const std::out_of_range &) { - return false; - } + return integral_conversion(input, output); } -/// Unsigned integers +/// char values template <typename T, - enable_if_t<classify_object<T>::value == object_category::unsigned_integral, detail::enabler> = detail::dummy> + enable_if_t<classify_object<T>::value == object_category::char_value, 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. - - try { - std::size_t n = 0; - std::uint64_t output_ll = std::stoull(input, &n, 0); - output = static_cast<T>(output_ll); - return n == input.size() && static_cast<std::uint64_t>(output) == output_ll; - } catch(const std::invalid_argument &) { - return false; - } catch(const std::out_of_range &) { - return false; + if(input.size() == 1) { + output = static_cast<T>(input[0]); + return true; } + return integral_conversion(input, output); } /// Boolean values @@ -867,15 +896,13 @@ bool lexical_cast(const std::string &input, T &output) { template <typename T, enable_if_t<classify_object<T>::value == object_category::floating_point, detail::enabler> = detail::dummy> bool lexical_cast(const std::string &input, T &output) { - try { - std::size_t n = 0; - output = static_cast<T>(std::stold(input, &n)); - return n == input.size(); - } catch(const std::invalid_argument &) { - return false; - } catch(const std::out_of_range &) { + if(input.empty()) { return false; } + char *val = nullptr; + auto output_ld = std::strtold(input.c_str(), &val); + output = static_cast<T>(output_ld); + return val == (input.c_str() + input.size()); } /// complex @@ -932,8 +959,7 @@ template <typename T, enable_if_t<classify_object<T>::value == object_category::enumeration, detail::enabler> = detail::dummy> bool lexical_cast(const std::string &input, T &output) { typename std::underlying_type<T>::type val; - bool retval = detail::lexical_cast(input, val); - if(!retval) { + if(!integral_conversion(input, val)) { return false; } output = static_cast<T>(val); @@ -942,7 +968,22 @@ bool lexical_cast(const std::string &input, T &output) { /// wrapper types template <typename T, - enable_if_t<classify_object<T>::value == object_category::wrapper_value, detail::enabler> = detail::dummy> + enable_if_t<classify_object<T>::value == object_category::wrapper_value && + std::is_assignable<T &, typename T::value_type>::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 = val; + return true; + } + return from_stream(input, output); +} + +template <typename T, + enable_if_t<classify_object<T>::value == object_category::wrapper_value && + !std::is_assignable<T &, typename T::value_type>::value && std::is_assignable<T &, T>::value, + detail::enabler> = detail::dummy> bool lexical_cast(const std::string &input, T &output) { typename T::value_type val; if(lexical_cast(input, val)) { @@ -958,7 +999,7 @@ template < enable_if_t<classify_object<T>::value == object_category::number_constructible, detail::enabler> = detail::dummy> bool lexical_cast(const std::string &input, T &output) { int val; - if(lexical_cast(input, val)) { + if(integral_conversion(input, val)) { output = T(val); return true; } else { @@ -977,7 +1018,7 @@ template < enable_if_t<classify_object<T>::value == object_category::integer_constructible, detail::enabler> = detail::dummy> bool lexical_cast(const std::string &input, T &output) { int val; - if(lexical_cast(input, val)) { + if(integral_conversion(input, val)) { output = T(val); return true; } @@ -997,8 +1038,36 @@ bool lexical_cast(const std::string &input, T &output) { return from_stream(input, output); } +/// Non-string convertible from an int +template <typename T, + enable_if_t<classify_object<T>::value == object_category::other && std::is_assignable<T &, int>::value, + detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + int val; + if(integral_conversion(input, val)) { +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4800) +#endif + // with Atomic<XX> this could produce a warning due to the conversion but if atomic gets here it is an old style + // so will most likely still work + output = val; +#ifdef _MSC_VER +#pragma warning(pop) +#endif + return true; + } + // LCOV_EXCL_START + // This version of cast is only used for odd cases in an older compilers the fail over + // from_stream is tested elsewhere an not relevent for coverage here + return from_stream(input, output); + // LCOV_EXCL_STOP +} + /// Non-string parsable by a stream -template <typename T, enable_if_t<classify_object<T>::value == object_category::other, detail::enabler> = detail::dummy> +template <typename T, + enable_if_t<classify_object<T>::value == object_category::other && !std::is_assignable<T &, int>::value, + detail::enabler> = detail::dummy> bool lexical_cast(const std::string &input, T &output) { static_assert(is_istreamable<T>::value, "option object type must have a lexical cast overload or streaming input operator(>>) defined, if it " @@ -1021,7 +1090,7 @@ bool lexical_assign(const std::string &input, AssignTo &output) { /// Assign a value through lexical cast operations template <typename AssignTo, typename ConvertTo, - enable_if_t<std::is_same<AssignTo, ConvertTo>::value && + enable_if_t<std::is_same<AssignTo, ConvertTo>::value && std::is_assignable<AssignTo &, AssignTo>::value && classify_object<AssignTo>::value != object_category::string_assignable && classify_object<AssignTo>::value != object_category::string_constructible, detail::enabler> = detail::dummy> @@ -1030,9 +1099,46 @@ bool lexical_assign(const std::string &input, AssignTo &output) { output = AssignTo{}; return true; } + return lexical_cast(input, output); } +/// Assign a value through lexical cast operations +template <typename AssignTo, + typename ConvertTo, + enable_if_t<std::is_same<AssignTo, ConvertTo>::value && !std::is_assignable<AssignTo &, AssignTo>::value && + classify_object<AssignTo>::value == object_category::wrapper_value, + detail::enabler> = detail::dummy> +bool lexical_assign(const std::string &input, AssignTo &output) { + if(input.empty()) { + typename AssignTo::value_type emptyVal{}; + output = emptyVal; + return true; + } + return lexical_cast(input, output); +} + +/// Assign a value through lexical cast operations for int compatible values +/// mainly for atomic operations on some compilers +template <typename AssignTo, + typename ConvertTo, + enable_if_t<std::is_same<AssignTo, ConvertTo>::value && !std::is_assignable<AssignTo &, AssignTo>::value && + classify_object<AssignTo>::value != object_category::wrapper_value && + std::is_assignable<AssignTo &, int>::value, + detail::enabler> = detail::dummy> +bool lexical_assign(const std::string &input, AssignTo &output) { + if(input.empty()) { + output = 0; + return true; + } + int val; + if(lexical_cast(input, val)) { + output = val; + return true; + } + return false; +} + /// Assign a value converted from a string in lexical cast to the output value directly template <typename AssignTo, typename ConvertTo, @@ -1344,10 +1450,11 @@ bool lexical_conversion(const std::vector<std ::string> &strings, AssignTo &outp } /// conversion for wrapper types -template < - typename AssignTo, - class ConvertTo, - enable_if_t<classify_object<ConvertTo>::value == object_category::wrapper_value, detail::enabler> = detail::dummy> +template <typename AssignTo, + class ConvertTo, + enable_if_t<classify_object<ConvertTo>::value == object_category::wrapper_value && + std::is_assignable<ConvertTo &, ConvertTo>::value, + detail::enabler> = detail::dummy> bool lexical_conversion(const std::vector<std::string> &strings, AssignTo &output) { if(strings.empty() || strings.front().empty()) { output = ConvertTo{}; @@ -1361,6 +1468,26 @@ bool lexical_conversion(const std::vector<std::string> &strings, AssignTo &outpu return false; } +/// conversion for wrapper types +template <typename AssignTo, + class ConvertTo, + enable_if_t<classify_object<ConvertTo>::value == object_category::wrapper_value && + !std::is_assignable<AssignTo &, ConvertTo>::value, + detail::enabler> = detail::dummy> +bool lexical_conversion(const std::vector<std::string> &strings, AssignTo &output) { + using ConvertType = typename ConvertTo::value_type; + if(strings.empty() || strings.front().empty()) { + output = ConvertType{}; + return true; + } + ConvertType val; + if(lexical_conversion<typename ConvertTo::value_type, typename ConvertTo::value_type>(strings, val)) { + output = 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 @@ -1389,5 +1516,33 @@ void sum_flag_vector(const std::vector<std::string> &flags, T &output) { output = static_cast<T>(count); } +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4800) +#endif +// with Atomic<XX> this could produce a warning due to the conversion but if atomic gets here it is an old style so will +// most likely still work + +/// 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_signed<T>::value && !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) { + count += detail::to_flag_value(flag); + } + std::string out = detail::to_string(count); + lexical_cast(out, output); +} + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + } // namespace detail +// [CLI11:type_tools_hpp:end] } // namespace CLI diff --git a/packages/CLI11/include/CLI/Validators.hpp b/packages/CLI11/include/CLI/Validators.hpp index bc916f8615fef58ae0b7a6f011dedf6778298ac1..90b590f6b6f99cf3067683284146e849f673ec28 100644 --- a/packages/CLI11/include/CLI/Validators.hpp +++ b/packages/CLI11/include/CLI/Validators.hpp @@ -10,6 +10,7 @@ #include "StringTools.hpp" #include "TypeTools.hpp" +// [CLI11:public_includes:set] #include <cmath> #include <cstdint> #include <functional> @@ -20,8 +21,9 @@ #include <string> #include <utility> #include <vector> +// [CLI11:public_includes:end] -// [CLI11:verbatim] +// [CLI11:validators_hpp_filesystem:verbatim] // C standard library // Only needed for existence checking @@ -55,9 +57,10 @@ #include <sys/types.h> #endif -// [CLI11:verbatim] +// [CLI11:validators_hpp_filesystem:end] namespace CLI { +// [CLI11:validators_hpp:verbatim] class Option; @@ -419,53 +422,6 @@ class IPV4Validator : public Validator { } }; -/// Validate the argument is a number and greater than 0 -class PositiveNumber : public Validator { - public: - PositiveNumber() : Validator("POSITIVE") { - func_ = [](std::string &number_str) { - double number; - if(!detail::lexical_cast(number_str, number)) { - return std::string("Failed parsing number: (") + number_str + ')'; - } - if(number <= 0) { - return std::string("Number less or equal to 0: (") + number_str + ')'; - } - return std::string(); - }; - } -}; -/// Validate the argument is a number and greater than or equal to 0 -class NonNegativeNumber : public Validator { - public: - NonNegativeNumber() : Validator("NONNEGATIVE") { - func_ = [](std::string &number_str) { - double number; - if(!detail::lexical_cast(number_str, number)) { - return std::string("Failed parsing number: (") + number_str + ')'; - } - if(number < 0) { - return std::string("Number less than 0: (") + number_str + ')'; - } - return std::string(); - }; - } -}; - -/// Validate the argument is a number -class Number : public Validator { - public: - Number() : Validator("NUMBER") { - func_ = [](std::string &number_str) { - double number; - if(!detail::lexical_cast(number_str, number)) { - return std::string("Failed parsing as a number (") + number_str + ')'; - } - return std::string(); - }; - } -}; - } // namespace detail // Static is not needed here, because global const implies static. @@ -485,14 +441,23 @@ const detail::NonexistentPathValidator NonexistentPath; /// Check for an IP4 address const detail::IPV4Validator ValidIPV4; -/// Check for a positive number -const detail::PositiveNumber PositiveNumber; - -/// Check for a non-negative number -const detail::NonNegativeNumber NonNegativeNumber; +/// Validate the input as a particular type +template <typename DesiredType> class TypeValidator : public Validator { + public: + explicit TypeValidator(const std::string &validator_name) : Validator(validator_name) { + func_ = [](std::string &input_string) { + auto val = DesiredType(); + if(!detail::lexical_cast(input_string, val)) { + return std::string("Failed parsing ") + input_string + " as a " + detail::type_name<DesiredType>(); + } + return std::string(); + }; + } + TypeValidator() : TypeValidator(detail::type_name<DesiredType>()) {} +}; /// Check for a number -const detail::Number Number; +const TypeValidator<double> Number("NUMBER"); /// Produce a range (factory). Min and max are inclusive. class Range : public Validator { @@ -501,10 +466,13 @@ class Range : public Validator { /// /// Note that the constructor is templated, but the struct is not, so C++17 is not /// needed to provide nice syntax for Range(a,b). - template <typename T> Range(T min, T max) { - std::stringstream out; - out << detail::type_name<T>() << " in [" << min << " - " << max << "]"; - description(out.str()); + template <typename T> + Range(T min, T max, const std::string &validator_name = std::string{}) : Validator(validator_name) { + if(validator_name.empty()) { + std::stringstream out; + out << detail::type_name<T>() << " in [" << min << " - " << max << "]"; + description(out.str()); + } func_ = [min, max](std::string &input) { T val; @@ -518,9 +486,17 @@ class Range : public Validator { } /// Range of one value is 0 to value - template <typename T> explicit Range(T max) : Range(static_cast<T>(0), max) {} + template <typename T> + explicit Range(T max, const std::string &validator_name = std::string{}) + : Range(static_cast<T>(0), max, validator_name) {} }; +/// Check for a non negative number +const Range NonNegativeNumber(std::numeric_limits<double>::max(), "NONNEGATIVE"); + +/// Check for a positive valued number (val>0.0), min() her is the smallest positive number +const Range PositiveNumber(std::numeric_limits<double>::min(), std::numeric_limits<double>::max(), "POSITIVE"); + /// Produce a bounded range (factory). Min and max are inclusive. class Bound : public Validator { public: @@ -746,9 +722,7 @@ class IsMember : public Validator { } // If you reach this point, the result was not found - std::string out(" not in "); - out += detail::generate_set(detail::smart_deref(set)); - return out; + return input + " not in " + detail::generate_set(detail::smart_deref(set)); }; } @@ -968,14 +942,11 @@ class AsNumberWithUnit : public Validator { if(opts & CASE_INSENSITIVE) { unit = detail::to_lower(unit); } - - bool converted = detail::lexical_cast(input, num); - if(!converted) { - throw ValidationError(std::string("Value ") + input + " could not be converted to " + - detail::type_name<Number>()); - } - if(unit.empty()) { + if(!detail::lexical_cast(input, num)) { + throw ValidationError(std::string("Value ") + input + " could not be converted to " + + detail::type_name<Number>()); + } // No need to modify input if no unit passed return {}; } @@ -989,12 +960,22 @@ class AsNumberWithUnit : public Validator { detail::generate_map(mapping, true)); } - // perform safe multiplication - bool ok = detail::checked_multiply(num, it->second); - if(!ok) { - throw ValidationError(detail::to_string(num) + " multiplied by " + unit + - " factor would cause number overflow. Use smaller value."); + if(!input.empty()) { + bool converted = detail::lexical_cast(input, num); + if(!converted) { + throw ValidationError(std::string("Value ") + input + " could not be converted to " + + detail::type_name<Number>()); + } + // perform safe multiplication + bool ok = detail::checked_multiply(num, it->second); + if(!ok) { + throw ValidationError(detail::to_string(num) + " multiplied by " + unit + + " factor would cause number overflow. Use smaller value."); + } + } else { + num = static_cast<Number>(it->second); } + input = detail::to_string(num); return {}; @@ -1134,4 +1115,5 @@ inline std::pair<std::string, std::string> split_program_name(std::string comman } // namespace detail /// @} +// [CLI11:validators_hpp:end] } // namespace CLI diff --git a/packages/CLI11/include/CLI/Version.hpp b/packages/CLI11/include/CLI/Version.hpp index 3bfd8899df64165a9b358d9f76979257906533ad..dde959890da59b6640e9a16eefdc60b5a74e925f 100644 --- a/packages/CLI11/include/CLI/Version.hpp +++ b/packages/CLI11/include/CLI/Version.hpp @@ -6,11 +6,11 @@ #pragma once -// [CLI11:verbatim] +// [CLI11:version_hpp:verbatim] #define CLI11_VERSION_MAJOR 1 #define CLI11_VERSION_MINOR 9 -#define CLI11_VERSION_PATCH 0 -#define CLI11_VERSION "1.9.0" +#define CLI11_VERSION_PATCH 1 +#define CLI11_VERSION "1.9.1" -// [CLI11:verbatim] +// [CLI11:version_hpp:end] diff --git a/packages/CLI11/scripts/MakeSingleHeader.py b/packages/CLI11/scripts/MakeSingleHeader.py index 1c4385826a2413c60ee4f12a8e6fc18bc2a4bfb0..78714ffb5038a6a825de32b9a2d64f1314dd7da9 100755 --- a/packages/CLI11/scripts/MakeSingleHeader.py +++ b/packages/CLI11/scripts/MakeSingleHeader.py @@ -5,171 +5,136 @@ from __future__ import print_function, unicode_literals import os import re from argparse import ArgumentParser -from operator import add -from copy import copy -from functools import reduce from subprocess import Popen, PIPE - -includes_local = re.compile(r"""^#include "(.*)"$""", re.MULTILINE) -includes_system = re.compile(r"""^#include \<(.*)\>$""", re.MULTILINE) -version_finder = re.compile(r"""^#define CLI11_VERSION \"(.*)\"$""", re.MULTILINE) -verbatim_tag_str = r""" -^ # Begin of line -[^\n^\[]+ # Some characters, not including [ or the end of a line -\[ # A literal [ -[^\]^\n]* # Anything except a closing ] -CLI11:verbatim # The tag -[^\]^\n]* # Anything except a closing ] -\] # A literal ] -[^\n]* # Up to end of line -$ # End of a line +import warnings + +tag_str = r""" +^ # Begin of line +[/\s]+ # Whitespace or comment // chars +\[ # A literal [ +{tag}: # The tag +(?P<name>[\w_]+) # name: group name +: # Colon +(?P<action>[\w_]+) # action: type of include +\] # A literal ] +\s* # Whitespace +$ # End of a line + +(?P<content>.*) # All + +^ # Begin of line +[/\s]+ # Whitespace or comment // chars +\[ # A literal [ +{tag}: # The tag +(?P=name) # Repeated name +: # Colon +end # Literal "end" +\] # A literal ] +\s* # Whitespace +$ # End of a line """ -verbatim_all = re.compile( - verbatim_tag_str + "(.*)" + verbatim_tag_str, re.MULTILINE | re.DOTALL | re.VERBOSE -) DIR = os.path.dirname(os.path.abspath(__file__)) -class HeaderFile(object): - TAG = "Unknown git revision" - LICENSE = "// BSD 3 clause" - VERSION = "Unknown" +class HeaderGroups(dict): + def __init__(self, tag): + self.re_matcher = re.compile( + tag_str.format(tag=tag), re.MULTILINE | re.DOTALL | re.VERBOSE + ) + super(HeaderGroups, self).__init__() - def __init__(self, base, inc): - with open(os.path.join(base, inc)) as f: + def read_header(self, filename): + with open(filename) as f: inner = f.read() - version = version_finder.search(inner) - if version: - self.__class__.VERSION = version.groups()[0] - - # add self.verbatim - if "CLI11:verbatim" in inner: - self.verbatim = ["\n\n// Verbatim copy from {0}:".format(inc)] - self.verbatim += verbatim_all.findall(inner) - inner = verbatim_all.sub("", inner) - else: - self.verbatim = [] - - self.headers = set(includes_system.findall(inner)) - - self.body = "\n// From {0}:\n\n".format(inc) + inner[inner.find("namespace") :] - - self.namespace = None - - def __add__(self, other): - out = copy(self) - out.headers |= other.headers - out.body += other.body - out.verbatim += other.verbatim - return out - - @property - def header_str(self): - return "\n".join("#include <" + h + ">" for h in sorted(self.headers)) - - @property - def verbatim_str(self): - return "\n".join(self.verbatim) - - def insert_namespace(self, namespace): - self.namespace = namespace - - def macro_replacement(self, before, after): - self.verbatim = [x.replace(before, after) for x in self.verbatim] - self.body = self.body.replace(before, after) - - def __str__(self): - result = """\ -#pragma once - -// CLI11: Version {self.VERSION} -// Originally designed by Henry Schreiner -// https://github.com/CLIUtils/CLI11 -// -// This is a standalone header file generated by MakeSingleHeader.py in CLI11/scripts -// from: {self.TAG} -// -// From LICENSE: -// -{self.LICENSE} - -// Standard combined includes: - -{self.header_str} -""".format( - self=self - ) + matches = self.re_matcher.findall(inner) + + if not matches: + warnings.warn( + "Failed to find any matches in {filename}".format(filename=filename) + ) - if self.namespace: - result += "\nnamespace " + self.namespace + " {\n\n" - result += "{self.verbatim_str}\n{self.body}\n".format(self=self) - if self.namespace: - result += "} // namespace " + self.namespace + "\n\n" + for name, action, content in matches: + if action == "verbatim": + assert ( + name not in self + ), "{name} read in more than once! Quitting.".format(name=name) + self[name] = content + elif action == "set": + self[name] = self.get(name, set()) | set(content.strip().splitlines()) + else: + raise RuntimeError("Action not understood, must be verbatim or set") - return result + def post_process(self): + for key in self: + if isinstance(self[key], set): + self[key] = "\n".join(self[key]) -def MakeHeader( - output, main_header, include_dir="../include", namespace=None, macro=None -): +def MakeHeader(output, main_header, files, tag, namespace, macro=None, version=None): + groups = HeaderGroups(tag) + # Set tag if possible to class variable try: proc = Popen( ["git", "describe", "--tags", "--always"], cwd=str(DIR), stdout=PIPE ) out, _ = proc.communicate() + groups["git"] = out.decode("utf-8").strip() if proc.returncode == 0 else "" except OSError: - pass - else: - if proc.returncode == 0: - HeaderFile.TAG = out.decode("utf-8").strip() - - base_dir = os.path.abspath(os.path.join(DIR, include_dir)) - main_header = os.path.join(base_dir, main_header) - header_dir = os.path.dirname(main_header) - licence_file = os.path.abspath(os.path.join(DIR, "../LICENSE")) + groups["git"] = "" - with open(licence_file) as f: - HeaderFile.LICENSE = "".join("// " + line for line in f) + for f in files: + groups.read_header(f) - with open(main_header) as f: - header = f.read() + groups["namespace"] = namespace + groups["version"] = version or groups["git"] - include_files = includes_local.findall(header) + groups.post_process() - headers = [HeaderFile(header_dir, inc) for inc in include_files] - single_header = reduce(add, headers) + with open(main_header) as f: + single_header = f.read().format(**groups) if macro is not None: - before = "CLI11_" - print("Converting macros", before, "->", macro) - single_header.macro_replacement(before, macro) + before, after = macro + print("Converting macros", before, "->", after) + single_header.replace(before, after) - if namespace: - print("Adding namespace", namespace) - single_header.insert_namespace(namespace) + if output is not None: + with open(output, "w") as f: + f.write(single_header) - with open(output, "w") as f: - f.write(str(single_header)) - - print("Created", output) + print("Created", output) + else: + print(single_header) if __name__ == "__main__": parser = ArgumentParser( usage="Convert source to single header include. Can optionally add namespace and search-replace replacements (for macros)." ) - parser.add_argument("output", help="Single header file output") + parser.add_argument("--output", default=None, help="Single header file output") parser.add_argument( "--main", - default="CLI/CLI.hpp", + default="CLI11.hpp.in", help="The main include file that defines the other files", ) - parser.add_argument("--include", default="../include", help="The include directory") - parser.add_argument("--namespace", help="Add an optional namespace") - parser.add_argument("--macro", help="Replaces CLI11_ with NEW_PREFIX_") + parser.add_argument("files", nargs="*", help="The header files") + parser.add_argument("--namespace", default="CLI", help="Set the namespace") + parser.add_argument("--tag", default="CLI11", help="Tag to look up") + parser.add_argument( + "--macro", nargs=2, help="Replaces OLD_PREFIX_ with NEW_PREFIX_" + ) + parser.add_argument("--version", help="Include this version in the generated file") args = parser.parse_args() - MakeHeader(args.output, args.main, args.include, args.namespace, args.macro) + MakeHeader( + args.output, + args.main, + args.files, + args.tag, + args.namespace, + args.macro, + args.version, + ) diff --git a/packages/CLI11/tests/AppTest.cpp b/packages/CLI11/tests/AppTest.cpp index b0ca787af03cab9507ebc5063938288165648681..89f52c0853354280eee2745920a92b290a55fe4f 100644 --- a/packages/CLI11/tests/AppTest.cpp +++ b/packages/CLI11/tests/AppTest.cpp @@ -135,11 +135,14 @@ TEST_F(TApp, RequireOptionsError) { app.add_flag("-c"); app.add_flag("--q"); app.add_flag("--this,--that"); + app.set_help_flag("-h,--help"); + app.set_help_all_flag("--help_all"); app.require_option(1, 2); try { app.parse("-c --q --this --that"); } catch(const CLI::RequiredError &re) { EXPECT_THAT(re.what(), Not(HasSubstr("-h,--help"))); + EXPECT_THAT(re.what(), Not(HasSubstr("help_all"))); } EXPECT_NO_THROW(app.parse("-c --q")); @@ -561,6 +564,26 @@ TEST_F(TApp, LotsOfFlagsSingleStringExtraSpace) { EXPECT_EQ(1u, app.count("-A")); } +TEST_F(TApp, SingleArgVector) { + + std::vector<std::string> channels; + std::vector<std::string> iargs; + std::string path; + app.add_option("-c", channels)->type_size(1)->allow_extra_args(false); + app.add_option("args", iargs); + app.add_option("-p", path); + + app.parse("-c t1 -c t2 -c t3 a1 a2 a3 a4 -p happy"); + EXPECT_EQ(3u, channels.size()); + EXPECT_EQ(4u, iargs.size()); + EXPECT_EQ(path, "happy"); + + app.parse("-c t1 a1 -c t2 -c t3 a2 a3 a4 -p happy"); + EXPECT_EQ(3u, channels.size()); + EXPECT_EQ(4u, iargs.size()); + EXPECT_EQ(path, "happy"); +} + TEST_F(TApp, FlagLikeOption) { bool val{false}; auto opt = app.add_option("--flag", val)->type_size(0)->default_str("true"); @@ -1812,6 +1835,24 @@ TEST_F(TApp, RangeDouble) { run(); } +TEST_F(TApp, typeCheck) { + + /// Note that this must be a double in Range, too + app.add_option("--one")->check(CLI::TypeValidator<unsigned int>()); + + args = {"--one=1"}; + EXPECT_NO_THROW(run()); + + args = {"--one=-7"}; + EXPECT_THROW(run(), CLI::ValidationError); + + args = {"--one=error"}; + EXPECT_THROW(run(), CLI::ValidationError); + + args = {"--one=4.568"}; + EXPECT_THROW(run(), CLI::ValidationError); +} + // Check to make sure programmatic access to left over is available TEST_F(TApp, AllowExtras) { diff --git a/packages/CLI11/tests/CMakeLists.txt b/packages/CLI11/tests/CMakeLists.txt index 7e76321e4b0470415e4642f465f756006267d691..cce6e30b8f388309113bed5134a4221dc082e6fd 100644 --- a/packages/CLI11/tests/CMakeLists.txt +++ b/packages/CLI11/tests/CMakeLists.txt @@ -32,9 +32,11 @@ endif() set(GOOGLE_TEST_INDIVIDUAL OFF) include(AddGoogletest) -# Add boost to test boost::optional if available -find_package(Boost 1.61) - +# Add boost to test boost::optional (currently explicitly requested)" +option(CLI11_BOOST "Turn on boost test (currently may fail with Boost 1.70)" OFF) +if(CLI11_BOOST) + find_package(Boost 1.61 REQUIRED) +endif() set(boost-optional-def $<$<BOOL:${Boost_FOUND}>:CLI11_BOOST_OPTIONAL>) set(CLI11_TESTS diff --git a/packages/CLI11/tests/ConfigFileTest.cpp b/packages/CLI11/tests/ConfigFileTest.cpp index 370ad9769d3418a3ff70053206290858188adfbe..d8b19d142aff335f13e872d38113a405b54c840c 100644 --- a/packages/CLI11/tests/ConfigFileTest.cpp +++ b/packages/CLI11/tests/ConfigFileTest.cpp @@ -150,6 +150,47 @@ TEST(StringBased, Vector) { EXPECT_EQ("seven", output.at(2).inputs.at(2)); } +TEST(StringBased, TomlVector) { + std::stringstream ofile; + + ofile << "one = [three]\n"; + ofile << "two = [four]\n"; + ofile << "five = [six, and, seven]\n"; + ofile << "eight = [nine, \n" + "ten, eleven, twelve \n" + "]\n"; + ofile << "one_more = [one, \n" + "two, three ] \n"; + + ofile.seekg(0, std::ios::beg); + + std::vector<CLI::ConfigItem> output = CLI::ConfigINI().from_config(ofile); + + EXPECT_EQ(5u, output.size()); + EXPECT_EQ("one", output.at(0).name); + EXPECT_EQ(1u, output.at(0).inputs.size()); + EXPECT_EQ("three", output.at(0).inputs.at(0)); + EXPECT_EQ("two", output.at(1).name); + EXPECT_EQ(1u, output.at(1).inputs.size()); + EXPECT_EQ("four", output.at(1).inputs.at(0)); + EXPECT_EQ("five", output.at(2).name); + EXPECT_EQ(3u, output.at(2).inputs.size()); + EXPECT_EQ("six", output.at(2).inputs.at(0)); + EXPECT_EQ("and", output.at(2).inputs.at(1)); + EXPECT_EQ("seven", output.at(2).inputs.at(2)); + EXPECT_EQ("eight", output.at(3).name); + EXPECT_EQ(4u, output.at(3).inputs.size()); + EXPECT_EQ("nine", output.at(3).inputs.at(0)); + EXPECT_EQ("ten", output.at(3).inputs.at(1)); + EXPECT_EQ("eleven", output.at(3).inputs.at(2)); + EXPECT_EQ("twelve", output.at(3).inputs.at(3)); + EXPECT_EQ("one_more", output.at(4).name); + EXPECT_EQ(3u, output.at(4).inputs.size()); + EXPECT_EQ("one", output.at(4).inputs.at(0)); + EXPECT_EQ("two", output.at(4).inputs.at(1)); + EXPECT_EQ("three", output.at(4).inputs.at(2)); +} + TEST(StringBased, Spaces) { std::stringstream ofile; @@ -591,6 +632,89 @@ TEST_F(TApp, IniNotRequiredNotDefault) { EXPECT_EQ(app.get_config_ptr()->as<std::string>(), tmpini2.c_str()); } +TEST_F(TApp, MultiConfig) { + + TempFile tmpini{"TestIniTmp.ini"}; + TempFile tmpini2{"TestIniTmp2.ini"}; + + app.set_config("--config")->expected(1, 3); + + { + std::ofstream out{tmpini}; + out << "[default]" << std::endl; + out << "two=99" << std::endl; + out << "three=3" << std::endl; + } + + { + std::ofstream out{tmpini2}; + out << "[default]" << std::endl; + out << "one=55" << std::endl; + out << "three=4" << std::endl; + } + + int one{0}, two{0}, three{0}; + app.add_option("--one", one); + app.add_option("--two", two); + app.add_option("--three", three); + + args = {"--config", tmpini2, "--config", tmpini}; + run(); + + EXPECT_EQ(99, two); + EXPECT_EQ(3, three); + EXPECT_EQ(55, one); + + args = {"--config", tmpini, "--config", tmpini2}; + run(); + + EXPECT_EQ(99, two); + EXPECT_EQ(4, three); + EXPECT_EQ(55, one); +} + +TEST_F(TApp, MultiConfig_single) { + + TempFile tmpini{"TestIniTmp.ini"}; + TempFile tmpini2{"TestIniTmp2.ini"}; + + app.set_config("--config")->multi_option_policy(CLI::MultiOptionPolicy::TakeLast); + + { + std::ofstream out{tmpini}; + out << "[default]" << std::endl; + out << "two=99" << std::endl; + out << "three=3" << std::endl; + } + + { + std::ofstream out{tmpini2}; + out << "[default]" << std::endl; + out << "one=55" << std::endl; + out << "three=4" << std::endl; + } + + int one{0}, two{0}, three{0}; + app.add_option("--one", one); + app.add_option("--two", two); + app.add_option("--three", three); + + args = {"--config", tmpini2, "--config", tmpini}; + run(); + + EXPECT_EQ(99, two); + EXPECT_EQ(3, three); + EXPECT_EQ(0, one); + + two = 0; + args = {"--config", tmpini, "--config", tmpini2}; + run(); + + EXPECT_EQ(0, two); + EXPECT_EQ(4, three); + EXPECT_EQ(55, one); +} + TEST_F(TApp, IniRequiredNotFound) { std::string noini = "TestIniNotExist.ini"; @@ -770,6 +894,30 @@ TEST_F(TApp, TOMLVectordirect) { EXPECT_EQ(std::vector<int>({1, 2, 3}), three); } +TEST_F(TApp, TOMLStringVector) { + + TempFile tmptoml{"TestTomlTmp.toml"}; + + app.set_config("--config", tmptoml); + + { + std::ofstream out{tmptoml}; + out << "#this is a comment line\n"; + out << "[default]\n"; + out << "two=[\"2\",\"3\"]\n"; + out << "three=[\"1\",\"2\",\"3\"]\n"; + } + + std::vector<std::string> two, three; + app.add_option("--two", two)->required(); + app.add_option("--three", three)->required(); + + run(); + + EXPECT_EQ(std::vector<std::string>({"2", "3"}), two); + EXPECT_EQ(std::vector<std::string>({"1", "2", "3"}), three); +} + TEST_F(TApp, IniVectorCsep) { TempFile tmpini{"TestIniTmp.ini"}; diff --git a/packages/CLI11/tests/FormatterTest.cpp b/packages/CLI11/tests/FormatterTest.cpp index fd2547835733b00ea54ef5d1ffeb3b33fde5f48a..513f956a03bc1d20e01b6fb6a05821903a28e6f0 100644 --- a/packages/CLI11/tests/FormatterTest.cpp +++ b/packages/CLI11/tests/FormatterTest.cpp @@ -89,6 +89,25 @@ TEST(Formatter, OptCustomizeSimple) { " --opt INT (MUST HAVE) Something\n\n"); } +TEST(Formatter, OptCustomizeOptionText) { + CLI::App app{"My prog"}; + + app.get_formatter()->column_width(25); + + int v{0}; + app.add_option("--opt", v, "Something")->option_text("(ARG)"); + + std::string help = app.help(); + + EXPECT_THAT(help, HasSubstr("(ARG)")); + EXPECT_EQ(help, + "My prog\n" + "Usage: [OPTIONS]\n\n" + "Options:\n" + " -h,--help Print this help message and exit\n" + " --opt (ARG) Something\n\n"); +} + TEST(Formatter, FalseFlagExample) { CLI::App app{"My prog"}; diff --git a/packages/CLI11/tests/HelpTest.cpp b/packages/CLI11/tests/HelpTest.cpp index 161b0b9eea47a1f7b8a1b2635e681b5c7cd70860..a6169dfebbdcaa907f2163b57610941e6338a810 100644 --- a/packages/CLI11/tests/HelpTest.cpp +++ b/packages/CLI11/tests/HelpTest.cpp @@ -467,6 +467,36 @@ TEST(THelp, Subcom) { EXPECT_THAT(help, HasSubstr("Usage: ./myprogram sub2")); } +TEST(THelp, Subcom_alias) { + CLI::App app{"My prog"}; + + auto sub1 = app.add_subcommand("sub1", "Subcommand1 description test"); + sub1->alias("sub_alias1"); + sub1->alias("sub_alias2"); + + app.add_subcommand("sub2", "Subcommand2 description test"); + + std::string help = app.help(); + EXPECT_THAT(help, HasSubstr("Usage: [OPTIONS] [SUBCOMMAND]")); + EXPECT_THAT(help, HasSubstr("sub_alias1")); + EXPECT_THAT(help, HasSubstr("sub_alias2")); +} + +TEST(THelp, Subcom_alias_group) { + CLI::App app{"My prog"}; + + auto sub1 = app.add_subcommand("", "Subcommand1 description test"); + sub1->alias("sub_alias1"); + sub1->alias("sub_alias2"); + + app.add_subcommand("sub2", "Subcommand2 description test"); + + std::string help = app.help(); + EXPECT_THAT(help, HasSubstr("Usage: [OPTIONS] [SUBCOMMAND]")); + EXPECT_THAT(help, HasSubstr("sub_alias1")); + EXPECT_THAT(help, HasSubstr("sub_alias2")); +} + TEST(THelp, MasterName) { CLI::App app{"My prog", "MyRealName"}; diff --git a/packages/CLI11/tests/HelpersTest.cpp b/packages/CLI11/tests/HelpersTest.cpp index a60076261a2925ef4fbddab861c5f69be252e945..993b8a9f436e3916c3d37df22bd43bd904eedac9 100644 --- a/packages/CLI11/tests/HelpersTest.cpp +++ b/packages/CLI11/tests/HelpersTest.cpp @@ -7,6 +7,7 @@ #include "app_helper.hpp" #include <array> +#include <atomic> #include <climits> #include <complex> #include <cstdint> @@ -911,6 +912,9 @@ TEST(Types, TypeName) { std::string float_name = CLI::detail::type_name<double>(); EXPECT_EQ("FLOAT", float_name); + std::string char_name = CLI::detail::type_name<char>(); + EXPECT_EQ("CHAR", char_name); + std::string vector_name = CLI::detail::type_name<std::vector<int>>(); EXPECT_EQ("INT", vector_name); @@ -988,6 +992,8 @@ TEST(Types, TypeName) { 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); + + vclass = CLI::detail::classify_object<std::atomic<int>>::value; } TEST(Types, OverflowSmall) { @@ -1025,6 +1031,11 @@ TEST(Types, LexicalCastInt) { std::string extra_input = "912i"; EXPECT_FALSE(CLI::detail::lexical_cast(extra_input, y)); + + std::string empty_input{}; + EXPECT_FALSE(CLI::detail::lexical_cast(empty_input, x_signed)); + EXPECT_FALSE(CLI::detail::lexical_cast(empty_input, x_unsigned)); + EXPECT_FALSE(CLI::detail::lexical_cast(empty_input, y_signed)); } TEST(Types, LexicalCastDouble) { @@ -1037,10 +1048,14 @@ TEST(Types, LexicalCastDouble) { EXPECT_FALSE(CLI::detail::lexical_cast(bad_input, x)); std::string overflow_input = "1" + std::to_string(LDBL_MAX); - EXPECT_FALSE(CLI::detail::lexical_cast(overflow_input, x)); + EXPECT_TRUE(CLI::detail::lexical_cast(overflow_input, x)); + EXPECT_FALSE(std::isfinite(x)); std::string extra_input = "9.12i"; EXPECT_FALSE(CLI::detail::lexical_cast(extra_input, x)); + + std::string empty_input{}; + EXPECT_FALSE(CLI::detail::lexical_cast(empty_input, x)); } TEST(Types, LexicalCastBool) { diff --git a/packages/CLI11/tests/OptionTypeTest.cpp b/packages/CLI11/tests/OptionTypeTest.cpp index 4ee7deb818300863fe6636c55999b58deed24f2c..bbf71fdfb4a962984169895e6557b45fd2bc4805 100644 --- a/packages/CLI11/tests/OptionTypeTest.cpp +++ b/packages/CLI11/tests/OptionTypeTest.cpp @@ -5,6 +5,7 @@ // SPDX-License-Identifier: BSD-3-Clause #include "app_helper.hpp" +#include <atomic> #include <complex> #include <cstdint> #include <cstdlib> @@ -141,6 +142,33 @@ TEST_F(TApp, BoolAndIntFlags) { EXPECT_EQ((unsigned int)2, uflag); } +TEST_F(TApp, atomic_bool_flags) { + + std::atomic<bool> bflag{false}; + std::atomic<int> iflag{0}; + + app.add_flag("-b", bflag); + app.add_flag("-i,--int", iflag); + + args = {"-b", "-i"}; + run(); + EXPECT_TRUE(bflag.load()); + EXPECT_EQ(1, iflag.load()); + + args = {"-b", "-b"}; + ASSERT_NO_THROW(run()); + EXPECT_TRUE(bflag.load()); + + bflag = false; + + args = {"-iii"}; + run(); + EXPECT_FALSE(bflag.load()); + EXPECT_EQ(3, iflag.load()); + args = {"--int=notanumber"}; + EXPECT_THROW(run(), CLI::ConversionError); +} + TEST_F(TApp, BoolOption) { bool bflag{false}; app.add_option("-b", bflag); @@ -167,6 +195,51 @@ TEST_F(TApp, BoolOption) { EXPECT_FALSE(bflag); } +TEST_F(TApp, atomic_int_option) { + std::atomic<int> i{0}; + auto aopt = app.add_option("-i,--int", i); + args = {"-i4"}; + run(); + EXPECT_EQ(1u, app.count("--int")); + EXPECT_EQ(1u, app.count("-i")); + EXPECT_EQ(i, 4); + EXPECT_EQ(app["-i"]->as<std::string>(), "4"); + EXPECT_EQ(app["--int"]->as<double>(), 4.0); + + args = {"--int", "notAnInt"}; + EXPECT_THROW(run(), CLI::ConversionError); + + aopt->expected(0, 1); + args = {"--int"}; + run(); + EXPECT_EQ(i, 0); +} + +TEST_F(TApp, CharOption) { + char c1{'t'}; + app.add_option("-c", c1); + + args = {"-c", "g"}; + run(); + EXPECT_EQ(c1, 'g'); + + args = {"-c", "1"}; + run(); + EXPECT_EQ(c1, '1'); + + args = {"-c", "77"}; + run(); + EXPECT_EQ(c1, 77); + + // convert hex for digit + args = {"-c", "0x44"}; + run(); + EXPECT_EQ(c1, 0x44); + + args = {"-c", "751615654161688126132138844896646748852"}; + EXPECT_THROW(run(), CLI::ConversionError); +} + TEST_F(TApp, vectorDefaults) { std::vector<int> vals{4, 5}; auto opt = app.add_option("--long", vals, "", true); @@ -838,3 +911,29 @@ TEST_F(TApp, tupleTwoVectors) { EXPECT_EQ(std::get<0>(cv).size(), 2U); EXPECT_EQ(std::get<1>(cv).size(), 3U); } + +TEST_F(TApp, vectorSingleArg) { + + std::vector<int> cv; + app.add_option("-c", cv)->allow_extra_args(false); + std::string extra; + app.add_option("args", extra); + args = {"-c", "1", "-c", "2", "4"}; + + run(); + EXPECT_EQ(cv.size(), 2U); + EXPECT_EQ(extra, "4"); +} + +TEST_F(TApp, vectorDoubleArg) { + + std::vector<std::pair<int, std::string>> cv; + app.add_option("-c", cv)->allow_extra_args(false); + std::vector<std::string> extras; + app.add_option("args", extras); + args = {"-c", "1", "bob", "-c", "2", "apple", "4", "key"}; + + run(); + EXPECT_EQ(cv.size(), 2U); + EXPECT_EQ(extras.size(), 2U); +} diff --git a/packages/CLI11/tests/OptionalTest.cpp b/packages/CLI11/tests/OptionalTest.cpp index a52c389039580a0c13c446b36d048a1bbc09542d..51f159aa69975b0dff22ed06ef315f0be8797e76 100644 --- a/packages/CLI11/tests/OptionalTest.cpp +++ b/packages/CLI11/tests/OptionalTest.cpp @@ -117,6 +117,19 @@ TEST_F(TApp, StdOptionalComplexDirect) { EXPECT_EQ(*opt, val2); } +TEST_F(TApp, StdOptionalUint) { + std::optional<std::uint64_t> opt; + app.add_option("-i,--int", opt); + run(); + EXPECT_FALSE(opt); + + args = {"-i", "15"}; + run(); + EXPECT_EQ(*opt, 15U); + static_assert(CLI::detail::classify_object<std::optional<std::uint64_t>>::value == + CLI::detail::object_category::wrapper_value); +} + #ifdef _MSC_VER #pragma warning(default : 4244) #endif @@ -218,6 +231,7 @@ TEST_F(TApp, BoostOptionalEnumTest) { enum class eval : char { val0 = 0, val1 = 1, val2 = 2, val3 = 3, val4 = 4 }; 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(); diff --git a/packages/CLI11/tests/SubcommandTest.cpp b/packages/CLI11/tests/SubcommandTest.cpp index da6d3628c45b5369abdffac090e4c4be3ead85da..e6f961345ed1f784f9bb0a02a02899b8a9c4ec1d 100644 --- a/packages/CLI11/tests/SubcommandTest.cpp +++ b/packages/CLI11/tests/SubcommandTest.cpp @@ -1466,6 +1466,21 @@ TEST_F(ManySubcommands, SubcommandTriggeredOn) { EXPECT_THROW(run(), CLI::ExtrasError); } +TEST_F(ManySubcommands, SubcommandSilence) { + + sub1->silent(); + args = {"sub1", "sub2"}; + EXPECT_NO_THROW(run()); + + auto subs = app.get_subcommands(); + EXPECT_EQ(subs.size(), 1U); + sub1->silent(false); + EXPECT_FALSE(sub1->get_silent()); + run(); + subs = app.get_subcommands(); + EXPECT_EQ(subs.size(), 2U); +} + TEST_F(TApp, UnnamedSub) { double val{0.0}; auto sub = app.add_subcommand("", "empty name"); @@ -1622,6 +1637,23 @@ TEST_F(TApp, OptionGroupAlias) { EXPECT_EQ(val, -3); } +TEST_F(TApp, subcommand_help) { + auto sub1 = app.add_subcommand("help")->silent(); + bool flag{false}; + app.add_flag("--one", flag, "FLAGGER"); + sub1->parse_complete_callback([]() { throw CLI::CallForHelp(); }); + bool called{false}; + args = {"help"}; + try { + run(); + } catch(const CLI::CallForHelp &) { + called = true; + } + auto helpstr = app.help(); + EXPECT_THAT(helpstr, HasSubstr("FLAGGER")); + EXPECT_TRUE(called); +} + TEST_F(TApp, AliasErrors) { auto sub1 = app.add_subcommand("sub1"); auto sub2 = app.add_subcommand("sub2"); diff --git a/packages/CLI11/tests/TransformTest.cpp b/packages/CLI11/tests/TransformTest.cpp index ae802af079a8712bd4c64e16ab3f6dbd5e8f6434..53df504acadf14e91cfb7b58bde88917593664b9 100644 --- a/packages/CLI11/tests/TransformTest.cpp +++ b/packages/CLI11/tests/TransformTest.cpp @@ -7,6 +7,7 @@ #include "app_helper.hpp" #include <array> +#include <chrono> #include <cstdint> #include <unordered_map> @@ -691,7 +692,8 @@ TEST_F(TApp, NumberWithUnitBadInput) { args = {"-n", "13 c"}; EXPECT_THROW(run(), CLI::ValidationError); args = {"-n", "a"}; - EXPECT_THROW(run(), CLI::ValidationError); + // Assume 1.0 unit + EXPECT_NO_THROW(run()); args = {"-n", "12.0a"}; EXPECT_THROW(run(), CLI::ValidationError); args = {"-n", "a5"}; @@ -850,6 +852,22 @@ TEST_F(TApp, AsSizeValue1000_1024) { EXPECT_EQ(value, ki_value); } +TEST_F(TApp, duration_test) { + std::chrono::seconds duration{1}; + + app.option_defaults()->ignore_case(); + app.add_option_function<std::size_t>( + "--duration", + [&](size_t a_value) { duration = std::chrono::seconds{a_value}; }, + "valid units: sec, min, h, day.") + ->capture_default_str() + ->transform(CLI::AsNumberWithUnit( + std::map<std::string, std::size_t>{{"sec", 1}, {"min", 60}, {"h", 3600}, {"day", 24 * 3600}})); + EXPECT_NO_THROW(app.parse(std::vector<std::string>{"1 day", "--duration"})); + + EXPECT_EQ(duration, std::chrono::seconds(86400)); +} + TEST_F(TApp, AsSizeValue1024) { std::uint64_t value{0}; app.add_option("-s", value)->transform(CLI::AsSizeValue(false));