diff --git a/packages/CLI11/.appveyor.yml b/packages/CLI11/.appveyor.yml deleted file mode 100644 index 56b090cd8bda54f606b041d846546dc9a5e24a1f..0000000000000000000000000000000000000000 --- a/packages/CLI11/.appveyor.yml +++ /dev/null @@ -1,38 +0,0 @@ -version: 2.2.0.{build} - -branches: - only: - - main - - v1 - -install: - - git submodule update --init --recursive - - py -3 --version - - set PATH=C:\Python38-x64;C:\Python38-x64\Scripts;%PATH% - - cmake --version - - python --version - - python -m pip --version - - python -m pip install conan - - conan user - - conan --version - -build_script: - - mkdir build - - cd build - - ps: - cmake .. -DCLI11_WARNINGS_AS_ERRORS=ON -DCLI11_SINGLE_FILE_TESTS=ON - -DCMAKE_BUILD_TYPE=Debug -DCMAKE_GENERATOR="Visual Studio 14 2015" - - ps: cmake --build . - - cd .. - - conan create . CLIUtils/CLI11 - -test_script: - - cd build - - ps: ctest --output-on-failure -C Debug - -notifications: - - provider: Webhook - url: https://webhooks.gitter.im/e/0185e91c5d989a476d7b - on_build_success: false - on_build_failure: true - on_build_status_changed: true diff --git a/packages/CLI11/.codecov.yml b/packages/CLI11/.codecov.yml index 185bbad3f47599ffc86429c8dad69f5b61f64c32..61c2e2f21b3be697b43bb3776800b9575baa003f 100644 --- a/packages/CLI11/.codecov.yml +++ b/packages/CLI11/.codecov.yml @@ -1,3 +1,7 @@ ignore: - "tests" - "examples" + - "book" + - "docs" + - "test_package" + - "fuzz" diff --git a/packages/CLI11/.github/CONTRIBUTING.md b/packages/CLI11/.github/CONTRIBUTING.md index 7d376345b11fefd9b30f4069869a0a6ace195f66..93b7651cc8657a6febde2c6cd7a459104f5e3796 100644 --- a/packages/CLI11/.github/CONTRIBUTING.md +++ b/packages/CLI11/.github/CONTRIBUTING.md @@ -60,19 +60,6 @@ name, pre-commit): pre-commit install ``` -## For developers releasing to Conan.io - -This is now done by the CI system on tagged releases. Previously, the steps to -make a Conan.io release were: - -```bash -conan remove '*' # optional, I like to be clean -conan create . cliutils/stable -conan upload "*" -r cli11 --all -``` - -Here I've assumed that the remote is `cli11`. - ## For maintainers: remember to add contributions In a commit to a PR, just add diff --git a/packages/CLI11/.github/actions/quick_cmake/action.yml b/packages/CLI11/.github/actions/quick_cmake/action.yml index 811f73fe277bf4a2a63839790db9e42017e5c35b..8359fb0d01575c5225ab9bc09e657e58e76e4099 100644 --- a/packages/CLI11/.github/actions/quick_cmake/action.yml +++ b/packages/CLI11/.github/actions/quick_cmake/action.yml @@ -13,7 +13,7 @@ runs: using: composite steps: - name: CMake ${{ inputs.cmake-version }} - uses: jwlawson/actions-setup-cmake@v1.12 + uses: jwlawson/actions-setup-cmake@v1.13 with: cmake-version: "${{ inputs.cmake-version }}" - run: | diff --git a/packages/CLI11/.github/codecov.yml b/packages/CLI11/.github/codecov.yml index a0b066745da7867510001d22c19a367acc2ae88e..0e106f7103489b8215629c57ab067af89ca0a4b9 100644 --- a/packages/CLI11/.github/codecov.yml +++ b/packages/CLI11/.github/codecov.yml @@ -1,6 +1,6 @@ codecov: notify: - after_n_builds: 4 + after_n_builds: 8 coverage: status: project: diff --git a/packages/CLI11/.github/dependabot.yml b/packages/CLI11/.github/dependabot.yml index 2c7d1708395e202b3b3316391f35bf4183ebd045..f265d88d94951cbeb868e505d142cfe6232955c4 100644 --- a/packages/CLI11/.github/dependabot.yml +++ b/packages/CLI11/.github/dependabot.yml @@ -4,4 +4,6 @@ updates: - package-ecosystem: "github-actions" directory: "/" schedule: - interval: "daily" + interval: "weekly" + target-branch: "main" + open-pull-requests-limit: 10 diff --git a/packages/CLI11/.github/workflows/fuzz.yml b/packages/CLI11/.github/workflows/fuzz.yml new file mode 100644 index 0000000000000000000000000000000000000000..75d161b38a1f06cb91752e7b02e4f81e58071cca --- /dev/null +++ b/packages/CLI11/.github/workflows/fuzz.yml @@ -0,0 +1,54 @@ +name: Fuzz +on: + workflow_dispatch: + push: + branches: + - main + - v* + pull_request: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + quick_fuzz1: + name: quickfuzz1 + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - name: Configure + run: | + cmake -S . -B build \ + -DCMAKE_CXX_STANDARD=17 \ + -DCLI11_SINGLE_FILE_TESTS=OFF \ + -DCLI11_BUILD_EXAMPLES=OFF \ + -DCLI11_FUZZ_TARGET=ON \ + -DCLI11_BUILD_TESTS=OFF \ + -DCLI11_BUILD_DOCS=OFF \ + -DCMAKE_CXX_COMPILER=clang++ -DCMAKE_CXX_COMPILER_FORCED=ON \ + -DCMAKE_CXX_FLAGS="-g -O1 -fsanitize=fuzzer,undefined,address" + + - name: Build + run: cmake --build build -j4 + + - name: Test + run: | + cd build + make QUICK_CLI11_APP_FUZZ + + - name: Test2 + run: | + cd build + make QUICK_CLI11_FILE_FUZZ + + + - name: artifacts + if: failure() + uses: actions/upload-artifact@v3 + with: + name: file_failure + path: ./build/fuzz/cli11_*_fail_artifact.txt diff --git a/packages/CLI11/.github/workflows/tests.yml b/packages/CLI11/.github/workflows/tests.yml index a0b9e6c81282b15389affd44e9f0f5e84f03de54..2ab5a66692d9f433ba88aa12fcfff05c679a1e99 100644 --- a/packages/CLI11/.github/workflows/tests.yml +++ b/packages/CLI11/.github/workflows/tests.yml @@ -20,6 +20,8 @@ jobs: precompile: ["ON", "OFF"] steps: - uses: actions/checkout@v3 + with: + fetch-depth: 0 - name: Get LCov run: | @@ -46,16 +48,15 @@ jobs: - name: Prepare coverage run: | lcov --directory . --capture --output-file coverage.info - lcov --remove coverage.info '*/tests/*' '*/examples/*' '/usr/*' --output-file coverage.info + lcov --remove coverage.info '*/tests/*' '*/examples/*' '/usr/*' '*/book/*' '*/fuzz/*' --output-file coverage.info lcov --list coverage.info working-directory: build - - name: Upload coverage - run: | - curl -Os https://uploader.codecov.io/latest/linux/codecov - chmod +x codecov - ./codecov - working-directory: build + - uses: codecov/codecov-action@v3 + with: + files: build/coverage.info + fail_ci_if_error: true + functionalities: fixes clang-tidy: name: Clang-Tidy @@ -73,18 +74,31 @@ jobs: - name: Build run: cmake --build build -j4 -- --keep-going - cuda-build: - name: CUDA build only + cuda11-build: + name: CUDA 11 build only runs-on: ubuntu-latest - container: nvidia/cuda:10.2-devel-ubuntu18.04 + container: nvidia/cuda:11.8.0-devel-ubuntu22.04 steps: - - uses: actions/checkout@v1 + - name: Add build tools + run: apt-get update && apt-get install -y wget git cmake + - uses: actions/checkout@v3 + with: + submodules: true + - name: Configure + run: cmake -S . -B build -DCLI11_CUDA_TESTS=ON + - name: Build + run: cmake --build build -j2 + + cuda12-build: + name: CUDA 12 build only + runs-on: ubuntu-latest + container: nvidia/cuda:12.1.0-devel-ubuntu22.04 + steps: + - name: Add build tools + run: apt-get update && apt-get install -y wget git cmake + - uses: actions/checkout@v3 with: submodules: true - - name: Add wget - run: apt-get update && apt-get install -y wget - - name: Get cmake - uses: jwlawson/actions-setup-cmake@v1.12 - name: Configure run: cmake -S . -B build -DCLI11_CUDA_TESTS=ON - name: Build @@ -92,16 +106,15 @@ jobs: boost-build: name: Boost build - runs-on: ubuntu-latest - container: zouzias/boost:1.76.0 + runs-on: ubuntu-20.04 steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v3 with: submodules: true - - name: Add deps - run: apt-get update && apt-get install make - - name: Get CMake - uses: jwlawson/actions-setup-cmake@v1.12 + - name: Add boost + run: sudo apt-get update && sudo apt-get install -y libboost-dev + # NOTE: If a boost version matching all requirements cannot be found, + # this build step will fail - name: Configure run: cmake -S . -B build -DCLI11_BOOST=ON - name: Build @@ -127,9 +140,9 @@ jobs: - name: Build run: meson compile -C build-meson - cmake-config: - name: CMake config check - runs-on: ubuntu-latest + cmake-config-ubuntu-1804: + name: CMake config check (Ubuntu 18.04) + runs-on: ubuntu-18.04 steps: - uses: actions/checkout@v3 @@ -174,6 +187,12 @@ jobs: cmake-version: "3.10" if: success() || failure() + cmake-config-ubuntu-2004: + name: CMake config check (Ubuntu 20.04) + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v3 + - name: Check CMake 3.11 (full) uses: ./.github/actions/quick_cmake with: @@ -211,6 +230,12 @@ jobs: cmake-version: "3.16" if: success() || failure() + cmake-config-ubuntu-2204: + name: CMake config check (Ubuntu 22.04) + runs-on: ubuntu-22.04 + steps: + - uses: actions/checkout@v3 + - name: Check CMake 3.17 uses: ./.github/actions/quick_cmake with: @@ -235,16 +260,34 @@ jobs: cmake-version: "3.20" if: success() || failure() - - name: Check CMake 3.21 (full) + - name: Check CMake 3.21 uses: ./.github/actions/quick_cmake with: cmake-version: "3.21" - args: -DCLI11_SANITIZERS=ON -DCLI11_BUILD_EXAMPLES_JSON=ON if: success() || failure() - - name: Check CMake 3.22 (full) + - name: Check CMake 3.22 uses: ./.github/actions/quick_cmake with: cmake-version: "3.22" + if: success() || failure() + + - name: Check CMake 3.23 + uses: ./.github/actions/quick_cmake + with: + cmake-version: "3.23" + if: success() || failure() + + - name: Check CMake 3.24 (full) + uses: ./.github/actions/quick_cmake + with: + cmake-version: "3.24" + args: -DCLI11_SANITIZERS=ON -DCLI11_BUILD_EXAMPLES_JSON=ON + if: success() || failure() + + - name: Check CMake 3.25 (full) + uses: ./.github/actions/quick_cmake + with: + cmake-version: "3.25" args: -DCLI11_SANITIZERS=ON -DCLI11_BUILD_EXAMPLES_JSON=ON if: success() || failure() diff --git a/packages/CLI11/.gitignore b/packages/CLI11/.gitignore index cc1b9d0c7f77776f258bfccbe82224fe588e9582..7b9bcb27f4929ac6cef23bf8df3bc20a4dadf31f 100644 --- a/packages/CLI11/.gitignore +++ b/packages/CLI11/.gitignore @@ -6,10 +6,15 @@ a.out* /CMakeFiles/* /cmake_install.cmake /*.kdev4 +/.vscode /html/* !/meson.build +/CMakeUserPresets.json /node_modules/* /package.json /yarn.lock /CLI11.hpp + +/subprojects/Catch2-* +/subprojects/packagecache diff --git a/packages/CLI11/.gitrepo b/packages/CLI11/.gitrepo index 9d9e10f1cda0a2c2c89c32188c30c3a0d76fad5a..925d8dccefb3a338b66e4a57300b3d1e983bf56a 100644 --- a/packages/CLI11/.gitrepo +++ b/packages/CLI11/.gitrepo @@ -6,7 +6,7 @@ [subrepo] remote = git@github.com:CLIUtils/CLI11.git branch = main - commit = faea921e4004af91763b8fde905de3baf24d3945 - parent = 8a1fd5ded4230d6e9b9bf69951058fc12a19ad4a + commit = 784fa3ebd387e63feef41d174f587bbe4cfec4da + parent = 164bbb3a73dc902c29aa7ccabfaba50cefa6345d method = merge - cmdver = 0.4.3 + cmdver = 0.4.6 diff --git a/packages/CLI11/.pre-commit-config.yaml b/packages/CLI11/.pre-commit-config.yaml index 0d6d0c0116207798b0c1c88a525ed43359d63789..febf04dd23f4f524d646fefc2b093a35792dfdbe 100644 --- a/packages/CLI11/.pre-commit-config.yaml +++ b/packages/CLI11/.pre-commit-config.yaml @@ -1,27 +1,32 @@ +exclude: ^(.github/workflows/|docs/img/) ci: autoupdate_commit_msg: "chore(deps): pre-commit.ci autoupdate" autofix_commit_msg: "style: pre-commit.ci fixes" repos: - repo: https://github.com/psf/black - rev: 22.6.0 + rev: 23.3.0 hooks: - id: black - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.3.0 + rev: v4.4.0 hooks: - id: check-added-large-files - id: check-case-conflict - id: check-merge-conflict - id: check-symlinks - id: check-yaml + - id: check-toml - id: end-of-file-fixer - id: mixed-line-ending - id: trailing-whitespace + - id: check-shebang-scripts-are-executable + - id: check-executables-have-shebangs + - id: debug-statements - repo: https://github.com/pre-commit/mirrors-clang-format - rev: v14.0.6 + rev: v16.0.3 hooks: - id: clang-format types_or: [c++, c, cuda] @@ -33,17 +38,19 @@ repos: additional_dependencies: [pyyaml] - repo: https://github.com/pre-commit/mirrors-prettier - rev: "v2.7.1" + rev: "v3.0.0-alpha.9-for-vscode" hooks: - id: prettier types_or: [yaml, markdown, html, css, scss, javascript, json] args: [--prose-wrap=always] - repo: https://github.com/markdownlint/markdownlint - rev: v0.11.0 + rev: v0.12.0 hooks: - id: markdownlint args: ["--style=scripts/mdlint_style.rb"] + # Uncomment on macOS - Apple has deprecated Ruby, so macOS is stuck on 2.6 + # language_version: 3.1.2 # - repo: local # hooks: @@ -80,7 +87,7 @@ repos: exclude: .pre-commit-config.yaml - repo: https://github.com/codespell-project/codespell - rev: v2.1.0 + rev: v2.2.4 hooks: - id: codespell args: ["-L", "atleast,ans,doub,inout"] diff --git a/packages/CLI11/CHANGELOG.md b/packages/CLI11/CHANGELOG.md index 04b9cd2a9bee4ccf1845fd689e84b49a8e4ac4f4..4fc3ccdd145c93636f2e98a13f8b157a4ca27d31 100644 --- a/packages/CLI11/CHANGELOG.md +++ b/packages/CLI11/CHANGELOG.md @@ -1,5 +1,75 @@ # Changelog +## Version 2.3: Precompilation Support + +This version adds a pre-compiled mode to CLI11, which allows you to precompile +the library, saving time on incremental rebuilds, making CLI11 more competitive +on compile time with classic compiled CLI libraries. The header-only mode is +still default, and is not yet distributed via binaries. + +- Add `CLI11_PRECOMPILED` as an option. [#762][] +- Bugfix: Include `<functional>` in `FormatterFwd` [#727][] +- Bugfix: Add missing `Macros.hpp` to `Error.hpp` [#755][] +- Bugfix: Fix subcommand callback trigger [#733][] +- Bugfix: Variable rename to avoid warning [#734][] +- Bugfix: `split_program_name` single file name error [#740][] +- Bugfix: Better support for min/max overrides on MSVC [#741][] +- Bugfix: Support MSVC 2022 [#748][] +- Bugfix: Support negated flag in config file [#775][] +- Bugfix: Better errors for some confusing config file situations [#781][] +- Backend: Restore coverage testing (lost with Travis CI) [#747][] + +[#727]: https://github.com/CLIUtils/CLI11/pull/727 +[#733]: https://github.com/CLIUtils/CLI11/pull/733 +[#734]: https://github.com/CLIUtils/CLI11/pull/734 +[#740]: https://github.com/CLIUtils/CLI11/pull/740 +[#741]: https://github.com/CLIUtils/CLI11/pull/741 +[#747]: https://github.com/CLIUtils/CLI11/pull/747 +[#748]: https://github.com/CLIUtils/CLI11/pull/748 +[#755]: https://github.com/CLIUtils/CLI11/pull/755 +[#762]: https://github.com/CLIUtils/CLI11/pull/762 +[#775]: https://github.com/CLIUtils/CLI11/pull/775 +[#781]: https://github.com/CLIUtils/CLI11/pull/781 + +### Version 2.3.1: Missing implementation + +A function implementation was missing after the pre-compile move, missed due to +the fact we lost 100% after losing coverage checking. We are working on filling +out 100% coverage again to ensure this doesn't happen again! + +- Bugfix: `App::get_option_group` implementation missing [#793][] +- Bugfix: Fix spacing when setting an empty footer [#796][] +- Bugfix: Address Klocwork static analysis checking issues [#785][] + +[#785]: https://github.com/CLIUtils/CLI11/pull/785 +[#793]: https://github.com/CLIUtils/CLI11/pull/793 +[#796]: https://github.com/CLIUtils/CLI11/pull/796 + +### Version 2.3.2: Minor maintenance + +This version provides a few fixes collected over the last three months before +adding features for 2.4. + +- Bugfix: Consistently use ADL for `lexical_cast`, making it easier to extend + for custom template types [#820][] +- Bugfix: Tweak the parsing of files for flags with `disable_flag_override` + [#800][] +- Bugfix: Handle out of bounds long long [#807][] +- Bugfix: Spacing of `make_description` min option output [#808][] +- Bugfix: Print last parsed subcommand's help message [#822][] +- Bugfix: Avoid floating point warning in GCC 12 [#803][] +- Bugfix: Fix a few gcc warnings [#813][] +- Backend: Max CMake tested 3.22 -> 3.24 [#823][] + +[#800]: https://github.com/CLIUtils/CLI11/pull/800 +[#803]: https://github.com/CLIUtils/CLI11/pull/803 +[#807]: https://github.com/CLIUtils/CLI11/pull/807 +[#808]: https://github.com/CLIUtils/CLI11/pull/808 +[#813]: https://github.com/CLIUtils/CLI11/pull/813 +[#820]: https://github.com/CLIUtils/CLI11/pull/820 +[#822]: https://github.com/CLIUtils/CLI11/pull/822 +[#823]: https://github.com/CLIUtils/CLI11/pull/823 + ## Version 2.2: Option and Configuration Flexibility New features include support for output of an empty vector, a summing option diff --git a/packages/CLI11/CLI11.hpp.in b/packages/CLI11/CLI11.hpp.in index 83f228ebf0cfeb2eb9da1dd0e18e03ef0c731431..edc16bb15f6468287a4410d9c14fa258e0356003 100644 --- a/packages/CLI11/CLI11.hpp.in +++ b/packages/CLI11/CLI11.hpp.in @@ -5,7 +5,7 @@ // This is a standalone header file generated by MakeSingleHeader.py in CLI11/scripts // from: {git} // -// CLI11 {version} Copyright (c) 2017-2022 University of Cincinnati, developed by Henry +// CLI11 {version} Copyright (c) 2017-2023 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 @@ -42,8 +42,20 @@ {validators_hpp_filesystem} +{encoding_includes} + +{argv_inl_includes} + namespace {namespace} {{ +{encoding_hpp} + +{encoding_inl_hpp} + +{argv_hpp} + +{argv_inl_hpp} + {string_tools_hpp} {string_tools_inl_hpp} diff --git a/packages/CLI11/CMakeLists.txt b/packages/CLI11/CMakeLists.txt index 31bbbbb6cbf6cdda4b6512eb253a0dfec3868cc6..cdc2011d8800199f8c752523446391905eba808a 100644 --- a/packages/CLI11/CMakeLists.txt +++ b/packages/CLI11/CMakeLists.txt @@ -2,14 +2,14 @@ cmake_minimum_required(VERSION 3.4) # Note: this is a header only library. If you have an older CMake than 3.4, # just add the CLI11/include directory and that's all you need to do. -# Make sure users don't get warnings on a tested (3.4 to 3.22) version +# Make sure users don't get warnings on a tested (3.4 to 3.24) version # of CMake. For most of the policies, the new version is better (hence the change). -# We don't use the 3.4...3.21 syntax because of a bug in an older MSVC's +# We don't use the 3.4...3.24 syntax because of a bug in an older MSVC's # built-in and modified CMake 3.11 -if(${CMAKE_VERSION} VERSION_LESS 3.22) +if(${CMAKE_VERSION} VERSION_LESS 3.25) cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}) else() - cmake_policy(VERSION 3.22) + cmake_policy(VERSION 3.25) endif() set(VERSION_REGEX "#define CLI11_VERSION[ \t]+\"(.+)\"") @@ -132,6 +132,9 @@ endif() include(CLI11Warnings) +# build the fuzzing example or fuzz entry point +add_subdirectory(fuzz) + add_subdirectory(src) # Allow tests to be run on CUDA diff --git a/packages/CLI11/CPPLINT.cfg b/packages/CLI11/CPPLINT.cfg index 24dd86524af1957a994dba4fe53b65f4406d20ad..40bec3714ed59c7101b1d168b9ccdb087a611132 100644 --- a/packages/CLI11/CPPLINT.cfg +++ b/packages/CLI11/CPPLINT.cfg @@ -4,6 +4,7 @@ linelength=120 # As in .clang-format # Unused filters filter=-build/c++11 # Reports e.g. chrono and thread, which overlap with Chromium's API. Not applicable to general C++ projects. filter=-build/include_order # Requires unusual include order that encourages creating not self-contained headers +filter=-build/include_subdir # Prevents including files in current directory for whatever reason filter=-readability/nolint # Conflicts with clang-tidy filter=-readability/check # Catch uses CHECK(a == b) (Tests only) filter=-build/namespaces # Currently using it for one test (Tests only) diff --git a/packages/CLI11/LICENSE b/packages/CLI11/LICENSE index 71c4770317617032abe4bc36a4ab98d470f4d20c..aae15855ecf563d8e62cd6458a0c99b96be4e5b1 100644 --- a/packages/CLI11/LICENSE +++ b/packages/CLI11/LICENSE @@ -1,4 +1,4 @@ -CLI11 2.2 Copyright (c) 2017-2022 University of Cincinnati, developed by Henry +CLI11 2.2 Copyright (c) 2017-2023 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 diff --git a/packages/CLI11/README.md b/packages/CLI11/README.md index 989ef43b2a665865b897a96243e3d1f8c9c17921..bd9e58db71e02dfe4a479c283783b37bf281193f 100644 --- a/packages/CLI11/README.md +++ b/packages/CLI11/README.md @@ -50,6 +50,7 @@ set with a simple and intuitive interface. - [Formatting](#formatting) - [Subclassing](#subclassing) - [How it works](#how-it-works) + - [Unicode support](#unicode-support) - [Utilities](#utilities) - [Other libraries](#other-libraries) - [API](#api) @@ -164,9 +165,6 @@ this library: option to disable it). - Autocomplete: This might eventually be added to both Plumbum and CLI11, but it is not supported yet. -- Wide strings / unicode: Since this uses the standard library only, it might be - hard to properly implement, but I would be open to suggestions in how to do - this. ## Install @@ -278,13 +276,13 @@ To set up, add options, and run, your main function will look something like this: ```cpp -int main(int argc, char** argv) { +int main() { CLI::App app{"App description"}; std::string filename = "default"; app.add_option("-f,--file", filename, "A help string"); - CLI11_PARSE(app, argc, argv); + CLI11_PARSE(app); return 0; } ``` @@ -293,7 +291,7 @@ int main(int argc, char** argv) { ```cpp try { - app.parse(argc, argv); + app.parse(); } catch (const CLI::ParseError &e) { return app.exit(e); } @@ -306,6 +304,25 @@ inside the catch block; for example, help flags intentionally short-circuit all other processing for speed and to ensure required options and the like do not interfere. +</p></details> + +<details><summary>Note: Why are argc and argv not used? (click to expand)</summary><p> + +`argc` and `argv` may contain incorrect information on Windows when unicode text +is passed in. Check out a section on [unicode support](#unicode-support) below. + +If this is not a concern, you can explicitly pass `argc` and `argv` from main or +from an external preprocessor of CLI arguments to `parse`: + +```cpp +int main(int argc, char** argv) { + // ... + + CLI11_PARSE(app, argc, argv); + return 0; +} +``` + </p></details> </br> @@ -530,7 +547,7 @@ Before parsing, you can set the following options: are `CLI::MultiOptionPolicy::Throw`, `CLI::MultiOptionPolicy::Throw`, `CLI::MultiOptionPolicy::TakeLast`, `CLI::MultiOptionPolicy::TakeFirst`, `CLI::MultiOptionPolicy::Join`, `CLI::MultiOptionPolicy::TakeAll`, and - `CLI::MultiOptionPolicy::Sum` 🚧. + `CLI::MultiOptionPolicy::Sum` 🆕. - `->check(std::string(const std::string &), validator_name="",validator_description="")`: Define a check function. The function should return a non empty string with the error message if the check fails @@ -571,7 +588,7 @@ Before parsing, you can set the following options: - `->trigger_on_parse()`: If set, causes the callback and all associated validation checks for the option to be executed when the option value is parsed vs. at the end of all parsing. This could cause the callback to be - executed multiple times. Also works with positional options 🆕. + executed multiple times. Also works with positional options. 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 @@ -658,7 +675,7 @@ CLI11 has several Validators built-in that perform some common checks - `CLI::ExistingDirectory`: Requires that the directory exists. - `CLI::ExistingPath`: Requires that the path (file or directory) exists. - `CLI::NonexistentPath`: Requires that the path does not exist. -- `CLI::FileOnDefaultPath`: 🆕 Best used as a transform, Will check that a file +- `CLI::FileOnDefaultPath`: Best used as a transform, Will check that a file exists either directly or in a default path and update the path appropriately. See [Transforming Validators](#transforming-validators) for more details - `CLI::Range(min,max)`: Requires that the option be between min and max (make @@ -938,6 +955,20 @@ nameless subcommands are allowed. Callbacks for nameless subcommands are only triggered if any options from the subcommand were parsed. Subcommand names given through the `add_subcommand` method have the same restrictions as option names. +🚧 Options or flags in a subcommand may be directly specified using dot notation + +- `--subcommand.long=val` (long subcommand option) +- `--subcommand.long val` (long subcommand option) +- `--subcommand.f=val` (short form subcommand option) +- `--subcommand.f val` (short form subcommand option) +- `--subcommand.f` (short form subcommand flag) +- `--subcommand1.subsub.f val` (short form nested subcommand option) + +The use of dot notation in this form is equivalent `--subcommand.long <args>` => +`subcommand --long <args> ++`. Nested subcommands also work `"sub1.subsub"` +would trigger the subsub subcommand in `sub1`. This is equivalent to "sub1 +subsub" + #### Subcommand options There are several options that are supported on the main app and subcommands and @@ -1062,6 +1093,10 @@ option_groups. These are: - `.prefix_command()`: Like `allow_extras`, but stop immediately on the first unrecognized item. It is ideal for allowing your app or subcommand to be a "prefix" to calling another app. +- `.usage(message)`: Replace text to appear at the start of the help string + after description. +- `.usage(std::string())`: Set a callback to generate a string that will appear + at the start of the help string after description. - `.footer(message)`: Set text to appear at the bottom of the help string. - `.footer(std::string())`: Set a callback to generate a string that will appear at the end of the help string. @@ -1356,8 +1391,9 @@ multiple calls or using `|` operations with the transform. 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. +`usage`, `footer`, `immediate_callback` and maximum number of required +subcommands. The help flag existence, name, and description are inherited, as +well. Options have defaults for `group`, `required`, `multi_option_policy`, `ignore_case`, `ignore_underscore`, `delimiter`, and `disable_flag_override`. To @@ -1435,11 +1471,10 @@ provide a custom `operator>>` with an `istream` (inside the CLI namespace is fine if you don't want to interfere with an existing `operator>>`). If you wanted to extend this to support a completely new type, use a lambda or -add a specialization of the `lexical_cast` function template in the namespace of -the type you need to convert to. Some examples of some new parsers for -`complex<double>` that support all of the features of a standard `add_options` -call are in [one of the tests](./tests/NewParseTest.cpp). A simpler example is -shown below: +add an overload of the `lexical_cast` function in the namespace of the type you +need to convert to. Some examples of some new parsers for `complex<double>` that +support all of the features of a standard `add_options` call are in +[one of the tests](./tests/NewParseTest.cpp). A simpler example is shown below: #### Example @@ -1450,6 +1485,96 @@ app.add_option("--fancy-count", [](std::vector<std::string> val){ }); ``` +### Unicode support + +CLI11 supports Unicode and wide strings as defined in the +[UTF-8 Everywhere](http://utf8everywhere.org/) manifesto. In particular: + +- The library can parse a wide version of command-line arguments on Windows, + which are converted internally to UTF-8 (more on this below); +- You can store option values in `std::wstring`, in which case they will be + converted to a correct wide string encoding on your system (UTF-16 on Windows + and UTF-32 on most other systems); +- Instead of storing wide strings, it is recommended to use provided `widen` and + `narrow` functions to convert to and from wide strings when actually necessary + (such as when calling into Windows APIs). + +When using the command line on Windows with unicode arguments, your `main` +function may already receive broken Unicode. Parsing `argv` at that point will +not give you a correct string. To fix this, you have three options: + +1. If you pass unmodified command-line arguments to CLI11, call `app.parse()` + instead of `app.parse(argc, argv)` (or `CLI11_PARSE(app)` instead of + `CLI11_PARSE(app, argc, argv)`). The library will find correct arguments + itself. + + ```cpp + int main() { + CLI::App app; + // ... + CLI11_PARSE(app); + } + ``` + +2. Get correct arguments with which the program was originally executed using + provided functions: `CLI::argc()` and `CLI::argv()`. These two methods are + the only cross-platform ways of handling unicode correctly. + + ```cpp + int main() { + CLI::App app; + // ... + CLI11_PARSE(app, CLI::argc(), CLI::argv()); + } + ``` + +3. Use the Windows-only non-standard `wmain` function, which accepts + `wchar_t *argv[]` instead of `char* argv[]`. Parsing this will allow CLI to + convert wide strings to UTF-8 without losing information. + + ```cpp + int wmain(int argc, wchar_t *argv[]) { + CLI::App app; + // ... + CLI11_PARSE(app, argc, argv); + } + ``` + +4. Retrieve arguments yourself by using Windows APIs like + [`CommandLineToArgvW`](https://learn.microsoft.com/en-us/windows/win32/api/shellapi/nf-shellapi-commandlinetoargvw) + and pass them to CLI. This is what the library is doing under the hood in + `CLI::argv()`. + +The library provides functions to convert between UTF-8 and wide strings: + +```cpp +namespace CLI { + std::string narrow(const std::wstring &str); + std::string narrow(const wchar_t *str); + std::string narrow(const wchar_t *str, std::size_t size); + std::string narrow(std::wstring_view str); // C++17 + + std::wstring widen(const std::string &str); + std::wstring widen(const char *str); + std::wstring widen(const char *str, std::size_t size); + std::wstring widen(std::string_view str); // C++17 +} +``` + +#### Note on using Unicode paths + +When creating a `filesystem::path` from a UTF-8 path on Windows, you need to +convert it to a wide string first. CLI11 provides a platform-independent +`to_path` function, which will convert a UTF-8 string to path, the right way: + +```cpp +std::string utf8_name = "Hello Halló Привет 你好 👩🚀❤️.txt"; + +std::filesystem::path p = CLI::to_path(utf8_name); +std::ifstream stream(CLI::to_path(utf8_name)); +// etc. +``` + ### Utilities There are a few other utilities that are often useful in CLI programming. These diff --git a/packages/CLI11/azure-pipelines.yml b/packages/CLI11/azure-pipelines.yml index c519e153bada7ff93a1132256f285c5b75c3a053..1bb4d0771abf016d442eaa33300dc31698ca9825 100644 --- a/packages/CLI11/azure-pipelines.yml +++ b/packages/CLI11/azure-pipelines.yml @@ -28,7 +28,6 @@ jobs: - bash: cpplint --counting=detailed --recursive examples include/CLI tests displayName: Checking against google style guide - # TODO: Fix macOS error and windows warning in c++17 mode - job: Native strategy: matrix: @@ -38,13 +37,13 @@ jobs: vmImage: "ubuntu-latest" cli11.precompile: ON macOS17: - vmImage: "macOS-latest" + vmImage: "macOS-12" cli11.std: 17 macOS11: - vmImage: "macOS-latest" + vmImage: "macOS-11" cli11.std: 11 macOS11PC: - vmImage: "macOS-latest" + vmImage: "macOS-11" cli11.std: 11 cli11.precompile: ON Windows17: @@ -57,10 +56,14 @@ jobs: Windows11: vmImage: "windows-2019" cli11.std: 11 - WindowsLatest: - vmImage: "windows-2019" + Windows20: + vmImage: "windows-2022" cli11.std: 20 - cli11.options: -DCMAKE_CXX_FLAGS="/std:c++latest /EHsc" + cli11.options: -DCMAKE_CXX_FLAGS="/EHsc" + WindowsLatest: + vmImage: "windows-2022" + cli11.std: 23 + cli11.options: -DCMAKE_CXX_FLAGS="/EHsc" Linux17nortti: vmImage: "ubuntu-latest" cli11.std: 17 @@ -106,13 +109,14 @@ jobs: gcc11: containerImage: gcc:11 cli11.std: 20 - gcc8: - containerImage: gcc:8 - cli11.std: 17 + cli11.options: -DCMAKE_CXX_FLAGS="-Wredundant-decls -Wconversion" + gcc7: + containerImage: gcc:7 + cli11.std: 14 + cli11.options: -DCMAKE_CXX_FLAGS="-Wconversion" gcc4.8: containerImage: helics/buildenv:gcc4-8-builder cli11.std: 11 - cli11.options: clang3.4: containerImage: silkeh/clang:3.4 cli11.std: 11 diff --git a/packages/CLI11/book/chapters/config.md b/packages/CLI11/book/chapters/config.md index d062c659583a3eee22c53f15e6d6421e6be3442e..30ca48effb6a5370bd6bdba1fc73e521b5737339 100644 --- a/packages/CLI11/book/chapters/config.md +++ b/packages/CLI11/book/chapters/config.md @@ -87,6 +87,10 @@ app.allow_config_extras(CLI::config_extras_mode::ignore_all); will completely ignore any mismatches, extras, or other issues with the config file +Config file extras are stored in the remaining output as two components. The +first is the name of the field including subcommands using dot notation the +second (or more) are the argument fields. + ### Getting the used configuration file name If it is needed to get the configuration file name used this can be obtained via diff --git a/packages/CLI11/book/chapters/internals.md b/packages/CLI11/book/chapters/internals.md index f8479c545fe4489741041f8127d3fab8ee602165..c2c98b75bd8c9d8a9c02a1ca351204a7751834d5 100644 --- a/packages/CLI11/book/chapters/internals.md +++ b/packages/CLI11/book/chapters/internals.md @@ -8,9 +8,9 @@ classes or inheritance. This is accomplished through lambda functions. This looks like: ```cpp -Option* add_option(string name, T item) { +Option* add_option(string name, T &item) { this->function = [&item](string value){ - item = detail::lexical_cast<T>(value); + return lexical_cast(value, item); } } ``` diff --git a/packages/CLI11/conanfile.py b/packages/CLI11/conanfile.py deleted file mode 100644 index 9dc6ce3ade6d55676541b7e789c6a0dd3608f892..0000000000000000000000000000000000000000 --- a/packages/CLI11/conanfile.py +++ /dev/null @@ -1,49 +0,0 @@ -from conans import ConanFile, CMake -from conans.tools import load, cross_building -import re - - -def get_version(): - try: - content = load("include/CLI/Version.hpp") - version = re.search(r'#define CLI11_VERSION "(.*)"', content).group(1) - return version - except Exception: - return None - - -class CLI11Conan(ConanFile): - name = "CLI11" - version = get_version() - description = "Command Line Interface toolkit for C++11" - topics = ("cli", "c++11", "parser", "cli11") - url = "https://github.com/CLIUtils/CLI11" - homepage = "https://github.com/CLIUtils/CLI11" - author = "Henry Schreiner <hschrein@cern.ch>" - license = "BSD-3-Clause" - - settings = "os", "compiler", "arch", "build_type" - exports_sources = ( - "LICENSE", - "README.md", - "include/*", - "src/*", - "extern/*", - "cmake/*", - "CMakeLists.txt", - "CLI11.CPack.Description.txt", - "tests/*", - ) - - def build(self): # this is not building a library, just tests - cmake = CMake(self) - cmake.definitions["CLI11_EXAMPLES"] = "OFF" - cmake.definitions["CLI11_SINGLE_FILE"] = "OFF" - cmake.configure() - cmake.build() - if not cross_building(self.settings): - cmake.test() - cmake.install() - - def package_id(self): - self.info.header_only() diff --git a/packages/CLI11/examples/callback_passthrough.cpp b/packages/CLI11/examples/callback_passthrough.cpp index 833ef6fcf1e7593b34d5d6add8de93468b4c7909..1aac0df6beef22f1431159bda5099e9c6142ad6b 100644 --- a/packages/CLI11/examples/callback_passthrough.cpp +++ b/packages/CLI11/examples/callback_passthrough.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2022, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // diff --git a/packages/CLI11/examples/config_app.cpp b/packages/CLI11/examples/config_app.cpp index 986e80d9e2f468ea2f4857ad4a433c484b57be27..a0426ad616b7221533047ae16c1ae4a0f734712d 100644 --- a/packages/CLI11/examples/config_app.cpp +++ b/packages/CLI11/examples/config_app.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2022, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // diff --git a/packages/CLI11/examples/custom_parse.cpp b/packages/CLI11/examples/custom_parse.cpp index 793ddfab2b665308821309bf4c3fa56392a8e877..eaaedd552f65af734eec6c9d1a2e048cbfa53369 100644 --- a/packages/CLI11/examples/custom_parse.cpp +++ b/packages/CLI11/examples/custom_parse.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2022, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // diff --git a/packages/CLI11/examples/digit_args.cpp b/packages/CLI11/examples/digit_args.cpp index 023be6c63884e6617ab8664a33a1d1889424b503..a0785ddbdf7c1df5c8123e7cb2b24241b0b4be66 100644 --- a/packages/CLI11/examples/digit_args.cpp +++ b/packages/CLI11/examples/digit_args.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2022, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // diff --git a/packages/CLI11/examples/enum.cpp b/packages/CLI11/examples/enum.cpp index 90684333fbb106c84282fbab423d89e5eb394a1a..133adde9aca4c62e755b3597e6e299af2c0b7e54 100644 --- a/packages/CLI11/examples/enum.cpp +++ b/packages/CLI11/examples/enum.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2022, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // diff --git a/packages/CLI11/examples/enum_ostream.cpp b/packages/CLI11/examples/enum_ostream.cpp index 4ccc6a00c42d1c57f1a99641e12d2f9ce75d5b5b..1f8ac57e409d637f1533b4adbadf649d5976a134 100644 --- a/packages/CLI11/examples/enum_ostream.cpp +++ b/packages/CLI11/examples/enum_ostream.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2022, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // diff --git a/packages/CLI11/examples/formatter.cpp b/packages/CLI11/examples/formatter.cpp index 09d6dce10753479111a6ca97a82eda3a166445fb..4973cf95f9ec27334fda2a02fe3aef461d84da43 100644 --- a/packages/CLI11/examples/formatter.cpp +++ b/packages/CLI11/examples/formatter.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2022, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // diff --git a/packages/CLI11/examples/groups.cpp b/packages/CLI11/examples/groups.cpp index 2ebc1e88a5b7cd890a3041b3b107dbabb558c7fb..09c5d6ba2bdfea9108ad9cbc4bbaaf092e4be4c0 100644 --- a/packages/CLI11/examples/groups.cpp +++ b/packages/CLI11/examples/groups.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2022, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // diff --git a/packages/CLI11/examples/inter_argument_order.cpp b/packages/CLI11/examples/inter_argument_order.cpp index 8fe063e396be399bd7ec38b6b933bb3f5d37758c..e8c489c2ad34e7d349655dca9ab9ef5bf2cfb3b1 100644 --- a/packages/CLI11/examples/inter_argument_order.cpp +++ b/packages/CLI11/examples/inter_argument_order.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2022, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // diff --git a/packages/CLI11/examples/modhelp.cpp b/packages/CLI11/examples/modhelp.cpp index ac2ba1759d2952a96f5d4173aaaf8a66dff45d35..d0f8cf87525194e9f3ac69b8b9c80ea5c6efecac 100644 --- a/packages/CLI11/examples/modhelp.cpp +++ b/packages/CLI11/examples/modhelp.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2022, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // diff --git a/packages/CLI11/examples/nested.cpp b/packages/CLI11/examples/nested.cpp index 07aa75fb85f5959619d7caa0a3660722be9c52ed..3587023ac3b813413d033b7ba1b3d80178bcc5dc 100644 --- a/packages/CLI11/examples/nested.cpp +++ b/packages/CLI11/examples/nested.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2022, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // diff --git a/packages/CLI11/examples/option_groups.cpp b/packages/CLI11/examples/option_groups.cpp index 9799bdc6e4ac6f07ecbcf85526acfc1b4bcc3aaa..3a282536bc00ee11e97a15dbde36f6b846cf1e09 100644 --- a/packages/CLI11/examples/option_groups.cpp +++ b/packages/CLI11/examples/option_groups.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2022, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // diff --git a/packages/CLI11/examples/positional_arity.cpp b/packages/CLI11/examples/positional_arity.cpp index e8c45c4d61016596bfbdcd98d45a588d31a53e8d..d2d9b9c89a108cd82a04ae86f1375962889a90c9 100644 --- a/packages/CLI11/examples/positional_arity.cpp +++ b/packages/CLI11/examples/positional_arity.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2022, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // diff --git a/packages/CLI11/examples/positional_validation.cpp b/packages/CLI11/examples/positional_validation.cpp index ce676d9d185b812545f990a975f26ca4c03c8463..6b552daa536a4842b6600ced176ad37d30a3f0c1 100644 --- a/packages/CLI11/examples/positional_validation.cpp +++ b/packages/CLI11/examples/positional_validation.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2022, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // diff --git a/packages/CLI11/examples/prefix_command.cpp b/packages/CLI11/examples/prefix_command.cpp index 61639411fd0ca18065d4331147e35639934109e4..843f40374616283f9208004237f8e95fdf651bb8 100644 --- a/packages/CLI11/examples/prefix_command.cpp +++ b/packages/CLI11/examples/prefix_command.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2022, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // diff --git a/packages/CLI11/examples/ranges.cpp b/packages/CLI11/examples/ranges.cpp index 42b1659c3bf88897a5a732cf0e7c68c4631357fe..ec14905bf4be51920b6aee7df10cdb3dea3909aa 100644 --- a/packages/CLI11/examples/ranges.cpp +++ b/packages/CLI11/examples/ranges.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2022, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // diff --git a/packages/CLI11/examples/retired.cpp b/packages/CLI11/examples/retired.cpp index 0a3a5fbeee541fca69701dae47be5af1074e160c..28f61da04dfc5f867e5f6c79cd67cf4c1fc35c2c 100644 --- a/packages/CLI11/examples/retired.cpp +++ b/packages/CLI11/examples/retired.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2022, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // diff --git a/packages/CLI11/examples/shapes.cpp b/packages/CLI11/examples/shapes.cpp index d9f47cab101261afee5c23118a539c70a3e0a10a..d3f48ac73c40bb395321fbc88532b341af33eddc 100644 --- a/packages/CLI11/examples/shapes.cpp +++ b/packages/CLI11/examples/shapes.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2022, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // diff --git a/packages/CLI11/examples/simple.cpp b/packages/CLI11/examples/simple.cpp index 5f94bc835186f619362b5d304fd4c27a9fcdb8cc..b7095dd2cc703648668e8921efa43b01c6308ad2 100644 --- a/packages/CLI11/examples/simple.cpp +++ b/packages/CLI11/examples/simple.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2022, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // diff --git a/packages/CLI11/examples/subcom_help.cpp b/packages/CLI11/examples/subcom_help.cpp index 952060de9a4b345d4c5343956acc41da5b88d22f..65030eb86aa0bae0f9c6a29fe0fdc71ccc967adc 100644 --- a/packages/CLI11/examples/subcom_help.cpp +++ b/packages/CLI11/examples/subcom_help.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2022, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // diff --git a/packages/CLI11/examples/subcom_in_files/subcommand_a.cpp b/packages/CLI11/examples/subcom_in_files/subcommand_a.cpp index 9ad65e01e2021212d8de3e68be82fe6d954ea8f8..bb1a6a13d4bc1c6a7d2acffc2998caae8c8bb2f0 100644 --- a/packages/CLI11/examples/subcom_in_files/subcommand_a.cpp +++ b/packages/CLI11/examples/subcom_in_files/subcommand_a.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2022, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // diff --git a/packages/CLI11/examples/subcom_in_files/subcommand_a.hpp b/packages/CLI11/examples/subcom_in_files/subcommand_a.hpp index cfa4883ea0f1770c1be703c76d163217e9faf65e..6a8395d1a3219efc67417b563ecf75da3eea1943 100644 --- a/packages/CLI11/examples/subcom_in_files/subcommand_a.hpp +++ b/packages/CLI11/examples/subcom_in_files/subcommand_a.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2022, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // diff --git a/packages/CLI11/examples/subcom_in_files/subcommand_main.cpp b/packages/CLI11/examples/subcom_in_files/subcommand_main.cpp index 1ff5fdad9af3e98601ad182d5541895a9d79be4f..e65339c901c52689c3e3b1b045d6d8ca463a70f0 100644 --- a/packages/CLI11/examples/subcom_in_files/subcommand_main.cpp +++ b/packages/CLI11/examples/subcom_in_files/subcommand_main.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2022, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // diff --git a/packages/CLI11/examples/subcom_partitioned.cpp b/packages/CLI11/examples/subcom_partitioned.cpp index 493ac0e383be0a2775ec1d9131562635926cb039..b6273eaed6fdd1737e0a92008958a81237a457d2 100644 --- a/packages/CLI11/examples/subcom_partitioned.cpp +++ b/packages/CLI11/examples/subcom_partitioned.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2022, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // diff --git a/packages/CLI11/examples/subcommands.cpp b/packages/CLI11/examples/subcommands.cpp index 44d4df925e7256012365a47ecea4fc3948babf2d..e69c04eed06971905c53d0b4490028d373fd704b 100644 --- a/packages/CLI11/examples/subcommands.cpp +++ b/packages/CLI11/examples/subcommands.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2022, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // diff --git a/packages/CLI11/examples/testEXE.cpp b/packages/CLI11/examples/testEXE.cpp index b63fa9499ac49d9e841036247b6842e854b5b773..b2cac7fbae1d93ef91eeb09a593c8bf8ee1e0e35 100644 --- a/packages/CLI11/examples/testEXE.cpp +++ b/packages/CLI11/examples/testEXE.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2022, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // diff --git a/packages/CLI11/examples/validators.cpp b/packages/CLI11/examples/validators.cpp index 050be00fb8b5b7e64d82be0d8888c5762255904c..87eb07ab20e09fbe3558c122406aa03ec52ca7e0 100644 --- a/packages/CLI11/examples/validators.cpp +++ b/packages/CLI11/examples/validators.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2022, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // diff --git a/packages/CLI11/fuzz/CMakeLists.txt b/packages/CLI11/fuzz/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..21df4028f53bcb747c1716260238863dfb2c60b8 --- /dev/null +++ b/packages/CLI11/fuzz/CMakeLists.txt @@ -0,0 +1,48 @@ +# Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner +# under NSF AWARD 1414736 and by the respective contributors. +# All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause + +if(CMAKE_CXX_STANDARD GREATER 16) + if(CLI11_FUZZ_TARGET) + + add_executable(cli11_app_fuzzer cli11_app_fuzz.cpp fuzzApp.cpp fuzzApp.hpp) + target_link_libraries(cli11_app_fuzzer PUBLIC CLI11) + set_property(TARGET cli11_app_fuzzer PROPERTY FOLDER "Tests") + + add_executable(cli11_file_fuzzer cli11_file_fuzz.cpp fuzzApp.cpp fuzzApp.hpp) + target_link_libraries(cli11_file_fuzzer PUBLIC CLI11) + set_property(TARGET cli11_file_fuzzer PROPERTY FOLDER "Tests") + + if(NOT CLI11_FUZZ_ARTIFACT_PATH) + set(CLI11_FUZZ_ARTIFACT_PATH ${PROJECT_BINARY_DIR}/fuzz) + endif() + + if(NOT CLI11_FUZZ_TIME) + set(CLI11_FUZZ_TIME 360) + endif() + add_custom_target( + QUICK_CLI11_APP_FUZZ + COMMAND ${CMAKE_COMMAND} -E make_directory corp + COMMAND + cli11_app_fuzzer corp -max_total_time=${CLI11_FUZZ_TIME} -max_len=2048 + -dict=${CMAKE_CURRENT_SOURCE_DIR}/fuzz_dictionary1.txt + -exact_artifact_path=${CLI11_FUZZ_ARTIFACT_PATH}/cli11_app_fail_artifact.txt) + + add_custom_target( + QUICK_CLI11_FILE_FUZZ + COMMAND ${CMAKE_COMMAND} -E make_directory corp + COMMAND + cli11_file_fuzzer corp -max_total_time=${CLI11_FUZZ_TIME} -max_len=2048 + -dict=${CMAKE_CURRENT_SOURCE_DIR}/fuzz_dictionary2.txt + -exact_artifact_path=${CLI11_FUZZ_ARTIFACT_PATH}/cli11_file_fail_artifact.txt) + + else() + if(CLI11_BUILD_EXAMPLES) + add_executable(cli11Fuzz fuzzCommand.cpp fuzzApp.cpp fuzzApp.hpp) + target_link_libraries(cli11Fuzz PUBLIC CLI11) + set_property(TARGET cli11Fuzz PROPERTY FOLDER "Examples") + endif() + endif() +endif() diff --git a/packages/CLI11/fuzz/cli11_app_fuzz.cpp b/packages/CLI11/fuzz/cli11_app_fuzz.cpp new file mode 100644 index 0000000000000000000000000000000000000000..7cd10b889899141bd09d7811ea8c0106fb95b8ae --- /dev/null +++ b/packages/CLI11/fuzz/cli11_app_fuzz.cpp @@ -0,0 +1,30 @@ +// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner +// under NSF AWARD 1414736 and by the respective contributors. +// All rights reserved. +// +// SPDX-License-Identifier: BSD-3-Clause + +#include "fuzzApp.hpp" +#include <CLI/CLI.hpp> +#include <cstring> +#include <exception> +#include <string> + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) { + if(Size == 0) { + return 0; + } + std::string parseString(reinterpret_cast<const char *>(Data), Size); + + CLI::FuzzApp fuzzdata; + + auto app = fuzzdata.generateApp(); + try { + app->parse(parseString); + } catch(const CLI::ParseError &e) { + //(app)->exit(e); + // this just indicates we caught an error known by CLI + } + + return 0; // Non-zero return values are reserved for future use. +} diff --git a/packages/CLI11/fuzz/cli11_file_fuzz.cpp b/packages/CLI11/fuzz/cli11_file_fuzz.cpp new file mode 100644 index 0000000000000000000000000000000000000000..e769114eb9b1ab5ee2fcd8ffc60fed9b29f1710f --- /dev/null +++ b/packages/CLI11/fuzz/cli11_file_fuzz.cpp @@ -0,0 +1,31 @@ +// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner +// under NSF AWARD 1414736 and by the respective contributors. +// All rights reserved. +// +// SPDX-License-Identifier: BSD-3-Clause + +#include "fuzzApp.hpp" +#include <CLI/CLI.hpp> +#include <cstring> +#include <exception> +#include <sstream> +#include <string> + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) { + if(Size == 0) { + return 0; + } + std::string parseString(reinterpret_cast<const char *>(Data), Size); + std::stringstream out(parseString); + CLI::FuzzApp fuzzdata; + + auto app = fuzzdata.generateApp(); + try { + app->parse_from_stream(out); + } catch(const CLI::ParseError &e) { + (app)->exit(e); + // this just indicates we caught an error known by CLI + } + + return 0; // Non-zero return values are reserved for future use. +} diff --git a/packages/CLI11/fuzz/fuzzApp.cpp b/packages/CLI11/fuzz/fuzzApp.cpp new file mode 100644 index 0000000000000000000000000000000000000000..dc401f933a56921c37a45201e767c60fadbfde00 --- /dev/null +++ b/packages/CLI11/fuzz/fuzzApp.cpp @@ -0,0 +1,85 @@ +// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner +// under NSF AWARD 1414736 and by the respective contributors. +// All rights reserved. +// +// SPDX-License-Identifier: BSD-3-Clause + +#include "fuzzApp.hpp" + +namespace CLI { +/* +int32_t val32{0}; + int16_t val16{0}; + int8_t val8{0}; + int64_t val64{0}; + + uint32_t uval32{0}; + uint16_t uval16{0}; + uint8_t uval8{0}; + uint64_t uval64{0}; + + std::atomic<int64_t> atomicval64{0}; + std::atomic<uint64_t> atomicuval64{0}; + + double v1{0}; + float v2{0}; + + std::vector<double> vv1; + std::vector<std::string> vstr; + std::vector<std::vector<double>> vecvecd; + std::vector<std::vector<std::string>> vvs; + std::optional<double> od1; + std::optional<std::string> ods; + std::pair<double, std::string> p1; + std::pair<std::vector<double>, std::string> p2; + std::tuple<int64_t, uint16_t, std::optional<double>> t1; + std::tuple<std::tuple<std::tuple<std::string, double, std::vector<int>>,std::string, double>,std::vector<int>, +std::optional<std::string>> tcomplex; std::string_view vstrv; + + bool flag1{false}; + int flagCnt{0}; + std::atomic<bool> flagAtomic{false}; + */ +std::shared_ptr<CLI::App> FuzzApp::generateApp() { + auto fApp = std::make_shared<CLI::App>("fuzzing App", "fuzzer"); + fApp->set_config("--config"); + fApp->add_flag("-a,--flag"); + fApp->add_flag("-b,--flag2", flag1); + fApp->add_flag("-c{34},--flag3{1}", flagCnt)->disable_flag_override(); + fApp->add_flag("-e,--flagA", flagAtomic); + + fApp->add_option("-d,--opt1", val8); + fApp->add_option("--opt2", val16); + fApp->add_option("--opt3", val32); + fApp->add_option("--opt4", val64); + + fApp->add_option("--opt5", uval8); + fApp->add_option("--opt6", uval16); + fApp->add_option("--opt7", uval32); + fApp->add_option("--opt8", uval64); + + fApp->add_option("--aopt1", atomicval64); + fApp->add_option("--aopt2", atomicuval64); + + fApp->add_option("--dopt1", v1); + fApp->add_option("--dopt2", v2); + + fApp->add_option("--vopt1", vv1); + fApp->add_option("--vopt2", vvs); + fApp->add_option("--vopt3", vstr); + fApp->add_option("--vopt4", vecvecd); + + fApp->add_option("--oopt1", od1); + fApp->add_option("--oopt2", ods); + + fApp->add_option("--tup1", p1); + fApp->add_option("--tup2", t1); + fApp->add_option("--tup4", tcomplex); + + fApp->add_option("--dwrap", dwrap); + fApp->add_option("--iwrap", iwrap); + + return fApp; +} + +} // namespace CLI diff --git a/packages/CLI11/fuzz/fuzzApp.hpp b/packages/CLI11/fuzz/fuzzApp.hpp new file mode 100644 index 0000000000000000000000000000000000000000..01600cc259dce0697c5202bc30a321920a9c36e9 --- /dev/null +++ b/packages/CLI11/fuzz/fuzzApp.hpp @@ -0,0 +1,92 @@ +// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner +// under NSF AWARD 1414736 and by the respective contributors. +// All rights reserved. +// +// SPDX-License-Identifier: BSD-3-Clause +#pragma once + +#ifdef CLI11_SINGLE_FILE +#include "CLI11.hpp" +#else +#include "CLI/CLI.hpp" +#endif + +#include <atomic> +#include <iostream> +#include <memory> +#include <optional> +#include <string> +#include <string_view> +#include <vector> + +namespace CLI { + +class intWrapper64 { + public: + intWrapper64() = default; + explicit intWrapper64(int64_t v) : val(v){}; + CLI11_NODISCARD int64_t value() const { return val; } + + private: + int64_t val{0}; +}; + +class doubleWrapper { + public: + doubleWrapper() = default; + explicit doubleWrapper(double v) : val(v){}; + CLI11_NODISCARD double value() const { return val; } + + private: + double val{0.0}; +}; + +class FuzzApp { + public: + FuzzApp() = default; + + std::shared_ptr<CLI::App> generateApp(); + + int32_t val32{0}; + int16_t val16{0}; + int8_t val8{0}; + int64_t val64{0}; + + uint32_t uval32{0}; + uint16_t uval16{0}; + uint8_t uval8{0}; + uint64_t uval64{0}; + + std::atomic<int64_t> atomicval64{0}; + std::atomic<uint64_t> atomicuval64{0}; + + double v1{0}; + float v2{0}; + + std::vector<double> vv1{}; + std::vector<std::string> vstr{}; + std::vector<std::vector<double>> vecvecd{}; + std::vector<std::vector<std::string>> vvs{}; + std::optional<double> od1{}; + std::optional<std::string> ods{}; + std::pair<double, std::string> p1{}; + std::pair<std::vector<double>, std::string> p2{}; + std::tuple<int64_t, uint16_t, std::optional<double>> t1{}; + std::tuple<std::tuple<std::tuple<std::string, double, std::vector<int>>, std::string, double>, + std::vector<int>, + std::optional<std::string>> + tcomplex{}; + std::tuple<std::tuple<std::tuple<std::string, double, std::vector<int>>, std::string, double>, + std::vector<int>, + std::optional<std::string>> + tcomplex2{}; + std::string_view vstrv = ""; + + bool flag1{false}; + int flagCnt{0}; + std::atomic<bool> flagAtomic{false}; + + intWrapper64 iwrap{0}; + doubleWrapper dwrap{0.0}; +}; +} // namespace CLI diff --git a/packages/CLI11/fuzz/fuzzCommand.cpp b/packages/CLI11/fuzz/fuzzCommand.cpp new file mode 100644 index 0000000000000000000000000000000000000000..07ab0df2e24d8390ef490fa09597cc991430d8a8 --- /dev/null +++ b/packages/CLI11/fuzz/fuzzCommand.cpp @@ -0,0 +1,24 @@ +// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner +// under NSF AWARD 1414736 and by the respective contributors. +// All rights reserved. +// +// SPDX-License-Identifier: BSD-3-Clause + +#include "fuzzApp.hpp" +#include <CLI/CLI.hpp> +#include <iostream> + +int main(int argc, char **argv) { + + CLI::FuzzApp fuzzdata; + + auto app = fuzzdata.generateApp(); + try { + + app->parse(argc, argv); + } catch(const CLI::ParseError &e) { + (app)->exit(e); + // this just indicates we caught an error known by CLI + } + return 0; +} diff --git a/packages/CLI11/fuzz/fuzz_dictionary1.txt b/packages/CLI11/fuzz/fuzz_dictionary1.txt new file mode 100644 index 0000000000000000000000000000000000000000..c044eecd4b56e798960acdbfb87220203fa43031 --- /dev/null +++ b/packages/CLI11/fuzz/fuzz_dictionary1.txt @@ -0,0 +1,34 @@ +###### Recommended dictionary. +"-a" +"-b" +"-c" +"-d" +"-e" +"--flag1" +"--flag" +"--flag2" +"--flagA" +"--opt1" +"--opt2" +"--opt3" +"--opt4" +"--opt5" +"--opt6" +"--opt7" +"--opt8" +"--opt9" +"--aopt1" +"--aopt2" +"--dopt1" +"--dopt2" +"--vopt1" +"--vopt2" +"--vopt3" +"--vopt4" +"--oopt1" +"--oopt2" +"--tup1" +"--tup2" +"--tup4" +"--dwrap" +"--iwrap" diff --git a/packages/CLI11/fuzz/fuzz_dictionary2.txt b/packages/CLI11/fuzz/fuzz_dictionary2.txt new file mode 100644 index 0000000000000000000000000000000000000000..12dd8f1f6c8fb74cb207817ebd0ea5169354b512 --- /dev/null +++ b/packages/CLI11/fuzz/fuzz_dictionary2.txt @@ -0,0 +1,37 @@ +###### Recommended dictionary. +"a" +"b" +"c" +"d" +"e" +"flag1" +"flag" +"flag2" +"flagA" +"opt1" +"opt2" +"opt3" +"opt4" +"opt5" +"opt6" +"opt7" +"opt8" +"opt9" +"aopt1" +"aopt2" +"dopt1" +"dopt2" +"vopt1" +"vopt2" +"vopt3" +"vopt4" +"=" +"oopt1" +"oopt2" +"tup1" +"tup2" +"tup4" +"tup2" +"tup4" +"dwrap" +"iwrap" diff --git a/packages/CLI11/include/CLI/App.hpp b/packages/CLI11/include/CLI/App.hpp index d29aa6a89ca7c9c9af9ce6c29eefd1c8e037df8d..2676445d155562f77aee64738768c64d024e5ec3 100644 --- a/packages/CLI11/include/CLI/App.hpp +++ b/packages/CLI11/include/CLI/App.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2022, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // @@ -35,9 +35,9 @@ namespace CLI { // [CLI11:app_hpp:verbatim] #ifndef CLI11_PARSE -#define CLI11_PARSE(app, argc, argv) \ +#define CLI11_PARSE(app, ...) \ try { \ - (app).parse((argc), (argv)); \ + (app).parse(__VA_ARGS__); \ } catch(const CLI::ParseError &e) { \ return (app).exit(e); \ } @@ -49,8 +49,11 @@ struct AppFriend; } // namespace detail namespace FailureMessage { -std::string simple(const App *app, const Error &e); -std::string help(const App *app, const Error &e); +/// Printout a clean, simple message on error (the default in CLI11 1.5+) +CLI11_INLINE std::string simple(const App *app, const Error &e); + +/// Printout the full help string on error (if this fn is set, the old default for CLI11) +CLI11_INLINE std::string help(const App *app, const Error &e); } // namespace FailureMessage /// enumeration of modes of how to deal with extras in config files @@ -147,6 +150,12 @@ class App { /// @name Help ///@{ + /// Usage to put after program/subcommand description in the help output INHERITABLE + std::string usage_{}; + + /// This is a function that generates a usage to put after program/subcommand description in help output + std::function<std::string()> usage_callback_{}; + /// Footer to put after all options in the help output INHERITABLE std::string footer_{}; @@ -623,7 +632,8 @@ class App { std::string flag_description = "") { CLI::callback_t fun = [&flag_result](const CLI::results_t &res) { - return CLI::detail::lexical_cast(res[0], flag_result); + using CLI::detail::lexical_cast; + return lexical_cast(res[0], flag_result); }; auto *opt = _add_flag_internal(flag_name, std::move(fun), std::move(flag_description)); return detail::default_flag_modifiers<T>(opt); @@ -639,8 +649,9 @@ class App { CLI::callback_t fun = [&flag_results](const CLI::results_t &res) { bool retval = true; for(const auto &elem : res) { + using CLI::detail::lexical_cast; flag_results.emplace_back(); - retval &= detail::lexical_cast(elem, flag_results.back()); + retval &= lexical_cast(elem, flag_results.back()); } return retval; }; @@ -826,15 +837,25 @@ class App { /// Reset the parsed data void clear(); + /// Parse the command-line arguments passed to the main function of the executable. + /// This overload will correctly parse unicode arguments on Windows. + void parse(); + /// Parses the command line - throws errors. /// This must be called after the options are in but before the rest of the program. void parse(int argc, const char *const *argv); + void parse(int argc, const wchar_t *const *argv); + + private: + template <class CharT> void parse_char_t(int argc, const CharT *const *argv); + public: /// Parse a single string as if it contained command line arguments. /// This function splits the string into arguments then calls parse(std::vector<std::string> &) /// the function takes an optional boolean argument specifying if the programName is included in the string to /// process void parse(std::string commandline, bool program_name_included = false); + void parse(std::wstring commandline, bool program_name_included = false); /// The real work is done here. Expects a reversed vector. /// Changes the vector to the remaining options. @@ -942,6 +963,16 @@ class App { /// @name Help ///@{ + /// Set usage. + App *usage(std::string usage_string) { + usage_ = std::move(usage_string); + return this; + } + /// Set usage. + App *usage(std::function<std::string()> usage_function) { + usage_callback_ = std::move(usage_function); + return this; + } /// Set footer. App *footer(std::string footer_string) { footer_ = std::move(footer_string); @@ -1050,6 +1081,11 @@ class App { /// Get the group of this subcommand CLI11_NODISCARD const std::string &get_group() const { return group_; } + /// Generate and return the usage. + CLI11_NODISCARD std::string get_usage() const { + return (usage_callback_) ? usage_callback_() + '\n' + usage_ : usage_; + } + /// Generate and return the footer. CLI11_NODISCARD std::string get_footer() const { return (footer_callback_) ? footer_callback_() + '\n' + footer_ : footer_; @@ -1259,8 +1295,9 @@ class App { bool _parse_subcommand(std::vector<std::string> &args); /// Parse a short (false) or long (true) argument, must be at the top of the list + /// if local_processing_only is set to true then fallthrough is disabled will return false if not found /// return true if the argument was processed or false if nothing was done - bool _parse_arg(std::vector<std::string> &args, detail::Classifier current_type); + bool _parse_arg(std::vector<std::string> &args, detail::Classifier current_type, bool local_processing_only); /// Trigger the pre_parse callback if needed void _trigger_pre_parse(std::size_t remaining_args); @@ -1352,16 +1389,6 @@ CLI11_INLINE void retire_option(App *app, const std::string &option_name); /// Helper function to mark an option as retired CLI11_INLINE void retire_option(App &app, const std::string &option_name); -namespace FailureMessage { - -/// Printout a clean, simple message on error (the default in CLI11 1.5+) -CLI11_INLINE std::string simple(const App *app, const Error &e); - -/// Printout the full help string on error (if this fn is set, the old default for CLI11) -CLI11_INLINE std::string help(const App *app, const Error &e); - -} // namespace FailureMessage - namespace detail { /// This class is simply to allow tests access to App's protected functions struct AppFriend { diff --git a/packages/CLI11/include/CLI/Argv.hpp b/packages/CLI11/include/CLI/Argv.hpp new file mode 100644 index 0000000000000000000000000000000000000000..35d81a6eaf3d888ccd245469bce2386800053b95 --- /dev/null +++ b/packages/CLI11/include/CLI/Argv.hpp @@ -0,0 +1,25 @@ +// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner +// under NSF AWARD 1414736 and by the respective contributors. +// All rights reserved. +// +// SPDX-License-Identifier: BSD-3-Clause + +#pragma once + +#include <CLI/Macros.hpp> + +namespace CLI { +// [CLI11:argv_hpp:verbatim] + +/// argc as passed in to this executable. +CLI11_INLINE int argc(); + +/// argv as passed in to this executable, converted to utf-8 on Windows. +CLI11_INLINE const char *const *argv(); + +// [CLI11:argv_hpp:end] +} // namespace CLI + +#ifndef CLI11_COMPILE +#include "impl/Argv_inl.hpp" +#endif diff --git a/packages/CLI11/include/CLI/CLI.hpp b/packages/CLI11/include/CLI/CLI.hpp index 0b6c3448a450497948a480ebf18200f792098874..fa9d4bb5394e6a88f146ffde82b01bcfc2e26f75 100644 --- a/packages/CLI11/include/CLI/CLI.hpp +++ b/packages/CLI11/include/CLI/CLI.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2022, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // @@ -13,6 +13,10 @@ #include "Macros.hpp" +#include "Encoding.hpp" + +#include "Argv.hpp" + #include "StringTools.hpp" #include "Error.hpp" diff --git a/packages/CLI11/include/CLI/Config.hpp b/packages/CLI11/include/CLI/Config.hpp index 685981c2fbd9eb62356a242780ae29a4e3c7804e..a91f0da6e8d371d6002066020df9f396260f32dd 100644 --- a/packages/CLI11/include/CLI/Config.hpp +++ b/packages/CLI11/include/CLI/Config.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2022, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // @@ -14,7 +14,7 @@ #include <string> #include <utility> #include <vector> -// [CLI11:public_includes:set] +// [CLI11:public_includes:end] #include "App.hpp" #include "ConfigFwd.hpp" diff --git a/packages/CLI11/include/CLI/ConfigFwd.hpp b/packages/CLI11/include/CLI/ConfigFwd.hpp index 44454b41a0ec9ba07a3410c6fbf035f5b32c3e51..a9ae2176a953c40cd0f41cc5eb85c408f6174721 100644 --- a/packages/CLI11/include/CLI/ConfigFwd.hpp +++ b/packages/CLI11/include/CLI/ConfigFwd.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2022, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // @@ -61,7 +61,7 @@ class Config { if(item.inputs.empty()) { return "{}"; } - throw ConversionError::TooManyInputsFlag(item.fullname()); + throw ConversionError::TooManyInputsFlag(item.fullname()); // LCOV_EXCL_LINE } /// Parse a config file, throw an error (ParseError:ConfigParseError or FileError) on failure diff --git a/packages/CLI11/include/CLI/Encoding.hpp b/packages/CLI11/include/CLI/Encoding.hpp new file mode 100644 index 0000000000000000000000000000000000000000..379e33b20ce9e4ae58b49f15ce1f06eee4427107 --- /dev/null +++ b/packages/CLI11/include/CLI/Encoding.hpp @@ -0,0 +1,54 @@ +// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner +// under NSF AWARD 1414736 and by the respective contributors. +// All rights reserved. +// +// SPDX-License-Identifier: BSD-3-Clause + +#pragma once + +#include <CLI/Macros.hpp> + +// [CLI11:public_includes:set] +#include <string> +// [CLI11:public_includes:end] + +// [CLI11:encoding_includes:verbatim] +#ifdef CLI11_CPP17 +#include <string_view> +#endif // CLI11_CPP17 + +#if defined CLI11_HAS_FILESYSTEM && CLI11_HAS_FILESYSTEM > 0 +#include <filesystem> +#include <string_view> // NOLINT(build/include) +#endif // CLI11_HAS_FILESYSTEM +// [CLI11:encoding_includes:end] + +namespace CLI { +// [CLI11:encoding_hpp:verbatim] + +/// Convert a wide string to a narrow string. +CLI11_INLINE std::string narrow(const std::wstring &str); +CLI11_INLINE std::string narrow(const wchar_t *str); +CLI11_INLINE std::string narrow(const wchar_t *str, std::size_t size); + +/// Convert a narrow string to a wide string. +CLI11_INLINE std::wstring widen(const std::string &str); +CLI11_INLINE std::wstring widen(const char *str); +CLI11_INLINE std::wstring widen(const char *str, std::size_t size); + +#ifdef CLI11_CPP17 +CLI11_INLINE std::string narrow(std::wstring_view str); +CLI11_INLINE std::wstring widen(std::string_view str); +#endif // CLI11_CPP17 + +#if defined CLI11_HAS_FILESYSTEM && CLI11_HAS_FILESYSTEM > 0 +/// Convert a char-string to a native path correctly. +CLI11_INLINE std::filesystem::path to_path(std::string_view str); +#endif // CLI11_HAS_FILESYSTEM + +// [CLI11:encoding_hpp:end] +} // namespace CLI + +#ifndef CLI11_COMPILE +#include "impl/Encoding_inl.hpp" +#endif diff --git a/packages/CLI11/include/CLI/Error.hpp b/packages/CLI11/include/CLI/Error.hpp index 45df83bb60264209ed1dbc043f99948db71804df..0900da53ce374d07fa1cceaa23a55bf4e19b2875 100644 --- a/packages/CLI11/include/CLI/Error.hpp +++ b/packages/CLI11/include/CLI/Error.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2022, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // diff --git a/packages/CLI11/include/CLI/Formatter.hpp b/packages/CLI11/include/CLI/Formatter.hpp index c1eceac1224bdefbbe2d20418e7cfbbec585b9d4..f58058f27add2842b57d776b54e91b7ea930e62a 100644 --- a/packages/CLI11/include/CLI/Formatter.hpp +++ b/packages/CLI11/include/CLI/Formatter.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2022, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // diff --git a/packages/CLI11/include/CLI/FormatterFwd.hpp b/packages/CLI11/include/CLI/FormatterFwd.hpp index 792ebbc888dba07f2f36a2f19aa8461d597a7ad7..5ef0a5b585e377554c036ea5e7f41994e476105a 100644 --- a/packages/CLI11/include/CLI/FormatterFwd.hpp +++ b/packages/CLI11/include/CLI/FormatterFwd.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2022, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // @@ -57,6 +57,8 @@ class FormatterBase { FormatterBase() = default; FormatterBase(const FormatterBase &) = default; FormatterBase(FormatterBase &&) = default; + FormatterBase &operator=(const FormatterBase &) = default; + FormatterBase &operator=(FormatterBase &&) = default; /// Adding a destructor in this form to work around bug in GCC 4.7 virtual ~FormatterBase() noexcept {} // NOLINT(modernize-use-equals-default) @@ -118,6 +120,8 @@ class Formatter : public FormatterBase { Formatter() = default; Formatter(const Formatter &) = default; Formatter(Formatter &&) = default; + Formatter &operator=(const Formatter &) = default; + Formatter &operator=(Formatter &&) = default; /// @name Overridables ///@{ diff --git a/packages/CLI11/include/CLI/Macros.hpp b/packages/CLI11/include/CLI/Macros.hpp index 690670619a2d8ef56f21740d12f4506c4718183c..c7ac94e8741370f3a1633dfcf8568af8ac8339d8 100644 --- a/packages/CLI11/include/CLI/Macros.hpp +++ b/packages/CLI11/include/CLI/Macros.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2022, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // @@ -66,6 +66,62 @@ #endif #endif +/** <filesystem> availability */ +#if defined CLI11_CPP17 && defined __has_include && !defined CLI11_HAS_FILESYSTEM +#if __has_include(<filesystem>) +// Filesystem cannot be used if targeting macOS < 10.15 +#if defined __MAC_OS_X_VERSION_MIN_REQUIRED && __MAC_OS_X_VERSION_MIN_REQUIRED < 101500 +#define CLI11_HAS_FILESYSTEM 0 +#elif defined(__wasi__) +// As of wasi-sdk-14, filesystem is not implemented +#define CLI11_HAS_FILESYSTEM 0 +#else +#include <filesystem> +#if defined __cpp_lib_filesystem && __cpp_lib_filesystem >= 201703 +#if defined _GLIBCXX_RELEASE && _GLIBCXX_RELEASE >= 9 +#define CLI11_HAS_FILESYSTEM 1 +#elif defined(__GLIBCXX__) +// if we are using gcc and Version <9 default to no filesystem +#define CLI11_HAS_FILESYSTEM 0 +#else +#define CLI11_HAS_FILESYSTEM 1 +#endif +#else +#define CLI11_HAS_FILESYSTEM 0 +#endif +#endif +#endif +#endif + +/** <codecvt> availability */ +#if defined(__GNUC__) && !defined(__llvm__) && !defined(__INTEL_COMPILER) && __GNUC__ < 5 +#define CLI11_HAS_CODECVT 0 +#else +#define CLI11_HAS_CODECVT 1 +#include <codecvt> +#endif + +/** disable deprecations */ +#if defined(__GNUC__) // GCC or clang +#define CLI11_DIAGNOSTIC_PUSH _Pragma("GCC diagnostic push") +#define CLI11_DIAGNOSTIC_POP _Pragma("GCC diagnostic pop") + +#define CLI11_DIAGNOSTIC_IGNORE_DEPRECATED _Pragma("GCC diagnostic ignored \"-Wdeprecated-declarations\"") + +#elif defined(_MSC_VER) +#define CLI11_DIAGNOSTIC_PUSH __pragma(warning(push)) +#define CLI11_DIAGNOSTIC_POP __pragma(warning(pop)) + +#define CLI11_DIAGNOSTIC_IGNORE_DEPRECATED __pragma(warning(disable : 4996)) + +#else +#define CLI11_DIAGNOSTIC_PUSH +#define CLI11_DIAGNOSTIC_POP + +#define CLI11_DIAGNOSTIC_IGNORE_DEPRECATED + +#endif + /** Inline macro **/ #ifdef CLI11_COMPILE #define CLI11_INLINE diff --git a/packages/CLI11/include/CLI/Option.hpp b/packages/CLI11/include/CLI/Option.hpp index 458d9ffcbbc2d74a58d65a3a9e61e129d5b1606a..d32350738e34031a14c3be68e59d75a09b20831b 100644 --- a/packages/CLI11/include/CLI/Option.hpp +++ b/packages/CLI11/include/CLI/Option.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2022, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // diff --git a/packages/CLI11/include/CLI/Split.hpp b/packages/CLI11/include/CLI/Split.hpp index 14be822893b010b76f80ce1b2eb5b89e1596197a..d00e7f8cbe6b5bd5f0c395de7b96164e575e20e1 100644 --- a/packages/CLI11/include/CLI/Split.hpp +++ b/packages/CLI11/include/CLI/Split.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2022, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // diff --git a/packages/CLI11/include/CLI/StringTools.hpp b/packages/CLI11/include/CLI/StringTools.hpp index a891b1279d8d6fc0a3cd61501dd21b6874483efc..2a31005c858a00a71f489cf4c485d7805cb410cb 100644 --- a/packages/CLI11/include/CLI/StringTools.hpp +++ b/packages/CLI11/include/CLI/StringTools.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2022, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // diff --git a/packages/CLI11/include/CLI/Timer.hpp b/packages/CLI11/include/CLI/Timer.hpp index c6898204e203bf1e860ab07e164849a2b60ba9f2..b185d3302a7ef82703bc642a635f5224c6dcdf6d 100644 --- a/packages/CLI11/include/CLI/Timer.hpp +++ b/packages/CLI11/include/CLI/Timer.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2022, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // diff --git a/packages/CLI11/include/CLI/TypeTools.hpp b/packages/CLI11/include/CLI/TypeTools.hpp index e3c97b17e9990bdc539f39e659807f4886a87f48..9d43ea3614011db9e59f9827676b5a34ed976294 100644 --- a/packages/CLI11/include/CLI/TypeTools.hpp +++ b/packages/CLI11/include/CLI/TypeTools.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2022, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // @@ -7,6 +7,7 @@ #pragma once // [CLI11:public_includes:set] +#include <cmath> #include <cstdint> #include <exception> #include <limits> @@ -17,6 +18,7 @@ #include <vector> // [CLI11:public_includes:end] +#include "Encoding.hpp" #include "StringTools.hpp" namespace CLI { @@ -42,7 +44,9 @@ constexpr enabler dummy = {}; template <bool B, class T = void> using enable_if_t = typename std::enable_if<B, T>::type; /// A copy of std::void_t from C++17 (helper for C++11 and C++14) -template <typename... Ts> struct make_void { using type = void; }; +template <typename... Ts> struct make_void { + using type = void; +}; /// A copy of std::void_t from C++17 - same reasoning as enable_if_t, it does not hurt to redefine template <typename... Ts> using void_t = typename make_void<Ts...>::type; @@ -71,10 +75,14 @@ template <typename T> struct is_copyable_ptr { }; /// This can be specialized to override the type deduction for IsMember. -template <typename T> struct IsMemberType { using type = T; }; +template <typename T> struct IsMemberType { + using type = T; +}; /// The main custom type needed here is const char * should be a string. -template <> struct IsMemberType<const char *> { using type = std::string; }; +template <> struct IsMemberType<const char *> { + using type = std::string; +}; namespace detail { @@ -84,7 +92,9 @@ namespace detail { /// pointer_traits<T> be valid. /// not a pointer -template <typename T, typename Enable = void> struct element_type { using type = T; }; +template <typename T, typename Enable = void> struct element_type { + using type = T; +}; template <typename T> struct element_type<T, typename std::enable_if<is_copyable_ptr<T>::value>::type> { using type = typename std::pointer_traits<T>::element_type; @@ -92,7 +102,9 @@ template <typename T> struct element_type<T, typename std::enable_if<is_copyable /// Combination of the element type and value type - remove pointer (including smart pointers) and get the value_type of /// the container -template <typename T> struct element_value_type { using type = typename element_type<T>::type::value_type; }; +template <typename T> struct element_value_type { + using type = typename element_type<T>::type::value_type; +}; /// Adaptor for set-like structure: This just wraps a normal container in a few utilities that do almost nothing. template <typename T, typename _ = void> struct pair_adaptor : std::false_type { @@ -147,11 +159,19 @@ template <typename T, typename C> class is_direct_constructible { static auto test(int, std::true_type) -> decltype( // NVCC warns about narrowing conversions here #ifdef __CUDACC__ +#ifdef __NVCC_DIAG_PRAGMA_SUPPORT__ +#pragma nv_diag_suppress 2361 +#else #pragma diag_suppress 2361 +#endif #endif TT{std::declval<CC>()} #ifdef __CUDACC__ +#ifdef __NVCC_DIAG_PRAGMA_SUPPORT__ +#pragma nv_diag_default 2361 +#else #pragma diag_default 2361 +#endif #endif , std::is_move_assignable<TT>()); @@ -231,8 +251,10 @@ struct is_mutable_container< decltype(std::declval<T>().clear()), decltype(std::declval<T>().insert(std::declval<decltype(std::declval<T>().end())>(), std::declval<const typename T::value_type &>()))>, - void>> - : public conditional_t<std::is_constructible<T, std::string>::value, std::false_type, std::true_type> {}; + void>> : public conditional_t<std::is_constructible<T, std::string>::value || + std::is_constructible<T, std::wstring>::value, + std::false_type, + std::true_type> {}; // check to see if an object is a mutable container (fail by default) template <typename T, typename _ = void> struct is_readable_container : std::false_type {}; @@ -353,7 +375,9 @@ auto value_string(const T &value) -> decltype(to_string(value)) { } /// template to get the underlying value type if it exists or use a default -template <typename T, typename def, typename Enable = void> struct wrapped_type { using type = def; }; +template <typename T, typename def, typename Enable = void> struct wrapped_type { + using type = def; +}; /// Type size for regular object types that do not look like a tuple template <typename T, typename def> struct wrapped_type<T, def, typename std::enable_if<is_wrapper<T>::value>::type> { @@ -361,7 +385,9 @@ template <typename T, typename def> struct wrapped_type<T, def, typename std::en }; /// This will only trigger for actual void type -template <typename T, typename Enable = void> struct type_count_base { static const int value{0}; }; +template <typename T, typename Enable = void> struct type_count_base { + static const int value{0}; +}; /// Type size for regular object types that do not look like a tuple template <typename T> @@ -391,7 +417,9 @@ template <typename T> struct subtype_count; template <typename T> struct subtype_count_min; /// This will only trigger for actual void type -template <typename T, typename Enable = void> struct type_count { static const int value{0}; }; +template <typename T, typename Enable = void> struct type_count { + static const int value{0}; +}; /// Type size for regular object types that do not look like a tuple template <typename T> @@ -442,7 +470,9 @@ template <typename T> struct subtype_count { }; /// This will only trigger for actual void type -template <typename T, typename Enable = void> struct type_count_min { static const int value{0}; }; +template <typename T, typename Enable = void> struct type_count_min { + static const int value{0}; +}; /// Type size for regular object types that do not look like a tuple template <typename T> @@ -491,7 +521,9 @@ template <typename T> struct subtype_count_min { }; /// This will only trigger for actual void type -template <typename T, typename Enable = void> struct expected_count { static const int value{0}; }; +template <typename T, typename Enable = void> struct expected_count { + static const int value{0}; +}; /// For most types the number of expected items is 1 template <typename T> @@ -525,6 +557,8 @@ enum class object_category : int { // string like types string_assignable = 23, string_constructible = 24, + wstring_assignable = 25, + wstring_constructible = 26, other = 45, // special wrapper or container types wrapper_value = 50, @@ -592,6 +626,27 @@ struct classify_object< static constexpr object_category value{object_category::string_constructible}; }; +/// Wide strings +template <typename T> +struct classify_object<T, + typename std::enable_if<!std::is_floating_point<T>::value && !std::is_integral<T>::value && + !std::is_assignable<T &, std::string>::value && + !std::is_constructible<T, std::string>::value && + std::is_assignable<T &, std::wstring>::value>::type> { + static constexpr object_category value{object_category::wstring_assignable}; +}; + +template <typename T> +struct classify_object< + T, + typename std::enable_if<!std::is_floating_point<T>::value && !std::is_integral<T>::value && + !std::is_assignable<T &, std::string>::value && + !std::is_constructible<T, std::string>::value && + !std::is_assignable<T &, std::wstring>::value && (type_count<T>::value == 1) && + std::is_constructible<T, std::wstring>::value>::type> { + static constexpr object_category value{object_category::wstring_constructible}; +}; + /// Enumerations template <typename T> struct classify_object<T, typename std::enable_if<std::is_enum<T>::value>::type> { static constexpr object_category value{object_category::enumeration}; @@ -604,12 +659,13 @@ template <typename T> struct classify_object<T, typename std::enable_if<is_compl /// Handy helper to contain a bunch of checks that rule out many common types (integers, string like, floating point, /// vectors, and enumerations template <typename T> struct uncommon_type { - using type = typename std::conditional<!std::is_floating_point<T>::value && !std::is_integral<T>::value && - !std::is_assignable<T &, std::string>::value && - !std::is_constructible<T, std::string>::value && !is_complex<T>::value && - !is_mutable_container<T>::value && !std::is_enum<T>::value, - std::true_type, - std::false_type>::type; + using type = typename std::conditional< + !std::is_floating_point<T>::value && !std::is_integral<T>::value && + !std::is_assignable<T &, std::string>::value && !std::is_constructible<T, std::string>::value && + !std::is_assignable<T &, std::wstring>::value && !std::is_constructible<T, std::wstring>::value && + !is_complex<T>::value && !is_mutable_container<T>::value && !std::is_enum<T>::value, + std::true_type, + std::false_type>::type; static constexpr bool value = type::value; }; @@ -655,7 +711,8 @@ struct classify_object< typename std::enable_if<is_tuple_like<T>::value && ((type_count<T>::value >= 2 && !is_wrapper<T>::value) || (uncommon_type<T>::value && !is_direct_constructible<T, double>::value && - !is_direct_constructible<T, int>::value))>::type> { + !is_direct_constructible<T, int>::value) || + (uncommon_type<T>::value && type_count<T>::value >= 2))>::type> { static constexpr object_category value{object_category::tuple_value}; // the condition on this class requires it be like a tuple, but on some compilers (like Xcode) tuples can be // constructed from just the first element so tuples of <string, int,int> can be constructed from a string, which @@ -794,11 +851,15 @@ inline std::string type_name() { /// 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()) { + if(input.empty() || input.front() == '-') { return false; } char *val = nullptr; + errno = 0; std::uint64_t output_ll = std::strtoull(input.c_str(), &val, 0); + if(errno == ERANGE) { + return false; + } output = static_cast<T>(output_ll); if(val == (input.c_str() + input.size()) && static_cast<std::uint64_t>(output) == output_ll) { return true; @@ -819,7 +880,11 @@ bool integral_conversion(const std::string &input, T &output) noexcept { return false; } char *val = nullptr; + errno = 0; std::int64_t output_ll = std::strtoll(input.c_str(), &val, 0); + if(errno == ERANGE) { + return false; + } output = static_cast<T>(output_ll); if(val == (input.c_str() + input.size()) && static_cast<std::int64_t>(output) == output_ll) { return true; @@ -936,18 +1001,18 @@ bool lexical_cast(const std::string &input, T &output) { bool worked = false; auto nloc = str1.find_last_of("+-"); if(nloc != std::string::npos && nloc > 0) { - worked = detail::lexical_cast(str1.substr(0, nloc), x); + worked = lexical_cast(str1.substr(0, nloc), x); str1 = str1.substr(nloc); if(str1.back() == 'i' || str1.back() == 'j') str1.pop_back(); - worked = worked && detail::lexical_cast(str1, y); + worked = worked && lexical_cast(str1, y); } else { if(str1.back() == 'i' || str1.back() == 'j') { str1.pop_back(); - worked = detail::lexical_cast(str1, y); + worked = lexical_cast(str1, y); x = XC{0}; } else { - worked = detail::lexical_cast(str1, x); + worked = lexical_cast(str1, x); y = XC{0}; } } @@ -975,6 +1040,23 @@ bool lexical_cast(const std::string &input, T &output) { return true; } +/// Wide strings +template < + typename T, + enable_if_t<classify_object<T>::value == object_category::wstring_assignable, detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + output = widen(input); + return true; +} + +template < + typename T, + enable_if_t<classify_object<T>::value == object_category::wstring_constructible, detail::enabler> = detail::dummy> +bool lexical_cast(const std::string &input, T &output) { + output = T{widen(input)}; + return true; +} + /// Enumerations template <typename T, enable_if_t<classify_object<T>::value == object_category::enumeration, detail::enabler> = detail::dummy> @@ -1103,7 +1185,9 @@ template <typename AssignTo, typename ConvertTo, enable_if_t<std::is_same<AssignTo, ConvertTo>::value && (classify_object<AssignTo>::value == object_category::string_assignable || - classify_object<AssignTo>::value == object_category::string_constructible), + classify_object<AssignTo>::value == object_category::string_constructible || + classify_object<AssignTo>::value == object_category::wstring_assignable || + classify_object<AssignTo>::value == object_category::wstring_constructible), detail::enabler> = detail::dummy> bool lexical_assign(const std::string &input, AssignTo &output) { return lexical_cast(input, output); @@ -1114,7 +1198,9 @@ 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::string_assignable && - classify_object<AssignTo>::value != object_category::string_constructible, + classify_object<AssignTo>::value != object_category::string_constructible && + classify_object<AssignTo>::value != object_category::wstring_assignable && + classify_object<AssignTo>::value != object_category::wstring_constructible, detail::enabler> = detail::dummy> bool lexical_assign(const std::string &input, AssignTo &output) { if(input.empty()) { @@ -1153,9 +1239,17 @@ bool lexical_assign(const std::string &input, AssignTo &output) { output = 0; return true; } - int val = 0; + int val{0}; if(lexical_cast(input, val)) { +#if defined(__clang__) +/* on some older clang compilers */ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wsign-conversion" +#endif output = val; +#if defined(__clang__) +#pragma clang diagnostic pop +#endif return true; } return false; @@ -1168,7 +1262,7 @@ template <typename AssignTo, detail::enabler> = detail::dummy> bool lexical_assign(const std::string &input, AssignTo &output) { ConvertTo val{}; - bool parse_result = (!input.empty()) ? lexical_cast<ConvertTo>(input, val) : true; + bool parse_result = (!input.empty()) ? lexical_cast(input, val) : true; if(parse_result) { output = val; } @@ -1184,7 +1278,7 @@ template < detail::enabler> = detail::dummy> bool lexical_assign(const std::string &input, AssignTo &output) { ConvertTo val{}; - bool parse_result = input.empty() ? true : lexical_cast<ConvertTo>(input, val); + bool parse_result = input.empty() ? true : lexical_cast(input, val); if(parse_result) { output = AssignTo(val); // use () form of constructor to allow some implicit conversions } @@ -1210,11 +1304,13 @@ template <typename AssignTo, detail::enabler> = detail::dummy> bool lexical_conversion(const std::vector<std ::string> &strings, AssignTo &output) { // the remove const is to handle pair types coming from a container - typename std::remove_const<typename std::tuple_element<0, ConvertTo>::type>::type v1; - typename std::tuple_element<1, ConvertTo>::type v2; - bool retval = lexical_assign<decltype(v1), decltype(v1)>(strings[0], v1); + using FirstType = typename std::remove_const<typename std::tuple_element<0, ConvertTo>::type>::type; + using SecondType = typename std::tuple_element<1, ConvertTo>::type; + FirstType v1; + SecondType v2; + bool retval = lexical_assign<FirstType, FirstType>(strings[0], v1); if(strings.size() > 1) { - retval = retval && lexical_assign<decltype(v2), decltype(v2)>(strings[1], v2); + retval = retval && lexical_assign<SecondType, SecondType>(strings[1], v2); } if(retval) { output = AssignTo{v1, v2}; @@ -1262,7 +1358,7 @@ bool lexical_conversion(const std::vector<std::string> &strings, AssignTo &outpu if(str1.back() == 'i' || str1.back() == 'j') { str1.pop_back(); } - auto worked = detail::lexical_cast(strings[0], x) && detail::lexical_cast(str1, y); + auto worked = lexical_cast(strings[0], x) && lexical_cast(str1, y); if(worked) { output = ConvertTo{x, y}; } @@ -1374,7 +1470,7 @@ tuple_type_conversion(std::vector<std::string> &strings, AssignTo &output) { std::size_t index{subtype_count_min<ConvertTo>::value}; const std::size_t mx_count{subtype_count<ConvertTo>::value}; - const std::size_t mx{(std::max)(mx_count, strings.size())}; + const std::size_t mx{(std::min)(mx_count, strings.size() - 1)}; while(index < mx) { if(is_separator(strings[index])) { @@ -1384,7 +1480,11 @@ tuple_type_conversion(std::vector<std::string> &strings, AssignTo &output) { } bool retval = lexical_conversion<AssignTo, ConvertTo>( std::vector<std::string>(strings.begin(), strings.begin() + static_cast<std::ptrdiff_t>(index)), output); - strings.erase(strings.begin(), strings.begin() + static_cast<std::ptrdiff_t>(index) + 1); + if(strings.size() > index) { + strings.erase(strings.begin(), strings.begin() + static_cast<std::ptrdiff_t>(index) + 1); + } else { + strings.clear(); + } return retval; } @@ -1526,7 +1626,7 @@ inline std::string sum_string_vector(const std::vector<std::string> &values) { std::string output; for(const auto &arg : values) { double tv{0.0}; - auto comp = detail::lexical_cast<double>(arg, tv); + auto comp = lexical_cast(arg, tv); if(!comp) { try { tv = static_cast<double>(detail::to_flag_value(arg)); @@ -1544,8 +1644,7 @@ inline std::string sum_string_vector(const std::vector<std::string> &values) { } else { if(val <= static_cast<double>((std::numeric_limits<std::int64_t>::min)()) || val >= static_cast<double>((std::numeric_limits<std::int64_t>::max)()) || - // NOLINTNEXTLINE(clang-diagnostic-float-equal,bugprone-narrowing-conversions) - val == static_cast<std::int64_t>(val)) { + std::ceil(val) == std::floor(val)) { output = detail::value_string(static_cast<int64_t>(val)); } else { output = detail::value_string(val); diff --git a/packages/CLI11/include/CLI/Validators.hpp b/packages/CLI11/include/CLI/Validators.hpp index 9a6a364e35225fcfcd98d4a9db66da57c859e7a7..59d800de860e70b98005038038d77729cc895d33 100644 --- a/packages/CLI11/include/CLI/Validators.hpp +++ b/packages/CLI11/include/CLI/Validators.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2022, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // @@ -26,34 +26,6 @@ // [CLI11:validators_hpp_filesystem:verbatim] -// C standard library -// Only needed for existence checking -#if defined CLI11_CPP17 && defined __has_include && !defined CLI11_HAS_FILESYSTEM -#if __has_include(<filesystem>) -// Filesystem cannot be used if targeting macOS < 10.15 -#if defined __MAC_OS_X_VERSION_MIN_REQUIRED && __MAC_OS_X_VERSION_MIN_REQUIRED < 101500 -#define CLI11_HAS_FILESYSTEM 0 -#elif defined(__wasi__) -// As of wasi-sdk-14, filesystem is not implemented -#define CLI11_HAS_FILESYSTEM 0 -#else -#include <filesystem> -#if defined __cpp_lib_filesystem && __cpp_lib_filesystem >= 201703 -#if defined _GLIBCXX_RELEASE && _GLIBCXX_RELEASE >= 9 -#define CLI11_HAS_FILESYSTEM 1 -#elif defined(__GLIBCXX__) -// if we are using gcc and Version <9 default to no filesystem -#define CLI11_HAS_FILESYSTEM 0 -#else -#define CLI11_HAS_FILESYSTEM 1 -#endif -#else -#define CLI11_HAS_FILESYSTEM 0 -#endif -#endif -#endif -#endif - #if defined CLI11_HAS_FILESYSTEM && CLI11_HAS_FILESYSTEM > 0 #include <filesystem> // NOLINT(build/include) #else @@ -270,8 +242,9 @@ template <typename DesiredType> class TypeValidator : public Validator { public: explicit TypeValidator(const std::string &validator_name) : Validator(validator_name, [](std::string &input_string) { + using CLI::detail::lexical_cast; auto val = DesiredType(); - if(!detail::lexical_cast(input_string, val)) { + if(!lexical_cast(input_string, val)) { return std::string("Failed parsing ") + input_string + " as a " + detail::type_name<DesiredType>(); } return std::string(); @@ -305,8 +278,9 @@ class Range : public Validator { } func_ = [min_val, max_val](std::string &input) { + using CLI::detail::lexical_cast; T val; - bool converted = detail::lexical_cast(input, val); + bool converted = lexical_cast(input, val); if((!converted) || (val < min_val || val > max_val)) { std::stringstream out; out << "Value " << input << " not in range ["; @@ -342,8 +316,9 @@ class Bound : public Validator { description(out.str()); func_ = [min_val, max_val](std::string &input) { + using CLI::detail::lexical_cast; T val; - bool converted = detail::lexical_cast(input, val); + bool converted = lexical_cast(input, val); if(!converted) { return std::string("Value ") + input + " could not be converted"; } @@ -534,8 +509,9 @@ class IsMember : public Validator { // This is the function that validates // It stores a copy of the set pointer-like, so shared_ptr will stay alive func_ = [set, filter_fn](std::string &input) { + using CLI::detail::lexical_cast; local_item_t b; - if(!detail::lexical_cast(input, b)) { + if(!lexical_cast(input, b)) { throw ValidationError(input); // name is added later } if(filter_fn) { @@ -602,8 +578,9 @@ class Transformer : public Validator { desc_function_ = [mapping]() { return detail::generate_map(detail::smart_deref(mapping)); }; func_ = [mapping, filter_fn](std::string &input) { + using CLI::detail::lexical_cast; local_item_t b; - if(!detail::lexical_cast(input, b)) { + if(!lexical_cast(input, b)) { return std::string(); // there is no possible way we can match anything in the mapping if we can't convert so just return } @@ -671,8 +648,9 @@ class CheckedTransformer : public Validator { desc_function_ = tfunc; func_ = [mapping, tfunc, filter_fn](std::string &input) { + using CLI::detail::lexical_cast; local_item_t b; - bool converted = detail::lexical_cast(input, b); + bool converted = lexical_cast(input, b); if(converted) { if(filter_fn) { b = filter_fn(b); @@ -750,7 +728,7 @@ class AsNumberWithUnit : public Validator { // transform function func_ = [mapping, opts](std::string &input) -> std::string { - Number num; + Number num{}; detail::rtrim(input); if(input.empty()) { @@ -774,7 +752,8 @@ class AsNumberWithUnit : public Validator { unit = detail::to_lower(unit); } if(unit.empty()) { - if(!detail::lexical_cast(input, num)) { + using CLI::detail::lexical_cast; + if(!lexical_cast(input, num)) { throw ValidationError(std::string("Value ") + input + " could not be converted to " + detail::type_name<Number>()); } @@ -792,7 +771,8 @@ class AsNumberWithUnit : public Validator { } if(!input.empty()) { - bool converted = detail::lexical_cast(input, num); + using CLI::detail::lexical_cast; + bool converted = lexical_cast(input, num); if(!converted) { throw ValidationError(std::string("Value ") + input + " could not be converted to " + detail::type_name<Number>()); diff --git a/packages/CLI11/include/CLI/Version.hpp b/packages/CLI11/include/CLI/Version.hpp index b03141b82829ef47539bd048bccfee71a70a9ae3..d5c817a9c3d84c2b17d40e3d61e20d57e2697bf4 100644 --- a/packages/CLI11/include/CLI/Version.hpp +++ b/packages/CLI11/include/CLI/Version.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2022, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // @@ -9,8 +9,8 @@ // [CLI11:version_hpp:verbatim] #define CLI11_VERSION_MAJOR 2 -#define CLI11_VERSION_MINOR 2 -#define CLI11_VERSION_PATCH 0 -#define CLI11_VERSION "2.2.0" +#define CLI11_VERSION_MINOR 3 +#define CLI11_VERSION_PATCH 2 +#define CLI11_VERSION "2.3.2" // [CLI11:version_hpp:end] diff --git a/packages/CLI11/include/CLI/impl/App_inl.hpp b/packages/CLI11/include/CLI/impl/App_inl.hpp index bbda621df0a5bf9f6f34c8a8e7a452e789ac6ad7..7d487442f924d56758e68d8d88eae1767a26daf0 100644 --- a/packages/CLI11/include/CLI/impl/App_inl.hpp +++ b/packages/CLI11/include/CLI/impl/App_inl.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2022, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // @@ -9,6 +9,9 @@ // This include is only needed for IDEs to discover symbols #include <CLI/App.hpp> +#include <CLI/Argv.hpp> +#include <CLI/Encoding.hpp> + // [CLI11:public_includes:set] #include <algorithm> #include <memory> @@ -46,6 +49,7 @@ CLI11_INLINE App::App(std::string app_description, std::string app_name, App *pa configurable_ = parent_->configurable_; allow_windows_style_options_ = parent_->allow_windows_style_options_; group_ = parent_->group_; + usage_ = parent_->usage_; footer_ = parent_->footer_; formatter_ = parent_->formatter_; config_formatter_ = parent_->config_formatter_; @@ -265,8 +269,9 @@ CLI11_INLINE Option *App::add_flag_callback(std::string flag_name, std::string flag_description) { CLI::callback_t fun = [function](const CLI::results_t &res) { + using CLI::detail::lexical_cast; bool trigger{false}; - auto result = CLI::detail::lexical_cast(res[0], trigger); + auto result = lexical_cast(res[0], trigger); if(result && trigger) { function(); } @@ -281,8 +286,9 @@ App::add_flag_function(std::string flag_name, std::string flag_description) { CLI::callback_t fun = [function](const CLI::results_t &res) { + using CLI::detail::lexical_cast; std::int64_t flag_count{0}; - CLI::detail::lexical_cast(res[0], flag_count); + lexical_cast(res[0], flag_count); function(flag_count); return true; }; @@ -433,6 +439,15 @@ CLI11_NODISCARD CLI11_INLINE CLI::App_p App::get_subcommand_ptr(int index) const throw OptionNotFound(std::to_string(index)); } +CLI11_NODISCARD CLI11_INLINE CLI::App *App::get_option_group(std::string group_name) const { + for(const App_p &app : subcommands_) { + if(app->name_.empty() && app->group_ == group_name) { + return app.get(); + } + } + throw OptionNotFound(group_name); +} + CLI11_NODISCARD CLI11_INLINE std::size_t App::count_all() const { std::size_t cnt{0}; for(const auto &opt : options_) { @@ -462,17 +477,31 @@ CLI11_INLINE void App::clear() { } } -CLI11_INLINE void App::parse(int argc, const char *const *argv) { +CLI11_INLINE void App::parse() { parse(argc(), argv()); } // LCOV_EXCL_LINE + +CLI11_INLINE void App::parse(int argc, const char *const *argv) { parse_char_t(argc, argv); } +CLI11_INLINE void App::parse(int argc, const wchar_t *const *argv) { parse_char_t(argc, argv); } + +namespace detail { + +// Do nothing or perform narrowing +CLI11_INLINE const char *maybe_narrow(const char *str) { return str; } +CLI11_INLINE std::string maybe_narrow(const wchar_t *str) { return narrow(str); } + +} // namespace detail + +template <class CharT> CLI11_INLINE void App::parse_char_t(int argc, const CharT *const *argv) { // If the name is not set, read from command line if(name_.empty() || has_automatic_name_) { has_automatic_name_ = true; - name_ = argv[0]; + name_ = detail::maybe_narrow(argv[0]); } std::vector<std::string> args; args.reserve(static_cast<std::size_t>(argc) - 1U); for(auto i = static_cast<std::size_t>(argc) - 1U; i > 0U; --i) - args.emplace_back(argv[i]); + args.emplace_back(detail::maybe_narrow(argv[i])); + parse(std::move(args)); } @@ -503,6 +532,10 @@ CLI11_INLINE void App::parse(std::string commandline, bool program_name_included parse(std::move(args)); } +CLI11_INLINE void App::parse(std::wstring commandline, bool program_name_included) { + parse(narrow(commandline), program_name_included); +} + CLI11_INLINE void App::parse(std::vector<std::string> &args) { // Clear if parsed if(parsed_ > 0) @@ -657,7 +690,7 @@ CLI11_NODISCARD CLI11_INLINE std::string App::help(std::string prev, AppFormatMo // Delegate to subcommand if needed auto selected_subcommands = get_subcommands(); if(!selected_subcommands.empty()) { - return selected_subcommands.at(0)->help(prev, mode); + return selected_subcommands.back()->help(prev, mode); } return formatter_->make_help(this, prev, mode); } @@ -964,6 +997,16 @@ CLI11_NODISCARD CLI11_INLINE detail::Classifier App::_recognize(const std::strin return detail::Classifier::WINDOWS_STYLE; if((current == "++") && !name_.empty() && parent_ != nullptr) return detail::Classifier::SUBCOMMAND_TERMINATOR; + auto dotloc = current.find_first_of('.'); + if(dotloc != std::string::npos) { + auto *cm = _find_subcommand(current.substr(0, dotloc), true, ignore_used_subcommands); + if(cm != nullptr) { + auto res = cm->_recognize(current.substr(dotloc + 1), ignore_used_subcommands); + if(res == detail::Classifier::SUBCOMMAND) { + return res; + } + } + } return detail::Classifier::NONE; } @@ -1371,6 +1414,9 @@ CLI11_INLINE bool App::_parse_single_config(const ConfigItem &item, std::size_t if(get_allow_config_extras() == config_extras_mode::capture) // Should we worry about classifying the extras properly? missing_.emplace_back(detail::Classifier::NONE, item.fullname()); + for(const auto &input : item.inputs) { + missing_.emplace_back(detail::Classifier::NONE, input); + } return false; } @@ -1384,16 +1430,38 @@ CLI11_INLINE bool App::_parse_single_config(const ConfigItem &item, std::size_t if(op->empty()) { if(op->get_expected_min() == 0) { - // Flag parsing - auto res = config_formatter_->to_flag(item); - res = op->get_flag_value(item.name, res); + if(item.inputs.size() <= 1) { + // Flag parsing + auto res = config_formatter_->to_flag(item); + bool converted{false}; + if(op->get_disable_flag_override()) { + + try { + auto val = detail::to_flag_value(res); + if(val == 1) { + res = op->get_flag_value(item.name, "{}"); + converted = true; + } + } catch(...) { + } + } - op->add_result(res); + if(!converted) { + res = op->get_flag_value(item.name, res); + } - } else { - op->add_result(item.inputs); - op->run_callback(); + op->add_result(res); + return true; + } + if(static_cast<int>(item.inputs.size()) > op->get_items_expected_max()) { + if(op->get_items_expected_max() > 1) { + throw ArgumentMismatch::AtMost(item.fullname(), op->get_items_expected_max(), item.inputs.size()); + } + throw ConversionError::TooManyInputsFlag(item.fullname()); + } } + op->add_result(item.inputs); + op->run_callback(); } return true; @@ -1424,7 +1492,7 @@ CLI11_INLINE bool App::_parse_single(std::vector<std::string> &args, bool &posit case detail::Classifier::SHORT: case detail::Classifier::WINDOWS_STYLE: // If already parsed a subcommand, don't accept options_ - _parse_arg(args, classifier); + _parse_arg(args, classifier, false); break; case detail::Classifier::NONE: // Probably a positional or something for a parent (sub)command @@ -1612,6 +1680,17 @@ CLI11_INLINE bool App::_parse_subcommand(std::vector<std::string> &args) { return true; } auto *com = _find_subcommand(args.back(), true, true); + if(com == nullptr) { + // the main way to get here is using .notation + auto dotloc = args.back().find_first_of('.'); + if(dotloc != std::string::npos) { + com = _find_subcommand(args.back().substr(0, dotloc), true, true); + if(com != nullptr) { + args.back() = args.back().substr(dotloc + 1); + args.push_back(com->get_display_name()); + } + } + } if(com != nullptr) { args.pop_back(); if(!com->silent_) { @@ -1634,7 +1713,8 @@ CLI11_INLINE bool App::_parse_subcommand(std::vector<std::string> &args) { return false; } -CLI11_INLINE bool App::_parse_arg(std::vector<std::string> &args, detail::Classifier current_type) { +CLI11_INLINE bool +App::_parse_arg(std::vector<std::string> &args, detail::Classifier current_type, bool local_processing_only) { std::string current = args.back(); @@ -1676,7 +1756,7 @@ CLI11_INLINE bool App::_parse_arg(std::vector<std::string> &args, detail::Classi if(op_ptr == std::end(options_)) { for(auto &subc : subcommands_) { if(subc->name_.empty() && !subc->disabled_) { - if(subc->_parse_arg(args, current_type)) { + if(subc->_parse_arg(args, current_type, local_processing_only)) { if(!subc->pre_parse_called_) { subc->_trigger_pre_parse(args.size()); } @@ -1690,9 +1770,57 @@ CLI11_INLINE bool App::_parse_arg(std::vector<std::string> &args, detail::Classi return false; } + // now check for '.' notation of subcommands + auto dotloc = arg_name.find_first_of('.', 1); + if(dotloc != std::string::npos) { + // using dot notation is equivalent to single argument subcommand + auto *sub = _find_subcommand(arg_name.substr(0, dotloc), true, false); + if(sub != nullptr) { + auto v = args.back(); + args.pop_back(); + arg_name = arg_name.substr(dotloc + 1); + if(arg_name.size() > 1) { + args.push_back(std::string("--") + v.substr(dotloc + 3)); + current_type = detail::Classifier::LONG; + } else { + auto nval = v.substr(dotloc + 2); + nval.front() = '-'; + if(nval.size() > 2) { + // '=' not allowed in short form arguments + args.push_back(nval.substr(3)); + nval.resize(2); + } + args.push_back(nval); + current_type = detail::Classifier::SHORT; + } + auto val = sub->_parse_arg(args, current_type, true); + if(val) { + if(!sub->silent_) { + parsed_subcommands_.push_back(sub); + } + // deal with preparsing + increment_parsed(); + _trigger_pre_parse(args.size()); + // run the parse complete callback since the subcommand processing is now complete + if(sub->parse_complete_callback_) { + sub->_process_env(); + sub->_process_callbacks(); + sub->_process_help_flags(); + sub->_process_requirements(); + sub->run_callback(false, true); + } + return true; + } + args.pop_back(); + args.push_back(v); + } + } + if(local_processing_only) { + return false; + } // If a subcommand, try the main command if(parent_ != nullptr && fallthrough_) - return _get_fallthrough_parent()->_parse_arg(args, current_type); + return _get_fallthrough_parent()->_parse_arg(args, current_type, false); // Otherwise, add to missing args.pop_back(); diff --git a/packages/CLI11/include/CLI/impl/Argv_inl.hpp b/packages/CLI11/include/CLI/impl/Argv_inl.hpp new file mode 100644 index 0000000000000000000000000000000000000000..3d00a570d1e5032ed72bd4a255c8e60be50934d2 --- /dev/null +++ b/packages/CLI11/include/CLI/impl/Argv_inl.hpp @@ -0,0 +1,183 @@ +// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner +// under NSF AWARD 1414736 and by the respective contributors. +// All rights reserved. +// +// SPDX-License-Identifier: BSD-3-Clause + +#pragma once + +// This include is only needed for IDEs to discover symbols +#include <CLI/Argv.hpp> + +#include <CLI/Encoding.hpp> + +// [CLI11:public_includes:set] +#include <algorithm> +#include <memory> +#include <stdexcept> +#include <string> +#include <vector> +// [CLI11:public_includes:end] + +// [CLI11:argv_inl_includes:verbatim] +#if defined(_WIN32) +#if !(defined(_AMD64_) || defined(_X86_) || defined(_ARM_)) +#if defined(__amd64__) || defined(__amd64) || defined(__x86_64__) || defined(__x86_64) || defined(_M_X64) || \ + defined(_M_AMD64) +#define _AMD64_ +#elif defined(i386) || defined(__i386) || defined(__i386__) || defined(__i386__) || defined(_M_IX86) +#define _X86_ +#elif defined(__arm__) || defined(_M_ARM) || defined(_M_ARMT) +#define _ARM_ +#endif +#endif + +// first +#ifndef NOMINMAX +// if NOMINMAX is already defined we don't want to mess with that either way +#define NOMINMAX +#include <windef.h> +#undef NOMINMAX +#else +#include <windef.h> +#endif + +// second +#include <winbase.h> +// third +#include <processthreadsapi.h> +#include <shellapi.h> + +#elif defined(__APPLE__) +#include <crt_externs.h> +#endif +// [CLI11:argv_inl_includes:end] + +namespace CLI { +// [CLI11:argv_inl_hpp:verbatim] + +namespace detail { + +#ifdef __APPLE__ +// Copy argc and argv as early as possible to avoid modification +static const std::vector<const char *> static_args = [] { + static const std::vector<std::string> static_args_as_strings = [] { + std::vector<std::string> args_as_strings; + int argc = *_NSGetArgc(); + char **argv = *_NSGetArgv(); + + args_as_strings.reserve(static_cast<size_t>(argc)); + for(size_t i = 0; i < static_cast<size_t>(argc); i++) { + args_as_strings.push_back(argv[i]); + } + + return args_as_strings; + }(); + + std::vector<const char *> static_args_result; + static_args_result.reserve(static_args_as_strings.size()); + + for(const auto &arg : static_args_as_strings) { + static_args_result.push_back(arg.data()); + } + + return static_args_result; +}(); +#endif + +/// Command-line arguments, as passed in to this executable, converted to utf-8 on Windows. +CLI11_INLINE const std::vector<const char *> &args() { + // This function uses initialization via lambdas extensively to take advantage of the thread safety of static + // variable initialization [stmt.dcl.3] + +#ifdef _WIN32 + static const std::vector<const char *> static_args = [] { + static const std::vector<std::string> static_args_as_strings = [] { + // On Windows, take arguments from GetCommandLineW and convert them to utf-8. + std::vector<std::string> args_as_strings; + int argc = 0; + + auto deleter = [](wchar_t **ptr) { LocalFree(ptr); }; + // NOLINTBEGIN(*-avoid-c-arrays) + auto wargv = + std::unique_ptr<wchar_t *[], decltype(deleter)>(CommandLineToArgvW(GetCommandLineW(), &argc), deleter); + // NOLINTEND(*-avoid-c-arrays) + + if(wargv == nullptr) { + throw std::runtime_error("CommandLineToArgvW failed with code " + std::to_string(GetLastError())); + } + + args_as_strings.reserve(static_cast<size_t>(argc)); + for(size_t i = 0; i < static_cast<size_t>(argc); ++i) { + args_as_strings.push_back(narrow(wargv[i])); + } + + return args_as_strings; + }(); + + std::vector<const char *> static_args_result; + static_args_result.reserve(static_args_as_strings.size()); + + for(const auto &arg : static_args_as_strings) { + static_args_result.push_back(arg.data()); + } + + return static_args_result; + }(); + + return static_args; + +#elif defined(__APPLE__) + + return static_args; + +#else + static const std::vector<const char *> static_args = [] { + static const std::vector<char> static_cmdline = [] { + // On posix, retrieve arguments from /proc/self/cmdline, separated by null terminators. + std::vector<char> cmdline; + + auto deleter = [](FILE *f) { std::fclose(f); }; + std::unique_ptr<FILE, decltype(deleter)> fp_unique(std::fopen("/proc/self/cmdline", "r"), deleter); + FILE *fp = fp_unique.get(); + if(!fp) { + throw std::runtime_error("could not open /proc/self/cmdline for reading"); // LCOV_EXCL_LINE + } + + size_t size = 0; + while(std::feof(fp) == 0) { + cmdline.resize(size + 128); + size += std::fread(cmdline.data() + size, 1, 128, fp); + + if(std::ferror(fp) != 0) { + throw std::runtime_error("error during reading /proc/self/cmdline"); // LCOV_EXCL_LINE + } + } + cmdline.resize(size); + + return cmdline; + }(); + + std::size_t argc = static_cast<std::size_t>(std::count(static_cmdline.begin(), static_cmdline.end(), '\0')); + std::vector<const char *> static_args_result; + static_args_result.reserve(argc); + + for(auto it = static_cmdline.begin(); it != static_cmdline.end(); + it = std::find(it, static_cmdline.end(), '\0') + 1) { + static_args_result.push_back(static_cmdline.data() + (it - static_cmdline.begin())); + } + + return static_args_result; + }(); + + return static_args; +#endif +} + +} // namespace detail + +CLI11_INLINE const char *const *argv() { return detail::args().data(); } +CLI11_INLINE int argc() { return static_cast<int>(detail::args().size()); } + +// [CLI11:argv_inl_hpp:end] +} // namespace CLI diff --git a/packages/CLI11/include/CLI/impl/Config_inl.hpp b/packages/CLI11/include/CLI/impl/Config_inl.hpp index 0f9695f2922384cc938df24ad26aaa15ab030984..8021d5f63aa34e1cfeac43e37b274829c49a7dd1 100644 --- a/packages/CLI11/include/CLI/impl/Config_inl.hpp +++ b/packages/CLI11/include/CLI/impl/Config_inl.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2022, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // @@ -31,8 +31,9 @@ CLI11_INLINE std::string convert_arg_for_ini(const std::string &arg, char string } // floating point conversion can convert some hex codes, but don't try that here if(arg.compare(0, 2, "0x") != 0 && arg.compare(0, 2, "0X") != 0) { + using CLI::detail::lexical_cast; double val = 0.0; - if(detail::lexical_cast(arg, val)) { + if(lexical_cast(arg, val)) { return arg; } } @@ -344,6 +345,9 @@ ConfigBase::to_config(const App *app, bool default_also, bool write_description, } if(!value.empty()) { + if(!opt->get_fnames().empty()) { + value = opt->get_flag_value(name, value); + } if(write_description && opt->has_description()) { out << '\n'; out << commentLead << detail::fix_newlines(commentLead, opt->get_description()) << '\n'; diff --git a/packages/CLI11/include/CLI/impl/Encoding_inl.hpp b/packages/CLI11/include/CLI/impl/Encoding_inl.hpp new file mode 100644 index 0000000000000000000000000000000000000000..f5d7e9a83fbb42f7b47408cb02b67c9be48d8b3c --- /dev/null +++ b/packages/CLI11/include/CLI/impl/Encoding_inl.hpp @@ -0,0 +1,154 @@ +// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner +// under NSF AWARD 1414736 and by the respective contributors. +// All rights reserved. +// +// SPDX-License-Identifier: BSD-3-Clause + +#pragma once + +// This include is only needed for IDEs to discover symbols +#include <CLI/Encoding.hpp> +#include <CLI/Macros.hpp> + +// [CLI11:public_includes:set] +#include <array> +#include <clocale> +#include <cstdlib> +#include <cstring> +#include <cwchar> +#include <locale> +#include <stdexcept> +#include <string> +#include <type_traits> +#include <utility> +// [CLI11:public_includes:end] + +namespace CLI { +// [CLI11:encoding_inl_hpp:verbatim] + +namespace detail { + +#if !CLI11_HAS_CODECVT +/// Attempt to set one of the acceptable unicode locales for conversion +CLI11_INLINE void set_unicode_locale() { + static const std::array<const char *, 3> unicode_locales{{"C.UTF-8", "en_US.UTF-8", ".UTF-8"}}; + + for(const auto &locale_name : unicode_locales) { + if(std::setlocale(LC_ALL, locale_name) != nullptr) { + return; + } + } + throw std::runtime_error("CLI::narrow: could not set locale to C.UTF-8"); +} + +template <typename F> struct scope_guard_t { + F closure; + + explicit scope_guard_t(F closure_) : closure(closure_) {} + ~scope_guard_t() { closure(); } +}; + +template <typename F> CLI11_NODISCARD CLI11_INLINE scope_guard_t<F> scope_guard(F &&closure) { + return scope_guard_t<F>{std::forward<F>(closure)}; +} + +#endif // !CLI11_HAS_CODECVT + +CLI11_DIAGNOSTIC_PUSH +CLI11_DIAGNOSTIC_IGNORE_DEPRECATED + +CLI11_INLINE std::string narrow_impl(const wchar_t *str, std::size_t str_size) { +#if CLI11_HAS_CODECVT +#ifdef _WIN32 + return std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>>().to_bytes(str, str + str_size); + +#else + return std::wstring_convert<std::codecvt_utf8<wchar_t>>().to_bytes(str, str + str_size); + +#endif // _WIN32 +#else // CLI11_HAS_CODECVT + (void)str_size; + std::mbstate_t state = std::mbstate_t(); + const wchar_t *it = str; + + std::string old_locale = std::setlocale(LC_ALL, nullptr); + auto sg = scope_guard([&] { std::setlocale(LC_ALL, old_locale.c_str()); }); + set_unicode_locale(); + + std::size_t new_size = std::wcsrtombs(nullptr, &it, 0, &state); + if(new_size == static_cast<std::size_t>(-1)) { + throw std::runtime_error("CLI::narrow: conversion error in std::wcsrtombs at offset " + + std::to_string(it - str)); + } + std::string result(new_size, '\0'); + std::wcsrtombs(const_cast<char *>(result.data()), &str, new_size, &state); + + return result; + +#endif // CLI11_HAS_CODECVT +} + +CLI11_INLINE std::wstring widen_impl(const char *str, std::size_t str_size) { +#if CLI11_HAS_CODECVT +#ifdef _WIN32 + return std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>>().from_bytes(str, str + str_size); + +#else + return std::wstring_convert<std::codecvt_utf8<wchar_t>>().from_bytes(str, str + str_size); + +#endif // _WIN32 +#else // CLI11_HAS_CODECVT + (void)str_size; + std::mbstate_t state = std::mbstate_t(); + const char *it = str; + + std::string old_locale = std::setlocale(LC_ALL, nullptr); + auto sg = scope_guard([&] { std::setlocale(LC_ALL, old_locale.c_str()); }); + set_unicode_locale(); + + std::size_t new_size = std::mbsrtowcs(nullptr, &it, 0, &state); + if(new_size == static_cast<std::size_t>(-1)) { + throw std::runtime_error("CLI::widen: conversion error in std::mbsrtowcs at offset " + + std::to_string(it - str)); + } + std::wstring result(new_size, L'\0'); + std::mbsrtowcs(const_cast<wchar_t *>(result.data()), &str, new_size, &state); + + return result; + +#endif // CLI11_HAS_CODECVT +} + +CLI11_DIAGNOSTIC_POP + +} // namespace detail + +CLI11_INLINE std::string narrow(const wchar_t *str, std::size_t str_size) { return detail::narrow_impl(str, str_size); } +CLI11_INLINE std::string narrow(const std::wstring &str) { return detail::narrow_impl(str.data(), str.size()); } +// Flawfinder: ignore +CLI11_INLINE std::string narrow(const wchar_t *str) { return detail::narrow_impl(str, std::wcslen(str)); } + +CLI11_INLINE std::wstring widen(const char *str, std::size_t str_size) { return detail::widen_impl(str, str_size); } +CLI11_INLINE std::wstring widen(const std::string &str) { return detail::widen_impl(str.data(), str.size()); } +// Flawfinder: ignore +CLI11_INLINE std::wstring widen(const char *str) { return detail::widen_impl(str, std::strlen(str)); } + +#ifdef CLI11_CPP17 +CLI11_INLINE std::string narrow(std::wstring_view str) { return detail::narrow_impl(str.data(), str.size()); } +CLI11_INLINE std::wstring widen(std::string_view str) { return detail::widen_impl(str.data(), str.size()); } +#endif // CLI11_CPP17 + +#if defined CLI11_HAS_FILESYSTEM && CLI11_HAS_FILESYSTEM > 0 +CLI11_INLINE std::filesystem::path to_path(std::string_view str) { + return std::filesystem::path{ +#ifdef _WIN32 + widen(str) +#else + str +#endif // _WIN32 + }; +} +#endif // CLI11_HAS_FILESYSTEM + +// [CLI11:encoding_inl_hpp:end] +} // namespace CLI diff --git a/packages/CLI11/include/CLI/impl/Formatter_inl.hpp b/packages/CLI11/include/CLI/impl/Formatter_inl.hpp index 37249e082e23d9e3bf947eceb0dd8eb721d11391..84652fefa4aebcb2ef5257c887d0e5494d40c5f1 100644 --- a/packages/CLI11/include/CLI/impl/Formatter_inl.hpp +++ b/packages/CLI11/include/CLI/impl/Formatter_inl.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2022, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // @@ -69,13 +69,13 @@ CLI11_INLINE std::string Formatter::make_description(const App *app) const { auto min_options = app->get_require_option_min(); auto max_options = app->get_require_option_max(); if(app->get_required()) { - desc += " REQUIRED "; + desc += " " + get_label("REQUIRED") + " "; } if((max_options == min_options) && (min_options > 0)) { if(min_options == 1) { desc += " \n[Exactly 1 of the following options is required]"; } else { - desc += " \n[Exactly " + std::to_string(min_options) + "options from the following list are required]"; + desc += " \n[Exactly " + std::to_string(min_options) + " options from the following list are required]"; } } else if(max_options > 0) { if(min_options > 0) { @@ -91,6 +91,11 @@ CLI11_INLINE std::string Formatter::make_description(const App *app) const { } CLI11_INLINE std::string Formatter::make_usage(const App *app, std::string name) const { + std::string usage = app->get_usage(); + if(!usage.empty()) { + return usage + "\n"; + } + std::stringstream out; out << get_label("Usage") << ":" << (name.empty() ? "" : " ") << name; @@ -137,7 +142,7 @@ CLI11_INLINE std::string Formatter::make_footer(const App *app) const { if(footer.empty()) { return std::string{}; } - return footer + "\n"; + return "\n" + footer + "\n"; } CLI11_INLINE std::string Formatter::make_help(const App *app, std::string name, AppFormatMode mode) const { @@ -159,7 +164,7 @@ CLI11_INLINE std::string Formatter::make_help(const App *app, std::string name, out << make_positionals(app); out << make_groups(app, mode); out << make_subcommands(app, mode); - out << '\n' << make_footer(app); + out << make_footer(app); return out.str(); } @@ -208,7 +213,10 @@ CLI11_INLINE std::string Formatter::make_subcommands(const App *app, AppFormatMo CLI11_INLINE std::string Formatter::make_subcommand(const App *sub) const { std::stringstream out; - detail::format_help(out, sub->get_display_name(true), sub->get_description(), column_width_); + detail::format_help(out, + sub->get_display_name(true) + (sub->get_required() ? " " + get_label("REQUIRED") : ""), + sub->get_description(), + column_width_); return out.str(); } diff --git a/packages/CLI11/include/CLI/impl/Option_inl.hpp b/packages/CLI11/include/CLI/impl/Option_inl.hpp index a97346235a417acad32b764a8efeb4f0ff1b6559..a24df9ab293700a31bba830581247d5b01d5b412 100644 --- a/packages/CLI11/include/CLI/impl/Option_inl.hpp +++ b/packages/CLI11/include/CLI/impl/Option_inl.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2022, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // diff --git a/packages/CLI11/include/CLI/impl/Split_inl.hpp b/packages/CLI11/include/CLI/impl/Split_inl.hpp index 03478b209bf95afaf6a8aca43a10fdcc636560a4..d974f80a6f7618de7a8fb2cdf2ddade30cb48fc7 100644 --- a/packages/CLI11/include/CLI/impl/Split_inl.hpp +++ b/packages/CLI11/include/CLI/impl/Split_inl.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2022, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // @@ -34,7 +34,7 @@ CLI11_INLINE bool split_short(const std::string ¤t, std::string &name, std } CLI11_INLINE bool split_long(const std::string ¤t, std::string &name, std::string &value) { - if(current.size() > 2 && current.substr(0, 2) == "--" && valid_first_char(current[2])) { + if(current.size() > 2 && current.compare(0, 2, "--") == 0 && valid_first_char(current[2])) { auto loc = current.find_first_of('='); if(loc != std::string::npos) { name = current.substr(2, loc - 2); diff --git a/packages/CLI11/include/CLI/impl/StringTools_inl.hpp b/packages/CLI11/include/CLI/impl/StringTools_inl.hpp index 1a7cdd818a5f0a9053bae2f3e59cc7c77fcfea5d..9b81fbde3475b03f67bc1f4330b6efdfe0d87d93 100644 --- a/packages/CLI11/include/CLI/impl/StringTools_inl.hpp +++ b/packages/CLI11/include/CLI/impl/StringTools_inl.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2022, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // diff --git a/packages/CLI11/include/CLI/impl/Validators_inl.hpp b/packages/CLI11/include/CLI/impl/Validators_inl.hpp index f2fe5e55de7258ef29535d0b6339221c41572641..a2295ecdf8954d63cede377731710397ecd85de3 100644 --- a/packages/CLI11/include/CLI/impl/Validators_inl.hpp +++ b/packages/CLI11/include/CLI/impl/Validators_inl.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2022, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // @@ -8,6 +8,7 @@ #include <CLI/Validators.hpp> +#include <CLI/Encoding.hpp> #include <CLI/Macros.hpp> #include <CLI/StringTools.hpp> #include <CLI/TypeTools.hpp> @@ -127,12 +128,12 @@ namespace detail { #if defined CLI11_HAS_FILESYSTEM && CLI11_HAS_FILESYSTEM > 0 CLI11_INLINE path_type check_path(const char *file) noexcept { std::error_code ec; - auto stat = std::filesystem::status(file, ec); + auto stat = std::filesystem::status(to_path(file), ec); if(ec) { return path_type::nonexistent; } switch(stat.type()) { - case std::filesystem::file_type::none: + case std::filesystem::file_type::none: // LCOV_EXCL_LINE case std::filesystem::file_type::not_found: return path_type::nonexistent; case std::filesystem::file_type::directory: @@ -219,7 +220,8 @@ CLI11_INLINE IPV4Validator::IPV4Validator() : Validator("IPV4") { } int num = 0; for(const auto &var : result) { - bool retval = detail::lexical_cast(var, num); + using CLI::detail::lexical_cast; + bool retval = lexical_cast(var, num); if(!retval) { return std::string("Failed parsing number (") + var + ')'; } diff --git a/packages/CLI11/scripts/MakeSingleHeader.py b/packages/CLI11/scripts/MakeSingleHeader.py index 4bc53dd1509d144e5255434cab7e8056a00d8c8d..7cca6f70ae6e0cb07632bb2e6c6658f018077509 100755 --- a/packages/CLI11/scripts/MakeSingleHeader.py +++ b/packages/CLI11/scripts/MakeSingleHeader.py @@ -80,7 +80,7 @@ class HeaderGroups(dict): """ for key in self: if isinstance(self[key], set): - self[key] = "\n".join(self[key]) + self[key] = "\n".join(sorted(self[key])) def make_header(output, main_header, files, tag, namespace, macro=None, version=None): @@ -100,6 +100,8 @@ def make_header(output, main_header, files, tag, namespace, macro=None, version= groups["git"] = "" for f in files: + if os.path.isdir(f): + continue groups.read_header(f) groups["namespace"] = namespace diff --git a/packages/CLI11/scripts/mdlint_style.rb b/packages/CLI11/scripts/mdlint_style.rb index 6fca85b1a3d05a43aba1fca2e3aa256676e1f11d..5cb3a9792d9dc4beb7b1cc93c59b6104c1d20a5d 100644 --- a/packages/CLI11/scripts/mdlint_style.rb +++ b/packages/CLI11/scripts/mdlint_style.rb @@ -6,3 +6,4 @@ exclude_rule 'MD034' # Bare URL (for now) rule 'MD026', punctuation: '.,;:!' # Trailing punctuation in header (& in this case) rule 'MD029', style: :ordered +rule 'MD007', indent: 2 diff --git a/packages/CLI11/src/CMakeLists.txt b/packages/CLI11/src/CMakeLists.txt index 341cae42c5233649106f11d55557af4d938d6b70..4f7af6ad190295688e2cdad3d8bd6fa7845ede5a 100644 --- a/packages/CLI11/src/CMakeLists.txt +++ b/packages/CLI11/src/CMakeLists.txt @@ -2,7 +2,6 @@ set(CLI11_headerLoc "${PROJECT_SOURCE_DIR}/include/CLI") set(CLI11_headers ${CLI11_headerLoc}/App.hpp - ${CLI11_headerLoc}/CLI.hpp ${CLI11_headerLoc}/Config.hpp ${CLI11_headerLoc}/ConfigFwd.hpp ${CLI11_headerLoc}/Error.hpp @@ -12,10 +11,11 @@ set(CLI11_headers ${CLI11_headerLoc}/Option.hpp ${CLI11_headerLoc}/Split.hpp ${CLI11_headerLoc}/StringTools.hpp - ${CLI11_headerLoc}/Timer.hpp ${CLI11_headerLoc}/TypeTools.hpp ${CLI11_headerLoc}/Validators.hpp - ${CLI11_headerLoc}/Version.hpp) + ${CLI11_headerLoc}/Version.hpp + ${CLI11_headerLoc}/Encoding.hpp + ${CLI11_headerLoc}/Argv.hpp) set(CLI11_implLoc "${PROJECT_SOURCE_DIR}/include/CLI/impl") @@ -26,12 +26,17 @@ set(CLI11_impl_headers ${CLI11_implLoc}/Option_inl.hpp ${CLI11_implLoc}/Split_inl.hpp ${CLI11_implLoc}/StringTools_inl.hpp - ${CLI11_implLoc}/Validators_inl.hpp) + ${CLI11_implLoc}/Validators_inl.hpp + ${CLI11_implLoc}/Encoding_inl.hpp + ${CLI11_implLoc}/Argv_inl.hpp) + +set(CLI11_library_headers ${CLI11_headerLoc}/CLI.hpp ${CLI11_headerLoc}/Timer.hpp) if(CLI11_PRECOMPILED) # Create static lib file(GLOB CLI11_precompile_sources "${PROJECT_SOURCE_DIR}/src/*.cpp") - add_library(CLI11 STATIC ${CLI11_headers} ${CLI11_impl_headers} ${CLI11_precompile_sources}) + add_library(CLI11 STATIC ${CLI11_headers} ${CLI11_library_headers} ${CLI11_impl_headers} + ${CLI11_precompile_sources}) target_compile_definitions(CLI11 PUBLIC -DCLI11_COMPILE) set(PUBLIC_OR_INTERFACE PUBLIC) @@ -39,7 +44,7 @@ else() add_library(CLI11 INTERFACE) if(CMAKE_VERSION VERSION_GREATER 3.19) # This is only useful for visual studio and other IDE builds - target_sources(CLI11 PRIVATE ${CLI11_headers} ${CLI11_impl_headers}) + target_sources(CLI11 PRIVATE ${CLI11_headers} ${CLI11_library_headers} ${CLI11_impl_headers}) endif() set(PUBLIC_OR_INTERFACE INTERFACE) @@ -48,25 +53,35 @@ endif() # Allow IDE's to group targets into folders add_library(CLI11::CLI11 ALIAS CLI11) # for add_subdirectory calls +if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME) + set(SYSTEM_INCL "") +else() + # If this project is included from somewhere else, we mark our headers as system headers to avoid + # the compiler emitting any warnings about them + set(SYSTEM_INCL "SYSTEM") +endif() + # Duplicated because CMake adds the current source dir if you don't. target_include_directories( - CLI11 ${PUBLIC_OR_INTERFACE} $<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include> + CLI11 ${SYSTEM_INCL} ${PUBLIC_OR_INTERFACE} $<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include> $<INSTALL_INTERFACE:include>) -if(CMAKE_VERSION VERSION_LESS 3.8) - # This might not be a complete list - target_compile_features( - CLI11 - INTERFACE cxx_lambdas - cxx_nullptr - cxx_override - cxx_range_for - cxx_right_angle_brackets - cxx_strong_enums - cxx_constexpr - cxx_auto_type) -else() - target_compile_features(CLI11 INTERFACE cxx_std_11) +if(CMAKE_CXX_STANDARD LESS 14) + if(CMAKE_VERSION VERSION_LESS 3.8) + # This might not be a complete list + target_compile_features( + CLI11 + INTERFACE cxx_lambdas + cxx_nullptr + cxx_override + cxx_range_for + cxx_right_angle_brackets + cxx_strong_enums + cxx_constexpr + cxx_auto_type) + else() + target_compile_features(CLI11 INTERFACE cxx_std_11) + endif() endif() if(CLI11_SINGLE_FILE) @@ -112,7 +127,8 @@ if(CLI11_INSTALL) # Make an export target install(TARGETS CLI11 EXPORT CLI11Targets) if(NOT CLI11_SINGLE_FILE) - install(FILES ${CLI11_headers} DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/CLI") + install(FILES ${CLI11_headers} ${CLI11_library_headers} + DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/CLI") if(NOT CLI11_COMPILE) install(FILES ${CLI11_impl_headers} DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/CLI/impl") endif() diff --git a/packages/CLI11/src/Precompile.cpp b/packages/CLI11/src/Precompile.cpp index 81900ced37e7edf8a5f37bc37aeac659e1946944..5afd54cb99b1a9511a36e7b30de3a5317d1d8bad 100644 --- a/packages/CLI11/src/Precompile.cpp +++ b/packages/CLI11/src/Precompile.cpp @@ -1,11 +1,13 @@ -// Copyright (c) 2017-2022, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // // SPDX-License-Identifier: BSD-3-Clause #include <CLI/impl/App_inl.hpp> +#include <CLI/impl/Argv_inl.hpp> #include <CLI/impl/Config_inl.hpp> +#include <CLI/impl/Encoding_inl.hpp> #include <CLI/impl/Formatter_inl.hpp> #include <CLI/impl/Option_inl.hpp> #include <CLI/impl/Split_inl.hpp> diff --git a/packages/CLI11/test_package/CMakeLists.txt b/packages/CLI11/test_package/CMakeLists.txt deleted file mode 100644 index 48f6d9903248c09c1c897f5266dab257f42907e2..0000000000000000000000000000000000000000 --- a/packages/CLI11/test_package/CMakeLists.txt +++ /dev/null @@ -1,16 +0,0 @@ -project(PackageTest CXX) -cmake_minimum_required(VERSION 3.1) - -include(${CMAKE_BINARY_DIR}/conanbuildinfo.cmake) -conan_basic_setup() - -set(CMAKE_CXX_STANDARD 11) -set(CMAKE_CXX_EXTENSIONS OFF) -set(CMAKE_CXX_STANDARD_REQUIRED ON) - -message(STATUS "${CMAKE_PREFIX_PATH}") - -find_package(CLI11 CONFIG REQUIRED) - -add_executable(example example.cpp) -target_link_libraries(example CLI11::CLI11) diff --git a/packages/CLI11/test_package/conanfile.py b/packages/CLI11/test_package/conanfile.py deleted file mode 100644 index 4c5c028ecee13664aaad7ca19cd9f74f271896ea..0000000000000000000000000000000000000000 --- a/packages/CLI11/test_package/conanfile.py +++ /dev/null @@ -1,21 +0,0 @@ -from conans import ConanFile, CMake, tools -import os - - -class HelloTestConan(ConanFile): - settings = "os", "compiler", "build_type", "arch" - generators = "cmake" - - def build(self): - cmake = CMake(self) - cmake.configure() - cmake.build() - - def imports(self): - self.copy("*.dll", dst="bin", src="bin") - self.copy("*.dylib*", dst="bin", src="lib") - - def test(self): - if not tools.cross_building(self.settings): - os.chdir("bin") - self.run(".%sexample" % os.sep) diff --git a/packages/CLI11/test_package/example.cpp b/packages/CLI11/test_package/example.cpp deleted file mode 100644 index 464cf751896f183fa1bba19d965ac521c8b60a52..0000000000000000000000000000000000000000 --- a/packages/CLI11/test_package/example.cpp +++ /dev/null @@ -1,20 +0,0 @@ -// This file is a "Hello, world!" CLI11 program - -#include "CLI/CLI.hpp" - -#include <iostream> - -int main(int argc, char **argv) { - - CLI::App app("Some nice description"); - - int x = 0; - app.add_option("-x", x, "an integer value")->capture_default_str(); - - bool flag; - app.add_flag("-f,--flag", flag, "a flag option"); - - CLI11_PARSE(app, argc, argv); - - return 0; -} diff --git a/packages/CLI11/tests/AppTest.cpp b/packages/CLI11/tests/AppTest.cpp index 994c890ca5a605fb58f657f5ff656b426bf398a1..2cdefc4ee61d0770802c491d698e52c9a8b8e4a3 100644 --- a/packages/CLI11/tests/AppTest.cpp +++ b/packages/CLI11/tests/AppTest.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2022, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // @@ -7,6 +7,7 @@ #include "app_helper.hpp" #include <cmath> +#include <array> #include <complex> #include <cstdint> #include <cstdlib> @@ -141,8 +142,6 @@ TEST_CASE_METHOD(TApp, "StrangeFlagNames", "[app]") { } TEST_CASE_METHOD(TApp, "RequireOptionsError", "[app]") { - using Catch::Matchers::Contains; - app.add_flag("-c"); app.add_flag("--q"); app.add_flag("--this,--that"); @@ -261,6 +260,28 @@ TEST_CASE_METHOD(TApp, "OneString", "[app]") { CHECK("mystring" == str); } +TEST_CASE_METHOD(TApp, "OneWideString", "[app]") { + std::wstring str; + app.add_option("-s,--string", str); + args = {"--string", "mystring"}; + run(); + CHECK(app.count("-s") == 1u); + CHECK(app.count("--string") == 1u); + CHECK(L"mystring" == str); +} + +TEST_CASE_METHOD(TApp, "OneStringWideInput", "[app][unicode]") { + std::string str; + app.add_option("-s,--string", str); + + std::array<const wchar_t *, 3> cmdline{{L"app", L"--string", L"mystring"}}; + app.parse(static_cast<int>(cmdline.size()), cmdline.data()); + + CHECK(app.count("-s") == 1u); + CHECK(app.count("--string") == 1u); + CHECK("mystring" == str); +} + TEST_CASE_METHOD(TApp, "OneStringWindowsStyle", "[app]") { std::string str; app.add_option("-s,--string", str); @@ -282,6 +303,16 @@ TEST_CASE_METHOD(TApp, "OneStringSingleStringInput", "[app]") { CHECK("mystring" == str); } +TEST_CASE_METHOD(TApp, "OneStringSingleWideStringInput", "[app][unicode]") { + std::string str; + app.add_option("-s,--string", str); + + app.parse(L"--string mystring"); + CHECK(app.count("-s") == 1u); + CHECK(app.count("--string") == 1u); + CHECK("mystring" == str); +} + TEST_CASE_METHOD(TApp, "OneStringEqualVersion", "[app]") { std::string str; app.add_option("-s,--string", str); @@ -981,7 +1012,9 @@ TEST_CASE_METHOD(TApp, "emptyVectorReturn", "[app]") { std::vector<std::string> strs; std::vector<std::string> strs2; + std::vector<std::string> strs3; auto *opt1 = app.add_option("--str", strs)->required()->expected(0, 2); + app.add_option("--str3", strs3)->expected(1, 3); app.add_option("--str2", strs2); args = {"--str"}; @@ -1004,6 +1037,11 @@ TEST_CASE_METHOD(TApp, "emptyVectorReturn", "[app]") { CHECK_NOTHROW(run()); CHECK(strs.empty()); + opt1->required(false); + args = {"--str3", "{}"}; + + CHECK_NOTHROW(run()); + CHECK_FALSE(strs3.empty()); } TEST_CASE_METHOD(TApp, "RequiredOptsDoubleShort", "[app]") { @@ -1988,6 +2026,31 @@ TEST_CASE_METHOD(TApp, "typeCheck", "[app]") { CHECK_THROWS_AS(run(), CLI::ValidationError); } +TEST_CASE_METHOD(TApp, "NeedsTrue", "[app]") { + std::string str; + app.add_option("-s,--string", str); + app.add_flag("--opt1")->check([&](const std::string &) { + return (str != "val_with_opt1") ? std::string("--opt1 requires --string val_with_opt1") : std::string{}; + }); + + run(); + + args = {"--opt1"}; + CHECK_THROWS_AS(run(), CLI::ValidationError); + + args = {"--string", "val"}; + run(); + + args = {"--string", "val", "--opt1"}; + CHECK_THROWS_AS(run(), CLI::ValidationError); + + args = {"--string", "val_with_opt1", "--opt1"}; + run(); + + args = {"--opt1", "--string", "val_with_opt1"}; // order is not revelant + run(); +} + // Check to make sure programmatic access to left over is available TEST_CASE_METHOD(TApp, "AllowExtras", "[app]") { @@ -2086,21 +2149,23 @@ TEST_CASE_METHOD(TApp, "AllowExtrasArgModify", "[app]") { TEST_CASE_METHOD(TApp, "CheckShortFail", "[app]") { args = {"--two"}; - CHECK_THROWS_AS(CLI::detail::AppFriend::parse_arg(&app, args, CLI::detail::Classifier::SHORT), CLI::HorribleError); + CHECK_THROWS_AS(CLI::detail::AppFriend::parse_arg(&app, args, CLI::detail::Classifier::SHORT, false), + CLI::HorribleError); } // Test horrible error TEST_CASE_METHOD(TApp, "CheckLongFail", "[app]") { args = {"-t"}; - CHECK_THROWS_AS(CLI::detail::AppFriend::parse_arg(&app, args, CLI::detail::Classifier::LONG), CLI::HorribleError); + CHECK_THROWS_AS(CLI::detail::AppFriend::parse_arg(&app, args, CLI::detail::Classifier::LONG, false), + CLI::HorribleError); } // Test horrible error TEST_CASE_METHOD(TApp, "CheckWindowsFail", "[app]") { args = {"-t"}; - CHECK_THROWS_AS(CLI::detail::AppFriend::parse_arg(&app, args, CLI::detail::Classifier::WINDOWS_STYLE), + CHECK_THROWS_AS(CLI::detail::AppFriend::parse_arg(&app, args, CLI::detail::Classifier::WINDOWS_STYLE, false), CLI::HorribleError); } @@ -2108,7 +2173,8 @@ TEST_CASE_METHOD(TApp, "CheckWindowsFail", "[app]") { TEST_CASE_METHOD(TApp, "CheckOtherFail", "[app]") { args = {"-t"}; - CHECK_THROWS_AS(CLI::detail::AppFriend::parse_arg(&app, args, CLI::detail::Classifier::NONE), CLI::HorribleError); + CHECK_THROWS_AS(CLI::detail::AppFriend::parse_arg(&app, args, CLI::detail::Classifier::NONE, false), + CLI::HorribleError); } // Test horrible error @@ -2428,3 +2494,21 @@ TEST_CASE("C20_compile", "simple") { app.parse("--flag"); CHECK_FALSE(flag->empty()); } + +// #14 +TEST_CASE("System Args", "[app]") { + const char *commandline = CLI11_SYSTEM_ARGS_EXE " 1234 false \"hello world\""; + int retval = std::system(commandline); + + if(retval == -1) { + FAIL("Executable '" << commandline << "' reported different argc count"); + } + + if(retval > 0) { + FAIL("Executable '" << commandline << "' reported different argv at index " << (retval - 1)); + } + + if(retval != 0) { + FAIL("Executable '" << commandline << "' failed with an unknown return code"); + } +} diff --git a/packages/CLI11/tests/BoostOptionTypeTest.cpp b/packages/CLI11/tests/BoostOptionTypeTest.cpp index c3cd81852a8124bb3bb1427a1a667d1f13176703..1dabc37da9204b754bca3dc923a4e6a431e65507 100644 --- a/packages/CLI11/tests/BoostOptionTypeTest.cpp +++ b/packages/CLI11/tests/BoostOptionTypeTest.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2022, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // diff --git a/packages/CLI11/tests/CMakeLists.txt b/packages/CLI11/tests/CMakeLists.txt index 7d89e128b713ac91de5b03d56db0cec355fcea7e..7bd47744cb7e6e625b55e6fccad83faf4eedf565 100644 --- a/packages/CLI11/tests/CMakeLists.txt +++ b/packages/CLI11/tests/CMakeLists.txt @@ -4,7 +4,7 @@ if(CLI11_SANITIZERS) sanitizers GIT_REPOSITORY https://github.com/arsenm/sanitizers-cmake.git GIT_SHALLOW 1 - GIT_TAG 99e159e) + GIT_TAG c3dc841) FetchContent_GetProperties(sanitizers) @@ -49,46 +49,92 @@ set(CLI11_TESTS StringParseTest ComplexTypeTest TrueFalseTest - OptionGroupTest) + OptionGroupTest + EncodingTest) if(WIN32) list(APPEND CLI11_TESTS WindowsTest) endif() +if(CMAKE_CXX_STANDARD GREATER 16) + list(APPEND CLI11_TESTS FuzzFailTest) +endif() + if(Boost_FOUND) list(APPEND CLI11_TESTS BoostOptionTypeTest) endif() set(CLI11_MULTIONLY_TESTS TimerTest) -add_library(catch_main main.cpp catch.hpp) -target_include_directories(catch_main PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}") - find_package(Catch2 CONFIG) if(Catch2_FOUND) if(NOT TARGET Catch2::Catch2) message(FATAL_ERROR "Found Catch2 at ${Catch2_DIR} but targets are missing.") endif() - message(STATUS "Found Catch2") - target_link_libraries(catch_main PUBLIC Catch2::Catch2) + message(STATUS "Found Catch2 ${Catch2_VERSION}") + + if(Catch2_VERSION VERSION_LESS 3) + add_library(catch_main main.cpp catch.hpp) + target_include_directories(catch_main PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}") + target_link_libraries(catch_main PUBLIC Catch2::Catch2) + else() + add_library(catch_main ALIAS Catch2::Catch2WithMain) + target_compile_definitions(Catch2::Catch2WithMain INTERFACE -DCLI11_CATCH3) + endif() else() message(STATUS "Downloading Catch2") # FetchContent would be better, but requires newer CMake. file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/catch2") - set(url https://github.com/philsquared/Catch/releases/download/v2.13.7/catch.hpp) + set(url https://github.com/philsquared/Catch/releases/download/v2.13.10/catch.hpp) file( DOWNLOAD ${url} "${CMAKE_CURRENT_BINARY_DIR}/catch2/catch.hpp" STATUS status - EXPECTED_HASH SHA256=ea379c4a3cb5799027b1eb451163dff065a3d641aaba23bf4e24ee6b536bd9bc) + EXPECTED_HASH SHA256=3725c0f0a75f376a5005dde31ead0feb8f7da7507644c201b814443de8355170) list(GET status 0 error) if(error) message(FATAL_ERROR "Could not download ${url}, and Catch2 not found on your system.") endif() - target_include_directories(catch_main PUBLIC "${CMAKE_CURRENT_BINARY_DIR}") + add_library(catch_main main.cpp catch.hpp) + target_include_directories(catch_main PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}" + "${CMAKE_CURRENT_BINARY_DIR}") endif() +# Add special target that copies the data directory for tests +file( + GLOB_RECURSE DATA_FILES + LIST_DIRECTORIES false + RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" + "${CMAKE_CURRENT_SOURCE_DIR}/data/*") + +foreach(DATA_FILE IN LISTS DATA_FILES) + add_custom_command( + OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/${DATA_FILE}" + COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_CURRENT_SOURCE_DIR}/${DATA_FILE}" + "${CMAKE_CURRENT_BINARY_DIR}/${DATA_FILE}" + MAIN_DEPENDENCY "${CMAKE_CURRENT_SOURCE_DIR}/${DATA_FILE}" + VERBATIM) + target_sources(catch_main PRIVATE "${CMAKE_CURRENT_BINARY_DIR}/${DATA_FILE}") +endforeach() + +# Build dependent applications which are launched from test code +set(CLI11_DEPENDENT_APPLICATIONS system_args) + +foreach(APP IN LISTS CLI11_DEPENDENT_APPLICATIONS) + add_executable(${APP} applications/${APP}.cpp) + target_include_directories(${APP} PRIVATE ${CMAKE_SOURCE_DIR}/include) + add_dependencies(catch_main ${APP}) +endforeach() + +function(add_dependent_application_definitions TARGET) + foreach(APP IN LISTS CLI11_DEPENDENT_APPLICATIONS) + string(TOUPPER ${APP} APP_UPPERCASE) + target_compile_definitions(${TARGET} + PRIVATE CLI11_${APP_UPPERCASE}_EXE="$<TARGET_FILE:${APP}>") + endforeach() +endfunction() + # Target must already exist macro(add_catch_test TESTNAME) target_link_libraries(${TESTNAME} PUBLIC catch_main) @@ -108,6 +154,8 @@ foreach(T IN LISTS CLI11_TESTS) set_property(SOURCE ${T}.cpp PROPERTY LANGUAGE CUDA) endif() add_executable(${T} ${T}.cpp) + + add_dependent_application_definitions(${T}) add_sanitizers(${T}) if(NOT CLI11_CUDA_TESTS) target_link_libraries(${T} PRIVATE CLI11_warnings) @@ -117,6 +165,7 @@ foreach(T IN LISTS CLI11_TESTS) if(CLI11_SINGLE_FILE AND CLI11_SINGLE_FILE_TESTS) add_executable(${T}_Single ${T}.cpp) + add_dependent_application_definitions(${T}_Single) target_link_libraries(${T}_Single PRIVATE CLI11_SINGLE) add_catch_test(${T}_Single) set_property(TARGET ${T}_Single PROPERTY FOLDER "Tests Single File") @@ -125,11 +174,22 @@ endforeach() foreach(T IN LISTS CLI11_MULTIONLY_TESTS) add_executable(${T} ${T}.cpp) + add_dependent_application_definitions(${T}) add_sanitizers(${T}) target_link_libraries(${T} PUBLIC CLI11) add_catch_test(${T}) endforeach() +if(CMAKE_CXX_STANDARD GREATER 16) + set(TEST_FILE_FOLDER ${CMAKE_CURRENT_SOURCE_DIR}) + target_compile_definitions(FuzzFailTest PUBLIC -DTEST_FILE_FOLDER="${TEST_FILE_FOLDER}") + target_sources(FuzzFailTest PUBLIC ${PROJECT_SOURCE_DIR}/fuzz/fuzzApp.cpp) + if(CLI11_SINGLE_FILE AND CLI11_SINGLE_FILE_TESTS) + target_compile_definitions(FuzzFailTest_Single PUBLIC -DTEST_FILE_FOLDER="${TEST_FILE_FOLDER}") + target_sources(FuzzFailTest_Single PUBLIC ${PROJECT_SOURCE_DIR}/fuzz/fuzzApp.cpp) + endif() +endif() + # Add -Wno-deprecated-declarations to DeprecatedTest set(no-deprecated-declarations $<$<CXX_COMPILER_ID:MSVC>:/wd4996> $<$<NOT:$<CXX_COMPILER_ID:MSVC>>:-Wno-deprecated-declarations>) diff --git a/packages/CLI11/tests/ComplexTypeTest.cpp b/packages/CLI11/tests/ComplexTypeTest.cpp index 156798badc6bfbb30b01160e7b0fb88c780be8b3..adcd26c4b24da2a5d4205a8a3d262b0d8c237dcc 100644 --- a/packages/CLI11/tests/ComplexTypeTest.cpp +++ b/packages/CLI11/tests/ComplexTypeTest.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2022, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // @@ -9,8 +9,6 @@ #include <complex> #include <cstdint> -using Catch::Matchers::Contains; - using cx = std::complex<double>; CLI::Option * diff --git a/packages/CLI11/tests/ConfigFileTest.cpp b/packages/CLI11/tests/ConfigFileTest.cpp index 0b11ffefbd9a4076be7febce20cd43481e801248..206872728c1734d1632000d9f9c208cb23839060 100644 --- a/packages/CLI11/tests/ConfigFileTest.cpp +++ b/packages/CLI11/tests/ConfigFileTest.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2022, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // @@ -9,8 +9,6 @@ #include <cstdio> #include <sstream> -using Catch::Matchers::Contains; - TEST_CASE("StringBased: convert_arg_for_ini", "[config]") { CHECK("\"\"" == CLI::detail::convert_arg_for_ini(std::string{})); @@ -435,6 +433,8 @@ TEST_CASE("StringBased: file_error", "[config]") { CHECK_THROWS_AS(CLI::ConfigINI().from_file("nonexist_file"), CLI::FileError); } +static const int fclear1 = fileClear("TestIniTmp.ini"); + TEST_CASE_METHOD(TApp, "IniNotRequired", "[config]") { TempFile tmpini{"TestIniTmp.ini"}; @@ -507,10 +507,47 @@ TEST_CASE_METHOD(TApp, "IniGetRemainingOption", "[config]") { int two{0}; app.add_option("--two", two); REQUIRE_NOTHROW(run()); - std::vector<std::string> ExpectedRemaining = {ExtraOption}; + std::vector<std::string> ExpectedRemaining = {ExtraOption, "3"}; CHECK(ExpectedRemaining == app.remaining()); } +TEST_CASE_METHOD(TApp, "IniRemainingSub", "[config]") { + TempFile tmpini{"TestIniTmp.ini"}; + + app.set_config("--config", tmpini); + auto *map = app.add_subcommand("map"); + map->allow_config_extras(); + + { + std::ofstream out{tmpini}; + out << "[map]\n"; + out << "a = 1\n"; + out << "b=[1,2,3]\n"; + out << "c = 3" << std::endl; + } + + REQUIRE_NOTHROW(run()); + std::vector<std::string> rem = map->remaining(); + REQUIRE(rem.size() == 8U); + CHECK(rem[0] == "map.a"); + CHECK(rem[2] == "map.b"); + CHECK(rem[6] == "map.c"); + CHECK(rem[5] == "3"); + + int a{0}; + int c{0}; + std::vector<int> b; + map->add_option("-a", a); + map->add_option("-b", b); + map->add_option("-c", c); + + CHECK_NOTHROW(app.parse(app.remaining_for_passthrough())); + CHECK(a == 1); + CHECK(c == 3); + REQUIRE(b.size() == 3U); + CHECK(b[1] == 2); +} + TEST_CASE_METHOD(TApp, "IniGetNoRemaining", "[config]") { TempFile tmpini{"TestIniTmp.ini"}; @@ -595,6 +632,8 @@ TEST_CASE_METHOD(TApp, "IniNotRequiredbadConfigurator", "[config]") { REQUIRE_NOTHROW(run()); } +static const int fclear2 = fileClear("TestIniTmp2.ini"); + TEST_CASE_METHOD(TApp, "IniNotRequiredNotDefault", "[config]") { TempFile tmpini{"TestIniTmp.ini"}; @@ -1017,17 +1056,19 @@ TEST_CASE_METHOD(TApp, "TOMLStringVector", "[config]") { out << "zero1=[]\n"; out << "zero2={}\n"; out << "zero3={}\n"; + out << "zero4=[\"{}\",\"\"]\n"; out << "nzero={}\n"; out << "one=[\"1\"]\n"; out << "two=[\"2\",\"3\"]\n"; out << "three=[\"1\",\"2\",\"3\"]\n"; } - std::vector<std::string> nzero, zero1, zero2, zero3, one, two, three; + std::vector<std::string> nzero, zero1, zero2, zero3, zero4, one, two, three; app.add_option("--zero1", zero1)->required()->expected(0, 99)->default_str("{}"); app.add_option("--zero2", zero2)->required()->expected(0, 99)->default_val(std::vector<std::string>{}); // if no default is specified the argument results in an empty string app.add_option("--zero3", zero3)->required()->expected(0, 99); + app.add_option("--zero4", zero4)->required()->expected(0, 99); app.add_option("--nzero", nzero)->required(); app.add_option("--one", one)->required(); app.add_option("--two", two)->required(); @@ -1038,6 +1079,7 @@ TEST_CASE_METHOD(TApp, "TOMLStringVector", "[config]") { CHECK(zero1 == std::vector<std::string>({})); CHECK(zero2 == std::vector<std::string>({})); CHECK(zero3 == std::vector<std::string>({""})); + CHECK(zero4 == std::vector<std::string>({"{}"})); CHECK(nzero == std::vector<std::string>({"{}"})); CHECK(one == std::vector<std::string>({"1"})); CHECK(two == std::vector<std::string>({"2", "3"})); @@ -1735,6 +1777,23 @@ TEST_CASE_METHOD(TApp, "IniFlagDual", "[config]") { CHECK_THROWS_AS(run(), CLI::ConversionError); } +TEST_CASE_METHOD(TApp, "IniVectorMax", "[config]") { + + TempFile tmpini{"TestIniTmp.ini"}; + + std::vector<std::string> v1; + app.config_formatter(std::make_shared<CLI::ConfigINI>()); + app.add_option("--vec", v1)->expected(0, 2); + app.set_config("--config", tmpini); + + { + std::ofstream out{tmpini}; + out << "vec=[a,b,c]" << std::endl; + } + + CHECK_THROWS_AS(run(), CLI::ArgumentMismatch); +} + TEST_CASE_METHOD(TApp, "IniShort", "[config]") { TempFile tmpini{"TestIniTmp.ini"}; @@ -2000,6 +2059,51 @@ TEST_CASE_METHOD(TApp, "IniFalseFlagsDefDisableOverrideSuccess", "[config]") { CHECK(val == 15); } +static const int fclear3 = fileClear("TestIniTmp3.ini"); + +TEST_CASE_METHOD(TApp, "IniDisableFlagOverride", "[config]") { + + TempFile tmpini{"TestIniTmp.ini"}; + TempFile tmpini2{"TestIniTmp2.ini"}; + TempFile tmpini3{"TestIniTmp3.ini"}; + + app.set_config("--config", tmpini); + + { + std::ofstream out{tmpini}; + out << "[default]" << std::endl; + out << "two=2" << std::endl; + } + + { + std::ofstream out{tmpini2}; + out << "[default]" << std::endl; + out << "two=7" << std::endl; + } + + { + std::ofstream out{tmpini3}; + out << "[default]" << std::endl; + out << "three=true" << std::endl; + } + + int val{0}; + app.add_flag("--one{1},--two{2},--three{3}", val)->disable_flag_override(); + + run(); + CHECK(tmpini.c_str() == app["--config"]->as<std::string>()); + CHECK(val == 2); + + args = {"--config", tmpini2}; + CHECK_THROWS_AS(run(), CLI::ArgumentMismatch); + + args = {"--config", tmpini3}; + run(); + + CHECK(val == 3); + CHECK(tmpini3.c_str() == app.get_config_ptr()->as<std::string>()); +} + TEST_CASE_METHOD(TApp, "TomlOutputSimple", "[config]") { int v{0}; @@ -2497,6 +2601,29 @@ TEST_CASE_METHOD(TApp, "ConfigWriteReadWrite", "[config]") { CHECK(config2 == config1); } +TEST_CASE_METHOD(TApp, "ConfigWriteReadNegated", "[config]") { + + TempFile tmpini{"TestIniTmp.ini"}; + bool flag{true}; + app.add_flag("!--no-flag", flag); + args = {"--no-flag"}; + run(); + + // Save config, with default values too + std::string config1 = app.config_to_str(false, false); + { + std::ofstream out{tmpini}; + out << config1 << std::endl; + } + CHECK_FALSE(flag); + args.clear(); + flag = true; + app.set_config("--config", tmpini, "Read an ini file", true); + run(); + + CHECK_FALSE(flag); +} + /////// INI output tests TEST_CASE_METHOD(TApp, "IniOutputSimple", "[config]") { diff --git a/packages/CLI11/tests/CreationTest.cpp b/packages/CLI11/tests/CreationTest.cpp index b58e0aabe07325a37dbe9ba76e4b4507a9cd2bfd..a51abd4889244d4fbd453db9b98d7a6a0d9c7ba0 100644 --- a/packages/CLI11/tests/CreationTest.cpp +++ b/packages/CLI11/tests/CreationTest.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2022, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // @@ -455,6 +455,7 @@ TEST_CASE_METHOD(TApp, "SubcommandDefaults", "[creation]") { CHECK(!app.get_configurable()); CHECK(!app.get_validate_positionals()); + CHECK(app.get_usage().empty()); CHECK(app.get_footer().empty()); CHECK("Subcommands" == app.get_group()); CHECK(0u == app.get_require_subcommand_min()); @@ -474,6 +475,7 @@ TEST_CASE_METHOD(TApp, "SubcommandDefaults", "[creation]") { app.fallthrough(); app.validate_positionals(); + app.usage("ussy"); app.footer("footy"); app.group("Stuff"); app.require_subcommand(2, 3); @@ -494,6 +496,7 @@ TEST_CASE_METHOD(TApp, "SubcommandDefaults", "[creation]") { CHECK(app2->get_fallthrough()); CHECK(app2->get_validate_positionals()); CHECK(app2->get_configurable()); + CHECK("ussy" == app2->get_usage()); CHECK("footy" == app2->get_footer()); CHECK("Stuff" == app2->get_group()); CHECK(0u == app2->get_require_subcommand_min()); @@ -549,6 +552,25 @@ TEST_CASE_METHOD(TApp, "GetOptionList", "[creation]") { } } +TEST_CASE_METHOD(TApp, "GetOptionListFilter", "[creation]") { + int two{0}; + auto *flag = app.add_flag("--one"); + app.add_option("--two", two); + + const CLI::App &const_app = app; // const alias to force use of const-methods + std::vector<const CLI::Option *> opt_listc = + const_app.get_options([](const CLI::Option *opt) { return opt->get_name() == "--one"; }); + + REQUIRE(static_cast<std::size_t>(1) == opt_listc.size()); + CHECK(flag == opt_listc.at(0)); + + std::vector<CLI::Option *> opt_list = + app.get_options([](const CLI::Option *opt) { return opt->get_name() == "--one"; }); + + REQUIRE(static_cast<std::size_t>(1) == opt_list.size()); + CHECK(flag == opt_list.at(0)); +} + TEST_CASE("ValidatorTests: TestValidatorCreation", "[creation]") { std::function<std::string(std::string &)> op1 = [](std::string &val) { return (val.size() >= 5) ? std::string{} : val; diff --git a/packages/CLI11/tests/DeprecatedTest.cpp b/packages/CLI11/tests/DeprecatedTest.cpp index 8c45f24950e392d03b4eaf13b1afde1da3d1879a..063c67e5e884dc6f004b23176eabb9b57bc63f3c 100644 --- a/packages/CLI11/tests/DeprecatedTest.cpp +++ b/packages/CLI11/tests/DeprecatedTest.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2022, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // @@ -6,8 +6,6 @@ #include "app_helper.hpp" -using Catch::Matchers::Contains; - TEST_CASE("Deprecated: Empty", "[deprecated]") { // No deprecated features at this time. CHECK(true); diff --git a/packages/CLI11/tests/EncodingTest.cpp b/packages/CLI11/tests/EncodingTest.cpp new file mode 100644 index 0000000000000000000000000000000000000000..b026ee0143dc3331f8b49f306358db7031233bf2 --- /dev/null +++ b/packages/CLI11/tests/EncodingTest.cpp @@ -0,0 +1,104 @@ +// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner +// under NSF AWARD 1414736 and by the respective contributors. +// All rights reserved. +// +// SPDX-License-Identifier: BSD-3-Clause + +#include "app_helper.hpp" + +#include <array> +#include <string> + +#if defined CLI11_HAS_FILESYSTEM && CLI11_HAS_FILESYSTEM > 0 +#include <filesystem> +#endif // CLI11_HAS_FILESYSTEM + +// "abcd" +static const std::string abcd_str = "abcd"; // NOLINT(runtime/string) +static const std::wstring abcd_wstr = L"abcd"; // NOLINT(runtime/string) + +// "𓂀𓂀𓂀" - 4-byte utf8 characters +static const std::array<uint8_t, 12 + 1> egypt_utf8_codeunits{ + {0xF0, 0x93, 0x82, 0x80, 0xF0, 0x93, 0x82, 0x80, 0xF0, 0x93, 0x82, 0x80}}; +static const std::string egypt_str(reinterpret_cast<const char *>(egypt_utf8_codeunits.data())); + +#ifdef _WIN32 +static const std::array<uint16_t, 6 + 1> egypt_utf16_codeunits{{0xD80C, 0xDC80, 0xD80C, 0xDC80, 0xD80C, 0xDC80}}; +static const std::wstring egypt_wstr(reinterpret_cast<const wchar_t *>(egypt_utf16_codeunits.data())); + +#else +static const std::array<uint32_t, 3 + 1> egypt_utf32_codeunits{{0x00013080, 0x00013080, 0x00013080}}; +static const std::wstring egypt_wstr(reinterpret_cast<const wchar_t *>(egypt_utf32_codeunits.data())); + +#endif + +// "Hello Halló Привет 你好 👩🚀❤️" - many languages and complex emojis +static const std::array<uint8_t, 50 + 1> hello_utf8_codeunits{ + {0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x48, 0x61, 0x6c, 0x6c, 0xc3, 0xb3, 0x20, 0xd0, 0x9f, 0xd1, 0x80, + 0xd0, 0xb8, 0xd0, 0xb2, 0xd0, 0xb5, 0xd1, 0x82, 0x20, 0xe4, 0xbd, 0xa0, 0xe5, 0xa5, 0xbd, 0x20, 0xf0, + 0x9f, 0x91, 0xa9, 0xe2, 0x80, 0x8d, 0xf0, 0x9f, 0x9a, 0x80, 0xe2, 0x9d, 0xa4, 0xef, 0xb8, 0x8f}}; +static const std::string hello_str(reinterpret_cast<const char *>(hello_utf8_codeunits.data())); + +#ifdef _WIN32 +static const std::array<uint16_t, 29 + 1> hello_utf16_codeunits{ + {0x0048, 0x0065, 0x006c, 0x006c, 0x006f, 0x0020, 0x0048, 0x0061, 0x006c, 0x006c, + 0x00f3, 0x0020, 0x041f, 0x0440, 0x0438, 0x0432, 0x0435, 0x0442, 0x0020, 0x4f60, + 0x597d, 0x0020, 0xd83d, 0xdc69, 0x200d, 0xd83d, 0xde80, 0x2764, 0xfe0f}}; +static const std::wstring hello_wstr(reinterpret_cast<const wchar_t *>(hello_utf16_codeunits.data())); + +#else +static const std::array<uint32_t, 27 + 1> hello_utf32_codeunits{ + {0x00000048, 0x00000065, 0x0000006c, 0x0000006c, 0x0000006f, 0x00000020, 0x00000048, 0x00000061, 0x0000006c, + 0x0000006c, 0x000000f3, 0x00000020, 0x0000041f, 0x00000440, 0x00000438, 0x00000432, 0x00000435, 0x00000442, + 0x00000020, 0x00004f60, 0x0000597d, 0x00000020, 0x0001f469, 0x0000200d, 0x0001f680, 0x00002764, 0x0000fe0f}}; +static const std::wstring hello_wstr(reinterpret_cast<const wchar_t *>(hello_utf32_codeunits.data())); + +#endif + +// #14 +TEST_CASE("Encoding: Widen", "[unicode]") { + using CLI::widen; + + CHECK(abcd_wstr == widen(abcd_str)); + CHECK(egypt_wstr == widen(egypt_str)); + CHECK(hello_wstr == widen(hello_str)); + + CHECK(hello_wstr == widen(hello_str.c_str())); + CHECK(hello_wstr == widen(hello_str.c_str(), hello_str.size())); + +#ifdef CLI11_CPP17 + CHECK(hello_wstr == widen(std::string_view{hello_str})); +#endif // CLI11_CPP17 +} + +// #14 +TEST_CASE("Encoding: Narrow", "[unicode]") { + using CLI::narrow; + + CHECK(abcd_str == narrow(abcd_wstr)); + CHECK(egypt_str == narrow(egypt_wstr)); + CHECK(hello_str == narrow(hello_wstr)); + + CHECK(hello_str == narrow(hello_wstr.c_str())); + CHECK(hello_str == narrow(hello_wstr.c_str(), hello_wstr.size())); + +#ifdef CLI11_CPP17 + CHECK(hello_str == narrow(std::wstring_view{hello_wstr})); +#endif // CLI11_CPP17 +} + +#if defined CLI11_HAS_FILESYSTEM && CLI11_HAS_FILESYSTEM > 0 +// #14 +TEST_CASE("Encoding: to_path roundtrip", "[unicode]") { + using std::filesystem::path; + +#ifdef _WIN32 + std::wstring native_str = CLI::widen(hello_str); +#else + std::string native_str = hello_str; +#endif // _WIN32 + + CHECK(CLI::to_path(hello_str).native() == native_str); +} + +#endif // CLI11_HAS_FILESYSTEM diff --git a/packages/CLI11/tests/FormatterTest.cpp b/packages/CLI11/tests/FormatterTest.cpp index 7f68cf17659ec0d6e3591520298e6fd17395a7a7..2563c9421c54ea03c2a5a9ea7284a8a42db11288 100644 --- a/packages/CLI11/tests/FormatterTest.cpp +++ b/packages/CLI11/tests/FormatterTest.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2022, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // @@ -13,8 +13,6 @@ #include "catch.hpp" #include <fstream> -using Catch::Matchers::Contains; - class SimpleFormatter : public CLI::FormatterBase { public: SimpleFormatter() : FormatterBase() {} @@ -63,7 +61,7 @@ TEST_CASE("Formatter: OptCustomize", "[formatter]") { "Usage: [OPTIONS]\n\n" "Options:\n" " -h,--help Print this help message and exit\n" - " --opt INT (MUST HAVE) Something\n\n"); + " --opt INT (MUST HAVE) Something\n"); } TEST_CASE("Formatter: OptCustomizeSimple", "[formatter]") { @@ -82,7 +80,7 @@ TEST_CASE("Formatter: OptCustomizeSimple", "[formatter]") { "Usage: [OPTIONS]\n\n" "Options:\n" " -h,--help Print this help message and exit\n" - " --opt INT (MUST HAVE) Something\n\n"); + " --opt INT (MUST HAVE) Something\n"); } TEST_CASE("Formatter: OptCustomizeOptionText", "[formatter]") { @@ -100,7 +98,7 @@ TEST_CASE("Formatter: OptCustomizeOptionText", "[formatter]") { "Usage: [OPTIONS]\n\n" "Options:\n" " -h,--help Print this help message and exit\n" - " --opt (ARG) Something\n\n"); + " --opt (ARG) Something\n"); } TEST_CASE("Formatter: FalseFlagExample", "[formatter]") { @@ -140,7 +138,7 @@ TEST_CASE("Formatter: AppCustomize", "[formatter]") { " -h,--help Print this help message and exit\n\n" "Subcommands:\n" " subcom1 This\n" - " subcom2 This\n\n"); + " subcom2 This\n"); } TEST_CASE("Formatter: AppCustomizeSimple", "[formatter]") { @@ -159,7 +157,7 @@ TEST_CASE("Formatter: AppCustomizeSimple", "[formatter]") { " -h,--help Print this help message and exit\n\n" "Subcommands:\n" " subcom1 This\n" - " subcom2 This\n\n"); + " subcom2 This\n"); } TEST_CASE("Formatter: AllSub", "[formatter]") { diff --git a/packages/CLI11/tests/FuzzFailTest.cpp b/packages/CLI11/tests/FuzzFailTest.cpp new file mode 100644 index 0000000000000000000000000000000000000000..22148368819a9a4763dde949b68c5082c445694d --- /dev/null +++ b/packages/CLI11/tests/FuzzFailTest.cpp @@ -0,0 +1,37 @@ +// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner +// under NSF AWARD 1414736 and by the respective contributors. +// All rights reserved. +// +// SPDX-License-Identifier: BSD-3-Clause + +#include "../fuzz/fuzzApp.hpp" +#include "app_helper.hpp" + +std::string loadFailureFile(const std::string &type, int index) { + std::string fileName(TEST_FILE_FOLDER "/fuzzFail/"); + fileName.append(type); + fileName += std::to_string(index); + std::ifstream crashFile(fileName, std::ios::in | std::ios::binary); + if(crashFile) { + std::vector<char> buffer(std::istreambuf_iterator<char>(crashFile), {}); + + std::string cdata(buffer.begin(), buffer.end()); + return cdata; + } + return std::string{}; +} + +TEST_CASE("app_fail") { + CLI::FuzzApp fuzzdata; + + auto app = fuzzdata.generateApp(); + + int index = GENERATE(range(1, 3)); + + auto parseData = loadFailureFile("fuzz_app_fail", index); + try { + + app->parse(parseData); + } catch(const CLI::ParseError & /*e*/) { + } +} diff --git a/packages/CLI11/tests/HelpTest.cpp b/packages/CLI11/tests/HelpTest.cpp index eb0edd6a0ba8f8be54fc3402bacdcf601c3838ed..c4403f754f48ed99c8188bf3cdecae64448da4ac 100644 --- a/packages/CLI11/tests/HelpTest.cpp +++ b/packages/CLI11/tests/HelpTest.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2022, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // @@ -13,8 +13,6 @@ #include "catch.hpp" #include <fstream> -using Catch::Matchers::Contains; - TEST_CASE("THelp: Basic", "[help]") { CLI::App app{"My prog"}; @@ -26,6 +24,43 @@ TEST_CASE("THelp: Basic", "[help]") { CHECK_THAT(help, Contains("Usage:")); } +TEST_CASE("THelp: Usage", "[help]") { + CLI::App app{"My prog"}; + app.usage("use: just use it"); + + std::string help = app.help(); + + CHECK_THAT(help, Contains("My prog")); + CHECK_THAT(help, Contains("-h,--help")); + CHECK_THAT(help, Contains("Options:")); + CHECK_THAT(help, Contains("use: just use it")); +} + +TEST_CASE("THelp: UsageCallback", "[help]") { + CLI::App app{"My prog"}; + app.usage([]() { return "use: just use it"; }); + + std::string help = app.help(); + + CHECK_THAT(help, Contains("My prog")); + CHECK_THAT(help, Contains("-h,--help")); + CHECK_THAT(help, Contains("Options:")); + CHECK_THAT(help, Contains("use: just use it")); +} + +TEST_CASE("THelp: UsageCallbackBoth", "[help]") { + CLI::App app{"My prog"}; + app.usage([]() { return "use: just use it"; }); + app.usage("like 1, 2, and 3"); + std::string help = app.help(); + + CHECK_THAT(help, Contains("My prog")); + CHECK_THAT(help, Contains("-h,--help")); + CHECK_THAT(help, Contains("Options:")); + CHECK_THAT(help, Contains("use: just use it")); + CHECK_THAT(help, Contains("like 1, 2, and 3")); +} + TEST_CASE("THelp: Footer", "[help]") { CLI::App app{"My prog"}; app.footer("Report bugs to bugs@example.com"); @@ -813,7 +848,7 @@ TEST_CASE_METHOD(CapturedHelp, "CallForAllHelpOutput", "[help]") { " One description\n\n" "two\n" " Options:\n" - " --three \n\n\n"); + " --three \n\n"); } TEST_CASE_METHOD(CapturedHelp, "NewFormattedHelp", "[help]") { app.formatter_fn([](const CLI::App *, std::string, CLI::AppFormatMode) { return "New Help"; }); @@ -974,6 +1009,16 @@ TEST_CASE("THelp: GroupOrder", "[help]") { CHECK(aee_loc > zee_loc); } +TEST_CASE("THelp: GroupNameError", "[help]") { + CLI::App app; + + auto *f1 = app.add_flag("--one"); + auto *f2 = app.add_flag("--two"); + + CHECK_THROWS_AS(f1->group("evil group name\non two lines"), CLI::IncorrectConstruction); + CHECK_THROWS_AS(f2->group(std::string(5, '\0')), CLI::IncorrectConstruction); +} + TEST_CASE("THelp: ValidatorsText", "[help]") { CLI::App app; @@ -1266,7 +1311,7 @@ TEST_CASE("TVersion: parse_throw", "[help]") { try { app.parse("--Version"); } catch(const CLI::CallForVersion &v) { - CHECK_THAT(CLI11_VERSION, Catch::Equals(v.what())); + CHECK_THAT(CLI11_VERSION, Equals(v.what())); CHECK(0 == v.get_exit_code()); const auto &appc = app; const auto *cptr = appc.get_version_ptr(); diff --git a/packages/CLI11/tests/HelpersTest.cpp b/packages/CLI11/tests/HelpersTest.cpp index 798a6d13d5e06767946f76863dc32fa6edd942ba..5186b47fcfc2a5648048d42cb82570437d1962ac 100644 --- a/packages/CLI11/tests/HelpersTest.cpp +++ b/packages/CLI11/tests/HelpersTest.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2022, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // @@ -525,6 +525,10 @@ TEST_CASE("Validators: ProgramNameSplit", "[helpers]") { res = CLI::detail::split_program_name(std::string(" ./") + std::string(myfile) + " "); CHECK(std::string("./") + std::string(myfile) == res.first); CHECK(res.second.empty()); + + res = CLI::detail::split_program_name("'odd_program_name.exe --arg --arg2=5"); + CHECK("'odd_program_name.exe" == res.first); + CHECK_FALSE(res.second.empty()); } TEST_CASE("CheckedMultiply: Int", "[helpers]") { @@ -1065,6 +1069,10 @@ TEST_CASE("Types: LexicalCastInt", "[helpers]") { std::string extra_input = "912i"; CHECK_FALSE(CLI::detail::lexical_cast(extra_input, y)); + extra_input = "true"; + CHECK(CLI::detail::lexical_cast(extra_input, x_signed)); + CHECK(x_signed != 0); + std::string empty_input{}; CHECK_FALSE(CLI::detail::lexical_cast(empty_input, x_signed)); CHECK_FALSE(CLI::detail::lexical_cast(empty_input, x_unsigned)); @@ -1228,6 +1236,8 @@ TEST_CASE("Types: LexicalConversionTuple3", "[helpers]") { TEST_CASE("Types: LexicalConversionTuple4", "[helpers]") { CLI::results_t input = {"9.12", "19", "18.6", "5.87"}; std::array<double, 4> x; + auto tsize = CLI::detail::type_count<decltype(x)>::value; + CHECK(tsize == 4); bool res = CLI::detail::lexical_conversion<decltype(x), decltype(x)>(input, x); CHECK(res); CHECK(19 == Approx(std::get<1>(x))); diff --git a/packages/CLI11/tests/NewParseTest.cpp b/packages/CLI11/tests/NewParseTest.cpp index a4ca09987afebfbfdcac84fdeb08f75f8aa532f7..a72af823bb8ce9c596351f3bb77fa67dde750325 100644 --- a/packages/CLI11/tests/NewParseTest.cpp +++ b/packages/CLI11/tests/NewParseTest.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2022, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // @@ -10,8 +10,6 @@ #include <cstdint> #include <utility> -using Catch::Matchers::Contains; - using cx = std::complex<double>; TEST_CASE_METHOD(TApp, "ComplexOption", "[newparse]") { @@ -163,14 +161,10 @@ class spair { std::string first{}; std::string second{}; }; -// an example of custom converter that can be used to add new parsing options -// On MSVC and possibly some other new compilers this can be a free standing function without the template -// specialization but this is compiler dependent -namespace CLI { -namespace detail { - -template <> bool lexical_cast<spair>(const std::string &input, spair &output) { +// Example of a custom converter that can be used to add new parsing options. +// It will be found via argument-dependent lookup, so should be in the same namespace as the `spair` type. +bool lexical_cast(const std::string &input, spair &output) { auto sep = input.find_first_of(':'); if((sep == std::string::npos) && (sep > 0)) { return false; @@ -178,8 +172,6 @@ template <> bool lexical_cast<spair>(const std::string &input, spair &output) { output = {input.substr(0, sep), input.substr(sep + 1)}; return true; } -} // namespace detail -} // namespace CLI TEST_CASE_METHOD(TApp, "custom_string_converter", "[newparse]") { spair val; @@ -201,6 +193,96 @@ TEST_CASE_METHOD(TApp, "custom_string_converterFail", "[newparse]") { CHECK_THROWS_AS(run(), CLI::ConversionError); } +/// Wrapper with an unconvenient interface +template <class T> class badlywrapped { + public: + badlywrapped() : value() {} + + CLI11_NODISCARD T get() const { return value; } + + void set(T val) { value = val; } + + private: + T value; +}; + +// Example of a custom converter for a template type. +// It will be found via argument-dependent lookup, so should be in the same namespace as the `badlywrapped` type. +template <class T> bool lexical_cast(const std::string &input, badlywrapped<T> &output) { + // This using declaration lets us use an unqualified call to lexical_cast below. This is important because + // unqualified call finds the proper overload via argument-dependent lookup, and thus it will be able to find + // an overload for `spair` type, which is not in `CLI::detail`. + using CLI::detail::lexical_cast; + + T value; + if(!lexical_cast(input, value)) + return false; + output.set(value); + return true; +} + +TEST_CASE_METHOD(TApp, "custom_string_converter_flag", "[newparse]") { + badlywrapped<bool> val; + std::vector<badlywrapped<bool>> vals; + app.add_flag("-1", val); + app.add_flag("-2", vals); + + val.set(false); + args = {"-1"}; + run(); + CHECK(true == val.get()); + + args = {"-2", "-2"}; + run(); + CHECK(2 == vals.size()); + CHECK(true == vals[0].get()); + CHECK(true == vals[1].get()); +} + +TEST_CASE_METHOD(TApp, "custom_string_converter_adl", "[newparse]") { + // This test checks that the lexical_cast calls route as expected. + badlywrapped<spair> val; + + app.add_option("-d,--dual_string", val); + + args = {"-d", "string1:string2"}; + + run(); + CHECK("string1" == val.get().first); + CHECK("string2" == val.get().second); +} + +/// Another wrapper to test that specializing CLI::detail::lexical_cast works +struct anotherstring { + anotherstring() = default; + std::string s{}; +}; + +// This is a custom converter done via specializing the CLI::detail::lexical_cast template. This was the recommended +// mechanism for extending the library before, so we need to test it. Don't do this in your code, use +// argument-dependent lookup as outlined in the examples for spair and template badlywrapped. +namespace CLI { +namespace detail { +template <> bool lexical_cast<anotherstring>(const std::string &input, anotherstring &output) { + bool result = lexical_cast(input, output.s); + if(result) + output.s += "!"; + return result; +} +} // namespace detail +} // namespace CLI + +TEST_CASE_METHOD(TApp, "custom_string_converter_specialize", "[newparse]") { + anotherstring s; + + app.add_option("-s", s); + + args = {"-s", "something"}; + + run(); + CHECK("something!" == s.s); +} + /// simple class to wrap another with a very specific type constructor and assignment operators to test out some of the /// option assignments template <class X> class objWrapper { @@ -221,6 +303,27 @@ template <class X> class objWrapper { X val_{}; }; +/// simple class to wrap another with a very specific type constructor and assignment operators to test out some of the +/// option assignments +template <class X> class objWrapperRestricted { + public: + objWrapperRestricted() = default; + explicit objWrapperRestricted(int val) : val_{val} {}; + objWrapperRestricted(const objWrapperRestricted &) = delete; + objWrapperRestricted(objWrapperRestricted &&) = delete; + objWrapperRestricted &operator=(const objWrapperRestricted &) = delete; + objWrapperRestricted &operator=(objWrapperRestricted &&) = delete; + + objWrapperRestricted &operator=(int val) { + val_ = val; + return *this; + } + CLI11_NODISCARD const X &value() const { return val_; } + + private: + X val_{}; +}; + // I think there is a bug with the is_assignable in visual studio 2015 it is fixed in later versions // so this test will not compile in that compiler #if !defined(_MSC_VER) || _MSC_VER >= 1910 @@ -263,6 +366,26 @@ TEST_CASE_METHOD(TApp, "doubleWrapper", "[newparse]") { CHECK_THROWS_AS(run(), CLI::ConversionError); } +TEST_CASE_METHOD(TApp, "intWrapperRestricted", "[newparse]") { + objWrapperRestricted<double> dWrapper; + app.add_option("-v", dWrapper); + args = {"-v", "4"}; + + run(); + + CHECK(4.0 == dWrapper.value()); + + args = {"-v", "thing"}; + + CHECK_THROWS_AS(run(), CLI::ConversionError); + + args = {"-v", ""}; + + run(); + + CHECK(0.0 == dWrapper.value()); +} + static_assert(CLI::detail::is_direct_constructible<objWrapper<int>, int>::value, "int wrapper is not constructible from int64"); diff --git a/packages/CLI11/tests/OptionGroupTest.cpp b/packages/CLI11/tests/OptionGroupTest.cpp index bc3a9455ff71bcfaaa35edb8acfd18a238818edd..ab4d3c638d29c07ba0d0c54f3d9b216b870f6af3 100644 --- a/packages/CLI11/tests/OptionGroupTest.cpp +++ b/packages/CLI11/tests/OptionGroupTest.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2022, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // @@ -6,8 +6,6 @@ #include "app_helper.hpp" -using Catch::Matchers::Contains; - using vs_t = std::vector<std::string>; TEST_CASE_METHOD(TApp, "BasicOptionGroup", "[optiongroup]") { @@ -448,6 +446,12 @@ TEST_CASE_METHOD(ManyGroups, "SingleGroup", "[optiongroup]") { CHECK_THROWS_AS(run(), CLI::RequiredError); } +TEST_CASE_METHOD(ManyGroups, "getGroup", "[optiongroup]") { + auto *mn = app.get_option_group("main"); + CHECK(mn == main); + CHECK_THROWS_AS(app.get_option_group("notfound"), CLI::OptionNotFound); +} + TEST_CASE_METHOD(ManyGroups, "ExcludesGroup", "[optiongroup]") { // only 1 group can be used g1->excludes(g2); diff --git a/packages/CLI11/tests/OptionTypeTest.cpp b/packages/CLI11/tests/OptionTypeTest.cpp index b48ba6d5bb85252c5f2593420783bca7aee9c790..6d06a5af3ebabbe695825183d936cb81f0c2a4d2 100644 --- a/packages/CLI11/tests/OptionTypeTest.cpp +++ b/packages/CLI11/tests/OptionTypeTest.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2022, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // @@ -336,6 +336,36 @@ TEST_CASE_METHOD(TApp, "pair_check", "[optiontype]") { CHECK_THROWS_AS(run(), CLI::ValidationError); } +TEST_CASE_METHOD(TApp, "pair_check_string", "[optiontype]") { + std::string myfile{"pair_check_file.txt"}; + bool ok = static_cast<bool>(std::ofstream(myfile.c_str()).put('a')); // create file + CHECK(ok); + + CHECK(CLI::ExistingFile(myfile).empty()); + std::pair<std::string, std::string> findex; + + auto v0 = CLI::ExistingFile; + v0.application_index(0); + auto v1 = CLI::PositiveNumber; + v1.application_index(1); + app.add_option("--file", findex)->check(v0)->check(v1); + + args = {"--file", myfile, "2"}; + + CHECK_NOTHROW(run()); + + CHECK(myfile == findex.first); + CHECK("2" == findex.second); + + args = {"--file", myfile, "-3"}; + + CHECK_THROWS_AS(run(), CLI::ValidationError); + + args = {"--file", myfile, "2"}; + std::remove(myfile.c_str()); + CHECK_THROWS_AS(run(), CLI::ValidationError); +} + TEST_CASE_METHOD(TApp, "pair_check_take_first", "[optiontype]") { std::string myfile{"pair_check_file2.txt"}; bool ok = static_cast<bool>(std::ofstream(myfile.c_str()).put('a')); // create file @@ -405,6 +435,86 @@ TEST_CASE_METHOD(TApp, "VectorIndexedValidator", "[optiontype]") { CHECK_THROWS_AS(run(), CLI::ValidationError); } +TEST_CASE_METHOD(TApp, "IntegerOverFlowShort", "[optiontype]") { + std::int16_t A{0}; + std::uint16_t B{0}; + + app.add_option("-a", A); + app.add_option("-b", B); + + args = {"-a", "2626254242"}; + CHECK_THROWS_AS(run(), CLI::ConversionError); + + args = {"-b", "2626254242"}; + CHECK_THROWS_AS(run(), CLI::ConversionError); + + args = {"-b", "-26262"}; + CHECK_THROWS_AS(run(), CLI::ConversionError); + + args = {"-b", "-262624262525"}; + CHECK_THROWS_AS(run(), CLI::ConversionError); +} + +TEST_CASE_METHOD(TApp, "IntegerOverFlowInt", "[optiontype]") { + int A{0}; + unsigned int B{0}; + + app.add_option("-a", A); + app.add_option("-b", B); + + args = {"-a", "262625424225252"}; + CHECK_THROWS_AS(run(), CLI::ConversionError); + + args = {"-b", "262625424225252"}; + CHECK_THROWS_AS(run(), CLI::ConversionError); + + args = {"-b", "-2626225252"}; + CHECK_THROWS_AS(run(), CLI::ConversionError); + + args = {"-b", "-26262426252525252"}; + CHECK_THROWS_AS(run(), CLI::ConversionError); +} + +TEST_CASE_METHOD(TApp, "IntegerOverFlowLong", "[optiontype]") { + std::int32_t A{0}; + std::uint32_t B{0}; + + app.add_option("-a", A); + app.add_option("-b", B); + + args = {"-a", "1111111111111111111111111111"}; + CHECK_THROWS_AS(run(), CLI::ConversionError); + + args = {"-b", "1111111111111111111111111111"}; + CHECK_THROWS_AS(run(), CLI::ConversionError); + + args = {"-b", "-2626225252"}; + CHECK_THROWS_AS(run(), CLI::ConversionError); + + args = {"-b", "-111111111111111111111111"}; + CHECK_THROWS_AS(run(), CLI::ConversionError); +} + +TEST_CASE_METHOD(TApp, "IntegerOverFlowLongLong", "[optiontype]") { + std::int64_t A{0}; + std::uint64_t B{0}; + + app.add_option("-a", A); + app.add_option("-b", B); + + args = {"-a", "1111111111111111111111111111111111111111111111111111111111"}; + CHECK_THROWS_AS(run(), CLI::ConversionError); + + args = {"-b", "1111111111111111111111111111111111111111111111111111111111"}; + CHECK_THROWS_AS(run(), CLI::ConversionError); + + args = {"-b", "-2626225252"}; + CHECK_THROWS_AS(run(), CLI::ConversionError); + + args = {"-b", "-111111111111111111111111111111111111111111111111111111111"}; + CHECK_THROWS_AS(run(), CLI::ConversionError); +} + TEST_CASE_METHOD(TApp, "VectorUnlimString", "[optiontype]") { std::vector<std::string> strvec; std::vector<std::string> answer{"mystring", "mystring2", "mystring3"}; diff --git a/packages/CLI11/tests/OptionalTest.cpp b/packages/CLI11/tests/OptionalTest.cpp index 4094c44b6c4687bcc928bd06d24187fb3082509a..3d78e3498c9a3ff99096867786552fc548d40212 100644 --- a/packages/CLI11/tests/OptionalTest.cpp +++ b/packages/CLI11/tests/OptionalTest.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2022, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // diff --git a/packages/CLI11/tests/SetTest.cpp b/packages/CLI11/tests/SetTest.cpp index 8184350dc92dcbc68bccae95cba20ac294ea9534..b326989997824f18fdca60c47a09b38c243df9e0 100644 --- a/packages/CLI11/tests/SetTest.cpp +++ b/packages/CLI11/tests/SetTest.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2022, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // @@ -492,6 +492,23 @@ TEST_CASE_METHOD(TApp, "FailSet", "[set]") { CHECK_THROWS_AS(run(), CLI::ValidationError); } +TEST_CASE_METHOD(TApp, "shortStringCheck", "[set]") { + + std::string choice; + app.add_option("-q,--quick", choice)->check([](const std::string &v) { + if(v.size() > 5) { + return std::string{"string too long"}; + } + return std::string{}; + }); + + args = {"--quick", "3"}; + CHECK_NOTHROW(run()); + + args = {"--quick=hello_goodbye"}; + CHECK_THROWS_AS(run(), CLI::ValidationError); +} + TEST_CASE_METHOD(TApp, "FailMutableSet", "[set]") { int choice{0}; diff --git a/packages/CLI11/tests/SimpleTest.cpp b/packages/CLI11/tests/SimpleTest.cpp index 84ec6f0c409d50fae2af0e07f759f2687fc8dce0..14d6558b46c843a10763a0c6e0891b37806bf184 100644 --- a/packages/CLI11/tests/SimpleTest.cpp +++ b/packages/CLI11/tests/SimpleTest.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2022, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // diff --git a/packages/CLI11/tests/StringParseTest.cpp b/packages/CLI11/tests/StringParseTest.cpp index 6a889e45c7b297b0ece75e465584544b9b0581c0..cc1205fe3b7b6050e4e2e56f242dc024c3694b8b 100644 --- a/packages/CLI11/tests/StringParseTest.cpp +++ b/packages/CLI11/tests/StringParseTest.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2022, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // diff --git a/packages/CLI11/tests/SubcommandTest.cpp b/packages/CLI11/tests/SubcommandTest.cpp index a01b8863d76ec2a33fc55f3430cffa6153e5e62d..25415eaa7cf1241f46ac6788f6cfca285543c594 100644 --- a/packages/CLI11/tests/SubcommandTest.cpp +++ b/packages/CLI11/tests/SubcommandTest.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2022, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // @@ -6,8 +6,6 @@ #include "app_helper.hpp" -using Catch::Matchers::Contains; - using vs_t = std::vector<std::string>; TEST_CASE_METHOD(TApp, "BasicSubcommands", "[subcom]") { @@ -916,6 +914,10 @@ TEST_CASE_METHOD(TApp, "SubcomInheritCaseCheck", "[subcom]") { CHECK(app.get_subcommands({}).size() == 2u); CHECK(app.get_subcommands([](const CLI::App *s) { return s->get_name() == "sub1"; }).size() == 1u); + // check the const version of get_subcommands + const auto &app_const = app; + CHECK(app_const.get_subcommands([](const CLI::App *s) { return s->get_name() == "sub1"; }).size() == 1u); + args = {"SuB1"}; run(); CHECK(app.get_subcommands().at(0) == sub1); @@ -1192,6 +1194,18 @@ TEST_CASE_METHOD(ManySubcommands, "manyIndexQueryPtr", "[subcom]") { CHECK_THROWS_AS(app.get_subcommand_ptr(4), CLI::OptionNotFound); } +TEST_CASE_METHOD(ManySubcommands, "manyIndexQueryPtrByName", "[subcom]") { + auto s1 = app.get_subcommand_ptr("sub1"); + auto s2 = app.get_subcommand_ptr("sub2"); + auto s3 = app.get_subcommand_ptr("sub3"); + auto s4 = app.get_subcommand_ptr("sub4"); + CHECK(sub1 == s1.get()); + CHECK(sub2 == s2.get()); + CHECK(sub3 == s3.get()); + CHECK(sub4 == s4.get()); + CHECK_THROWS_AS(app.get_subcommand_ptr("sub5"), CLI::OptionNotFound); +} + TEST_CASE_METHOD(ManySubcommands, "Required1Fuzzy", "[subcom]") { app.require_subcommand(0, 1); @@ -1394,6 +1408,15 @@ TEST_CASE_METHOD(ManySubcommands, "SubcommandNeedsOptionsCallbackOrdering", "[su CHECK_NOTHROW(run()); } +TEST_CASE_METHOD(ManySubcommands, "SubcommandParseCompleteDotNotation", "[subcom]") { + int count{0}; + sub1->add_flag("--flag1"); + sub1->parse_complete_callback([&count]() { ++count; }); + args = {"--sub1.flag1", "--sub1.flag1"}; + run(); + CHECK(count == 2); +} + TEST_CASE_METHOD(ManySubcommands, "SubcommandNeedsFail", "[subcom]") { auto *opt = app.add_flag("--subactive"); @@ -1968,3 +1991,126 @@ TEST_CASE_METHOD(TApp, "SubcommandInOptionGroupCallbackCount", "[subcom]") { run(); CHECK(subcount == 1); } + +TEST_CASE_METHOD(TApp, "DotNotationSubcommand", "[subcom]") { + std::string v1, v2, vbase; + + auto *sub1 = app.add_subcommand("sub1"); + auto *sub2 = app.add_subcommand("sub2"); + sub1->add_option("--value", v1); + sub2->add_option("--value", v2); + app.add_option("--value", vbase); + args = {"--sub1.value", "val1"}; + run(); + CHECK(v1 == "val1"); + + args = {"--sub2.value", "val2", "--value", "base"}; + run(); + CHECK(v2 == "val2"); + CHECK(vbase == "base"); + v1.clear(); + v2.clear(); + vbase.clear(); + + args = {"--sub2.value=val2", "--value=base"}; + run(); + CHECK(v2 == "val2"); + CHECK(vbase == "base"); + + auto subs = app.get_subcommands(); + REQUIRE(!subs.empty()); + CHECK(subs.front()->get_name() == "sub2"); +} + +TEST_CASE_METHOD(TApp, "DotNotationSubcommandSingleChar", "[subcom]") { + std::string v1, v2, vbase; + + auto *sub1 = app.add_subcommand("sub1"); + auto *sub2 = app.add_subcommand("sub2"); + sub1->add_option("-v", v1); + sub2->add_option("-v", v2); + app.add_option("-v", vbase); + args = {"--sub1.v", "val1"}; + run(); + CHECK(v1 == "val1"); + + args = {"--sub2.v", "val2", "-v", "base"}; + run(); + CHECK(v2 == "val2"); + CHECK(vbase == "base"); + v1.clear(); + v2.clear(); + vbase.clear(); + + args = {"--sub2.v=val2", "-vbase"}; + run(); + CHECK(v2 == "val2"); + CHECK(vbase == "base"); + + auto subs = app.get_subcommands(); + REQUIRE(!subs.empty()); + CHECK(subs.front()->get_name() == "sub2"); +} + +TEST_CASE_METHOD(TApp, "DotNotationSubcommandRecusive", "[subcom]") { + std::string v1, v2, v3, vbase; + + auto *sub1 = app.add_subcommand("sub1"); + auto *sub2 = sub1->add_subcommand("sub2"); + auto *sub3 = sub2->add_subcommand("sub3"); + + sub1->add_option("--value", v1); + sub2->add_option("--value", v2); + sub3->add_option("--value", v3); + app.add_option("--value", vbase); + args = {"--sub1.sub2.sub3.value", "val1"}; + run(); + CHECK(v3 == "val1"); + + args = {"--sub1.sub2.value", "val2"}; + run(); + CHECK(v2 == "val2"); + + args = {"--sub1.sub2.bob", "val2"}; + CHECK_THROWS_AS(run(), CLI::ExtrasError); + app.allow_extras(); + CHECK_NOTHROW(run()); + auto extras = app.remaining(); + CHECK(extras.size() == 2); + CHECK(extras.front() == "--sub1.sub2.bob"); +} + +TEST_CASE_METHOD(TApp, "DotNotationSubcommandRecusive2", "[subcom]") { + std::string v1, v2, v3, vbase; + + auto *sub1 = app.add_subcommand("sub1"); + auto *sub2 = sub1->add_subcommand("sub2"); + auto *sub3 = sub2->add_subcommand("sub3"); + + sub1->add_option("--value", v1); + sub2->add_option("--value", v2); + sub3->add_option("--value", v3); + app.add_option("--value", vbase); + args = {"sub1.sub2.sub3", "--value", "val1"}; + run(); + CHECK(v3 == "val1"); + + args = {"sub1.sub2", "--value", "val2"}; + run(); + CHECK(v2 == "val2"); + + args = {"sub1.bob", "--value", "val2"}; + CHECK_THROWS_AS(run(), CLI::ExtrasError); + + args = {"sub1.sub2.bob", "--value", "val2"}; + CHECK_THROWS_AS(run(), CLI::ExtrasError); + + args = {"sub1.sub2.sub3.bob", "--value", "val2"}; + CHECK_THROWS_AS(run(), CLI::ExtrasError); + + app.allow_extras(); + CHECK_NOTHROW(run()); + auto extras = app.remaining(); + CHECK(extras.size() == 1); + CHECK(extras.front() == "sub1.sub2.sub3.bob"); +} diff --git a/packages/CLI11/tests/TimerTest.cpp b/packages/CLI11/tests/TimerTest.cpp index a3d8c1d9ee9b9af7919e152b480e4668690cc260..e15d928cf7ee2a49d5176641e77d601bf74c76eb 100644 --- a/packages/CLI11/tests/TimerTest.cpp +++ b/packages/CLI11/tests/TimerTest.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2022, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // @@ -12,8 +12,6 @@ #include <string> #include <thread> -using Catch::Matchers::Contains; - TEST_CASE("Timer: MSTimes", "[timer]") { CLI::Timer timer{"My Timer"}; std::this_thread::sleep_for(std::chrono::milliseconds(123)); diff --git a/packages/CLI11/tests/TransformTest.cpp b/packages/CLI11/tests/TransformTest.cpp index 5ace484ecce831b21013bf02b64d2d7dc42f5687..9406e0254d0a5d57b34ff005aa6c2bd89cb727a8 100644 --- a/packages/CLI11/tests/TransformTest.cpp +++ b/packages/CLI11/tests/TransformTest.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2022, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // diff --git a/packages/CLI11/tests/TrueFalseTest.cpp b/packages/CLI11/tests/TrueFalseTest.cpp index d7d2f2cd0bfc599bbcb625d1eec49bd99f5b4dee..93f2f3fb8109ccb40370bbf7ed330b3360c33e89 100644 --- a/packages/CLI11/tests/TrueFalseTest.cpp +++ b/packages/CLI11/tests/TrueFalseTest.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2022, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // diff --git a/packages/CLI11/tests/WindowsTest.cpp b/packages/CLI11/tests/WindowsTest.cpp index 0527cfb082fedc498ab0903a431b743218ef6d4c..a17d58735a66a29fa5b2a02d614420396f6943eb 100644 --- a/packages/CLI11/tests/WindowsTest.cpp +++ b/packages/CLI11/tests/WindowsTest.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2022, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // diff --git a/packages/CLI11/tests/app_helper.hpp b/packages/CLI11/tests/app_helper.hpp index f8d7e7221b395777f60088308c2bfaf11cef86b5..5479e486392ba432f6f2d25f45d6bcf40812a315 100644 --- a/packages/CLI11/tests/app_helper.hpp +++ b/packages/CLI11/tests/app_helper.hpp @@ -13,6 +13,9 @@ #endif #include "catch.hpp" +#include <array> +#include <fstream> +#include <iomanip> #include <iostream> #include <string> #include <utility> @@ -33,6 +36,8 @@ class TApp { } }; +CLI11_INLINE int fileClear(const std::string &name) { return std::remove(name.c_str()); } + class TempFile { std::string _name{}; @@ -65,3 +70,54 @@ inline void unset_env(std::string name) { unsetenv(name.c_str()); #endif } + +CLI11_INLINE void check_identical_files(const char *path1, const char *path2) { + std::string err1 = CLI::ExistingFile(path1); + if(!err1.empty()) { + FAIL("Could not open " << path1 << ": " << err1); + } + + std::string err2 = CLI::ExistingFile(path2); + if(!err2.empty()) { + FAIL("Could not open " << path2 << ": " << err2); + } + + // open files at the end to compare size first + std::ifstream file1(path1, std::ifstream::ate | std::ifstream::binary); + std::ifstream file2(path2, std::ifstream::ate | std::ifstream::binary); + + if(!file1.good()) { + FAIL("File " << path1 << " is corrupted"); + } + + if(!file2.good()) { + FAIL("File " << path2 << " is corrupted"); + } + + if(file1.tellg() != file2.tellg()) { + FAIL("Different file sizes:\n " << file1.tellg() << " bytes in " << path1 << "\n " << file2.tellg() + << " bytes in " << path2); + } + + // rewind files + file1.seekg(0); + file2.seekg(0); + + std::array<uint8_t, 10240> buffer1; + std::array<uint8_t, 10240> buffer2; + + for(size_t ibuffer = 0; file1.good(); ++ibuffer) { + // Flawfinder: ignore + file1.read(reinterpret_cast<char *>(buffer1.data()), static_cast<std::streamsize>(buffer1.size())); + // Flawfinder: ignore + file2.read(reinterpret_cast<char *>(buffer2.data()), static_cast<std::streamsize>(buffer2.size())); + + for(size_t i = 0; i < static_cast<size_t>(file1.gcount()); ++i) { + if(buffer1[i] != buffer2[i]) { + FAIL(std::hex << std::setfill('0') << "Different bytes at position " << (ibuffer * 10240 + i) << ":\n " + << "0x" << std::setw(2) << static_cast<int>(buffer1[i]) << " in " << path1 << "\n " + << "0x" << std::setw(2) << static_cast<int>(buffer2[i]) << " in " << path2); + } + } + } +} diff --git a/packages/CLI11/tests/applications/system_args.cpp b/packages/CLI11/tests/applications/system_args.cpp new file mode 100644 index 0000000000000000000000000000000000000000..e1e77ba673ce56f69117569b731475372d618a02 --- /dev/null +++ b/packages/CLI11/tests/applications/system_args.cpp @@ -0,0 +1,22 @@ +// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner +// under NSF AWARD 1414736 and by the respective contributors. +// All rights reserved. +// +// SPDX-License-Identifier: BSD-3-Clause + +#include <CLI/CLI.hpp> +#include <cstring> + +int main(int argc, char **argv) { + if(argc != CLI::argc()) { + return -1; + } + + for(int i = 0; i < argc; i++) { + if(std::strcmp(argv[i], CLI::argv()[i]) != 0) { + return i + 1; + } + } + + return 0; +} diff --git a/packages/CLI11/tests/catch.hpp b/packages/CLI11/tests/catch.hpp index da41d685d1ec30e5d278595dca96403831713524..e6de667325f48d65ea5145207bb4a608ae22c209 100644 --- a/packages/CLI11/tests/catch.hpp +++ b/packages/CLI11/tests/catch.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2022, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // @@ -6,4 +6,26 @@ #pragma once +#include <string> + +#ifdef CLI11_CATCH3 + +#include <catch2/catch_approx.hpp> +#include <catch2/catch_template_test_macros.hpp> +#include <catch2/catch_test_macros.hpp> +#include <catch2/generators/catch_generators.hpp> +#include <catch2/matchers/catch_matchers_string.hpp> + +using Catch::Approx; // NOLINT(google-global-names-in-headers) +using Catch::Matchers::Equals; // NOLINT(google-global-names-in-headers) + +inline auto Contains(const std::string &x) { return Catch::Matchers::ContainsSubstring(x); } + +#else + #include <catch2/catch.hpp> + +using Catch::Equals; // NOLINT(google-global-names-in-headers) +using Catch::Matchers::Contains; // NOLINT(google-global-names-in-headers) + +#endif diff --git a/packages/CLI11/tests/data/unicode.txt b/packages/CLI11/tests/data/unicode.txt new file mode 100644 index 0000000000000000000000000000000000000000..f430f542318bc153a004b5235eb23eb6a915825b --- /dev/null +++ b/packages/CLI11/tests/data/unicode.txt @@ -0,0 +1 @@ +Hello Halló Привет 你好 👩🚀❤️ diff --git a/packages/CLI11/tests/fuzzFail/fuzz_app_fail1 b/packages/CLI11/tests/fuzzFail/fuzz_app_fail1 new file mode 100644 index 0000000000000000000000000000000000000000..d133524b2731b74ba6845719bae4200eda67c09a Binary files /dev/null and b/packages/CLI11/tests/fuzzFail/fuzz_app_fail1 differ diff --git a/packages/CLI11/tests/fuzzFail/fuzz_app_fail2 b/packages/CLI11/tests/fuzzFail/fuzz_app_fail2 new file mode 100644 index 0000000000000000000000000000000000000000..ac301930cd0f7e04764af2a100d2feed860e0c76 --- /dev/null +++ b/packages/CLI11/tests/fuzzFail/fuzz_app_fail2 @@ -0,0 +1 @@ +1 c e g0 g0 �� --tup4 N3CLI10ParseErrorE% 0 %% 0 i��������������wrap $ \ No newline at end of file diff --git a/packages/CLI11/tests/informational.cpp b/packages/CLI11/tests/informational.cpp index 9df227fab8f053eefa765e81a53befd9f0023181..4f7f27b52b89a1742df5fd1d2fd7fa01f698b8c0 100644 --- a/packages/CLI11/tests/informational.cpp +++ b/packages/CLI11/tests/informational.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2022, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // diff --git a/packages/CLI11/tests/link_test_1.cpp b/packages/CLI11/tests/link_test_1.cpp index 1cbec7b772c602177f1cf5941282fcc4b68ed943..ba1b2d83767f4e66825529312512aaeb59fb09cc 100644 --- a/packages/CLI11/tests/link_test_1.cpp +++ b/packages/CLI11/tests/link_test_1.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2022, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // diff --git a/packages/CLI11/tests/link_test_2.cpp b/packages/CLI11/tests/link_test_2.cpp index 9dcc3db230db2eb78fba82f3218f2a45a2279ae9..46d77be26308976e07dd8025451f3d24bc620b05 100644 --- a/packages/CLI11/tests/link_test_2.cpp +++ b/packages/CLI11/tests/link_test_2.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2022, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // diff --git a/packages/CLI11/tests/main.cpp b/packages/CLI11/tests/main.cpp index 20bb46d615f52eb8a62dc836e8e6292926ff364f..451f65038588121bdb253ac2071bc3f09e0fe93a 100644 --- a/packages/CLI11/tests/main.cpp +++ b/packages/CLI11/tests/main.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2022, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // diff --git a/packages/CLI11/tests/meson.build b/packages/CLI11/tests/meson.build index 27e221619f3c7bf733e4755fcac3a142a4021aa0..4847985550397356655366953240b526a5c3d768 100644 --- a/packages/CLI11/tests/meson.build +++ b/packages/CLI11/tests/meson.build @@ -1,14 +1,21 @@ catch2 = dependency('catch2') -testmain = static_library( - 'catch_main', - 'main.cpp', 'catch.hpp', - dependencies: catch2, -) -testdep = declare_dependency( - link_with: testmain, - dependencies: [catch2, CLI11_dep] -) +if catch2.version().version_compare('<3') + testmain = static_library( + 'catch_main', + 'main.cpp', 'catch.hpp', + dependencies: catch2, + ) + testdep = declare_dependency( + link_with: testmain, + dependencies: [catch2, CLI11_dep] + ) +else + testdep = declare_dependency( + dependencies: [CLI11_dep, dependency('catch2-with-main')], + compile_args: '-DCLI11_CATCH3' + ) +endif link_test_lib = library( 'link_test_1', @@ -57,6 +64,20 @@ testnames = [ ['link_test_2', {'link_with': link_test_lib}], ] +dependent_applications = [ + 'system_args' +] +dependent_applications_definitions = [] +#dependent_applications_targets = [] +foreach app: dependent_applications + app_target = executable(app, 'applications'/app + '.cpp', + build_by_default: false + ) + + #dependent_applications_targets += dependency(app_target) + dependent_applications_definitions += '-DCLI11_@0@_EXE="@1@"'.format(app.to_upper(), app_target) +endforeach + if host_machine.system() == 'windows' testnames += [['WindowsTest', {}]] endif @@ -69,7 +90,7 @@ foreach n: testnames name = n[0] kwargs = n[1] t = executable(name, name + '.cpp', - cpp_args: kwargs.get('cpp_args', []), + cpp_args: kwargs.get('cpp_args', []) + dependent_applications_definitions, build_by_default: false, dependencies: [testdep] + kwargs.get('dependencies', []), link_with: kwargs.get('link_with', []) diff --git a/packages/CLI11/tests/mesonTest/main.cpp b/packages/CLI11/tests/mesonTest/main.cpp index 64d45eb6f1e7e909c64a5540f7bcd60ebe878327..39bb7845cc3fcfab96731000dcda7611402e5441 100644 --- a/packages/CLI11/tests/mesonTest/main.cpp +++ b/packages/CLI11/tests/mesonTest/main.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. //