diff --git a/packages/CLI11/.all-contributorsrc b/packages/CLI11/.all-contributorsrc index f699ad2b03d93291886734c05d338cadd23269b0..7de67853e063fa7ea75d853506c59ea571d265ef 100644 --- a/packages/CLI11/.all-contributorsrc +++ b/packages/CLI11/.all-contributorsrc @@ -440,6 +440,42 @@ "contributions": [ "doc" ] + }, + { + "login": "paddy-hack", + "name": "Olaf Meeuwissen", + "avatar_url": "https://avatars.githubusercontent.com/u/6804372?v=4", + "profile": "https://github.com/paddy-hack", + "contributions": [ + "code" + ] + }, + { + "login": "dryleev", + "name": "dryleev", + "avatar_url": "https://avatars.githubusercontent.com/u/83670813?v=4", + "profile": "https://github.com/dryleev", + "contributions": [ + "code" + ] + }, + { + "login": "AnticliMaxtic", + "name": "Max", + "avatar_url": "https://avatars.githubusercontent.com/u/43995389?v=4", + "profile": "https://github.com/AnticliMaxtic", + "contributions": [ + "code" + ] + }, + { + "login": "alexdewar", + "name": "Alex Dewar", + "avatar_url": "https://avatars.githubusercontent.com/u/23149834?v=4", + "profile": "https://profiles.sussex.ac.uk/p281168-alex-dewar/publications", + "contributions": [ + "code" + ] } ], "contributorsPerLine": 7, diff --git a/packages/CLI11/.appveyor.yml b/packages/CLI11/.appveyor.yml index a4ae11883dc71dfee74dd77f83747897ba4d01d3..18915a1748725d232d7c75bf36f912e46f2244da 100644 --- a/packages/CLI11/.appveyor.yml +++ b/packages/CLI11/.appveyor.yml @@ -1,4 +1,4 @@ -version: 1.9.1.{build} +version: 2.1.1.{build} branches: only: diff --git a/packages/CLI11/.ci/azure-build.yml b/packages/CLI11/.ci/azure-build.yml index 06d60cec91733405a0e03e1c84ef899e59001248..f51620c52b512f376e0663da65ba7360951b9415 100644 --- a/packages/CLI11/.ci/azure-build.yml +++ b/packages/CLI11/.ci/azure-build.yml @@ -8,4 +8,3 @@ steps: - script: cmake --build . displayName: 'Build' workingDirectory: build - diff --git a/packages/CLI11/.ci/azure-test.yml b/packages/CLI11/.ci/azure-test.yml index ec1d1f55633b2ee51630d1ce042f6728a74e780e..cf89a99957a02488a4208fac757cdc454168678a 100644 --- a/packages/CLI11/.ci/azure-test.yml +++ b/packages/CLI11/.ci/azure-test.yml @@ -8,5 +8,3 @@ steps: inputs: testResultsFormat: 'cTest' testResultsFiles: '**/Test.xml' - - diff --git a/packages/CLI11/.ci/build_doxygen.sh b/packages/CLI11/.ci/build_doxygen.sh index 4474f696ab76f493077f834873b4a55f5bab0246..bd877d53eff538b421943a44ed0f246c0a5b5b1b 100644 --- a/packages/CLI11/.ci/build_doxygen.sh +++ b/packages/CLI11/.ci/build_doxygen.sh @@ -23,4 +23,3 @@ export PATH="${DEPS_DIR}/doxygen/build/bin:${PATH}" cd "${TRAVIS_BUILD_DIR}" set +evx - diff --git a/packages/CLI11/.ci/make_and_test.sh b/packages/CLI11/.ci/make_and_test.sh index af8de340f53ac65911ab305c0949aaf793804fde..07df22e881c04a2e5dd70cd7702b3159a765390b 100755 --- a/packages/CLI11/.ci/make_and_test.sh +++ b/packages/CLI11/.ci/make_and_test.sh @@ -1,14 +1,14 @@ #!/usr/bin/env bash echo -en "travis_fold:start:script.build\\r" echo "Building..." -STD=$1 +STD="$1" shift set -evx mkdir -p build cd build -cmake .. -DCLI11_WARNINGS_AS_ERRORS=ON -DCLI11_SINGLE_FILE=ON -DCMAKE_CXX_STANDARD=$STD -DCLI11_SINGLE_FILE_TESTS=ON -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_COMPILER_LAUNCHER=ccache $@ +cmake .. -DCLI11_WARNINGS_AS_ERRORS=ON -DCLI11_SINGLE_FILE=ON -DCMAKE_CXX_STANDARD="$STD" -DCLI11_SINGLE_FILE_TESTS=ON -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_COMPILER_LAUNCHER=ccache $@ cmake --build . -- -j2 set +evx diff --git a/packages/CLI11/.ci/run_codecov.sh b/packages/CLI11/.ci/run_codecov.sh index 28d149a530571c3eb8d58dec4c2ac61284e504a9..fbc199ca579d50b6bdcbd9cd899b6db7c6ab7e0c 100755 --- a/packages/CLI11/.ci/run_codecov.sh +++ b/packages/CLI11/.ci/run_codecov.sh @@ -4,7 +4,7 @@ echo -en "travis_fold:start:script.build\\r" echo "Building..." set -evx -cd ${TRAVIS_BUILD_DIR} +cd "${TRAVIS_BUILD_DIR}" mkdir -p build cd build cmake .. -DCLI11_SINGLE_FILE_TESTS=OFF -DCLI11_EXAMPLES=OFF -DCMAKE_BUILD_TYPE=Coverage diff --git a/packages/CLI11/.clang-tidy b/packages/CLI11/.clang-tidy index 09875c87a6b7adbc0aa8f5b0ba872493f2c82e63..075f35dc58ed5cfc91a62048bdbd91c319718685 100644 --- a/packages/CLI11/.clang-tidy +++ b/packages/CLI11/.clang-tidy @@ -22,4 +22,3 @@ HeaderFilterRegex: '.*hpp' CheckOptions: - key: google-readability-braces-around-statements.ShortStatementLines value: '3' - diff --git a/packages/CLI11/.cmake-format.yaml b/packages/CLI11/.cmake-format.yaml new file mode 100644 index 0000000000000000000000000000000000000000..543ddf19f87ff18972f60b3a7329431eb3d81964 --- /dev/null +++ b/packages/CLI11/.cmake-format.yaml @@ -0,0 +1,6 @@ +format: + line_width: 99 + +# Causes a few issues - can be solved later, possibly. +markup: + enable_markup: false diff --git a/packages/CLI11/.github/CONTRIBUTING.md b/packages/CLI11/.github/CONTRIBUTING.md index fcee45d43671f3facfd2a133e075fcbd3300f757..330d8b15b9636b092c3fb37a9c93daa4da7fa810 100644 --- a/packages/CLI11/.github/CONTRIBUTING.md +++ b/packages/CLI11/.github/CONTRIBUTING.md @@ -1,8 +1,11 @@ +# Contributing + Thanks for considering to write a Pull Request (PR) for CLI11! Here are a few guidelines to get you started: Make sure you are comfortable with the license; all contributions are licensed under the original license. ## Adding functionality + Make sure any new functions you add are are: * Documented by `///` documentation for Doxygen @@ -12,7 +15,7 @@ Make sure any new functions you add are are: In general, make sure the addition is well thought out and does not increase the complexity of CLI11 needlessly. -## Things you should know: +## Things you should know * Once you make the PR, tests will run to make sure your code works on all supported platforms * The test coverage is also measured, and that should remain 100% @@ -20,10 +23,9 @@ In general, make sure the addition is well thought out and does not increase the * Everything must pass clang-tidy as well, run with `-DCLI11_CLANG_TIDY=ON` (if you set `-DCLI11_CLANG_TIDY_OPTIONS="-fix"`, make sure you use a single threaded build process, or just build one example target). * Your changes must also conform to most of the [Google C++ Style Guide](https://google.github.io/styleguide/cppguide.html) rules checked by [cpplint](https://github.com/cpplint/cpplint). For unused cpplint filters and justifications, see [CPPLINT.cfg](/CPPLINT.cfg). - ## Pre-commit -Format is handled by pre-commit. You should install it: +Format is handled by pre-commit. You should install it (or use [pipx](https://pypa.github.io/pipx/)): ```bash python3 -m pip install pre-commit @@ -31,13 +33,12 @@ python3 -m pip install pre-commit Then, you can run it on the items you've added to your staging area, or all files: -``` +```bash pre-commit run # OR pre-commit run --all-files ``` - And, if you want to always use it, you can install it as a git hook (hence the name, pre-commit): ```bash @@ -72,9 +73,12 @@ yarn all-contributors add username code,bug Remember to replace the emoji in the readme, being careful not to replace the ones in all-contributors if any overlap. Steps: + * Update changelog if needed * Update the version in `.appveyor.yml` and `include/CLI/Version.hpp`. * Find and replace in README: - * Replace " 🆕" and "🆕 " with "" (ignores the description line) - * Check for `\/\/$` (vi syntax) to catch leftover `// 🆕` - * Replace "🚧" with "🆕" (manually ignore the description line) + * Replace " 🆕" and "🆕 " with "" (ignores the description line) + * Check for `\/\/$` (vi syntax) to catch leftover `// 🆕` + * Replace "🚧" with "🆕" (manually ignore the description line) +* Make a release in the GitHub UI, use a name such as "Version X.Y(.Z): Title" +* Currently, the release action wipes the title after you release, so remember to edit the title back to the original name after the `CLI11.hpp` file gets uploaded. diff --git a/packages/CLI11/.github/actions/quick_cmake/action.yml b/packages/CLI11/.github/actions/quick_cmake/action.yml index da721a78c32e275b7f8c93e7a8457b1d009925fc..28a82780c77e531a783e311cada57f78a4a71e13 100644 --- a/packages/CLI11/.github/actions/quick_cmake/action.yml +++ b/packages/CLI11/.github/actions/quick_cmake/action.yml @@ -5,14 +5,21 @@ inputs: description: 'Other arguments' required: false default: '' + cmake-version: + description: 'The CMake version to run' + required: true runs: using: composite steps: + - name: CMake ${{ inputs.cmake-version }} + uses: jwlawson/actions-setup-cmake@v1.11 + with: + cmake-version: "${{ inputs.cmake-version }}" - run: | mkdir -p build-tmp touch build-tmp/tmp rm -r build-tmp/* - (cd build-tmp && cmake .. ${{ inputs.args}}) + (cd build-tmp && cmake .. ${{ inputs.args }}) rm -r build-tmp shell: bash diff --git a/packages/CLI11/.github/workflows/build.yml b/packages/CLI11/.github/workflows/build.yml index d3f8043a5440de0aee244565809397a5950a50e4..11ed11ca405d775ba9517c60cdef9d9de77f60ea 100644 --- a/packages/CLI11/.github/workflows/build.yml +++ b/packages/CLI11/.github/workflows/build.yml @@ -37,10 +37,13 @@ jobs: - name: Make header run: cmake --build build --target CLI11-generate-single-file + - name: Copy file to main folder + run: cp build/include/CLI11.hpp CLI11.hpp + - uses: actions/upload-artifact@v2 with: name: CLI11.hpp - path: build/include/CLI11.hpp + path: CLI11.hpp - uses: actions/upload-artifact@v2 with: diff --git a/packages/CLI11/.github/workflows/tests.yml b/packages/CLI11/.github/workflows/tests.yml index 60f10f9623eaf0218344a63d60737eea92d5ace2..020ba3b2b94d03c2e3e7519a5f2877620d542385 100644 --- a/packages/CLI11/.github/workflows/tests.yml +++ b/packages/CLI11/.github/workflows/tests.yml @@ -15,7 +15,7 @@ jobs: steps: - uses: actions/checkout@v2 - uses: actions/setup-python@v2 - - uses: pre-commit/action@v2.0.2 + - uses: pre-commit/action@v2.0.3 cuda-build: name: CUDA build only @@ -28,7 +28,7 @@ jobs: - name: Add wget run: apt-get update && apt-get install -y wget - name: Setup cmake - uses: jwlawson/actions-setup-cmake@v1.8 + uses: jwlawson/actions-setup-cmake@v1.11 - name: Configure run: cmake -S . -B build -DCLI11_CUDA_TESTS=ON - name: Build @@ -40,143 +40,111 @@ jobs: steps: - uses: actions/checkout@v2 - - name: CMake 3.4 - uses: jwlawson/actions-setup-cmake@v1.8 + - name: Check CMake 3.4 with: cmake-version: "3.4" - - name: Check CMake 3.4 uses: ./.github/actions/quick_cmake - - name: CMake 3.5 - uses: jwlawson/actions-setup-cmake@v1.8 - with: - cmake-version: "3.5" - name: Check CMake 3.5 uses: ./.github/actions/quick_cmake + with: + cmake-version: "3.5" if: success() || failure() - - name: CMake 3.6 - uses: jwlawson/actions-setup-cmake@v1.8 - with: - cmake-version: "3.6" - name: Check CMake 3.6 uses: ./.github/actions/quick_cmake + with: + cmake-version: "3.6" if: success() || failure() - - name: CMake 3.7 - uses: jwlawson/actions-setup-cmake@v1.8 - with: - cmake-version: "3.7" - name: Check CMake 3.7 uses: ./.github/actions/quick_cmake + with: + cmake-version: "3.7" if: success() || failure() - - name: CMake 3.8 - uses: jwlawson/actions-setup-cmake@v1.8 - with: - cmake-version: "3.8" - name: Check CMake 3.8 uses: ./.github/actions/quick_cmake + with: + cmake-version: "3.8" if: success() || failure() - - name: CMake 3.9 - uses: jwlawson/actions-setup-cmake@v1.8 - with: - cmake-version: "3.9" - name: Check CMake 3.9 uses: ./.github/actions/quick_cmake + with: + cmake-version: "3.9" if: success() || failure() - - name: CMake 3.10 - uses: jwlawson/actions-setup-cmake@v1.8 - with: - cmake-version: "3.10" - name: Check CMake 3.10 uses: ./.github/actions/quick_cmake + with: + cmake-version: "3.10" if: success() || failure() - - name: CMake 3.11 - uses: jwlawson/actions-setup-cmake@v1.8 - with: - cmake-version: "3.11" - name: Check CMake 3.11 (full) uses: ./.github/actions/quick_cmake with: + cmake-version: "3.11" args: -DCLI11_SANITIZERS=ON -DCLI11_BUILD_EXAMPLES_JSON=ON if: success() || failure() - - name: CMake 3.12 - uses: jwlawson/actions-setup-cmake@v1.8 - with: - cmake-version: "3.12" - name: Check CMake 3.12 uses: ./.github/actions/quick_cmake + with: + cmake-version: "3.12" if: success() || failure() - - name: CMake 3.13 - uses: jwlawson/actions-setup-cmake@v1.8 - with: - cmake-version: "3.13" - name: Check CMake 3.13 uses: ./.github/actions/quick_cmake + with: + cmake-version: "3.13" if: success() || failure() - - name: CMake 3.14 - uses: jwlawson/actions-setup-cmake@v1.8 - with: - cmake-version: "3.14" - name: Check CMake 3.14 uses: ./.github/actions/quick_cmake + with: + cmake-version: "3.14" if: success() || failure() - - name: CMake 3.15 - uses: jwlawson/actions-setup-cmake@v1.8 - with: - cmake-version: "3.15" - name: Check CMake 3.15 uses: ./.github/actions/quick_cmake + with: + cmake-version: "3.15" if: success() || failure() - - name: CMake 3.16 - uses: jwlawson/actions-setup-cmake@v1.8 - with: - cmake-version: "3.16" - name: Check CMake 3.16 uses: ./.github/actions/quick_cmake + with: + cmake-version: "3.16" if: success() || failure() - - name: CMake 3.17 - uses: jwlawson/actions-setup-cmake@v1.8 - with: - cmake-version: "3.17" - name: Check CMake 3.17 uses: ./.github/actions/quick_cmake + with: + cmake-version: "3.17" if: success() || failure() - - name: CMake 3.18 - uses: jwlawson/actions-setup-cmake@v1.8 - with: - cmake-version: "3.18" - name: Check CMake 3.18 uses: ./.github/actions/quick_cmake + with: + cmake-version: "3.18" if: success() || failure() - - name: CMake 3.19 - uses: jwlawson/actions-setup-cmake@v1.8 - with: - cmake-version: "3.19" - - name: Check CMake 3.19 (full) + - name: Check CMake 3.19 uses: ./.github/actions/quick_cmake with: - args: -DCLI11_SANITIZERS=ON -DCLI11_BUILD_EXAMPLES_JSON=ON + cmake-version: "3.19" if: success() || failure() - - name: CMake 3.20 - uses: jwlawson/actions-setup-cmake@v1.8 - with: - cmake-version: "3.20" - name: Check CMake 3.20 uses: ./.github/actions/quick_cmake + with: + cmake-version: "3.20" if: success() || failure() - + - name: Check CMake 3.21 (full) + uses: ./.github/actions/quick_cmake + with: + cmake-version: "3.21" + args: -DCLI11_SANITIZERS=ON -DCLI11_BUILD_EXAMPLES_JSON=ON + if: success() || failure() diff --git a/packages/CLI11/.gitrepo b/packages/CLI11/.gitrepo index 732e03b962d7b98a783a3ae9f93d8ac9232673e5..6e7179c3001b3661e634d9725debd699b8d62d92 100644 --- a/packages/CLI11/.gitrepo +++ b/packages/CLI11/.gitrepo @@ -6,7 +6,7 @@ [subrepo] remote = git@github.com:CLIUtils/CLI11.git branch = master - commit = 4af78beef777e313814b4daff70e2da9171a385a - parent = 015d2fd5332b347d28c47c8dfe3f401382724178 + commit = b440890eaf29d526e13997f67c2e0288c7c3c60f + parent = 4bd033645f2c3c03bdf1682e34ec57bbd2b5dd21 cmdver = 0.4.3 method = merge diff --git a/packages/CLI11/.pre-commit-config.yaml b/packages/CLI11/.pre-commit-config.yaml index f48801a6e65cb8c7d64f86de091d41f348f19a00..00d77d22fa0a21d7e121628ad9adaaa111a9c717 100644 --- a/packages/CLI11/.pre-commit-config.yaml +++ b/packages/CLI11/.pre-commit-config.yaml @@ -1,20 +1,27 @@ +ci: + autoupdate_commit_msg: "chore(deps): pre-commit.ci autoupdate" + autofix_commit_msg: "style: pre-commit.ci fixes" + skip: + - docker-clang-format + repos: - repo: https://github.com/psf/black - rev: 20.8b1 + rev: 21.9b0 hooks: - id: black - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v3.4.0 + rev: v4.0.1 hooks: - id: check-added-large-files - - id: mixed-line-ending - - id: trailing-whitespace - - id: check-merge-conflict - id: check-case-conflict + - id: check-merge-conflict - id: check-symlinks - id: check-yaml + - id: end-of-file-fixer + - id: mixed-line-ending + - id: trailing-whitespace - repo: local hooks: @@ -27,3 +34,39 @@ repos: args: - -style=file - -i + +- repo: https://github.com/cheshirekow/cmake-format-precommit + rev: v0.6.13 + hooks: + - id: cmake-format + additional_dependencies: [pyyaml] + +- repo: https://github.com/markdownlint/markdownlint + rev: v0.11.0 + hooks: + - id: markdownlint + args: ["--style=scripts/mdlint_style.rb"] + +- repo: local + hooks: + - id: remarklint + name: remarklint + language: node + entry: remark + types: [markdown] + args: ["--frail", "--quiet"] + additional_dependencies: [remark, remark-lint, remark-cli, remark-preset-lint-recommended, remark-lint-list-item-indent, remark-lint-no-undefined-references] + +- repo: local + hooks: + - id: disallow-caps + name: Disallow improper capitalization + language: pygrep + entry: PyBind|Numpy|Cmake|CCache|PyTest|Github + exclude: .pre-commit-config.yaml + +- repo: https://github.com/codespell-project/codespell + rev: v2.1.0 + hooks: + - id: codespell + args: ["-L", "atleast,ans,doub,inout"] diff --git a/packages/CLI11/.pre-commit-nodocker.yaml b/packages/CLI11/.pre-commit-nodocker.yaml deleted file mode 100644 index 5fcaf3e53ef45d86dd497fe7240e46a2023d8a5b..0000000000000000000000000000000000000000 --- a/packages/CLI11/.pre-commit-nodocker.yaml +++ /dev/null @@ -1,27 +0,0 @@ - -repos: -- repo: https://github.com/psf/black - rev: 19.3b0 - hooks: - - id: black -- repo: https://github.com/pre-commit/pre-commit-hooks - rev: v2.3.0 - hooks: - - id: check-added-large-files - - id: mixed-line-ending - - id: trailing-whitespace - - id: check-merge-conflict - - id: check-case-conflict - - id: check-symlinks - - id: check-yaml -- repo: local - hooks: - - id: clang-format - name: Clang Format - language: system - types: - - c++ - entry: clang-format - args: - - -style=file - - -i diff --git a/packages/CLI11/.remarkrc b/packages/CLI11/.remarkrc new file mode 100644 index 0000000000000000000000000000000000000000..73cad832882f877af531bafa42808d99dadfd9ac --- /dev/null +++ b/packages/CLI11/.remarkrc @@ -0,0 +1,7 @@ +{ + "plugins": [ + "remark-preset-lint-recommended", + ["remark-lint-list-item-indent", "space"], + ["remark-lint-no-undefined-references", {"allow": ["^1"]}] + ] +} diff --git a/packages/CLI11/CHANGELOG.md b/packages/CLI11/CHANGELOG.md index d2a59b4786a836a07801228e5371e095b42c5e23..41c193814d3868ffe9895583a44205751485981d 100644 --- a/packages/CLI11/CHANGELOG.md +++ b/packages/CLI11/CHANGELOG.md @@ -1,28 +1,76 @@ -## Version 2.0: In progress +# Changelog + +## Version 2.1: Names and callbacks + +The name restrictions for options and subcommands are now much looser, allowing +a wider variety of characters than before, even spaces can be used (use quotes +to include a space in most shells). The default configuration parser was +improved, allowing your configuration to sit in a larger file. And option +callbacks have a few new settings, allowing them to be run even if the option +is not passed, or every time the option is parsed. + +* Option/subcommand name restrictions have been relaxed. Most characters are now allowed. [#627][] +* The config parser can accept streams, specify a specific section, and inline comment characters are supported [#630][] +* `force_callback` & `trigger_on_parse` added, allowing a callback to always run on parse even if not present or every time the option is parsed[#631][] +* Bugfix(cmake): Only add `CONFIGURE_DEPENDS` if CLI11 is the main project [#633][] +* Bugfix(cmake): Ensure the cmake/pkg-config files install to a arch independent path [#635][] +* Bugfix: The single header file generation was missing the include guard. [#620][] + +[#620]: https://github.com/CLIUtils/CLI11/pull/620 +[#627]: https://github.com/CLIUtils/CLI11/pull/627 +[#630]: https://github.com/CLIUtils/CLI11/pull/630 +[#631]: https://github.com/CLIUtils/CLI11/pull/631 +[#633]: https://github.com/CLIUtils/CLI11/pull/633 +[#635]: https://github.com/CLIUtils/CLI11/pull/635 + +### Version 2.1.1: Quick Windows fix + +* A collision with `min`/`max` macros on Windows has been fixed. [#642][] + +[#642]: https://github.com/CLIUtils/CLI11/pull/642 + +## Version 2.0: Simplification + +This version focuses on cleaning up deprecated functionality, and some minor +default changes. The config processing is TOML compliant now. Atomics and +complex numbers are directly supported, along with other container +improvements. A new version flag option has finally been added. Subcommands are +significantly improved with new features and bugfixes for corner cases. This +release contains a lot of backend cleanup, including a complete overhaul of the +testing system and single file generation system. * Built-in config format is TOML compliant now [#435][] - * Support multiline TOML [#528][] -* Support short/positional options in config mode [#443][] -* More powerful containers, `%%` separator [#423][] -* Add a version flag easily [#452][] -* Support atomic types [#520][] + * Support multiline TOML [#528][] + * Support for configurable quotes [#599][] + * Support short/positional options in config mode [#443][] +* More powerful containers, support for `%%` separator [#423][] +* Support atomic types [#520][] and complex types natively [#423][] * Add a type validator `CLI::TypeValidator<TYPE>` [#526][] +* Add a version flag easily [#452][], with help message [#601][] * Support `->silent()` on subcommands. [#529][] * Add alias section to help for subcommands [#545][] -* Redesigned MakeSingleFiles to have a higher level of manual control, to support future features. [#546][] -* Moved testing from GTest to Catch2 [#574][] - +* Allow quotes to specify a program name [#605][] +* Backend: redesigned MakeSingleFiles to have a higher level of manual control, to support future features. [#546][] +* Backend: moved testing from GTest to Catch2 [#574][] +* Bugfix: avoid duplicated and missed calls to the final callback [#584][] +* Bugfix: support embedded newlines in more places [#592][] * Bugfix: avoid listing helpall as a required flag [#530][] * Bugfix: avoid a clash with WINDOWS define [#563][] - -* Removed deprecated set commands, use validators instead. [#565][] - +* Bugfix: the help flag didn't get processed when a config file was required [#606][] +* Bugfix: fix description of non-configurable subcommands in config [#604][] * Build: support pkg-config [#523][] +> ### Converting from CLI11 1.9 +> +> * Removed deprecated set commands, use validators instead. [#565][] +> * The final "defaulted" bool has been removed, use `->capture_default_str()` +> instead. Use `app.option_defaults()->always_capture_default()` to set this for +> all future options. [#597][] +> * Use `add_option` on a complex number instead of `add_complex`, which has been removed. +[#423]: https://github.com/CLIUtils/CLI11/pull/423 [#435]: https://github.com/CLIUtils/CLI11/pull/435 [#443]: https://github.com/CLIUtils/CLI11/pull/443 -[#423]: https://github.com/CLIUtils/CLI11/pull/423 [#452]: https://github.com/CLIUtils/CLI11/pull/452 [#520]: https://github.com/CLIUtils/CLI11/pull/520 [#523]: https://github.com/CLIUtils/CLI11/pull/523 @@ -35,30 +83,14 @@ [#563]: https://github.com/CLIUtils/CLI11/pull/563 [#565]: https://github.com/CLIUtils/CLI11/pull/565 [#574]: https://github.com/CLIUtils/CLI11/pull/574 - - - - -### Version 1.9.1: Backporting fixes - -This is a patch version that backports fixes from the development of 2.0. - -* Support relative inclusion [#475][] -* Fix cases where spaces in paths could break CMake support [#471][] -* Fix an issue with string conversion [#421][] -* Cross-compiling improvement for Conan.io [#430][] -* Fix option group default propagation [#450][] -* Fix for C++20 [#459][] -* Support compiling with RTTI off [#461][] - -[#421]: https://github.com/CLIUtils/CLI11/pull/421 -[#430]: https://github.com/CLIUtils/CLI11/pull/430 -[#450]: https://github.com/CLIUtils/CLI11/pull/450 -[#459]: https://github.com/CLIUtils/CLI11/pull/459 -[#461]: https://github.com/CLIUtils/CLI11/pull/461 -[#471]: https://github.com/CLIUtils/CLI11/pull/471 -[#475]: https://github.com/CLIUtils/CLI11/pull/475 - +[#584]: https://github.com/CLIUtils/CLI11/pull/584 +[#592]: https://github.com/CLIUtils/CLI11/pull/592 +[#597]: https://github.com/CLIUtils/CLI11/pull/597 +[#599]: https://github.com/CLIUtils/CLI11/pull/599 +[#601]: https://github.com/CLIUtils/CLI11/pull/601 +[#604]: https://github.com/CLIUtils/CLI11/pull/604 +[#605]: https://github.com/CLIUtils/CLI11/pull/605 +[#606]: https://github.com/CLIUtils/CLI11/pull/606 ## Version 1.9: Config files and cleanup @@ -98,11 +130,11 @@ configuration options were added to facilitate a wider variety of apps. GCC * Build: GCC 4.7 is no longer supported, due mostly to GoogleTest. GCC 4.8+ is now required. [#160][] * Build: Restructured significant portions of CMake build system [#394][] -> ### Converting from CLI11 1.8: +> ### Converting from CLI11 1.8 > > * Some deprecated methods dropped -> - `add_set*` should be replaced with `->check`/`->transform` and `CLI::IsMember` since 1.8 -> - `get_defaultval` was replaced by `get_default_str` in 1.8 +> * `add_set*` should be replaced with `->check`/`->transform` and `CLI::IsMember` since 1.8 +> * `get_defaultval` was replaced by `get_default_str` in 1.8 > * The true/false 4th argument to `add_option` is expected to be removed in 2.0, use `->capture_default_str()` since 1.8 [#160]: https://github.com/CLIUtils/CLI11/pull/160 @@ -118,7 +150,6 @@ configuration options were added to facilitate a wider variety of apps. GCC [#307]: https://github.com/CLIUtils/CLI11/pull/307 [#309]: https://github.com/CLIUtils/CLI11/pull/309 [#310]: https://github.com/CLIUtils/CLI11/pull/310 -[#312]: https://github.com/CLIUtils/CLI11/pull/312 [#313]: https://github.com/CLIUtils/CLI11/pull/313 [#317]: https://github.com/CLIUtils/CLI11/pull/317 [#318]: https://github.com/CLIUtils/CLI11/pull/318 @@ -126,6 +157,7 @@ configuration options were added to facilitate a wider variety of apps. GCC [#325]: https://github.com/CLIUtils/CLI11/pull/325 [#333]: https://github.com/CLIUtils/CLI11/pull/333 [#336]: https://github.com/CLIUtils/CLI11/pull/336 +[#341]: https://github.com/CLIUtils/CLI11/pull/341 [#342]: https://github.com/CLIUtils/CLI11/pull/342 [#348]: https://github.com/CLIUtils/CLI11/pull/348 [#349]: https://github.com/CLIUtils/CLI11/pull/349 @@ -136,42 +168,63 @@ configuration options were added to facilitate a wider variety of apps. GCC [#360]: https://github.com/CLIUtils/CLI11/pull/360 [#362]: https://github.com/CLIUtils/CLI11/pull/362 [#365]: https://github.com/CLIUtils/CLI11/pull/365 +[#370]: https://github.com/CLIUtils/CLI11/pull/370 [#373]: https://github.com/CLIUtils/CLI11/pull/373 [#374]: https://github.com/CLIUtils/CLI11/pull/374 [#382]: https://github.com/CLIUtils/CLI11/pull/382 +[#387]: https://github.com/CLIUtils/CLI11/pull/387 [#390]: https://github.com/CLIUtils/CLI11/pull/390 [#394]: https://github.com/CLIUtils/CLI11/pull/394 [#400]: https://github.com/CLIUtils/CLI11/pull/400 +### Version 1.9.1: Backporting fixes + +This is a patch version that backports fixes from the development of 2.0. + +* Support relative inclusion [#475][] +* Fix cases where spaces in paths could break CMake support [#471][] +* Fix an issue with string conversion [#421][] +* Cross-compiling improvement for Conan.io [#430][] +* Fix option group default propagation [#450][] +* Fix for C++20 [#459][] +* Support compiling with RTTI off [#461][] + +[#421]: https://github.com/CLIUtils/CLI11/pull/421 +[#430]: https://github.com/CLIUtils/CLI11/pull/430 +[#450]: https://github.com/CLIUtils/CLI11/pull/450 +[#459]: https://github.com/CLIUtils/CLI11/pull/459 +[#461]: https://github.com/CLIUtils/CLI11/pull/461 +[#471]: https://github.com/CLIUtils/CLI11/pull/471 +[#475]: https://github.com/CLIUtils/CLI11/pull/475 ## Version 1.8: Transformers, default strings, and flags Set handling has been completely replaced by a new backend that works as a Validator or Transformer. This provides a single interface instead of the 16 different functions in App. It also allows ordered collections to be used, custom functions for filtering, and better help and error messages. You can also use a collection of pairs (like `std::map`) to transform the match into an output. Also new are inverted flags, which can cancel or reduce the count of flags, and can also support general flag types. A new `add_option_fn` lets you more easily program CLI11 options with the types you choose. Vector options now support a custom separator. Apps can now be composed with unnamed subcommand support. The final bool "defaults" flag when creating options has been replaced by `->capture_default_str()` (ending an old limitation in construction made this possible); the old method is still available but may be removed in future versions. -* Replaced default help capture: `.add_option("name", value, "", True)` becomes `.add_option("name", value)->capture_default_str()` [#242] -* Added `.always_capture_default()` [#242] -* New `CLI::IsMember` validator replaces set validation [#222] -* IsMember also supports container of pairs, transform allows modification of result [#228] -* Added new Transformers, `CLI::AsNumberWithUnit` and `CLI::AsSizeValue` [#253] -* Much more powerful flags with different values [#211], general types [#235] -* `add_option` now supports bool due to unified bool handling [#211] -* Support for composable unnamed subcommands [#216] -* Reparsing is better supported with `.remaining_for_passthrough()` [#265] -* Custom vector separator using `->delimiter(char)` [#209], [#221], [#240] -* Validators added for IP4 addresses and positive numbers [#210] and numbers [#262] -* Minimum required Boost for optional Optionals has been corrected to 1.61 [#226] -* Positionals can stop options from being parsed with `app.positionals_at_end()` [#223] -* Added `validate_positionals` [#262] -* Positional parsing is much more powerful [#251], duplicates supported []#247] -* Validators can be negated with `!` [#230], and now handle tname functions [#228] -* Better enum support and streaming helper [#233] and [#228] -* Cleanup for shadow warnings [#232] -* Better alignment on multiline descriptions [#269] -* Better support for aarch64 [#266] -* Respect `BUILD_TESTING` only if CLI11 is the main project; otherwise, `CLI11_TESTING` must be used [#277] -* Drop auto-detection of experimental optional and boost::optional; must be enabled explicitly (too fragile) [#277] [#279] - -> ### Converting from CLI11 1.7: +* Replaced default help capture: `.add_option("name", value, "", True)` becomes `.add_option("name", value)->capture_default_str()` [#242][] +* Added `.always_capture_default()` [#242][] +* New `CLI::IsMember` validator replaces set validation [#222][] +* `IsMember` also supports container of pairs, transform allows modification of result [#228][] +* Added new Transformers, `CLI::AsNumberWithUnit` and `CLI::AsSizeValue` [#253][] +* Much more powerful flags with different values [#211][], general types [#235][] +* `add_option` now supports bool due to unified bool handling [#211][] +* Support for composable unnamed subcommands [#216][] +* Reparsing is better supported with `.remaining_for_passthrough()` [#265][] +* Custom vector separator using `->delimiter(char)` [#209][], [#221][], [#240][] +* Validators added for IP4 addresses and positive numbers [#210][] and numbers [#262][] +* Minimum required Boost for optional Optionals has been corrected to 1.61 [#226][] +* Positionals can stop options from being parsed with `app.positionals_at_end()` [#223][] +* Added `validate_positionals` [#262][] +* Positional parsing is much more powerful [#251][], duplicates supported [#247][] +* Validators can be negated with `!` [#230][], and now handle tname functions [#228][] +* Better enum support and streaming helper [#233][] and [#228][] +* Cleanup for shadow warnings [#232][] +* Better alignment on multiline descriptions [#269][] +* Better support for aarch64 [#266][] +* Respect `BUILD_TESTING` only if CLI11 is the main project; otherwise, `CLI11_TESTING` must be used [#277][] +* Drop auto-detection of experimental optional and boost::optional; must be enabled explicitly (too fragile) [#277][] [#279][] + +> ### Converting from CLI11 1.7 > > * `.add_option(..., true)` should be replaced by `.add_option(...)->capture_default_str()` or `app.option_defaults()->always_capture_default()` can be used > * `app.add_set("--name", value, {"choice1", "choice2"})` should become `app.add_option("--name", value)->check(CLI::IsMember({"choice1", "choice2"}))` @@ -207,47 +260,33 @@ Set handling has been completely replaced by a new backend that works as a Valid [#277]: https://github.com/CLIUtils/CLI11/pull/277 [#279]: https://github.com/CLIUtils/CLI11/pull/279 - -## Version 1.7.1: Quick patch - -This version provides a quick patch for a (correct) warning from GCC 8 for the windows options code. - - -* Fix for Windows style option parsing [#201] -* Improve `add_subcommand` when throwing an exception [#204] -* Better metadata for Conan package [#202] - -[#201]: https://github.com/CLIUtils/CLI11/pull/201 -[#202]: https://github.com/CLIUtils/CLI11/pull/202 -[#204]: https://github.com/CLIUtils/CLI11/pull/204 - ## Version 1.7: Parse breakup The parsing procedure now maps much more sensibly to complex, nested subcommand structures. Each phase of the parsing happens on all subcommands before moving on with the next phase of the parse. This allows several features, like required environment variables, to work properly even through subcommand boundaries. Passing the same subcommand multiple times is better supported. Several new features were added as well, including Windows style option support, parsing strings directly, and ignoring underscores in names. Adding a set that you plan to change later must now be done with `add_mutable_set`. -* Support Windows style options with `->allow_windows_style_options`. [#187] On by default on Windows. [#190] -* Added `parse(string)` to split up and parse a command-line style string directly. [#186] -* Added `ignore_underscore` and related functions, to ignore underscores when matching names. [#185] -* The default INI Config will now add quotes to strings with spaces [#195] -* The default message now will mention the help-all flag also if present [#197] -* Added `->description` to set Option descriptions [#199] -* Mutating sets (introduced in Version 1.6) now have a clear add method, `add_mutable_set*`, since the set reference should not expire [#200] -* Subcommands now track how many times they were parsed in a parsing process. `count()` with no arguments will return the number of times a subcommand was encountered. [#179] -* Parsing is now done in phases: `shortcurcuits`, `ini`, `env`, `callbacks`, and `requirements`; all subcommands complete a phase before moving on. [#179] -* Calling parse multiple times is now officially supported without `clear` (automatic). [#179] -* Dropped the mostly undocumented `short_circuit` property, as help flag parsing is a bit more complex, and the default callback behavior of options now works properly. [#179] -* Use the standard `BUILD_TESTING` over `CLI11_TESTING` if defined [#183] -* Cleanup warnings [#191] -* Remove deprecated names: `set_footer`, `set_name`, `set_callback`, and `set_type_name`. Use without the `set_` instead. [#192] - -> ### Converting from CLI11 1.6: +* Support Windows style options with `->allow_windows_style_options`. [#187][] On by default on Windows. [#190][] +* Added `parse(string)` to split up and parse a command-line style string directly. [#186][] +* Added `ignore_underscore` and related functions, to ignore underscores when matching names. [#185][] +* The default INI Config will now add quotes to strings with spaces [#195][] +* The default message now will mention the help-all flag also if present [#197][] +* Added `->description` to set Option descriptions [#199][] +* Mutating sets (introduced in Version 1.6) now have a clear add method, `add_mutable_set*`, since the set reference should not expire [#200][] +* Subcommands now track how many times they were parsed in a parsing process. `count()` with no arguments will return the number of times a subcommand was encountered. [#178][] +* Parsing is now done in phases: `shortcurcuits`, `ini`, `env`, `callbacks`, and `requirements`; all subcommands complete a phase before moving on. [#178][] +* Calling parse multiple times is now officially supported without `clear` (automatic). [#178][] +* Dropped the mostly undocumented `short_circuit` property, as help flag parsing is a bit more complex, and the default callback behavior of options now works properly. [#179][] +* Use the standard `BUILD_TESTING` over `CLI11_TESTING` if defined [#183][] +* Cleanup warnings [#191][] +* Remove deprecated names: `set_footer`, `set_name`, `set_callback`, and `set_type_name`. Use without the `set_` instead. [#192][] + +> ### Converting from CLI11 1.6 > > * `->short_circuit()` is no longer needed, just remove it if you were using it - raising an exception will happen in the proper place now without it. > * `->add_set*` becomes `->add_mutable_set*` if you were using the editable set feature > * `footer`, `name`, `callback`, and `type_name` must be used instead of the `set_*` versions (deprecated previously). -[#179]: https://github.com/CLIUtils/CLI11/pull/179 +[#178]: https://github.com/CLIUtils/CLI11/pull/178 [#183]: https://github.com/CLIUtils/CLI11/pull/183 [#185]: https://github.com/CLIUtils/CLI11/pull/185 [#186]: https://github.com/CLIUtils/CLI11/pull/186 @@ -260,48 +299,21 @@ Passing the same subcommand multiple times is better supported. Several new feat [#199]: https://github.com/CLIUtils/CLI11/pull/199 [#200]: https://github.com/CLIUtils/CLI11/pull/200 -## Version 1.6.2: Help-all - -This version fixes some formatting bugs with help-all. It also adds fixes for several warnings, including an experimental optional error on Clang 7. Several smaller fixes. - -* Fixed help-all formatting [#163] - * Printing help-all on nested command now fixed (App) - * Missing space after help-all restored (Default formatter) - * More detail printed on help all (Default formatter) - * Help-all subcommands get indented with inner blank lines removed (Default formatter) - * `detail::find_and_replace` added to utilities -* Fixed CMake install as subproject with `CLI11_INSTALL` flag. [#156] -* Fixed warning about local variable hiding class member with MSVC [#157] -* Fixed compile error with default settings on Clang 7 and libc++ [#158] -* Fixed special case of `--help` on subcommands (general fix planned for 1.7) [#168] -* Removing an option with links [#179] - -[#156]: https://github.com/CLIUtils/CLI11/issues/156 -[#157]: https://github.com/CLIUtils/CLI11/issues/157 -[#158]: https://github.com/CLIUtils/CLI11/issues/158 -[#163]: https://github.com/CLIUtils/CLI11/pull/163 -[#168]: https://github.com/CLIUtils/CLI11/issues/168 -[#179]: https://github.com/CLIUtils/CLI11/pull/179 - +### Version 1.7.1: Quick patch -## Version 1.6.1: Platform fixes - -This version provides a few fixes for special cases, such as mixing with `Windows.h` and better defaults -for systems like Hunter. The one new feature is the ability to produce "branded" single file output for -providing custom namespaces or custom macro names. +This version provides a quick patch for a (correct) warning from GCC 8 for the windows options code. -* Added fix and test for including Windows.h [#145] -* No longer build single file by default if main project, supports systems stuck on Python 2.6 [#149], [#151] -* Branding support for single file output [#150] +* Fix for Windows style option parsing [#201][] +* Improve `add_subcommand` when throwing an exception [#204][] +* Better metadata for Conan package [#202][] -[#145]: https://github.com/CLIUtils/CLI11/pull/145 -[#149]: https://github.com/CLIUtils/CLI11/pull/149 -[#150]: https://github.com/CLIUtils/CLI11/pull/150 -[#151]: https://github.com/CLIUtils/CLI11/pull/151 +[#201]: https://github.com/CLIUtils/CLI11/pull/201 +[#202]: https://github.com/CLIUtils/CLI11/pull/202 +[#204]: https://github.com/CLIUtils/CLI11/pull/204 ## Version 1.6: Formatting help -Added a new formatting system [#109]. You can now set the formatter on Apps. This has also simplified the internals of Apps and Options a bit by separating most formatting code. +Added a new formatting system [#109][]. You can now set the formatter on Apps. This has also simplified the internals of Apps and Options a bit by separating most formatting code. * Added `CLI::Formatter` and `formatter` slot for apps, inherited. * `FormatterBase` is the minimum required. @@ -317,10 +329,9 @@ Changes to the help system (most normal users will not notice this): * Protected function `_has_help_positional` removed. * `format_help` can now be chained. * Added getters for the missing parts of options (help no longer uses any private parts). -* Help flags now use new `short_circuit` property to simplify parsing. [#121] +* Help flags now use new `short_circuit` property to simplify parsing. [#121][] - -New for Config file reading and writing [#121]: +New for Config file reading and writing [#121][]: * Overridable, bidirectional Config. * ConfigINI provided and used by default. @@ -328,10 +339,9 @@ New for Config file reading and writing [#121]: * Has `config_formatter()` and `get_config_formatter()`. * Dropped prefix argument from `config_to_str`. * Added `ConfigItem`. -* Added an example of a custom config format using [nlohmann/json]. [#138] - +* Added an example of a custom config format using [nlohmann/json][]. [#138][] -Validators are now much more powerful [#118], all built in validators upgraded to the new form: +Validators are now much more powerful [#118][], all built in validators upgraded to the new form: * A subclass of `CLI::Validator` is now also accepted. * They now can set the type name to things like `PATH` and `INT in [1-4]`. @@ -340,29 +350,29 @@ Validators are now much more powerful [#118], all built in validators upgraded t Other changes: -* Fixing `parse(args)`'s `args` setting and ordering after parse. [#141] -* Replaced `set_custom_option` with `type_name` and `type_size` instead of `set_custom_option`. Methods return `this`. [#136] -* Dropped `set_` on Option's `type_name`, `default_str`, and `default_val`. [#136] -* Removed `set_` from App's `failure_message`, `footer`, `callback`, and `name`. [#136] -* Fixed support `N<-1` for `type_size`. [#140] -* Added `->each()` to make adding custom callbacks easier. [#126] -* Allow empty options `add_option("-n",{})` to be edited later with `each` [#142] +* Fixing `parse(args)`'s `args` setting and ordering after parse. [#141][] +* Replaced `set_custom_option` with `type_name` and `type_size` instead of `set_custom_option`. Methods return `this`. [#136][] +* Dropped `set_` on Option's `type_name`, `default_str`, and `default_val`. [#136][] +* Removed `set_` from App's `failure_message`, `footer`, `callback`, and `name`. [#136][] +* Fixed support `N<-1` for `type_size`. [#140][] +* Added `->each()` to make adding custom callbacks easier. [#126][] +* Allow empty options `add_option("-n",{})` to be edited later with `each` [#142][] * Added filter argument to `get_subcommands`, `get_options`; use empty filter `{}` to avoid filtering. * Added `get_groups()` to get groups. -* Better support for manual options with `get_option`, `set_results`, and `empty`. [#119] -* `lname` and `sname` have getters, added `const get_parent`. [#120] -* Using `add_set` will now capture L-values for sets, allowing further modification. [#113] +* Better support for manual options with `get_option`, `set_results`, and `empty`. [#119][] +* `lname` and `sname` have getters, added `const get_parent`. [#120][] +* Using `add_set` will now capture L-values for sets, allowing further modification. [#113][] * Dropped duplicate way to run `get_type_name` (`get_typeval`). -* Removed `requires` in favor of `needs` (deprecated in last version). [#112] -* Const added to argv. [#126] +* Removed `requires` in favor of `needs` (deprecated in last version). [#112][] +* Const added to argv. [#126][] Backend and testing changes: -* Internally, `type_name` is now a lambda function; for sets, this reads the set live. [#116] -* Cleaner tests without `app.reset()` (and `reset` is now `clear`). [#141] -* Better CMake policy handling. [#110] -* Includes are properly sorted. [#120] -* Testing (only) now uses submodules. [#111] +* Internally, `type_name` is now a lambda function; for sets, this reads the set live. [#116][] +* Cleaner tests without `app.reset()` (and `reset` is now `clear`). [#141][] +* Better CMake policy handling. [#110][] +* Includes are properly sorted. [#120][] +* Testing (only) now uses submodules. [#111][] [#109]: https://github.com/CLIUtils/CLI11/pull/109 [#110]: https://github.com/CLIUtils/CLI11/pull/110 @@ -375,7 +385,7 @@ Backend and testing changes: [#120]: https://github.com/CLIUtils/CLI11/pull/120 [#121]: https://github.com/CLIUtils/CLI11/pull/121 [#126]: https://github.com/CLIUtils/CLI11/pull/126 -[#127]: https://github.com/CLIUtils/CLI11/pull/127 +[#136]: https://github.com/CLIUtils/CLI11/pull/136 [#138]: https://github.com/CLIUtils/CLI11/pull/138 [#140]: https://github.com/CLIUtils/CLI11/pull/140 [#141]: https://github.com/CLIUtils/CLI11/pull/141 @@ -383,33 +393,43 @@ Backend and testing changes: [nlohmann/json]: https://github.com/nlohmann/json -### Version 1.5.4: Optionals -This version fixes the optional search in the single file version; some macros were not yet defined when it did the search. You can define the `CLI11_*_OPTIONAL` macros to 0 if needed to eliminate the search. - -### Version 1.5.3: Compiler compatibility -This version fixes older AppleClang compilers by removing the optimization for casting. The minimum version of Boost Optional supported has been clarified to be 1.58. CUDA 7.0 NVCC is now supported. +### Version 1.6.1: Platform fixes -### Version 1.5.2: LICENSE in single header mode +This version provides a few fixes for special cases, such as mixing with `Windows.h` and better defaults +for systems like Hunter. The one new feature is the ability to produce "branded" single file output for +providing custom namespaces or custom macro names. -This is a quick patch release that makes LICENSE part of the single header file, making it easier to include. Minor cleanup from codacy. No significant code changes from 1.5.1. +* Added fix and test for including Windows.h [#145][] +* No longer build single file by default if main project, supports systems stuck on Python 2.6 [#149][], [#151][] +* Branding support for single file output [#150][] -### Version 1.5.1: Access +[#145]: https://github.com/CLIUtils/CLI11/pull/145 +[#149]: https://github.com/CLIUtils/CLI11/pull/149 +[#150]: https://github.com/CLIUtils/CLI11/pull/150 +[#151]: https://github.com/CLIUtils/CLI11/pull/151 -This patch release adds better access to the App programmatically, to assist with writing custom converters to other formats. It also improves the help output, and uses a new feature in CLI11 1.5 to fix an old "quirk" in the way unlimited options and positionals interact. +### Version 1.6.2: Help-all -* Make mixing unlimited positionals and options more intuitive [#102] -* Add missing getters `get_options` and `get_description` to App [#105] -* The app name now can be set, and will override the auto name if present [#105] -* Add `(REQUIRED)` for required options [#104] -* Print simple name for Needs/Excludes [#104] -* Use Needs instead of Requires in help print [#104] -* Groups now are listed in the original definition order [#106] +This version fixes some formatting bugs with help-all. It also adds fixes for several warnings, including an experimental optional error on Clang 7. Several smaller fixes. -[#102]: https://github.com/CLIUtils/CLI11/issues/102 -[#104]: https://github.com/CLIUtils/CLI11/pull/104 -[#105]: https://github.com/CLIUtils/CLI11/pull/105 -[#106]: https://github.com/CLIUtils/CLI11/pull/106 +* Fixed help-all formatting [#163][] + * Printing help-all on nested command now fixed (App) + * Missing space after help-all restored (Default formatter) + * More detail printed on help all (Default formatter) + * Help-all subcommands get indented with inner blank lines removed (Default formatter) + * `detail::find_and_replace` added to utilities +* Fixed CMake install as subproject with `CLI11_INSTALL` flag. [#156][] +* Fixed warning about local variable hiding class member with MSVC [#157][] +* Fixed compile error with default settings on Clang 7 and libc++ [#158][] +* Fixed special case of `--help` on subcommands (general fix planned for 1.7) [#168][] +* Removing an option with links [#179][] +[#156]: https://github.com/CLIUtils/CLI11/issues/156 +[#157]: https://github.com/CLIUtils/CLI11/issues/157 +[#158]: https://github.com/CLIUtils/CLI11/issues/158 +[#163]: https://github.com/CLIUtils/CLI11/pull/163 +[#168]: https://github.com/CLIUtils/CLI11/issues/168 +[#179]: https://github.com/CLIUtils/CLI11/pull/179 ## Version 1.5: Optionals @@ -417,24 +437,24 @@ This version introduced support for optionals, along with clarification and exam Note: This is the final release with `requires`, please switch to `needs`. -* Fix unlimited short options eating two values before checking for positionals when no space present [#90] -* Symmetric exclude text when excluding options, exclude can be called multiple times [#64] -* Support for `std::optional`, `std::experimental::optional`, and `boost::optional` added if `__has_include` is supported [#95] -* All macros/CMake variables now start with `CLI11_` instead of just `CLI_` [#95] -* The internal stream was not being cleared before use in some cases. Fixed. [#95] -* Using an enum now requires explicit conversion overload [#97] -* The separator `--` now is removed when it ends unlimited arguments [#100] +* Fix unlimited short options eating two values before checking for positionals when no space present [#90][] +* Symmetric exclude text when excluding options, exclude can be called multiple times [#64][] +* Support for `std::optional`, `std::experimental::optional`, and `boost::optional` added if `__has_include` is supported [#95][] +* All macros/CMake variables now start with `CLI11_` instead of just `CLI_` [#95][] +* The internal stream was not being cleared before use in some cases. Fixed. [#95][] +* Using an enum now requires explicit conversion overload [#97][] +* The separator `--` now is removed when it ends unlimited arguments [#100][] Other, non-user facing changes: -* Added `Macros.hpp` with better C++ mode discovery [#95] +* Added `Macros.hpp` with better C++ mode discovery [#95][] * Deprecated macros added for all platforms -* C++17 is now tested on supported platforms [#95] -* Informational printout now added to CTest [#95] -* Better single file generation [#95] +* C++17 is now tested on supported platforms [#95][] +* Informational printout now added to CTest [#95][] +* Better single file generation [#95][] * Added support for GTest on MSVC 2017 (but not in C++17 mode, will need next version of GTest) -* Types now have a specific size, separate from the expected number - cleaner and more powerful internally [#92] -* Examples now run as part of testing [#99] +* Types now have a specific size, separate from the expected number - cleaner and more powerful internally [#92][] +* Examples now run as part of testing [#99][] [#64]: https://github.com/CLIUtils/CLI11/issues/64 [#90]: https://github.com/CLIUtils/CLI11/issues/90 @@ -444,22 +464,50 @@ Other, non-user facing changes: [#99]: https://github.com/CLIUtils/CLI11/pull/99 [#100]: https://github.com/CLIUtils/CLI11/pull/100 +### Version 1.5.1: Access + +This patch release adds better access to the App programmatically, to assist with writing custom converters to other formats. It also improves the help output, and uses a new feature in CLI11 1.5 to fix an old "quirk" in the way unlimited options and positionals interact. + +* Make mixing unlimited positionals and options more intuitive [#102][] +* Add missing getters `get_options` and `get_description` to App [#105][] +* The app name now can be set, and will override the auto name if present [#105][] +* Add `(REQUIRED)` for required options [#104][] +* Print simple name for Needs/Excludes [#104][] +* Use Needs instead of Requires in help print [#104][] +* Groups now are listed in the original definition order [#106][] + +[#102]: https://github.com/CLIUtils/CLI11/issues/102 +[#104]: https://github.com/CLIUtils/CLI11/pull/104 +[#105]: https://github.com/CLIUtils/CLI11/pull/105 +[#106]: https://github.com/CLIUtils/CLI11/pull/106 + +### Version 1.5.2: LICENSE in single header mode + +This is a quick patch release that makes LICENSE part of the single header file, making it easier to include. Minor cleanup from codacy. No significant code changes from 1.5.1. + +### Version 1.5.3: Compiler compatibility + +This version fixes older AppleClang compilers by removing the optimization for casting. The minimum version of Boost Optional supported has been clarified to be 1.58. CUDA 7.0 NVCC is now supported. + +### Version 1.5.4: Optionals + +This version fixes the optional search in the single file version; some macros were not yet defined when it did the search. You can define the `CLI11_*_OPTIONAL` macros to 0 if needed to eliminate the search. ## Version 1.4: More feedback This version adds lots of smaller fixes and additions after the refactor in version 1.3. More ways to download and use CLI11 in CMake have been added. INI files have improved support. -* Lexical cast is now more strict than before [#68] and fails on overflow [#84] +* Lexical cast is now more strict than before [#68][] and fails on overflow [#84][] * Added `get_parent()` to access the parent from a subcommand -* Added `ExistingPath` validator [#73] -* `app.allow_ini_extras()` added to allow extras in INI files [#70] +* Added `ExistingPath` validator [#73][] +* `app.allow_ini_extras()` added to allow extras in INI files [#70][] * Multiline INI comments now supported -* Descriptions can now be written with `config_to_str` [#66] -* Double printing of error message fixed [#77] -* Renamed `requires` to `needs` to avoid C++20 keyword [#75], [#82] -* MakeSingleHeader now works if outside of git [#78] -* Adding install support for CMake [#79], improved support for `find_package` [#83], [#84] -* Added support for Conan.io [#83] +* Descriptions can now be written with `config_to_str` [#66][] +* Double printing of error message fixed [#77][] +* Renamed `requires` to `needs` to avoid C++20 keyword [#75][], [#82][] +* MakeSingleHeader now works if outside of git [#78][] +* Adding install support for CMake [#79][], improved support for `find_package` [#83][], [#84][] +* Added support for Conan.io [#83][] [#70]: https://github.com/CLIUtils/CLI11/issues/70 [#75]: https://github.com/CLIUtils/CLI11/issues/75 @@ -509,7 +557,7 @@ favorite CLI programs. Error messages and help messages are better and more flex * Single internal arg parse function [#56](https://github.com/CLIUtils/CLI11/pull/56) * Allow options to be disabled from INI file, rename `add_config` to `set_config` [#60](https://github.com/CLIUtils/CLI11/pull/60) -> ### Converting from CLI11 1.2: +> ### Converting from CLI11 1.2 > > * `app.parse` no longer returns a vector. Instead, use `app.remaining(true)`. > * `"hidden"` is no longer a special group name, instead use `""` diff --git a/packages/CLI11/CLI11.CPack.Description.txt b/packages/CLI11/CLI11.CPack.Description.txt index 1fd074a4b86d84a8d59234648edc8c75533dfaa4..9a018919cf715d4f02e8fa52be4b7ebace6ee202 100644 --- a/packages/CLI11/CLI11.CPack.Description.txt +++ b/packages/CLI11/CLI11.CPack.Description.txt @@ -1,2 +1 @@ CLI11 provides all the features you expect in a powerful command line parser, with a beautiful, minimal syntax and no dependencies beyond C++11. It is header only, and comes in a single file form for easy inclusion in projects. It is easy to use for small projects, but powerful enough for complex command line projects, and can be customized for frameworks. - diff --git a/packages/CLI11/CLI11.hpp.in b/packages/CLI11/CLI11.hpp.in index 8de9c532e300e14edf6c66550eea60431d724b1f..9439a4300340b110681be4d38e05da15451e7bf7 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-2020 University of Cincinnati, developed by Henry +// CLI11 {version} Copyright (c) 2017-2021 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 @@ -31,6 +31,8 @@ // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +#pragma once + // Standard combined includes: {public_includes} diff --git a/packages/CLI11/CMakeLists.txt b/packages/CLI11/CMakeLists.txt index ee0228ee5b20206c523c09325e1f8a3921e397b8..3227e409d9e1d982f4ffd3a58ae9e99d297663b4 100644 --- a/packages/CLI11/CMakeLists.txt +++ b/packages/CLI11/CMakeLists.txt @@ -2,31 +2,34 @@ 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.16) version +# Make sure users don't get warnings on a tested (3.4 to 3.21) version # of CMake. For most of the policies, the new version is better (hence the change). -# We don't use the 3.4...3.17 syntax because of a bug in an older MSVC's +# We don't use the 3.4...3.21 syntax because of a bug in an older MSVC's # built-in and modified CMake 3.11 -if(${CMAKE_VERSION} VERSION_LESS 3.17) - cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}) +if(${CMAKE_VERSION} VERSION_LESS 3.21) + cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}) else() - cmake_policy(VERSION 3.17) + cmake_policy(VERSION 3.21) endif() set(VERSION_REGEX "#define CLI11_VERSION[ \t]+\"(.+)\"") # Read in the line containing the version -file(STRINGS "${CMAKE_CURRENT_SOURCE_DIR}/include/CLI/Version.hpp" - VERSION_STRING REGEX ${VERSION_REGEX}) +file(STRINGS "${CMAKE_CURRENT_SOURCE_DIR}/include/CLI/Version.hpp" VERSION_STRING + REGEX ${VERSION_REGEX}) # Pick out just the version string(REGEX REPLACE ${VERSION_REGEX} "\\1" VERSION_STRING "${VERSION_STRING}") # Add the project -project(CLI11 LANGUAGES CXX VERSION ${VERSION_STRING}) +project( + CLI11 + LANGUAGES CXX + VERSION ${VERSION_STRING}) # Print the version number of CMake if this is the main project if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME) - message(STATUS "CMake ${CMAKE_VERSION}") + message(STATUS "CMake ${CMAKE_VERSION}") endif() include(CMakeDependentOption) @@ -34,7 +37,7 @@ include(GNUInstallDirs) include(CTest) if(NOT CMAKE_VERSION VERSION_LESS 3.11) - include(FetchContent) + include(FetchContent) endif() find_package(Doxygen) @@ -49,83 +52,76 @@ list(APPEND build-docs "Doxygen_FOUND") # Necessary to support paths with spaces, see #457 if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/docs") - set(docs_EXIST TRUE) + set(docs_EXIST TRUE) else() - set(docs_EXIST FALSE) + set(docs_EXIST FALSE) endif() list(APPEND build-docs "docs_EXIST") if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/examples") - set(examples_EXIST TRUE) + set(examples_EXIST TRUE) else() - set(examples_EXIST FALSE) + set(examples_EXIST FALSE) endif() option(CLI11_WARNINGS_AS_ERRORS "Turn all warnings into errors (for CI)") option(CLI11_SINGLE_FILE "Generate a single header file") -cmake_dependent_option(CLI11_SANITIZERS - "Download the sanitizers CMake config" OFF - "NOT CMAKE_VERSION VERSION_LESS 3.11" OFF) +cmake_dependent_option(CLI11_SANITIZERS "Download the sanitizers CMake config" OFF + "NOT CMAKE_VERSION VERSION_LESS 3.11" OFF) -cmake_dependent_option(CLI11_BUILD_DOCS - "Build CLI11 documentation" ON - "${build-docs}" OFF) +cmake_dependent_option(CLI11_BUILD_DOCS "Build CLI11 documentation" ON "${build-docs}" OFF) -cmake_dependent_option(CLI11_BUILD_TESTS - "Build CLI11 tests" ON - "BUILD_TESTING;CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME" OFF) +cmake_dependent_option(CLI11_BUILD_TESTS "Build CLI11 tests" ON + "BUILD_TESTING;CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME" OFF) -cmake_dependent_option(CLI11_BUILD_EXAMPLES - "Build CLI11 examples" ON - "CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME;${examples_EXIST}" OFF) +cmake_dependent_option(CLI11_BUILD_EXAMPLES "Build CLI11 examples" ON + "CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME;${examples_EXIST}" OFF) -cmake_dependent_option(CLI11_BUILD_EXAMPLES_JSON - "Build CLI11 json example" OFF - "CLI11_BUILD_EXAMPLES;NOT CMAKE_VERSION VERSION_LESS 3.11" OFF) +cmake_dependent_option(CLI11_BUILD_EXAMPLES_JSON "Build CLI11 json example" OFF + "CLI11_BUILD_EXAMPLES;NOT CMAKE_VERSION VERSION_LESS 3.11" OFF) -cmake_dependent_option(CLI11_SINGLE_FILE_TESTS - "Duplicate all the tests for a single file build" OFF - "BUILD_TESTING;CLI11_SINGLE_FILE" OFF) +cmake_dependent_option(CLI11_SINGLE_FILE_TESTS "Duplicate all the tests for a single file build" + OFF "BUILD_TESTING;CLI11_SINGLE_FILE" OFF) -cmake_dependent_option(CLI11_INSTALL - "Install the CLI11 folder to include during install process" ON - "CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME" OFF) +cmake_dependent_option(CLI11_INSTALL "Install the CLI11 folder to include during install process" + ON "CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME" OFF) -cmake_dependent_option(CLI11_FORCE_LIBCXX - "Force clang to use libc++ instead of libstdc++ (Linux only)" OFF - "${force-libcxx}" OFF) +cmake_dependent_option( + CLI11_FORCE_LIBCXX "Force clang to use libc++ instead of libstdc++ (Linux only)" OFF + "${force-libcxx}" OFF) -cmake_dependent_option(CLI11_CUDA_TESTS - "Build the tests with NVCC to check for warnings there - requires CMake 3.9+" OFF - "CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME" OFF) +cmake_dependent_option( + CLI11_CUDA_TESTS "Build the tests with NVCC to check for warnings there - requires CMake 3.9+" + OFF "CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME" OFF) -cmake_dependent_option(CLI11_CLANG_TIDY - "Look for and use Clang-Tidy" OFF - "CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME;NOT CMAKE_VERSION VERSION_LESS 3.6" OFF) -set(CLI11_CLANG_TIDY_OPTIONS "" CACHE STRING "Clang tidy options, such as -fix, semicolon separated") +cmake_dependent_option( + CLI11_CLANG_TIDY "Look for and use Clang-Tidy" OFF + "CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME;NOT CMAKE_VERSION VERSION_LESS 3.6" OFF) +set(CLI11_CLANG_TIDY_OPTIONS + "" + CACHE STRING "Clang tidy options, such as -fix, semicolon separated") if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME AND NOT DEFINED CMAKE_CXX_STANDARD) - set(CMAKE_CXX_STANDARD 11) + set(CMAKE_CXX_STANDARD 11) endif() if(NOT DEFINED CMAKE_CXX_EXTENSIONS) - set(CMAKE_CXX_EXTENSIONS OFF) + set(CMAKE_CXX_EXTENSIONS OFF) endif() if(NOT DEFINED CMAKE_CXX_STANDARD_REQUIRED) - set(CMAKE_CXX_STANDARD_REQUIRED ON) + set(CMAKE_CXX_STANDARD_REQUIRED ON) endif() - # Allow IDE's to group targets into folders if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME) - set_property(GLOBAL PROPERTY USE_FOLDERS ON) + set_property(GLOBAL PROPERTY USE_FOLDERS ON) endif() if(CMAKE_VERSION VERSION_LESS 3.10) - message(STATUS "CMake 3.10+ adds Doxygen support. Update CMake to build documentation") + message(STATUS "CMake 3.10+ adds Doxygen support. Update CMake to build documentation") elseif(NOT Doxygen_FOUND) - message(STATUS "Doxygen not found, building docs has been disabled") + message(STATUS "Doxygen not found, building docs has been disabled") endif() # Special target that adds warnings. Is not exported. @@ -135,140 +131,128 @@ set(unix-warnings -Wall -Wextra -pedantic -Wshadow -Wsign-conversion -Wswitch-en # Buggy in GCC 4.8 if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 4.9) - list(APPEND unix-warnings -Weffc++) + list(APPEND unix-warnings -Weffc++) endif() -target_compile_options(CLI11_warnings - INTERFACE - $<$<BOOL:${CLI11_FORCE_LIBCXX}>:-stdlib=libc++> - $<$<CXX_COMPILER_ID:MSVC>:/W4 $<$<BOOL:${CLI11_WARNINGS_AS_ERRORS}>:/WX>> - $<$<NOT:$<CXX_COMPILER_ID:MSVC>>:${unix-warnings} $<$<BOOL:${CLI11_WARNINGS_AS_ERRORS}>:-Werror>>) - - - if(NOT CMAKE_VERSION VERSION_LESS 3.13) - target_link_options(CLI11_warnings - INTERFACE - $<$<BOOL:${CLI11_FORCE_LIBCXX}>:-stdlib=libc++>) - endif() +target_compile_options( + CLI11_warnings + INTERFACE $<$<BOOL:${CLI11_FORCE_LIBCXX}>:-stdlib=libc++> + $<$<CXX_COMPILER_ID:MSVC>:/W4 + $<$<BOOL:${CLI11_WARNINGS_AS_ERRORS}>:/WX>> + $<$<NOT:$<CXX_COMPILER_ID:MSVC>>:${unix-warnings} + $<$<BOOL:${CLI11_WARNINGS_AS_ERRORS}>:-Werror>>) +if(NOT CMAKE_VERSION VERSION_LESS 3.13) + target_link_options(CLI11_warnings INTERFACE $<$<BOOL:${CLI11_FORCE_LIBCXX}>:-stdlib=libc++>) +endif() # Allow IDE's to group targets into folders add_library(CLI11 INTERFACE) add_library(CLI11::CLI11 ALIAS CLI11) # for add_subdirectory calls # Duplicated because CMake adds the current source dir if you don't. -target_include_directories(CLI11 INTERFACE - $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include> - $<INSTALL_INTERFACE:include>) - +target_include_directories(CLI11 INTERFACE $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include> + $<INSTALL_INTERFACE:include>) # To see in IDE, headers must be listed for target set(header-patterns "${PROJECT_SOURCE_DIR}/include/CLI/*") -if(NOT CMAKE_VERSION VERSION_LESS 3.12) +if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME AND NOT CMAKE_VERSION VERSION_LESS 3.12) list(INSERT header-patterns 0 CONFIGURE_DEPENDS) endif() file(GLOB CLI11_headers ${header-patterns}) # Allow tests to be run on CUDA - if(CLI11_CUDA_TESTS) - enable_language(CUDA) - - # Print out warning and error numbers - set(CMAKE_CUDA_FLAGS "${CMAKE_CUDA_FLAGS} -Xcudafe --display_error_number") - endif() +if(CLI11_CUDA_TESTS) + enable_language(CUDA) + # Print out warning and error numbers + set(CMAKE_CUDA_FLAGS "${CMAKE_CUDA_FLAGS} -Xcudafe --display_error_number") +endif() # Prepare Clang-Tidy if(CLI11_CLANG_TIDY) - find_program( - CLANG_TIDY_EXE - NAMES "clang-tidy" - DOC "Path to clang-tidy executable" - REQUIRED - ) - - set(DO_CLANG_TIDY "${CLANG_TIDY_EXE}" ${CLI11_CLANG_TIDY_OPTIONS}) -endif() + find_program( + CLANG_TIDY_EXE + NAMES "clang-tidy" + DOC "Path to clang-tidy executable" REQUIRED) + set(DO_CLANG_TIDY "${CLANG_TIDY_EXE}" ${CLI11_CLANG_TIDY_OPTIONS}) +endif() # This folder should be installed if(CLI11_INSTALL) - install(DIRECTORY "${PROJECT_SOURCE_DIR}/include/" - DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}") + install(DIRECTORY "${PROJECT_SOURCE_DIR}/include/" DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}") - # Make an export target - install(TARGETS CLI11 EXPORT CLI11Targets) + # Make an export target + install(TARGETS CLI11 EXPORT CLI11Targets) - # Use find_package on the installed package - # Since we have no custom code, we can directly write this - # to Config.cmake (otherwise we'd have a custom config and would - # import Targets.cmake + # Use find_package on the installed package + # Since we have no custom code, we can directly write this + # to Config.cmake (otherwise we'd have a custom config and would + # import Targets.cmake - # Add the version in a CMake readable way - configure_file("cmake/CLI11ConfigVersion.cmake.in" - "CLI11ConfigVersion.cmake" @ONLY) + # Add the version in a CMake readable way + configure_file("cmake/CLI11ConfigVersion.cmake.in" "CLI11ConfigVersion.cmake" @ONLY) - # Make version available in the install - install(FILES "${PROJECT_BINARY_DIR}/CLI11ConfigVersion.cmake" - DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/CLI11") + # Make version available in the install + install(FILES "${PROJECT_BINARY_DIR}/CLI11ConfigVersion.cmake" + DESTINATION "${CMAKE_INSTALL_DATADIR}/cmake/CLI11") - # Install the export target as a file - install(EXPORT CLI11Targets - FILE CLI11Config.cmake - NAMESPACE CLI11:: - DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/CLI11") + # Install the export target as a file + install( + EXPORT CLI11Targets + FILE CLI11Config.cmake + NAMESPACE CLI11:: + DESTINATION "${CMAKE_INSTALL_DATADIR}/cmake/CLI11") - # Use find_package on the installed package - export(TARGETS CLI11 - NAMESPACE CLI11:: - FILE CLI11Targets.cmake) + # Use find_package on the installed package + export( + TARGETS CLI11 + NAMESPACE CLI11:: + FILE CLI11Targets.cmake) - include(cmake/CLI11GeneratePkgConfig.cmake) + include(cmake/CLI11GeneratePkgConfig.cmake) - # Register in the user cmake package registry - export(PACKAGE CLI11) + # Register in the user cmake package registry + export(PACKAGE CLI11) endif() if(CLI11_SINGLE_FILE) - # Single file test - if(CMAKE_VERSION VERSION_LESS 3.12) - find_package(PythonInterp REQUIRED) - add_executable(Python::Interpreter IMPORTED) - set_target_properties(Python::Interpreter - PROPERTIES - IMPORTED_LOCATION "${PYTHON_EXECUTABLE}" - VERSION "${PYTHON_VERSION_STRING}") - else() - find_package(Python COMPONENTS Interpreter REQUIRED) - endif() - - file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/include") - add_custom_command(OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/include/CLI11.hpp" - COMMAND Python::Interpreter - "${CMAKE_CURRENT_SOURCE_DIR}/scripts/MakeSingleHeader.py" - ${CLI11_headers} - --main "${CMAKE_CURRENT_SOURCE_DIR}/CLI11.hpp.in" - --output "${CMAKE_CURRENT_BINARY_DIR}/include/CLI11.hpp" - --version "${CLI11_VERSION}" - DEPENDS - "${CMAKE_CURRENT_SOURCE_DIR}/include/CLI/CLI.hpp" - ${CLI11_headers}) - add_custom_target(CLI11-generate-single-file ALL - DEPENDS "${CMAKE_CURRENT_BINARY_DIR}/include/CLI11.hpp") - set_property(TARGET CLI11-generate-single-file PROPERTY FOLDER "Scripts") - install(FILES "${CMAKE_CURRENT_BINARY_DIR}/include/CLI11.hpp" - DESTINATION include) - add_library(CLI11_SINGLE INTERFACE) - target_link_libraries(CLI11_SINGLE INTERFACE CLI11) - add_dependencies(CLI11_SINGLE CLI11-generate-single-file) - target_compile_definitions(CLI11_SINGLE INTERFACE -DCLI11_SINGLE_FILE) - target_include_directories(CLI11_SINGLE INTERFACE - $<BUILD_INTERFACE:${PROJECT_BINARY_DIR}/include/> - $<INSTALL_INTERFACE:include>) + # Single file test + if(CMAKE_VERSION VERSION_LESS 3.12) + find_package(PythonInterp REQUIRED) + add_executable(Python::Interpreter IMPORTED) + set_target_properties(Python::Interpreter PROPERTIES IMPORTED_LOCATION "${PYTHON_EXECUTABLE}" + VERSION "${PYTHON_VERSION_STRING}") + else() + find_package( + Python + COMPONENTS Interpreter + REQUIRED) + endif() + + file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/include") + add_custom_command( + OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/include/CLI11.hpp" + COMMAND + Python::Interpreter "${CMAKE_CURRENT_SOURCE_DIR}/scripts/MakeSingleHeader.py" + ${CLI11_headers} --main "${CMAKE_CURRENT_SOURCE_DIR}/CLI11.hpp.in" --output + "${CMAKE_CURRENT_BINARY_DIR}/include/CLI11.hpp" --version "${CLI11_VERSION}" + DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/include/CLI/CLI.hpp" ${CLI11_headers}) + add_custom_target(CLI11-generate-single-file ALL + DEPENDS "${CMAKE_CURRENT_BINARY_DIR}/include/CLI11.hpp") + set_property(TARGET CLI11-generate-single-file PROPERTY FOLDER "Scripts") + install(FILES "${CMAKE_CURRENT_BINARY_DIR}/include/CLI11.hpp" DESTINATION include) + add_library(CLI11_SINGLE INTERFACE) + target_link_libraries(CLI11_SINGLE INTERFACE CLI11) + add_dependencies(CLI11_SINGLE CLI11-generate-single-file) + target_compile_definitions(CLI11_SINGLE INTERFACE -DCLI11_SINGLE_FILE) + target_include_directories( + CLI11_SINGLE INTERFACE $<BUILD_INTERFACE:${PROJECT_BINARY_DIR}/include/> + $<INSTALL_INTERFACE:include>) endif() - if(CLI11_BUILD_TESTS) add_subdirectory(tests) endif() @@ -283,7 +267,7 @@ endif() # From a build system, this might not be included. if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/book") - add_subdirectory(book) + add_subdirectory(book) endif() # Packaging support @@ -316,12 +300,10 @@ set(CPACK_SOURCE_IGNORE_FILES .swp /.all-contributorsrc /.appveyor.yml - /.pre-commit.*yaml -) + /.pre-commit.*yaml) set(CPACK_DEBIAN_PACKAGE_ARCHITECTURE "all") set(CPACK_DEBIAN_COMPRESSION_TYPE "xz") set(CPACK_DEBIAN_PACKAGE_NAME "libcli11-dev") include(CPack) - diff --git a/packages/CLI11/CPPLINT.cfg b/packages/CLI11/CPPLINT.cfg index 0a1758da0e5b703d4677bcaf60d171455c4aee66..24dd86524af1957a994dba4fe53b65f4406d20ad 100644 --- a/packages/CLI11/CPPLINT.cfg +++ b/packages/CLI11/CPPLINT.cfg @@ -11,4 +11,3 @@ filter=-runtime/references # Requires fundamental change of API, don't see need filter=-whitespace/blank_line # Unnecessarily strict with blank lines that otherwise help with readability filter=-whitespace/indent # Requires strange 3-space indent of private/protected/public markers filter=-whitespace/parens,-whitespace/braces # Conflict with clang-format - diff --git a/packages/CLI11/README.md b/packages/CLI11/README.md index 846d44ec7310e567fa32897088c03d659d1e3b56..229b65451b0665a5ae4ae8250be454818f025251 100644 --- a/packages/CLI11/README.md +++ b/packages/CLI11/README.md @@ -8,12 +8,13 @@ [![Actions Status][actions-badge]][actions-link] [![Code Coverage][codecov-badge]][codecov] [![Codacy Badge][codacy-badge]][codacy-link] -[![Join the chat at https://gitter.im/CLI11gitter/Lobby][gitter-badge]][gitter] +[![Gitter chat][gitter-badge]][gitter] [![License: BSD][license-badge]](./LICENSE) [![Latest release][releases-badge]][github releases] [![DOI][doi-badge]][doi-link] [![Conan.io][conan-badge]][conan-link] -[![Try CLI11 1.9 online][wandbox-badge]][wandbox-link] +[![Conda Version][conda-badge]][conda-link] +[![Try CLI11 2.0 online][wandbox-badge]][wandbox-link] [What's new](./CHANGELOG.md) • [Documentation][gitbook] • @@ -23,38 +24,38 @@ CLI11 is a command line parser for C++11 and beyond that provides a rich feature ## Table of Contents -- [Background](#background) - - [Introduction](#introduction) - - [Why write another CLI parser?](#why-write-another-cli-parser) - - [Other parsers](#other-parsers) - - [Features not supported by this library](#features-not-supported-by-this-library) -- [Install](#install) -- [Usage](#usage) - - [Adding options](#adding-options) - - [Option types](#option-types) - - [Example](#example) - - [Option options](#option-options) - - [Validators](#validators) - - [Transforming Validators](#transforming-validators) - - [Validator operations](#validator-operations) - - [Custom Validators](#custom-validators) - - [Querying Validators](#querying-validators) - - [Getting Results](#getting-results) - - [Subcommands](#subcommands) - - [Subcommand options](#subcommand-options) - - [Option groups](#option-groups) - - [Callbacks](#callbacks) - - [Configuration file](#configuration-file) - - [Inheriting defaults](#inheriting-defaults) - - [Formatting](#formatting) - - [Subclassing](#subclassing) - - [How it works](#how-it-works) - - [Utilities](#utilities) - - [Other libraries](#other-libraries) -- [API](#api) -- [Examples](#Examples) -- [Contribute](#contribute) -- [License](#license) +* [Background](#background) + * [Introduction](#introduction) + * [Why write another CLI parser?](#why-write-another-cli-parser) + * [Other parsers](#other-parsers) + * [Features not supported by this library](#features-not-supported-by-this-library) +* [Install](#install) +* [Usage](#usage) + * [Adding options](#adding-options) + * [Option types](#option-types) + * [Example](#example) + * [Option options](#option-options) + * [Validators](#validators) + * [Transforming Validators](#transforming-validators) + * [Validator operations](#validator-operations) + * [Custom Validators](#custom-validators) + * [Querying Validators](#querying-validators) + * [Getting Results](#getting-results) + * [Subcommands](#subcommands) + * [Subcommand options](#subcommand-options) + * [Option groups](#option-groups) + * [Callbacks](#callbacks) + * [Configuration file](#configuration-file) + * [Inheriting defaults](#inheriting-defaults) + * [Formatting](#formatting) + * [Subclassing](#subclassing) + * [How it works](#how-it-works) + * [Utilities](#utilities) + * [Other libraries](#other-libraries) +* [API](#api) +* [Examples](#Examples) +* [Contribute](#contribute) +* [License](#license) Features that were added in the last released major version are marked with "🆕". Features only available in master are marked with "🚧". @@ -64,7 +65,7 @@ Features that were added in the last released major version are marked with " CLI11 provides all the features you expect in a powerful command line parser, with a beautiful, minimal syntax and no dependencies beyond C++11. It is header only, and comes in a single file form for easy inclusion in projects. It is easy to use for small projects, but powerful enough for complex command line projects, and can be customized for frameworks. It is tested on [Travis][], [AppVeyor][], [Azure][], and [GitHub Actions][actions-link], and is used by the [GooFit GPU fitting framework][goofit]. It was inspired by [`plumbum.cli`][plumbum] for Python. CLI11 has a user friendly introduction in this README, a more in-depth tutorial [GitBook][], as well as [API documentation][api-docs] generated by Travis. -See the [changelog](./CHANGELOG.md) or [GitHub Releases][] for details for current and past releases. Also see the [Version 1.0 post][], [Version 1.3 post][], or [Version 1.6 post][] for more information. +See the [changelog](./CHANGELOG.md) or [GitHub Releases][] for details for current and past releases. Also see the [Version 1.0 post][], [Version 1.3 post][], [Version 1.6 post][], or [Version 2.0 post][] for more information. You can be notified when new releases are made by subscribing to <https://github.com/CLIUtils/CLI11/releases.atom> on an RSS reader, like Feedly, or use the releases mode of the GitHub watching tool. @@ -72,21 +73,21 @@ You can be notified when new releases are made by subscribing to <https://github An acceptable CLI parser library should be all of the following: -- Easy to include (i.e., header only, one file if possible, **no external requirements**). -- Short, simple syntax: This is one of the main reasons to use a CLI parser, it should make variables from the command line nearly as easy to define as any other variables. If most of your program is hidden in CLI parsing, this is a problem for readability. -- C++11 or better: Should work with GCC 4.8+ (default on CentOS/RHEL 7), Clang 3.4+, AppleClang 7+, NVCC 7.0+, or MSVC 2015+. -- Work on Linux, macOS, and Windows. -- Well tested using [Travis][] (Linux) and [AppVeyor][] (Windows) or [Azure][] (all three). "Well" is defined as having good coverage measured by [CodeCov][]. -- Clear help printing. -- Nice error messages. -- Standard shell idioms supported naturally, like grouping flags, a positional separator, etc. -- Easy to execute, with help, parse errors, etc. providing correct exit and details. -- Easy to extend as part of a framework that provides "applications" to users. -- Usable subcommand syntax, with support for multiple subcommands, nested subcommands, option groups, and optional fallthrough (explained later). -- Ability to add a configuration file (`ini` or `TOML`🆕 format), and produce it as well. -- Produce real values that can be used directly in code, not something you have pay compute time to look up, for HPC applications. -- Work with standard types, simple custom types, and extensible to exotic types. -- Permissively licensed. +* Easy to include (i.e., header only, one file if possible, **no external requirements**). +* Short, simple syntax: This is one of the main reasons to use a CLI parser, it should make variables from the command line nearly as easy to define as any other variables. If most of your program is hidden in CLI parsing, this is a problem for readability. +* C++11 or better: Should work with GCC 4.8+ (default on CentOS/RHEL 7), Clang 3.4+, AppleClang 7+, NVCC 7.0+, or MSVC 2015+. +* Work on Linux, macOS, and Windows. +* Well tested using [Travis][] (Linux) and [AppVeyor][] (Windows) or [Azure][] (all three). "Well" is defined as having good coverage measured by [CodeCov][]. +* Clear help printing. +* Nice error messages. +* Standard shell idioms supported naturally, like grouping flags, a positional separator, etc. +* Easy to execute, with help, parse errors, etc. providing correct exit and details. +* Easy to extend as part of a framework that provides "applications" to users. +* Usable subcommand syntax, with support for multiple subcommands, nested subcommands, option groups, and optional fallthrough (explained later). +* Ability to add a configuration file (`TOML`, `INI`, or custom format), and produce it as well. +* Produce real values that can be used directly in code, not something you have pay compute time to look up, for HPC applications. +* Work with standard types, simple custom types, and extensible to exotic types. +* Permissively licensed. ### Other parsers @@ -126,32 +127,36 @@ So, this library was designed to provide a great syntax, good compiler compatibi There are some other possible "features" that are intentionally not supported by this library: -- Non-standard variations on syntax, like `-long` options. This is non-standard and should be avoided, so that is enforced by this library. -- Completion of partial options, such as Python's `argparse` supplies for incomplete arguments. It's better not to guess. Most third party command line parsers for python actually reimplement command line parsing rather than using argparse because of this perceived design flaw. -- 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. +* Non-standard variations on syntax, like `-long` options. This is non-standard and should be avoided, so that is enforced by this library. +* Completion of partial options, such as Python's `argparse` supplies for incomplete arguments. It's better not to guess. Most third party command line parsers for python actually reimplement command line parsing rather than using argparse because of this perceived design flaw. +* 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 To use, there are several methods: -1. All-in-one local header: Copy `CLI11.hpp` from the [most recent release][github releases] into your include directory, and you are set. This is combined from the source files for every release. This includes the entire command parser library, but does not include separate utilities (like `Timer`, `AutoTimer`). The utilities are completely self contained and can be copied separately. -2. All-in-one global header: Like above, but copying the file to a shared folder location like `/opt/CLI11`. Then, the C++ include path has to be extended to point at this folder. With CMake, use `include_directories(/opt/CLI11)` -3. Local headers and target: Use `CLI/*.hpp` files. You could check out the repository as a git submodule, for example. With CMake, you can use `add_subdirectory` and the `CLI11::CLI11` interface target when linking. If not using a submodule, you must ensure that the copied files are located inside the same tree directory than your current project, to prevent an error with CMake and `add_subdirectory`. -4. Global headers: Use `CLI/*.hpp` files stored in a shared folder. You could check out the git repository in a system-wide folder, for example `/opt/`. With CMake, you could add to the include path via: +* All-in-one local header: Copy `CLI11.hpp` from the [most recent release][github releases] into your include directory, and you are set. This is combined from the source files for every release. This includes the entire command parser library, but does not include separate utilities (like `Timer`, `AutoTimer`). The utilities are completely self contained and can be copied separately. +* All-in-one global header: Like above, but copying the file to a shared folder location like `/opt/CLI11`. Then, the C++ include path has to be extended to point at this folder. With CMake, use `include_directories(/opt/CLI11)` +* Local headers and target: Use `CLI/*.hpp` files. You could check out the repository as a git submodule, for example. With CMake, you can use `add_subdirectory` and the `CLI11::CLI11` interface target when linking. If not using a submodule, you must ensure that the copied files are located inside the same tree directory than your current project, to prevent an error with CMake and `add_subdirectory`. +* Global headers: Use `CLI/*.hpp` files stored in a shared folder. You could check out the git repository in a system-wide folder, for example `/opt/`. With CMake, you could add to the include path via: + ```bash if(NOT DEFINED CLI11_DIR) set (CLI11_DIR "/opt/CLI11" CACHE STRING "CLI11 git repository") endif() include_directories(${CLI11_DIR}/include) ``` + And then in the source code (adding several headers might be needed to prevent linker errors): + ```cpp #include "CLI/App.hpp" #include "CLI/Formatter.hpp" #include "CLI/Config.hpp" ``` -5. Global headers and target: configuring and installing the project is required for linking CLI11 to your project in the same way as you would do with any other external library. With CMake, this step allows using `find_package(CLI11 CONFIG REQUIRED)` and then using the `CLI11::CLI11` target when linking. If `CMAKE_INSTALL_PREFIX` was changed during install to a specific folder like `/opt/CLI11`, then you have to pass `-DCLI11_DIR=/opt/CLI11` when building your current project. You can also use [Conan.io][conan-link] or [Hunter][]. + +* Global headers and target: configuring and installing the project is required for linking CLI11 to your project in the same way as you would do with any other external library. With CMake, this step allows using `find_package(CLI11 CONFIG REQUIRED)` and then using the `CLI11::CLI11` target when linking. If `CMAKE_INSTALL_PREFIX` was changed during install to a specific folder like `/opt/CLI11`, then you have to pass `-DCLI11_DIR=/opt/CLI11` when building your current project. You can also use [Conan.io][conan-link] or [Hunter][]. (These are just conveniences to allow you to use your favorite method of managing packages; it's just header only so including the correct path and using C++11 is all you really need.) @@ -224,18 +229,16 @@ While all options internally are the same type, there are several ways to add an app.add_option(option_name, help_str="") app.add_option(option_name, - variable_to_bind_to, // bool, char(see note)🚧, int, float, vector, enum, std::atomic 🚧, or string-like, or anything with a defined conversion from a string or that takes an int 🆕, double 🆕, or string in a constructor. Also allowed are tuples 🆕, std::array 🆕 or std::pair 🆕. Also supported are complex numbers🚧, wrapper types🚧, and containers besides vector🚧 of any other supported type. + variable_to_bind_to, // bool, char(see note), int, float, vector, enum, std::atomic, or string-like, or anything with a defined conversion from a string or that takes an int, double, or string in a constructor. Also allowed are tuples, std::array or std::pair. Also supported are complex numbers, wrapper types, and containers besides vectorof any other supported type. help_string="") app.add_option_function<type>(option_name, function <void(const type &value)>, // type can be any type supported by add_option help_string="") -app.add_complex(... // Special case: support for complex numbers ⚠️. Complex numbers are now fully supported in the add_option so this function is redundant. - // char as an option type is supported before 2.0 but in 2.0 it defaulted to allowing single non numerical characters in addition to the numeric values. -// 🆕 There is a template overload which takes two template parameters the first is the type of object to assign the value to, the second is the conversion type. The conversion type should have a known way to convert from a string, such as any of the types that work in the non-template version. If XC is a std::pair and T is some non pair type. Then a two argument constructor for T is called to assign the value. For tuples or other multi element types, XC must be a single type or a tuple like object of the same size as the assignment type +// There is a template overload which takes two template parameters the first is the type of object to assign the value to, the second is the conversion type. The conversion type should have a known way to convert from a string, such as any of the types that work in the non-template version. If XC is a std::pair and T is some non pair type. Then a two argument constructor for T is called to assign the value. For tuples or other multi element types, XC must be a single type or a tuple like object of the same size as the assignment type app.add_option<typename T, typename XC>(option_name, T &output, // output must be assignable or constructible from a value of type XC help_string="") @@ -245,7 +248,7 @@ app.add_flag(option_name, help_string="") app.add_flag(option_name, - variable_to_bind_to, // bool, int, float, complex, containers, enum, std::atomic 🚧, or string-like, or any singular object with a defined conversion from a string like add_option + variable_to_bind_to, // bool, int, float, complex, containers, enum, std::atomic, or string-like, or any singular object with a defined conversion from a string like add_option help_string="") app.add_flag_function(option_name, @@ -260,32 +263,38 @@ App* subcom = app.add_subcommand(name, description); Option_group *app.add_option_group(name,description); ``` -An option name must start with a alphabetic character, underscore, a number, '?', or '@'. For long options, after the first character '.', and '-' are also valid characters. For the `add_flag*` functions '{' has special meaning. Names are given as a comma separated string, with the dash or dashes. An option or flag can have as many names as you want, and afterward, using `count`, you can use any of the names, with dashes as needed, to count the options. One of the names is allowed to be given without proceeding dash(es); if present the option is a positional option, and that name will be used on the help line for its positional form. +An option name may start with any character except ('-', ' ', '\n', and '!') 🆕. For long options, after the first character all characters are allowed except ('=',':','{',' ', '\n')🆕. For the `add_flag*` functions '{' and '!' have special meaning which is why they are not allowed. Names are given as a comma separated string, with the dash or dashes. An option or flag can have as many names as you want, and afterward, using `count`, you can use any of the names, with dashes as needed, to count the options. One of the names is allowed to be given without proceeding dash(es); if present the option is a positional option, and that name will be used on the help line for its positional form. The `add_option_function<type>(...` function will typically require the template parameter be given unless a `std::function` object with an exact match is passed. The type can be any type supported by the `add_option` function. The function should throw an error (`CLI::ConversionError` or `CLI::ValidationError` possibly) if the value is not valid. -🆕 The two parameter template overload can be used in cases where you want to restrict the input such as -``` +The two parameter template overload can be used in cases where you want to restrict the input such as + +```cpp double val app.add_option<double,unsigned int>("-v",val); ``` + which would first verify the input is convertible to an `unsigned int` before assigning it. Or using some variant type -``` + +```cpp using vtype=std::variant<int, double, std::string>; vtype v1; app.add_option<vtype,std:string>("--vs",v1); app.add_option<vtype,int>("--vi",v1); app.add_option<vtype,double>("--vf",v1); ``` + otherwise the output would default to a string. The `add_option` can be used with any integral or floating point types, enumerations, or strings. Or any type that takes an int, double, or std\::string in an assignment operator or constructor. If an object can take multiple varieties of those, std::string takes precedence, then double then int. To better control which one is used or to use another type for the underlying conversions use the two parameter template to directly specify the conversion type. Types such as (std or boost) `optional<int>`, `optional<double>`, and `optional<string>` and any other wrapper types are supported directly. For purposes of CLI11 wrapper types are those which `value_type` definition. See [CLI11 Advanced Topics/Custom Converters][] for information on how you can add your own converters for additional types. Vector types can also be used in the two parameter template overload -``` + +```cpp std::vector<double> v1; app.add_option<std::vector<double>,int>("--vs",v1); ``` + would load a vector of doubles but ensure all values can be represented as integers. Automatic direct capture of the default string is disabled when using the two parameter template. Use `set_default_str(...)` or `->default_function(std::string())` to set the default string or capture function directly for these cases. @@ -308,76 +317,79 @@ The default value can be any value. For example if you wished to define a numeri app.add_flag("-1{1},-2{2},-3{3}",result,"numerical flag") ``` -using any of those flags on the command line will result in the specified number in the output. Similar things can be done for string values, and enumerations, as long as the default value can be converted to the given type. - +Using any of those flags on the command line will result in the specified number in the output. Similar things can be done for string values, and enumerations, as long as the default value can be converted to the given type. On a `C++14` compiler, you can pass a callback function directly to `.add_flag`, while in C++11 mode you'll need to use `.add_flag_function` if you want a callback function. The function will be given the number of times the flag was passed. You can throw a relevant `CLI::ParseError` to signal a failure. #### Example -- `"one,-o,--one"`: Valid as long as not a flag, would create an option that can be specified positionally, or with `-o` or `--one` -- `"this"` Can only be passed positionally -- `"-a,-b,-c"` No limit to the number of non-positional option names +* `"one,-o,--one"`: Valid as long as not a flag, would create an option that can be specified positionally, or with `-o` or `--one` +* `"this"` Can only be passed positionally +* `"-a,-b,-c"` No limit to the number of non-positional option names The add commands return a pointer to an internally stored `Option`. This option can be used directly to check for the count (`->count()`) after parsing to avoid a string based lookup. -⚠️ Deprecated: The `add_*` commands have a final argument than can be set to true, which causes the default value to be captured and printed on the command line with the help flag. Since CLI11 1.8, you can simply add `->capture_default_str()`. #### Option options Before parsing, you can set the following options: -- `->required()`: The program will quit if this option is not present. This is `mandatory` in Plumbum, but required options seems to be a more standard term. For compatibility, `->mandatory()` also works. -- `->expected(N)`: Take `N` values instead of as many as possible, only for vector args. If negative, require at least `-N`; end with `--` or another recognized option or subcommand. -- `->type_name(typename)`: Set the name of an Option's type (`type_name_fn` allows a function instead) -- `->type_size(N)`: Set the intrinsic size of an option. The parser will require multiples of this number if negative. -- `->needs(opt)`: This option requires another option to also be present, opt is an `Option` pointer. -- `->excludes(opt)`: This option cannot be given with `opt` present, opt is an `Option` pointer. -- `->envname(name)`: Gets the value from the environment if present and not passed on the command line. -- `->group(name)`: The help group to put the option in. No effect for positional options. Defaults to `"Options"`. `""` will not show up in the help print (hidden). -- `->ignore_case()`: Ignore the case on the command line (also works on subcommands, does not affect arguments). -- `->ignore_underscore()`: Ignore any underscores in the options names (also works on subcommands, does not affect arguments). For example "option_one" will match with "optionone". This does not apply to short form options since they only have one character -- `->disable_flag_override()`: From the command line long form flag options can be assigned a value on the command line using the `=` notation `--flag=value`. If this behavior is not desired, the `disable_flag_override()` disables it and will generate an exception if it is done on the command line. The `=` does not work with short form flag options. -- `->allow_extra_args(true/false)`: 🚧 If set to true the option will take an unlimited number of arguments like a vector, if false it will limit the number of arguments to the size of the type used in the option. Default value depends on the nature of the type use, containers default to true, others default to false. -- `->delimiter(char)`: Allows specification of a custom delimiter for separating single arguments into vector arguments, for example specifying `->delimiter(',')` on an option would result in `--opt=1,2,3` producing 3 elements of a vector and the equivalent of --opt 1 2 3 assuming opt is a vector value. -- `->description(str)`: Set/change the description. -- `->multi_option_policy(CLI::MultiOptionPolicy::Throw)`: Set the multi-option policy. Shortcuts available: `->take_last()`, `->take_first()`,`->take_all()`, and `->join()`. This will only affect options expecting 1 argument or bool flags (which do not inherit their default but always start with a specific policy). -- `->check(std::string(const std::string &), validator_name="",validator_description="")`: Define a check function. The function should return a non empty string with the error message if the check fails -- `->check(Validator)`: Use a Validator object to do the check see [Validators](#validators) for a description of available Validators and how to create new ones. -- `->transform(std::string(std::string &), validator_name="",validator_description=")`: Converts the input string into the output string, in-place in the parsed options. -- `->transform(Validator)`: Uses a Validator object to do the transformation see [Validators](#validators) for a description of available Validators and how to create new ones. -- `->each(void(const std::string &)>`: Run this function on each value received, as it is received. It should throw a `ValidationError` if an error is encountered. -- `->configurable(false)`: Disable this option from being in a configuration file. - `->capture_default_str()`: Store the current value attached and display it in the help string. -- `->default_function(std::string())`: Advanced: Change the function that `capture_default_str()` uses. -- `->always_capture_default()`: Always run `capture_default_str()` when creating new options. Only useful on an App's `option_defaults`. -- `default_str(string)`: Set the default string directly. This string will also be used as a default value if no arguments are passed and the value is requested. -- `default_val(value)`: 🆕 Generate the default string from a value and validate that the value is also valid. For options that assign directly to a value type the value in that type is also updated. Value must be convertible to a string(one of known types or have a stream operator). -- `->option_text(string)`: Sets the text between the option name and description. - +* `->required()`: The program will quit if this option is not present. This is `mandatory` in Plumbum, but required options seems to be a more standard term. For compatibility, `->mandatory()` also works. +* `->expected(N)`: Take `N` values instead of as many as possible, only for vector args. If negative, require at least `-N`; end with `--` or another recognized option or subcommand. +* `->expected(MIN,MAX)`: Set a range of expected values to accompany an option. `expected(0,1)` is the equivalent of making a flag. +* `->type_name(typename)`: Set the name of an Option's type (`type_name_fn` allows a function instead) +* `->type_size(N)`: Set the intrinsic size of an option value. The parser will require multiples of this number if negative. Most of the time this is detected automatically though can be modified for specific use cases. +* `->type_size(MIN,MAX)`: Set the intrinsic size of an option to a range. +* `->needs(opt)`: This option requires another option to also be present, opt is an `Option` pointer. Options can be removed from the `needs` with `remove_needs(opt)`. The option can also be specified with a string containing the name of the option +* `->excludes(opt)`: This option cannot be given with `opt` present, opt is an `Option` pointer. Can also be given as a string containing the name of the option. Options can be removed from the excludes list with `->remove_excludes(opt)` +* `->envname(name)`: Gets the value from the environment if present and not passed on the command line. +* `->group(name)`: The help group to put the option in. No effect for positional options. Defaults to `"Options"`. `""` will not show up in the help print (hidden). +* `->ignore_case()`: Ignore the case on the command line (also works on subcommands, does not affect arguments). +* `->ignore_underscore()`: Ignore any underscores in the options names (also works on subcommands, does not affect arguments). For example "option_one" will match with "optionone". This does not apply to short form options since they only have one character +* `->disable_flag_override()`: From the command line long form flag options can be assigned a value on the command line using the `=` notation `--flag=value`. If this behavior is not desired, the `disable_flag_override()` disables it and will generate an exception if it is done on the command line. The `=` does not work with short form flag options. +* `->allow_extra_args(true/false)`: If set to true the option will take an unlimited number of arguments like a vector, if false it will limit the number of arguments to the size of the type used in the option. Default value depends on the nature of the type use, containers default to true, others default to false. +* `->delimiter(char)`: Allows specification of a custom delimiter for separating single arguments into vector arguments, for example specifying `->delimiter(',')` on an option would result in `--opt=1,2,3` producing 3 elements of a vector and the equivalent of --opt 1 2 3 assuming opt is a vector value. +* `->description(str)`: Set/change the description. +* `->multi_option_policy(CLI::MultiOptionPolicy::Throw)`: Set the multi-option policy. Shortcuts available: `->take_last()`, `->take_first()`,`->take_all()`, and `->join()`. This will only affect options expecting 1 argument or bool flags (which do not inherit their default but always start with a specific policy). `->join(delim)` can also be used to join with a specific delimiter. This equivalent to calling `->delimiter(delim)` and `->join()` +* `->check(std::string(const std::string &), validator_name="",validator_description="")`: Define a check function. The function should return a non empty string with the error message if the check fails +* `->check(Validator)`: Use a Validator object to do the check see [Validators](#validators) for a description of available Validators and how to create new ones. +* `->transform(std::string(std::string &), validator_name="",validator_description=")`: Converts the input string into the output string, in-place in the parsed options. +* `->transform(Validator)`: Uses a Validator object to do the transformation see [Validators](#validators) for a description of available Validators and how to create new ones. +* `->each(void(const std::string &)>`: Run this function on each value received, as it is received. It should throw a `ValidationError` if an error is encountered. +* `->configurable(false)`: Disable this option from being in a configuration file. +* `->capture_default_str()`: Store the current value attached and display it in the help string. +* `->default_function(std::string())`: Advanced: Change the function that `capture_default_str()` uses. +* `->always_capture_default()`: Always run `capture_default_str()` when creating new options. Only useful on an App's `option_defaults`. +* `->default_str(string)`: Set the default string directly (NO VALIDATION OR CALLBACKS). This string will also be used as a default value if no arguments are passed and the value is requested. +* `->default_val(value)`: Generate the default string from a value and validate that the value is also valid. For options that assign directly to a value type the value in that type is also updated. Value must be convertible to a string(one of known types or have a stream operator). The callback may be triggered if the `run_callback_for_default` is set. +* `->run_callback_for_default()`: This will force the option callback to be executed or the variable set when the `default_val` is set. +* `->option_text(string)`: Sets the text between the option name and description. +* `->force_callback()`: 🆕 Causes the option callback or value set to be triggered even if the option was not present in parsing. +* `->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. These options return the `Option` pointer, so you can chain them together, and even skip storing the pointer entirely. The `each` function takes any function that has the signature `void(const std::string&)`; it should throw a `ValidationError` when validation fails. The help message will have the name of the parent option prepended. Since `each`, `check` and `transform` use the same underlying mechanism, you can chain as many as you want, and they will be executed in order. Operations added through `transform` are executed first in reverse order of addition, and `check` and `each` are run following the transform functions in order of addition. If you just want to see the unconverted values, use `.results()` to get the `std::vector<std::string>` of results. On the command line, options can be given as: -- `-a` (flag) -- `-abc` (flags can be combined) -- `-f filename` (option) -- `-ffilename` (no space required) -- `-abcf filename` (flags and option can be combined) -- `--long` (long flag) -- `--long_flag=true` (long flag with equals to override default value) -- `--file filename` (space) -- `--file=filename` (equals) +* `-a` (flag) +* `-abc` (flags can be combined) +* `-f filename` (option) +* `-ffilename` (no space required) +* `-abcf filename` (flags and option can be combined) +* `--long` (long flag) +* `--long_flag=true` (long flag with equals to override default value) +* `--file filename` (space) +* `--file=filename` (equals) If `allow_windows_style_options()` is specified in the application or subcommand options can also be given as: -- `/a` (flag) -- `/f filename` (option) -- `/long` (long flag) -- `/file filename` (space) -- `/file:filename` (colon) -- `/long_flag:false` (long flag with : to override the default value) -= Windows style options do not allow combining short options or values not separated from the short option like with `-` options + +* `/a` (flag) +* `/f filename` (option) +* `/long` (long flag) +* `/file filename` (space) +* `/file:filename` (colon) +* `/long_flag:false` (long flag with : to override the default value) + * Windows style options do not allow combining short options or values not separated from the short option like with `-` options Long flag options may be given with an `=<value>` to allow specifying a false value, or some other value to the flag. See [config files](#configuration-file) for details on the values supported. NOTE: only the `=` or `:` for windows-style options may be used for this, using a space will result in the argument being interpreted as a positional argument. This syntax can override the default values, and can be disabled by using `disable_flag_override()`. @@ -394,22 +406,22 @@ Validators are structures to check or modify inputs, they can be used to verify CLI11 has several Validators built-in that perform some common checks -- `CLI::IsMember(...)`: Require an option be a member of a given set. See [Transforming Validators](#transforming-validators) for more details. -- `CLI::Transformer(...)`: Modify the input using a map. See [Transforming Validators](#transforming-validators) for more details. -- `CLI::CheckedTransformer(...)`: Modify the input using a map, and require that the input is either in the set or already one of the outputs of the set. See [Transforming Validators](#transforming-validators) for more details. -- `CLI::AsNumberWithUnit(...)`: Modify the `<NUMBER> <UNIT>` pair by matching the unit and multiplying the number by the corresponding factor. It can be used as a base for transformers, that accept things like size values (`1 KB`) or durations (`0.33 ms`). -- `CLI::AsSizeValue(...)`: Convert inputs like `100b`, `42 KB`, `101 Mb`, `11 Mib` to absolute values. `KB` can be configured to be interpreted as 10^3 or 2^10. -- `CLI::ExistingFile`: Requires that the file exists if given. -- `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::Range(min,max)`: Requires that the option be between min and max (make sure to use floating point if needed). Min defaults to 0. -- `CLI::Bounded(min,max)`: Modify the input such that it is always between min and max (make sure to use floating point if needed). Min defaults to 0. Will produce an error if conversion is not possible. -- `CLI::PositiveNumber`: Requires the number be greater than 0 -- `CLI::NonNegativeNumber`: Requires the number be greater or equal to 0 -- `CLI::Number`: Requires the input be a number. -- `CLI::ValidIPV4`: Requires that the option be a valid IPv4 string e.g. `'255.255.255.255'`, `'10.1.1.7'`. -- `CLI::TypeValidator<TYPE>`:🚧 Requires that the option be convertible to the specified type e.g. `CLI::TypeValidator<unsigned int>()` would require that the input be convertible to an `unsigned int` regardless of the end conversion. +* `CLI::IsMember(...)`: Require an option be a member of a given set. See [Transforming Validators](#transforming-validators) for more details. +* `CLI::Transformer(...)`: Modify the input using a map. See [Transforming Validators](#transforming-validators) for more details. +* `CLI::CheckedTransformer(...)`: Modify the input using a map, and require that the input is either in the set or already one of the outputs of the set. See [Transforming Validators](#transforming-validators) for more details. +* `CLI::AsNumberWithUnit(...)`: Modify the `<NUMBER> <UNIT>` pair by matching the unit and multiplying the number by the corresponding factor. It can be used as a base for transformers, that accept things like size values (`1 KB`) or durations (`0.33 ms`). +* `CLI::AsSizeValue(...)`: Convert inputs like `100b`, `42 KB`, `101 Mb`, `11 Mib` to absolute values. `KB` can be configured to be interpreted as 10^3 or 2^10. +* `CLI::ExistingFile`: Requires that the file exists if given. +* `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::Range(min,max)`: Requires that the option be between min and max (make sure to use floating point if needed). Min defaults to 0. +* `CLI::Bounded(min,max)`: Modify the input such that it is always between min and max (make sure to use floating point if needed). Min defaults to 0. Will produce an error if conversion is not possible. +* `CLI::PositiveNumber`: Requires the number be greater than 0 +* `CLI::NonNegativeNumber`: Requires the number be greater or equal to 0 +* `CLI::Number`: Requires the input be a number. +* `CLI::ValidIPV4`: Requires that the option be a valid IPv4 string e.g. `'255.255.255.255'`, `'10.1.1.7'`. +* `CLI::TypeValidator<TYPE>`:Requires that the option be convertible to the specified type e.g. `CLI::TypeValidator<unsigned int>()` would require that the input be convertible to an `unsigned int` regardless of the end conversion. These Validators can be used by simply passing the name into the `check` or `transform` methods on an option @@ -433,36 +445,39 @@ will produce a check to ensure a value is between 0 and 10 or 20 and 30. will produce a check for a number less than or equal to 0. ##### Transforming Validators + There are a few built in Validators that let you transform values if used with the `transform` function. If they also do some checks then they can be used `check` but some may do nothing in that case. -- `CLI::Bounded(min,max)` will bound values between min and max and values outside of that range are limited to min or max, it will fail if the value cannot be converted and produce a `ValidationError` -- The `IsMember` Validator lets you specify a set of predefined options. You can pass any container or copyable pointer (including `std::shared_ptr`) to a container to this Validator; the container just needs to be iterable and have a `::value_type`. The key type should be convertible from a string, You can use an initializer list directly if you like. If you need to modify the set later, the pointer form lets you do that; the type message and check will correctly refer to the current version of the set. The container passed in can be a set, vector, or a map like structure. If used in the `transform` method the output value will be the matching key as it could be modified by filters. -After specifying a set of options, you can also specify "filter" functions of the form `T(T)`, where `T` is the type of the values. The most common choices probably will be `CLI::ignore_case` an `CLI::ignore_underscore`, and `CLI::ignore_space`. These all work on strings but it is possible to define functions that work on other types. -Here are some examples -of `IsMember`: - -- `CLI::IsMember({"choice1", "choice2"})`: Select from exact match to choices. -- `CLI::IsMember({"choice1", "choice2"}, CLI::ignore_case, CLI::ignore_underscore)`: Match things like `Choice_1`, too. -- `CLI::IsMember(std::set<int>({2,3,4}))`: Most containers and types work; you just need `std::begin`, `std::end`, and `::value_type`. -- `CLI::IsMember(std::map<std::string, TYPE>({{"one", 1}, {"two", 2}}))`: You can use maps; in `->transform()` these replace the matched value with the matched key. The value member of the map is not used in `IsMember`, so it can be any type. -- `auto p = std::make_shared<std::vector<std::string>>(std::initializer_list<std::string>("one", "two")); CLI::IsMember(p)`: You can modify `p` later. -- The `Transformer` and `CheckedTransformer` Validators transform one value into another. Any container or copyable pointer (including `std::shared_ptr`) to a container that generates pairs of values can be passed to these `Validator's`; the container just needs to be iterable and have a `::value_type` that consists of pairs. The key type should be convertible from a string, and the value type should be convertible to a string You can use an initializer list directly if you like. If you need to modify the map later, the pointer form lets you do that; the description message will correctly refer to the current version of the map. `Transformer` does not do any checking so values not in the map are ignored. `CheckedTransformer` takes an extra step of verifying that the value is either one of the map key values, in which case it is transformed, or one of the expected output values, and if not will generate a `ValidationError`. A Transformer placed using `check` will not do anything. + +* `CLI::Bounded(min,max)` will bound values between min and max and values outside of that range are limited to min or max, it will fail if the value cannot be converted and produce a `ValidationError` +* The `IsMember` Validator lets you specify a set of predefined options. You can pass any container or copyable pointer (including `std::shared_ptr`) to a container to this Validator; the container just needs to be iterable and have a `::value_type`. The key type should be convertible from a string, You can use an initializer list directly if you like. If you need to modify the set later, the pointer form lets you do that; the type message and check will correctly refer to the current version of the set. The container passed in can be a set, vector, or a map like structure. If used in the `transform` method the output value will be the matching key as it could be modified by filters. + +After specifying a set of options, you can also specify "filter" functions of the form `T(T)`, where `T` is the type of the values. The most common choices probably will be `CLI::ignore_case` an `CLI::ignore_underscore`, and `CLI::ignore_space`. These all work on strings but it is possible to define functions that work on other types. Here are some examples of `IsMember`: + +* `CLI::IsMember({"choice1", "choice2"})`: Select from exact match to choices. +* `CLI::IsMember({"choice1", "choice2"}, CLI::ignore_case, CLI::ignore_underscore)`: Match things like `Choice_1`, too. +* `CLI::IsMember(std::set<int>({2,3,4}))`: Most containers and types work; you just need `std::begin`, `std::end`, and `::value_type`. +* `CLI::IsMember(std::map<std::string, TYPE>({{"one", 1}, {"two", 2}}))`: You can use maps; in `->transform()` these replace the matched value with the matched key. The value member of the map is not used in `IsMember`, so it can be any type. +* `auto p = std::make_shared<std::vector<std::string>>(std::initializer_list<std::string>("one", "two")); CLI::IsMember(p)`: You can modify `p` later. +* The `Transformer` and `CheckedTransformer` Validators transform one value into another. Any container or copyable pointer (including `std::shared_ptr`) to a container that generates pairs of values can be passed to these `Validator's`; the container just needs to be iterable and have a `::value_type` that consists of pairs. The key type should be convertible from a string, and the value type should be convertible to a string You can use an initializer list directly if you like. If you need to modify the map later, the pointer form lets you do that; the description message will correctly refer to the current version of the map. `Transformer` does not do any checking so values not in the map are ignored. `CheckedTransformer` takes an extra step of verifying that the value is either one of the map key values, in which case it is transformed, or one of the expected output values, and if not will generate a `ValidationError`. A Transformer placed using `check` will not do anything. + After specifying a map of options, you can also specify "filter" just like in `CLI::IsMember`. Here are some examples (`Transformer` and `CheckedTransformer` are interchangeable in the examples) of `Transformer`: -- `CLI::Transformer({{"key1", "map1"},{"key2","map2"}})`: Select from key values and produce map values. - -- `CLI::Transformer(std::map<std::string,int>({"two",2},{"three",3},{"four",4}}))`: most maplike containers work, the `::value_type` needs to produce a pair of some kind. -- `CLI::CheckedTransformer(std::map<std::string, int>({{"one", 1}, {"two", 2}}))`: You can use maps; in `->transform()` these replace the matched key with the value. `CheckedTransformer` also requires that the value either match one of the keys or match one of known outputs. -- `auto p = std::make_shared<CLI::TransformPairs<std::string>>(std::initializer_list<std::pair<std::string,std::string>>({"key1", "map1"},{"key2","map2"})); CLI::Transformer(p)`: You can modify `p` later. `TransformPairs<T>` is an alias for `std::vector<std::pair<<std::string,T>>` +* `CLI::Transformer({{"key1", "map1"},{"key2","map2"}})`: Select from key values and produce map values. +* `CLI::Transformer(std::map<std::string,int>({"two",2},{"three",3},{"four",4}}))`: most maplike containers work, the `::value_type` needs to produce a pair of some kind. +* `CLI::CheckedTransformer(std::map<std::string, int>({{"one", 1}, {"two", 2}}))`: You can use maps; in `->transform()` these replace the matched key with the value. `CheckedTransformer` also requires that the value either match one of the keys or match one of known outputs. +* `auto p = std::make_shared<CLI::TransformPairs<std::string>>(std::initializer_list<std::pair<std::string,std::string>>({"key1", "map1"},{"key2","map2"})); CLI::Transformer(p)`: You can modify `p` later. `TransformPairs<T>` is an alias for `std::vector<std::pair<<std::string,T>>` NOTES: If the container used in `IsMember`, `Transformer`, or `CheckedTransformer` has a `find` function like `std::unordered_map` or `std::map` then that function is used to do the searching. If it does not have a `find` function a linear search is performed. If there are filters present, the fast search is performed first, and if that fails a linear search with the filters on the key values is performed. ##### Validator operations + Validators are copyable and have a few operations that can be performed on them to alter settings. Most of the built in Validators have a default description that is displayed in the help. This can be altered via `.description(validator_description)`. The name of a Validator, which is useful for later reference from the `get_validator(name)` method of an `Option` can be set via `.name(validator_name)` The operation function of a Validator can be set via -`.operation(std::function<std::string(std::string &>)`. The `.active()` function can activate or deactivate a Validator from the operation. A validator can be set to apply only to a specific element of the output. For example in a pair option `std::pair<int, std::string>` the first element may need to be a positive integer while the second may need to be a valid file. The `.application_index(int)` 🆕 function can specify this. It is zero based and negative indices apply to all values. +`.operation(std::function<std::string(std::string &>)`. The `.active()` function can activate or deactivate a Validator from the operation. A validator can be set to apply only to a specific element of the output. For example in a pair option `std::pair<int, std::string>` the first element may need to be a positive integer while the second may need to be a valid file. The `.application_index(int)` function can specify this. It is zero based and negative indices apply to all values. + ```cpp opt->check(CLI::Validator(CLI::PositiveNumber).application_index(0)); opt->check(CLI::Validator(CLI::ExistingFile).application_index(1)); @@ -507,27 +522,28 @@ opt->get_validator(name); This will retrieve a Validator with the given name or throw a `CLI::OptionNotFound` error. If no name is given or name is empty the first unnamed Validator will be returned or the first Validator if there is only one. -or 🆕 +or ```cpp opt->get_validator(index); ``` Which will return a validator in the index it is applied which isn't necessarily the order in which was defined. The pointer can be `nullptr` if an invalid index is given. -Validators have a few functions to query the current values -- `get_description()`: Will return a description string -- `get_name()`: Will return the Validator name -- `get_active()`: Will return the current active state, true if the Validator is active. -- `get_application_index()`: 🆕 Will return the current application index. -- `get_modifying()`: Will return true if the Validator is allowed to modify the input, this can be controlled via the `non_modifying()` method, though it is recommended to let `check` and `transform` option methods manipulate it if needed. +Validators have a few functions to query the current values: + +* `get_description()`: Will return a description string +* `get_name()`: Will return the Validator name +* `get_active()`: Will return the current active state, true if the Validator is active. +* `get_application_index()`: Will return the current application index. +* `get_modifying()`: Will return true if the Validator is allowed to modify the input, this can be controlled via the `non_modifying()` method, though it is recommended to let `check` and `transform` option methods manipulate it if needed. #### Getting results In most cases, the fastest and easiest way is to return the results through a callback or variable specified in one of the `add_*` functions. But there are situations where this is not possible or desired. For these cases the results may be obtained through one of the following functions. Please note that these functions will do any type conversions and processing during the call so should not used in performance critical code: -- `results()`: Retrieves a vector of strings with all the results in the order they were given. -- `results(variable_to_bind_to)`: Gets the results according to the MultiOptionPolicy and converts them just like the `add_option_function` with a variable. -- `Value=as<type>()`: Returns the result or default value directly as the specified type if possible, can be vector to return all results, and a non-vector to get the result according to the MultiOptionPolicy in place. +* `->results()`: Retrieves a vector of strings with all the results in the order they were given. +* `->results(variable_to_bind_to)`: Gets the results according to the MultiOptionPolicy and converts them just like the `add_option_function` with a variable. +* `Value=opt->as<type>()`: Returns the result or default value directly as the specified type if possible, can be vector to return all results, and a non-vector to get the result according to the MultiOptionPolicy in place. ### Subcommands @@ -546,72 +562,73 @@ even exit the program through the callback. Multiple subcommands are allowed, to allow [`Click`][click] like series of commands (order is preserved). The same subcommand can be triggered multiple times but all positional arguments will take precedence over the second and future calls of the subcommand. `->count()` on the subcommand will return the number of times the subcommand was called. The subcommand callback will only be triggered once unless the `.immediate_callback()` flag is set or the callback is specified through the `parse_complete_callback()` function. The `final_callback()` is triggered only once. In which case the callback executes on completion of the subcommand arguments but after the arguments for that subcommand have been parsed, and can be triggered multiple times. Subcommands may also have an empty name either by calling `add_subcommand` with an empty string for the name or with no arguments. -Nameless subcommands function a similarly to groups in the main `App`. See [Option groups](#option-groups) to see how this might work. If an option is not defined in the main App, all nameless subcommands are checked as well. This allows for the options to be defined in a composable group. The `add_subcommand` function has an overload for adding a `shared_ptr<App>` so the subcommand(s) could be defined in different components and merged into a main `App`, or possibly multiple `Apps`. Multiple nameless subcommands are allowed. Callbacks for nameless subcommands are only triggered if any options from the subcommand were parsed. +Nameless subcommands function a similarly to groups in the main `App`. See [Option groups](#option-groups) to see how this might work. If an option is not defined in the main App, all nameless subcommands are checked as well. This allows for the options to be defined in a composable group. The `add_subcommand` function has an overload for adding a `shared_ptr<App>` so the subcommand(s) could be defined in different components and merged into a main `App`, or possibly multiple `Apps`. Multiple 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. #### Subcommand options There are several options that are supported on the main app and subcommands and option_groups. These are: -- `.ignore_case()`: Ignore the case of this subcommand. Inherited by added subcommands, so is usually used on the main `App`. -- `.ignore_underscore()`: Ignore any underscores in the subcommand name. Inherited by added subcommands, so is usually used on the main `App`. -- `.allow_windows_style_options()`: Allow command line options to be parsed in the form of `/s /long /file:file_name.ext` This option does not change how options are specified in the `add_option` calls or the ability to process options in the form of `-s --long --file=file_name.ext`. -- `.fallthrough()`: Allow extra unmatched options and positionals to "fall through" and be matched on a parent option. Subcommands always are allowed to "fall through" as in they will first attempt to match on the current subcommand and if they fail will progressively check parents for matching subcommands. -- `.configurable()`: 🆕 Allow the subcommand to be triggered from a configuration file. By default subcommand options in a configuration file do not trigger a subcommand but will just update default values. -- `.disable()`: Specify that the subcommand is disabled, if given with a bool value it will enable or disable the subcommand or option group. -- `.disabled_by_default()`: Specify that at the start of parsing the subcommand/option_group should be disabled. This is useful for allowing some Subcommands to trigger others. -- `.enabled_by_default()`: Specify that at the start of each parse the subcommand/option_group should be enabled. This is useful for allowing some Subcommands to disable others. -- `.silent()`: 🚧 Specify that the subcommand is silent meaning that if used it won't show up in the subcommand list. This allows the use of subcommands as modifiers -- `.validate_positionals()`: Specify that positionals should pass validation before matching. Validation is specified through `transform`, `check`, and `each` for an option. If an argument fails validation it is not an error and matching proceeds to the next available positional or extra arguments. -- `.excludes(option_or_subcommand)`: If given an option pointer or pointer to another subcommand, these subcommands cannot be given together. In the case of options, if the option is passed the subcommand cannot be used and will generate an error. -- `.needs(option_or_subcommand)`: 🆕 If given an option pointer or pointer to another subcommand, the subcommands will require the given option to have been given before this subcommand is validated which occurs prior to execution of any callback or after parsing is completed. -- `.require_option()`: Require 1 or more options or option groups be used. -- `.require_option(N)`: Require `N` options or option groups, if `N>0`, or up to `N` if `N<0`. `N=0` resets to the default to 0 or more. -- `.require_option(min, max)`: Explicitly set min and max allowed options or option groups. Setting `max` to 0 implies unlimited options. -- `.require_subcommand()`: Require 1 or more subcommands. -- `.require_subcommand(N)`: Require `N` subcommands if `N>0`, or up to `N` if `N<0`. `N=0` resets to the default to 0 or more. -- `.require_subcommand(min, max)`: Explicitly set min and max allowed subcommands. Setting `max` to 0 is unlimited. -- `.add_subcommand(name="", description="")`: Add a subcommand, returns a pointer to the internally stored subcommand. -- `.add_subcommand(shared_ptr<App>)`: Add a subcommand by shared_ptr, returns a pointer to the internally stored subcommand. -- `.remove_subcommand(App)`: Remove a subcommand from the app or subcommand. -- `.got_subcommand(App_or_name)`: Check to see if a subcommand was received on the command line. -- `.get_subcommands(filter)`: The list of subcommands that match a particular filter function. -- `.add_option_group(name="", description="")`: Add an [option group](#option-groups) to an App, an option group is specialized subcommand intended for containing groups of options or other groups for controlling how options interact. -- `.get_parent()`: Get the parent App or `nullptr` if called on master App. -- `.get_option(name)`: Get an option pointer by option name will throw if the specified option is not available, nameless subcommands are also searched -- `.get_option_no_throw(name)`: Get an option pointer by option name. This function will return a `nullptr` instead of throwing if the option is not available. -- `.get_options(filter)`: Get the list of all defined option pointers (useful for processing the app for custom output formats). -- `.parse_order()`: Get the list of option pointers in the order they were parsed (including duplicates). -- `.formatter(fmt)`: Set a formatter, with signature `std::string(const App*, std::string, AppFormatMode)`. See Formatting for more details. -- `.description(str)`: Set/change the description. -- `.get_description()`: Access the description. -- `.alias(str)`: 🆕 set an alias for the subcommand, this allows subcommands to be called by more than one name. -- `.parsed()`: True if this subcommand was given on the command line. -- `.count()`: Returns the number of times the subcommand was called. -- `.count(option_name)`: Returns the number of times a particular option was called. -- `.count_all()`: Returns the total number of arguments a particular subcommand processed, on the master App it returns the total number of processed commands. -- `.name(name)`: Add or change the name. -- `.callback(void() function)`: Set the callback for an app. 🆕 either sets the pre_parse_callback or the final_callback depending on the value of `immediate_callback`. See [Subcommand callbacks](#callbacks) for some additional details. -- `.parse_complete_callback(void() function)`: 🆕 Set the callback that runs at the completion of parsing. for subcommands this is executed at the completion of the single subcommand and can be executed multiple times. See [Subcommand callbacks](#callbacks) for some additional details. -- `.final_callback(void() function)`: 🆕 Set the callback that runs at the end of all processing. This is the last thing that is executed before returning. See [Subcommand callbacks](#callbacks) for some additional details. -- `.immediate_callback()`: Specifies whether the callback for a subcommand should be run as a `parse_complete_callback`(true) or `final_callback`(false). When used on the main app 🆕 it will execute the main app callback prior to the callbacks for a subcommand if they do not also have the `immediate_callback` flag set. 🆕 It is preferable to use the `parse_complete_callback` or `final_callback` directly instead of the `callback` and `immediate_callback` if one wishes to control the ordering and timing of callback. Though `immediate_callback` can be used to swap them if that is needed. -- `.pre_parse_callback(void(std::size_t) function)`: Set a callback that executes after the first argument of an application is processed. See [Subcommand callbacks](#callbacks) for some additional details. -- `.allow_extras()`: Do not throw an error if extra arguments are left over. -- `.positionals_at_end()`: Specify that positional arguments occur as the last arguments and throw an error if an unexpected positional is encountered. -- `.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. -- `.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. -- `.set_help_flag(name, message)`: Set the help flag name and message, returns a pointer to the created option. -- `.set_help_all_flag(name, message)`: Set the help all flag name and message, returns a pointer to the created option. Expands subcommands. -- `.failure_message(func)`: Set the failure message function. Two provided: `CLI::FailureMessage::help` and `CLI::FailureMessage::simple` (the default). -- `.group(name)`: Set a group name, defaults to `"Subcommands"`. Setting `""` will be hide the subcommand. -- `[option_name]`: retrieve a const pointer to an option given by `option_name` for Example `app["--flag1"]` will get a pointer to the option for the "--flag1" value, `app["--flag1"]->as<bool>()` will get the results of the command line for a flag. The operation will throw an exception if the option name is not valid. +* `.ignore_case()`: Ignore the case of this subcommand. Inherited by added subcommands, so is usually used on the main `App`. +* `.ignore_underscore()`: Ignore any underscores in the subcommand name. Inherited by added subcommands, so is usually used on the main `App`. +* `.allow_windows_style_options()`: Allow command line options to be parsed in the form of `/s /long /file:file_name.ext` This option does not change how options are specified in the `add_option` calls or the ability to process options in the form of `-s --long --file=file_name.ext`. +* `.fallthrough()`: Allow extra unmatched options and positionals to "fall through" and be matched on a parent option. Subcommands always are allowed to "fall through" as in they will first attempt to match on the current subcommand and if they fail will progressively check parents for matching subcommands. +* `.configurable()`: Allow the subcommand to be triggered from a configuration file. By default subcommand options in a configuration file do not trigger a subcommand but will just update default values. +* `.disable()`: Specify that the subcommand is disabled, if given with a bool value it will enable or disable the subcommand or option group. +* `.disabled_by_default()`: Specify that at the start of parsing the subcommand/option_group should be disabled. This is useful for allowing some Subcommands to trigger others. +* `.enabled_by_default()`: Specify that at the start of each parse the subcommand/option_group should be enabled. This is useful for allowing some Subcommands to disable others. +* `.silent()`: Specify that the subcommand is silent meaning that if used it won't show up in the subcommand list. This allows the use of subcommands as modifiers +* `.validate_positionals()`: Specify that positionals should pass validation before matching. Validation is specified through `transform`, `check`, and `each` for an option. If an argument fails validation it is not an error and matching proceeds to the next available positional or extra arguments. +* `.excludes(option_or_subcommand)`: If given an option pointer or pointer to another subcommand, these subcommands cannot be given together. In the case of options, if the option is passed the subcommand cannot be used and will generate an error. +* `.needs(option_or_subcommand)`: If given an option pointer or pointer to another subcommand, the subcommands will require the given option to have been given before this subcommand is validated which occurs prior to execution of any callback or after parsing is completed. +* `.require_option()`: Require 1 or more options or option groups be used. +* `.require_option(N)`: Require `N` options or option groups, if `N>0`, or up to `N` if `N<0`. `N=0` resets to the default to 0 or more. +* `.require_option(min, max)`: Explicitly set min and max allowed options or option groups. Setting `max` to 0 implies unlimited options. +* `.require_subcommand()`: Require 1 or more subcommands. +* `.require_subcommand(N)`: Require `N` subcommands if `N>0`, or up to `N` if `N<0`. `N=0` resets to the default to 0 or more. +* `.require_subcommand(min, max)`: Explicitly set min and max allowed subcommands. Setting `max` to 0 is unlimited. +* `.add_subcommand(name="", description="")`: Add a subcommand, returns a pointer to the internally stored subcommand. +* `.add_subcommand(shared_ptr<App>)`: Add a subcommand by shared_ptr, returns a pointer to the internally stored subcommand. +* `.remove_subcommand(App)`: Remove a subcommand from the app or subcommand. +* `.got_subcommand(App_or_name)`: Check to see if a subcommand was received on the command line. +* `.get_subcommands(filter)`: The list of subcommands that match a particular filter function. +* `.add_option_group(name="", description="")`: Add an [option group](#option-groups) to an App, an option group is specialized subcommand intended for containing groups of options or other groups for controlling how options interact. +* `.get_parent()`: Get the parent App or `nullptr` if called on master App. +* `.get_option(name)`: Get an option pointer by option name will throw if the specified option is not available, nameless subcommands are also searched +* `.get_option_no_throw(name)`: Get an option pointer by option name. This function will return a `nullptr` instead of throwing if the option is not available. +* `.get_options(filter)`: Get the list of all defined option pointers (useful for processing the app for custom output formats). +* `.parse_order()`: Get the list of option pointers in the order they were parsed (including duplicates). +* `.formatter(fmt)`: Set a formatter, with signature `std::string(const App*, std::string, AppFormatMode)`. See Formatting for more details. +* `.description(str)`: Set/change the description. +* `.get_description()`: Access the description. +* `.alias(str)`: set an alias for the subcommand, this allows subcommands to be called by more than one name. +* `.parsed()`: True if this subcommand was given on the command line. +* `.count()`: Returns the number of times the subcommand was called. +* `.count(option_name)`: Returns the number of times a particular option was called. +* `.count_all()`: Returns the total number of arguments a particular subcommand processed, on the master App it returns the total number of processed commands. +* `.name(name)`: Add or change the name. +* `.callback(void() function)`: Set the callback for an app. Either sets the `pre_parse_callback` or the `final_callback` depending on the value of `immediate_callback`. See [Subcommand callbacks](#callbacks) for some additional details. +* `.parse_complete_callback(void() function)`: Set the callback that runs at the completion of parsing. For subcommands this is executed at the completion of the single subcommand and can be executed multiple times. See [Subcommand callbacks](#callbacks) for some additional details. +* `.final_callback(void() function)`: Set the callback that runs at the end of all processing. This is the last thing that is executed before returning. See [Subcommand callbacks](#callbacks) for some additional details. +* `.immediate_callback()`: Specifies whether the callback for a subcommand should be run as a `parse_complete_callback`(true) or `final_callback`(false). When used on the main app it will execute the main app callback prior to the callbacks for a subcommand if they do not also have the `immediate_callback` flag set. It is preferable to use the `parse_complete_callback` or `final_callback` directly instead of the `callback` and `immediate_callback` if one wishes to control the ordering and timing of callback. Though `immediate_callback` can be used to swap them if that is needed. +* `.pre_parse_callback(void(std::size_t) function)`: Set a callback that executes after the first argument of an application is processed. See [Subcommand callbacks](#callbacks) for some additional details. +* `.allow_extras()`: Do not throw an error if extra arguments are left over. +* `.positionals_at_end()`: Specify that positional arguments occur as the last arguments and throw an error if an unexpected positional is encountered. +* `.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. +* `.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. +* `.set_help_flag(name, message)`: Set the help flag name and message, returns a pointer to the created option. +* `.set_version_flag(name, versionString or callback, help_message)`: Set the version flag name and version string or callback and optional help message, returns a pointer to the created option. +* `.set_help_all_flag(name, message)`: Set the help all flag name and message, returns a pointer to the created option. Expands subcommands. +* `.failure_message(func)`: Set the failure message function. Two provided: `CLI::FailureMessage::help` and `CLI::FailureMessage::simple` (the default). +* `.group(name)`: Set a group name, defaults to `"Subcommands"`. Setting `""` will be hide the subcommand. +* `[option_name]`: retrieve a const pointer to an option given by `option_name` for Example `app["--flag1"]` will get a pointer to the option for the "--flag1" value, `app["--flag1"]->as<bool>()` will get the results of the command line for a flag. The operation will throw an exception if the option name is not valid. > Note: if you have a fixed number of required positional options, that will match before subcommand names. `{}` is an empty filter function, and any positional argument will match before repeated subcommand names. - #### Callbacks + A subcommand has three optional callbacks that are executed at different stages of processing. The `preparse_callback` is executed once after the first argument of a subcommand or application is processed and gives an argument for the number of remaining arguments to process. For the main app the first argument is considered the program name, for subcommands the first argument is the subcommand name. For Option groups and nameless subcommands the first argument is after the first argument or subcommand is processed from that group. -The second callback is executed after parsing. This is known as the `parse_complete_callback`. For subcommands this is executed immediately after parsing and can be executed multiple times if a subcommand is called multiple times. On the main app this callback is executed after all the `parse_complete_callback`s for the subcommands are executed but prior to any `final_callback` calls in the subcommand or option groups. If the main app or subcommand has a config file, no data from the config file will be reflected in `parse_complete_callback` on named subcommands 🆕. For `option_group`s the `parse_complete_callback` is executed prior to the `parse_complete_callback` on the main app but after the `config_file` is loaded (if specified). The 🆕 `final_callback` is executed after all processing is complete. After the `parse_complete_callback` is executed on the main app, the used subcommand `final_callback` are executed followed by the "final callback" for option groups. The last thing to execute is the `final_callback` for the `main_app`. +The second callback is executed after parsing. This is known as the `parse_complete_callback`. For subcommands this is executed immediately after parsing and can be executed multiple times if a subcommand is called multiple times. On the main app this callback is executed after all the `parse_complete_callback`s for the subcommands are executed but prior to any `final_callback` calls in the subcommand or option groups. If the main app or subcommand has a config file, no data from the config file will be reflected in `parse_complete_callback` on named subcommands. For `option_group`s the `parse_complete_callback` is executed prior to the `parse_complete_callback` on the main app but after the `config_file` is loaded (if specified). The `final_callback` is executed after all processing is complete. After the `parse_complete_callback` is executed on the main app, the used subcommand `final_callback` are executed followed by the "final callback" for option groups. The last thing to execute is the `final_callback` for the `main_app`. For example say an application was set up like ```cpp @@ -622,25 +639,25 @@ auto sub2=app.add_subcommand("sub2")->final_callback(c2)->preparse_callback(pc2) app.preparse_callback( pa); ... A bunch of other options - ``` Then the command line is given as -``` +```bash program --opt1 opt1_val sub1 --sub1opt --sub1optb val sub2 --sub2opt sub1 --sub1opt2 sub2 --sub2opt2 val ``` -- pa will be called prior to parsing any values with an argument of 13. -- pc1 will be called immediately after processing the sub1 command with a value of 10. -- c1 will be called when the `sub2` command is encountered. -- pc2 will be called with value of 6 after the sub2 command is encountered. -- c1 will be called again after the second sub2 command is encountered. -- ac1 will be called after processing of all arguments -- c2 will be called once after processing all arguments. -- ac2 will be called last after completing all lower level callbacks have been executed. +* `pa` will be called prior to parsing any values with an argument of 13. +* `pc1` will be called immediately after processing the `sub1` command with a value of 10. +* `c1` will be called when the `sub2` command is encountered. +* `pc2` will be called with value of 6 after the `sub2` command is encountered. +* `c1` will be called again after the second `sub2` command is encountered. +* `ac1` will be called after processing of all arguments +* `c2` will be called once after processing all arguments. +* `ac2` will be called last after completing all lower level callbacks have been executed. A subcommand is considered terminated when one of the following conditions are met. + 1. There are no more arguments to process 2. Another subcommand is encountered that would not fit in an optional slot of the subcommand 3. The `positional_mark` (`--`) is encountered and there are no available positional slots in the subcommand. @@ -648,8 +665,6 @@ A subcommand is considered terminated when one of the following conditions are m Prior to executed a `parse_complete_callback` all contained options are processed before the callback is triggered. If a subcommand with a `parse_complete_callback` is called again, then the contained options are reset, and can be triggered again. - - #### Option groups The subcommand method @@ -658,7 +673,7 @@ The subcommand method .add_option_group(name,description) ``` -Will create an option group, and return a pointer to it. The argument for `description` is optional and can be omitted. An option group allows creation of a collection of options, similar to the groups function on options, but with additional controls and requirements. They allow specific sets of options to be composed and controlled as a collective. For an example see [range example](https://github.com/CLIUtils/CLI11/blob/master/examples/ranges.cpp). Option groups are a specialization of an App so all [functions](#subcommand-options) that work with an App or subcommand also work on option groups. Options can be created as part of an option group using the add functions just like a subcommand, or previously created options can be added through +Will create an option group, and return a pointer to it. The argument for `description` is optional and can be omitted. An option group allows creation of a collection of options, similar to the groups function on options, but with additional controls and requirements. They allow specific sets of options to be composed and controlled as a collective. For an example see [range example](https://github.com/CLIUtils/CLI11/blob/master/examples/ranges.cpp). Option groups are a specialization of an App so all [functions](#subcommand-options) that work with an App or subcommand also work on option groups. Options can be created as part of an option group using the add functions just like a subcommand, or previously created options can be added through. The name given in an option group must not contain newlines or null characters.🆕 ```cpp ogroup->add_option(option_pointer); @@ -686,17 +701,20 @@ CLI::TriggerOff(group2_pointer, disabled_group); These functions make use of `preparse_callback`, `enabled_by_default()` and `disabled_by_default`. The triggered group may be a vector of group pointers. These methods should only be used once per group and will override any previous use of the underlying functions. More complex arrangements can be accomplished using similar methodology with a custom `preparse_callback` function that does more. -Additional helper functions `deprecate_option` 🆕 and `retire_option` 🆕 are available to deprecate or retire options +Additional helper functions `deprecate_option` and `retire_option` are available to deprecate or retire options + ```cpp CLI::deprecate_option(option *, replacement_name=""); CLI::deprecate_option(App,option_name,replacement_name=""); ``` + will specify that the option is deprecated which will display a message in the help and a warning on first usage. Deprecated options function normally but will add a message in the help and display a warning on first use. ```cpp CLI::retire_option(App,option *); CLI::retire_option(App,option_name); ``` + will create an option that does nothing by default and will display a warning on first usage that the option is retired and has no effect. If the option exists it is replaces with a dummy option that takes the same arguments. If an empty string is passed the option group name the entire group will be hidden in the help results. For example. @@ -704,6 +722,7 @@ If an empty string is passed the option group name the entire group will be hidd ```cpp auto hidden_group=app.add_option_group(""); ``` + will create a group such that no options in that group are displayed in the help string. ### Configuration file @@ -715,7 +734,7 @@ app.set_config(option_name="", required=false) ``` -If this is called with no arguments, it will remove the configuration file option (like `set_help_flag`). Setting a configuration option is special. If it is present, it will be read along with the normal command line arguments. The file will be read if it exists, and does not throw an error unless `required` is `true`. Configuration files are in [TOML] format by default 🚧, though the default reader can also accept files in INI format as well 🆕. It should be noted that CLI11 does not contain a full TOML parser but can read strings from most TOML file and run them through the CLI11 parser. Other formats can be added by an adept user, some variations are available through customization points in the default formatter. An example of a TOML file 🆕: +If this is called with no arguments, it will remove the configuration file option (like `set_help_flag`). Setting a configuration option is special. If it is present, it will be read along with the normal command line arguments. The file will be read if it exists, and does not throw an error unless `required` is `true`. Configuration files are in [TOML][] format by default, though the default reader can also accept files in INI format as well. It should be noted that CLI11 does not contain a full TOML parser but can read strings from most TOML file and run them through the CLI11 parser. Other formats can be added by an adept user, some variations are available through customization points in the default formatter. An example of a TOML file: ```toml # Comments are supported, using a # @@ -731,7 +750,9 @@ str_vector = ["one","two","and three"] in_subcommand = Wow sub.subcommand = true ``` + or equivalently in INI format + ```ini ; Comments are supported, using a ; ; The default section is [default], case insensitive @@ -747,10 +768,10 @@ in_subcommand = Wow sub.subcommand = true ``` -Spaces before and after the name and argument are ignored. Multiple arguments are separated by spaces. One set of quotes will be removed, preserving spaces (the same way the command line works). Boolean options can be `true`, `on`, `1`, `yes`, `enable`; or `false`, `off`, `0`, `no`, `disable` (case insensitive). Sections (and `.` separated names) are treated as subcommands (note: this does not necessarily mean that subcommand was passed, it just sets the "defaults"). You cannot set positional-only arguments. 🆕 Subcommands can be triggered from configuration files if the `configurable` flag was set on the subcommand. Then the use of `[subcommand]` notation will trigger a subcommand and cause it to act as if it were on the command line. +Spaces before and after the name and argument are ignored. Multiple arguments are separated by spaces. One set of quotes will be removed, preserving spaces (the same way the command line works). Boolean options can be `true`, `on`, `1`, `yes`, `enable`; or `false`, `off`, `0`, `no`, `disable` (case insensitive). Sections (and `.` separated names) are treated as subcommands (note: this does not necessarily mean that subcommand was passed, it just sets the "defaults"). You cannot set positional-only arguments. Subcommands can be triggered from configuration files if the `configurable` flag was set on the subcommand. Then the use of `[subcommand]` notation will trigger a subcommand and cause it to act as if it were on the command line. To print a configuration file from the passed -arguments, use `.config_to_str(default_also=false, write_description=false)`, where `default_also` will also show any defaulted arguments, and `write_description` will include the app and option descriptions. See [Config files](https://cliutils.github.io/CLI11/book/chapters/config.html) for some additional details. +arguments, use `.config_to_str(default_also=false, write_description=false)`, where `default_also` will also show any defaulted arguments, and `write_description` will include the app and option descriptions. See [Config files](https://cliutils.github.io/CLI11/book/chapters/config.html) for some additional details and customization points. If it is desired that multiple configuration be allowed. Use @@ -758,7 +779,7 @@ If it is desired that multiple configuration be allowed. Use app.set_config("--config")->expected(1, X); ``` -Where X is some positive number and will allow up to `X` configuration files to be specified by separate `--config` arguments. +Where X is some positive number and will allow up to `X` configuration files to be specified by separate `--config` arguments. Value strings with quote characters in it will be printed with a single quote. All other arguments will use double quote. Empty strings will use a double quoted argument. Numerical or boolean values are not quoted. ### Inheriting defaults @@ -788,8 +809,8 @@ The App class was designed allow toolkits to subclass it, to provide preset defa but before run behavior, while still giving the user freedom to `callback` on the main app. -The most important parse function is `parse(std::vector<std::string>)`, which takes a reversed list of arguments (so that `pop_back` processes the args in the correct order). `get_help_ptr` and `get_config_ptr` give you access to the help/config option pointers. The standard `parse` manually sets the name from the first argument, so it should not be in this vector. You can also use `parse(string, bool)` to split up and parse a string; the optional boolean should be set to true if you are -including the program name in the string, and false otherwise. +The most important parse function is `parse(std::vector<std::string>)`, which takes a reversed list of arguments (so that `pop_back` processes the args in the correct order). `get_help_ptr` and `get_config_ptr` give you access to the help/config option pointers. The standard `parse` manually sets the name from the first argument, so it should not be in this vector. You can also use `parse(string, bool)` to split up and parse a single string; the optional boolean should be set to true if you are +including the program name in the string, and false otherwise. The program name can contain spaces if it is an existing file, otherwise can be enclosed in quotes(single quote, double quote or backtick). Embedded quote characters can be escaped with `\`. Also, in a related note, the `App` you get a pointer to is stored in the parent `App` in a `shared_ptr`s (similar to `Option`s) and are deleted when the main `App` goes out of scope unless the object has another owner. @@ -800,7 +821,7 @@ Every `add_` option you have seen so far depends on one method that takes a lamb Other values can be added as long as they support `operator>>` (and defaults can be printed if they support `operator<<`). To add a new type, for example, 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 `CLI::detail` with 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: +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: #### Example @@ -871,28 +892,28 @@ The API is [documented here][api-docs]. Also see the [CLI11 tutorial GitBook][gi Several short examples of different features are included in the repository. A brief description of each is included here - - [callback_passthrough](https://github.com/CLIUtils/CLI11/blob/master/examples/callback_passthrough.cpp): Example of directly passing remaining arguments through to a callback function which generates a CLI11 application based on existing arguments. - - [digit_args](https://github.com/CLIUtils/CLI11/blob/master/examples/digit_args.cpp): Based on [Issue #123](https://github.com/CLIUtils/CLI11/issues/123), uses digit flags to pass a value - - [enum](https://github.com/CLIUtils/CLI11/blob/master/examples/enum.cpp): Using enumerations in an option, and the use of [CheckedTransformer](#transforming-validators) - - [enum_ostream](https://github.com/CLIUtils/CLI11/blob/master/examples/enum_ostream.cpp): In addition to the contents of example enum.cpp, this example shows how a custom ostream operator overrides CLI11's enum streaming. - - [formatter](https://github.com/CLIUtils/CLI11/blob/master/examples/formatter.cpp): Illustrating usage of a custom formatter - - [groups](https://github.com/CLIUtils/CLI11/blob/master/examples/groups.cpp): Example using groups of options for help grouping and a the timer helper class - - [inter_argument_order](https://github.com/CLIUtils/CLI11/blob/master/examples/inter_argument_order.cpp): An app to practice mixing unlimited arguments, but still recover the original order. - - [json](https://github.com/CLIUtils/CLI11/blob/master/examples/json.cpp): Using JSON as a config file parser - - [modhelp](https://github.com/CLIUtils/CLI11/blob/master/examples/modhelp.cpp): How to modify the help flag to do something other than default - - [nested](https://github.com/CLIUtils/CLI11/blob/master/examples/nested.cpp): Nested subcommands - - [option_groups](https://github.com/CLIUtils/CLI11/blob/master/examples/option_groups.cpp): illustrating the use of option groups and a required number of options. - based on [Issue #88](https://github.com/CLIUtils/CLI11/issues/88) to set interacting groups of options - - [positional_arity](https://github.com/CLIUtils/CLI11/blob/master/examples/positional_arity.cpp): Illustrating use of `preparse_callback` to handle situations where the number of arguments can determine which should get parsed, Based on [Issue #166](https://github.com/CLIUtils/CLI11/issues/166) - - [positional_validation](https://github.com/CLIUtils/CLI11/blob/master/examples/positional_validation.cpp): Example of how positional arguments are validated using the `validate_positional` flag, also based on [Issue #166](https://github.com/CLIUtils/CLI11/issues/166) - - [prefix_command](https://github.com/CLIUtils/CLI11/blob/master/examples/prefix_command.cpp): illustrating use of the `prefix_command` flag. - - [ranges](https://github.com/CLIUtils/CLI11/blob/master/examples/ranges.cpp): App to demonstrate exclusionary option groups based on [Issue #88](https://github.com/CLIUtils/CLI11/issues/88) - - [shapes](https://github.com/CLIUtils/CLI11/blob/master/examples/shapes.cpp): illustrating how to set up repeated subcommands Based on [gitter discussion](https://gitter.im/CLI11gitter/Lobby?at=5c7af6b965ffa019ea788cd5) - - [simple](https://github.com/CLIUtils/CLI11/blob/master/examples/simple.cpp): a simple example of how to set up a CLI11 Application with different flags and options - - [subcom_help](https://github.com/CLIUtils/CLI11/blob/master/examples/subcom_help.cpp): configuring help for subcommands - - [subcom_partitioned](https://github.com/CLIUtils/CLI11/blob/master/examples/subcom_partitioned.cpp): Example with a timer and subcommands generated separately and added to the main app later. - - [subcommands](https://github.com/CLIUtils/CLI11/blob/master/examples/subcommands.cpp): Short example of subcommands - - [validators](https://github.com/CLIUtils/CLI11/blob/master/examples/validators.cpp): Example illustrating use of validators +* [callback_passthrough](https://github.com/CLIUtils/CLI11/blob/master/examples/callback_passthrough.cpp): Example of directly passing remaining arguments through to a callback function which generates a CLI11 application based on existing arguments. +* [custom_parse](https://github.com/CLIUtils/CLI11/blob/master/examples/custom_parse.cpp): Based on [Issue #566](https://github.com/CLIUtils/CLI11/issues/566), example of custom parser +* [digit_args](https://github.com/CLIUtils/CLI11/blob/master/examples/digit_args.cpp): Based on [Issue #123](https://github.com/CLIUtils/CLI11/issues/123), uses digit flags to pass a value +* [enum](https://github.com/CLIUtils/CLI11/blob/master/examples/enum.cpp): Using enumerations in an option, and the use of [CheckedTransformer](#transforming-validators) +* [enum_ostream](https://github.com/CLIUtils/CLI11/blob/master/examples/enum_ostream.cpp): In addition to the contents of example enum.cpp, this example shows how a custom ostream operator overrides CLI11's enum streaming. +* [formatter](https://github.com/CLIUtils/CLI11/blob/master/examples/formatter.cpp): Illustrating usage of a custom formatter +* [groups](https://github.com/CLIUtils/CLI11/blob/master/examples/groups.cpp): Example using groups of options for help grouping and a the timer helper class +* [inter_argument_order](https://github.com/CLIUtils/CLI11/blob/master/examples/inter_argument_order.cpp): An app to practice mixing unlimited arguments, but still recover the original order. +* [json](https://github.com/CLIUtils/CLI11/blob/master/examples/json.cpp): Using JSON as a config file parser +* [modhelp](https://github.com/CLIUtils/CLI11/blob/master/examples/modhelp.cpp): How to modify the help flag to do something other than default +* [nested](https://github.com/CLIUtils/CLI11/blob/master/examples/nested.cpp): Nested subcommands +* [option_groups](https://github.com/CLIUtils/CLI11/blob/master/examples/option_groups.cpp): Illustrating the use of option groups and a required number of options. Based on [Issue #88](https://github.com/CLIUtils/CLI11/issues/88) to set interacting groups of options +* [positional_arity](https://github.com/CLIUtils/CLI11/blob/master/examples/positional_arity.cpp): Illustrating use of `preparse_callback` to handle situations where the number of arguments can determine which should get parsed, Based on [Issue #166](https://github.com/CLIUtils/CLI11/issues/166) +* [positional_validation](https://github.com/CLIUtils/CLI11/blob/master/examples/positional_validation.cpp): Example of how positional arguments are validated using the `validate_positional` flag, also based on [Issue #166](https://github.com/CLIUtils/CLI11/issues/166) +* [prefix_command](https://github.com/CLIUtils/CLI11/blob/master/examples/prefix_command.cpp): Illustrating use of the `prefix_command` flag. +* [ranges](https://github.com/CLIUtils/CLI11/blob/master/examples/ranges.cpp): App to demonstrate exclusionary option groups based on [Issue #88](https://github.com/CLIUtils/CLI11/issues/88) +* [shapes](https://github.com/CLIUtils/CLI11/blob/master/examples/shapes.cpp): Illustrating how to set up repeated subcommands Based on [gitter discussion](https://gitter.im/CLI11gitter/Lobby?at=5c7af6b965ffa019ea788cd5) +* [simple](https://github.com/CLIUtils/CLI11/blob/master/examples/simple.cpp): A simple example of how to set up a CLI11 Application with different flags and options +* [subcom_help](https://github.com/CLIUtils/CLI11/blob/master/examples/subcom_help.cpp): Configuring help for subcommands +* [subcom_partitioned](https://github.com/CLIUtils/CLI11/blob/master/examples/subcom_partitioned.cpp): Example with a timer and subcommands generated separately and added to the main app later. +* [subcommands](https://github.com/CLIUtils/CLI11/blob/master/examples/subcommands.cpp): Short example of subcommands +* [validators](https://github.com/CLIUtils/CLI11/blob/master/examples/validators.cpp): Example illustrating use of validators ## Contribute @@ -901,7 +922,6 @@ This readme roughly follows the [Standard Readme Style][] and includes a mention This project was created by [Henry Schreiner](https://github.com/henryiii) and major features were added by [Philip Top](https://github.com/phlptp). Special thanks to all the contributors ([emoji key](https://allcontributors.org/docs/en/emoji-key)): - <!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section --> <!-- prettier-ignore-start --> <!-- markdownlint-disable --> @@ -965,6 +985,12 @@ This project was created by [Henry Schreiner](https://github.com/henryiii) and m <td align="center"><a href="https://github.com/ZeeD26"><img src="https://avatars2.githubusercontent.com/u/2487468?v=4" width="100px;" alt=""/><br /><sub><b>Dominik Steinberger</b></sub></a><br /><a href="https://github.com/CLIUtils/CLI11/commits?author=ZeeD26" title="Code">💻</a></td> <td align="center"><a href="https://github.com/dfleury2"><img src="https://avatars1.githubusercontent.com/u/4805384?v=4" width="100px;" alt=""/><br /><sub><b>D. Fleury</b></sub></a><br /><a href="https://github.com/CLIUtils/CLI11/commits?author=dfleury2" title="Code">💻</a></td> <td align="center"><a href="https://github.com/dbarowy"><img src="https://avatars3.githubusercontent.com/u/573142?v=4" width="100px;" alt=""/><br /><sub><b>Dan Barowy</b></sub></a><br /><a href="https://github.com/CLIUtils/CLI11/commits?author=dbarowy" title="Documentation">📖</a></td> + <td align="center"><a href="https://github.com/paddy-hack"><img src="https://avatars.githubusercontent.com/u/6804372?v=4" width="100px;" alt=""/><br /><sub><b>Olaf Meeuwissen</b></sub></a><br /><a href="https://github.com/CLIUtils/CLI11/commits?author=paddy-hack" title="Code">💻</a></td> + <td align="center"><a href="https://github.com/dryleev"><img src="https://avatars.githubusercontent.com/u/83670813?v=4" width="100px;" alt=""/><br /><sub><b>dryleev</b></sub></a><br /><a href="https://github.com/CLIUtils/CLI11/commits?author=dryleev" title="Code">💻</a></td> + <td align="center"><a href="https://github.com/AnticliMaxtic"><img src="https://avatars.githubusercontent.com/u/43995389?v=4" width="100px;" alt=""/><br /><sub><b>Max</b></sub></a><br /><a href="https://github.com/CLIUtils/CLI11/commits?author=AnticliMaxtic" title="Code">💻</a></td> + </tr> + <tr> + <td align="center"><a href="https://profiles.sussex.ac.uk/p281168-alex-dewar/publications"><img src="https://avatars.githubusercontent.com/u/23149834?v=4" width="100px;" alt=""/><br /><sub><b>Alex Dewar</b></sub></a><br /><a href="https://github.com/CLIUtils/CLI11/commits?author=alexdewar" title="Code">💻</a></td> </tr> </table> @@ -972,7 +998,6 @@ This project was created by [Henry Schreiner](https://github.com/henryiii) and m <!-- prettier-ignore-end --> <!-- ALL-CONTRIBUTORS-LIST:END --> - This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome! ## License @@ -996,8 +1021,10 @@ CLI11 was developed at the [University of Cincinnati][] to support of the [GooFi [gitter-badge]: https://badges.gitter.im/CLI11gitter/Lobby.svg [gitter]: https://gitter.im/CLI11gitter/Lobby [license-badge]: https://img.shields.io/badge/License-BSD-blue.svg -[conan-badge]: https://api.bintray.com/packages/cliutils/CLI11/CLI11%3Acliutils/images/download.svg -[conan-link]: https://bintray.com/cliutils/CLI11/CLI11%3Acliutils/_latestVersion +[conan-badge]: https://img.shields.io/badge/conan-io-blue +[conan-link]: https://conan.io/center/cli11 +[conda-badge]: https://img.shields.io/conda/vn/conda-forge/cli11.svg +[conda-link]: https://github.com/conda-forge/cli11-feedstock [github releases]: https://github.com/CLIUtils/CLI11/releases [github issues]: https://github.com/CLIUtils/CLI11/issues [github pull requests]: https://github.com/CLIUtils/CLI11/pulls @@ -1028,16 +1055,18 @@ CLI11 was developed at the [University of Cincinnati][] to support of the [GooFi [version 1.0 post]: https://iscinumpy.gitlab.io/post/announcing-cli11-10/ [version 1.3 post]: https://iscinumpy.gitlab.io/post/announcing-cli11-13/ [version 1.6 post]: https://iscinumpy.gitlab.io/post/announcing-cli11-16/ -[wandbox-badge]: https://img.shields.io/badge/try_1.9-online-blue.svg -[wandbox-link]: https://wandbox.org/permlink/8SirASwhbFQZyDTW +[version 2.0 post]: https://iscinumpy.gitlab.io/post/announcing-cli11-20/ +[wandbox-badge]: https://img.shields.io/badge/try_2.0-online-blue.svg +[wandbox-link]: https://wandbox.org/permlink/650go2SXpfdvQ7ex [releases-badge]: https://img.shields.io/github/release/CLIUtils/CLI11.svg [cli11-po-compare]: https://iscinumpy.gitlab.io/post/comparing-cli11-and-boostpo/ [diana slides]: https://indico.cern.ch/event/619465/contributions/2507949/attachments/1448567/2232649/20170424-diana-2.pdf [awesome c++]: https://github.com/fffaraz/awesome-cpp/blob/master/README.md#cli [cli]: https://codesynthesis.com/projects/cli/ [single file libs]: https://github.com/nothings/single_file_libs/blob/master/README.md -[codacy-badge]: https://api.codacy.com/project/badge/Grade/ac0df3aead2a4421b02070c3f324a0b9 -[codacy-link]: https://www.codacy.com/app/henryiii/CLI11?utm_source=github.com&utm_medium=referral&utm_content=CLIUtils/CLI11&utm_campaign=Badge_Grade +[codacy-badge]: https://app.codacy.com/project/badge/Grade/2796b969c1b54321a02ad08affec0800 +[codacy-link]: https://www.codacy.com/gh/CLIUtils/CLI11/dashboard?utm_source=github.com&utm_medium=referral&utm_content=CLIUtils/CLI11&utm_campaign=Badge_Grade [hunter]: https://docs.hunter.sh/en/latest/packages/pkg/CLI11.html [standard readme style]: https://github.com/RichardLitt/standard-readme [argparse]: https://github.com/p-ranav/argparse +[toml]: https://toml.io diff --git a/packages/CLI11/book/CMakeLists.txt b/packages/CLI11/book/CMakeLists.txt index e99bde60600fa985350a6c76f2e63b10b64626a9..8934873a2e256f0372e871e54e0e80219bf3f7e1 100644 --- a/packages/CLI11/book/CMakeLists.txt +++ b/packages/CLI11/book/CMakeLists.txt @@ -1,10 +1,7 @@ +set(book_sources README.md SUMMARY.md) -set( - book_sources - README.md - SUMMARY.md -) - -file(GLOB book_chapters RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} chapters/*.md) +file( + GLOB book_chapters + RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} + chapters/*.md) add_custom_target(cli_book SOURCES ${book_sources} ${book_chapters}) - diff --git a/packages/CLI11/book/README.md b/packages/CLI11/book/README.md index 79a061fb1c6514e8f46f2829bcb49185f7d3706e..593adbc96dd168edefb419b4f470366d109640e1 100644 --- a/packages/CLI11/book/README.md +++ b/packages/CLI11/book/README.md @@ -1,8 +1,8 @@ # CLI11: An introduction -This gitbook is designed to provide an introduction to using the CLI11 library to write your own command line programs. The library is designed to be clean, intuitive, but powerful. There are no requirements beyond C++11 support (and even `<regex>` support not required). It works on Mac, Linux, and Windows, and has 100% test coverage on all three systems. You can simply drop in a single header file (`CLI11.hpp` available in [releases]) to use CLI11 in your own application. Other ways to integrate it into a build system are listed in the [README]. +This gitbook is designed to provide an introduction to using the CLI11 library to write your own command line programs. The library is designed to be clean, intuitive, but powerful. There are no requirements beyond C++11 support (and even `<regex>` support not required). It works on Mac, Linux, and Windows, and has 100% test coverage on all three systems. You can simply drop in a single header file (`CLI11.hpp` available in [releases][]) to use CLI11 in your own application. Other ways to integrate it into a build system are listed in the [README][]. -The library was inspired the Python libraries [Plumbum] and [Click], and incorporates many of their user friendly features. The library is extensively documented, with a [friendly introduction][README], this tutorial book, and more technical [API docs]. +The library was inspired the Python libraries [Plumbum][] and [Click][], and incorporates many of their user friendly features. The library is extensively documented, with a [friendly introduction][README], this tutorial book, and more technical [API docs][]. > Feel free to contribute to [this documentation here][CLI11Tutorial] if something can be improved! @@ -49,21 +49,17 @@ You can use any valid type; the above example could have used a `boost::file_sys You can use subcommands, as well. Subcommands support callback lambda functions when parsed, or they can be checked later. You can infinitely nest subcommands, and each is a full `App` instance, supporting everything listed above. -Reading/producing `.ini` files for configuration is also supported, as is using environment variables as input. The base `App` can be subclassed and customized for use in a toolkit (like [GooFit]). All the standard shell idioms, like `--`, work as well. +Reading/producing `.ini` files for configuration is also supported, as is using environment variables as input. The base `App` can be subclassed and customized for use in a toolkit (like [GooFit][]). All the standard shell idioms, like `--`, work as well. -CLI11 was developed at the [University of Cincinnati] in support of the [GooFit] library under [NSF Award 1414736][NSF 1414736]. It was featured in a [DIANA/HEP] meeting at CERN. Please give it a try! Feedback is always welcome. +CLI11 was developed at the [University of Cincinnati][] in support of the [GooFit][] library under [NSF Award 1414736][NSF 1414736]. It was featured in a [DIANA/HEP][] meeting at CERN. Please give it a try! Feedback is always welcome. -[GooFit]: https://github.com/GooFit/GooFit -[DIANA/HEP]: http://diana-hep.org -[CLI11]: https://github.com/CLIUtils/CLI11 -[CLI11Tutorial]: https://cliutils.github.io/CLI11/book +[goofit]: https://github.com/GooFit/GooFit +[diana/hep]: https://diana-hep.org +[cli11tutorial]: https://cliutils.github.io/CLI11/book [releases]: https://github.com/CLIUtils/CLI11/releases -[API docs]: https://cliutils.github.io/CLI11 -[README]: https://github.com/CLIUtils/CLI11/blob/master/README.md -[NSF 1414736]: https://nsf.gov/awardsearch/showAward?AWD_ID=1414736 -[University of Cincinnati]: http://www.uc.edu -[Plumbum]: http://plumbum.readthedocs.io/en/latest/ -[Click]: https://click.palletsprojects.com/ - - - +[api docs]: https://cliutils.github.io/CLI11 +[readme]: https://github.com/CLIUtils/CLI11/blob/master/README.md +[nsf 1414736]: https://nsf.gov/awardsearch/showAward?AWD_ID=1414736 +[university of cincinnati]: https://www.uc.edu +[plumbum]: https://plumbum.readthedocs.io/en/latest/ +[click]: https://click.palletsprojects.com diff --git a/packages/CLI11/book/SUMMARY.md b/packages/CLI11/book/SUMMARY.md index cdbc049f444f91b420eaa5cb163b6e3be1e19ad5..38261d52e2c23e55c595824029042fb392271a47 100644 --- a/packages/CLI11/book/SUMMARY.md +++ b/packages/CLI11/book/SUMMARY.md @@ -13,4 +13,3 @@ * [Toolkits](/chapters/toolkits.md) * [Advanced topics](/chapters/advanced-topics.md) * [Internals](/chapters/internals.md) - diff --git a/packages/CLI11/book/chapters/advanced-topics.md b/packages/CLI11/book/chapters/advanced-topics.md index 1a7ef53790226412e0766a737d099c79a81a50ab..fee282165ada96485d6b594f51f5494597c0dc1e 100644 --- a/packages/CLI11/book/chapters/advanced-topics.md +++ b/packages/CLI11/book/chapters/advanced-topics.md @@ -1,6 +1,5 @@ # Advanced topics - ## Environment variables Environment variables can be used to fill in the value of an option: @@ -9,6 +8,7 @@ Environment variables can be used to fill in the value of an option: std::string opt; app.add_option("--my_option", opt)->envname("MY_OPTION"); ``` + If not given on the command line, the environment variable will be checked and read from if it exists. All the standard tools, like default and required, work as expected. If passed on the command line, this will ignore the environment variable. @@ -55,7 +55,7 @@ add_option(CLI::App &app, std::string name, cx &variable, std::string descriptio Then you could use it like this: -``` +```cpp std::complex<double> comp{0, 0}; add_option(app, "-c,--complex", comp); ``` @@ -89,7 +89,6 @@ template <typename T> std::istringstream &operator>>(std::istringstream &in, boo This is an example of how to use the system only; if you are just looking for a way to activate `boost::optional` support on older compilers, you should define `CLI11_BOOST_OPTIONAL` before including a CLI11 file, you'll get the `boost::optional` support. - ## Custom converters and type names: std::chrono example An example of adding a custom converter and typename for `std::chrono` follows: @@ -97,40 +96,40 @@ An example of adding a custom converter and typename for `std::chrono` follows: ```cpp namespace CLI { - template <typename T, typename R> - std::istringstream &operator>>(std::istringstream &in, std::chrono::duration<T,R> &val) - { - T v; - in >> v; - val = std::chrono::duration<T,R>(v); - return in; - } - - template <typename T, typename R> - std::stringstream &operator<<(std::stringstream &in, std::chrono::duration<T,R> &val) - { - in << val.count(); - return in; - } + template <typename T, typename R> + std::istringstream &operator>>(std::istringstream &in, std::chrono::duration<T,R> &val) + { + T v; + in >> v; + val = std::chrono::duration<T,R>(v); + return in; + } + + template <typename T, typename R> + std::stringstream &operator<<(std::stringstream &in, std::chrono::duration<T,R> &val) + { + in << val.count(); + return in; + } } #include <CLI/CLI.hpp> namespace CLI { - namespace detail - { - template <> - constexpr const char *type_name<std::chrono::hours>() - { - return "TIME [H]"; - } - - template <> - constexpr const char *type_name<std::chrono::minutes>() - { - return "TIME [MIN]"; - } + namespace detail + { + template <> + constexpr const char *type_name<std::chrono::hours>() + { + return "TIME [H]"; + } + + template <> + constexpr const char *type_name<std::chrono::minutes>() + { + return "TIME [MIN]"; + } } } ``` diff --git a/packages/CLI11/book/chapters/an-advanced-example.md b/packages/CLI11/book/chapters/an-advanced-example.md index 2b20dde15d37fed0cb05094e7160a8887ef96050..82fe7b71ce1dabe7ee0de41a3b04f7575ac618e3 100644 --- a/packages/CLI11/book/chapters/an-advanced-example.md +++ b/packages/CLI11/book/chapters/an-advanced-example.md @@ -1,7 +1,5 @@ # Making a git clone - - Let's try our hand at a little `git` clone, called `geet`. It will just print it's intent, rather than running actual code, since it's just a demonstration. Let's start by adding an app and requiring 1 subcommand to run: [include:"Intro"](../code/geet.cpp) diff --git a/packages/CLI11/book/chapters/basics.md b/packages/CLI11/book/chapters/basics.md index aa52cffb7be15e0a97b8fef50e081939899578c1..0c55ec4f404a88e65d0f467e20f0f13ead31f025 100644 --- a/packages/CLI11/book/chapters/basics.md +++ b/packages/CLI11/book/chapters/basics.md @@ -24,4 +24,3 @@ Usage: ./a.out [OPTIONS] Options: -h,--help Print this help message and exit ``` - diff --git a/packages/CLI11/book/chapters/config.md b/packages/CLI11/book/chapters/config.md index 219c3d9dcde4cff4fbbcfe41a838446ddd0a7112..df004ce75c17a849a92d897b5dc59b00f7470700 100644 --- a/packages/CLI11/book/chapters/config.md +++ b/packages/CLI11/book/chapters/config.md @@ -5,36 +5,48 @@ You can tell your app to allow configure files with `set_config("--config")`. There are arguments: the first is the option name. If empty, it will clear the config flag. The second item is the default file name. If that is specified, the config will try to read that file. The third item is the help string, with a reasonable default, and the final argument is a boolean (default: false) that indicates that the configuration file is required and an error will be thrown if the file is not found and this is set to true. ### Extra fields + Sometimes configuration files are used for multiple purposes so CLI11 allows options on how to deal with extra fields ```cpp app.allow_config_extras(true); ``` + will allow capture the extras in the extras field of the app. (NOTE: This also sets the `allow_extras` in the app to true) ```cpp app.allow_config_extras(false); ``` + will generate an error if there are any extra fields +for slightly finer control there is a scoped enumeration of the modes or -for slightly finer control there is a scoped enumeration of the modes -or ```cpp app.allow_config_extras(CLI::config_extras_mode::ignore); ``` + will completely ignore extra parameters in the config file. This mode is the default. ```cpp app.allow_config_extras(CLI::config_extras_mode::capture); ``` + will store the unrecognized options in the app extras fields. This option is the closest equivalent to `app.allow_config_extras(true);` with the exception that it does not also set the `allow_extras` flag so using this option without also setting `allow_extras(true)` will generate an error which may or may not be the desired behavior. ```cpp app.allow_config_extras(CLI::config_extras_mode::error); ``` + is equivalent to `app.allow_config_extras(false);` +```cpp +app.allow_config_extras(CLI::config_extras_mode::ignore_all); +``` + +will completely ignore any mismatches, extras, or other issues with the config file + ### Getting the used configuration file name + If it is needed to get the configuration file name used this can be obtained via `app.get_config_ptr()->as<std::string>()` or `app["--config"]->as<std::string>()` assuming `--config` was the configuration option name. @@ -93,7 +105,6 @@ Where X is some positive integer and will allow up to `X` configuration files to To print a configuration file from the passed arguments, use `.config_to_str(default_also=false, write_description=false)`, where `default_also` will also show any defaulted arguments, and `write_description` will include option descriptions and the App description ```cpp - CLI::App app; app.add_option(...); // several other options @@ -105,7 +116,6 @@ To print a configuration file from the passed arguments, use `.config_to_str(def if a prefix is needed to print before the options, for example to print a config for just a subcommand, the config formatter can be obtained directly. ```cpp - auto fmtr=app.get_config_formatter(); //std::string to_config(const App *app, bool default_also, bool write_description, std::string prefix) fmtr->to_config(&app,true,true,"sub."); @@ -113,7 +123,9 @@ if a prefix is needed to print before the options, for example to print a config ``` ### Customization of configure file output -The default config parser/generator has some customization points that allow variations on the TOML format. The default formatter has a base configuration that matches the TOML format. It defines 5 characters that define how different aspects of the configuration are handled + +The default config parser/generator has some customization points that allow variations on the TOML format. The default formatter has a base configuration that matches the TOML format. It defines 5 characters that define how different aspects of the configuration are handled. You must use `get_config_formatter_base()` to have access to these fields + ```cpp /// the character used for comments char commentChar = '#'; @@ -125,16 +137,33 @@ char arrayEnd = ']'; char arraySeparator = ','; /// the character used separate the name from the value char valueDelimiter = '='; +/// the character to use around strings +char stringQuote = '"'; +/// the character to use around single characters +char characterQuote = '\''; +/// the maximum number of layers to allow +uint8_t maximumLayers{255}; +/// the separator used to separator parent layers +char parentSeparatorChar{'.'}; +/// Specify the configuration index to use for arrayed sections +uint16_t configIndex{0}; +/// Specify the configuration section that should be used +std::string configSection; ``` These can be modified via setter functions -- ` ConfigBase *comment(char cchar)` Specify the character to start a comment block -- `ConfigBase *arrayBounds(char aStart, char aEnd)` Specify the start and end characters for an array -- `ConfigBase *arrayDelimiter(char aSep)` Specify the delimiter character for an array -- `ConfigBase *valueSeparator(char vSep)` Specify the delimiter between a name and value +* `ConfigBase *comment(char cchar)`: Specify the character to start a comment block +* `ConfigBase *arrayBounds(char aStart, char aEnd)`: Specify the start and end characters for an array +* `ConfigBase *arrayDelimiter(char aSep)`: Specify the delimiter character for an array +* `ConfigBase *valueSeparator(char vSep)`: Specify the delimiter between a name and value +* `ConfigBase *quoteCharacter(char qString, char qChar)` :specify the characters to use around strings and single characters +* `ConfigBase *maxLayers(uint8_t layers)` : specify the maximum number of parent layers to process. This is useful to limit processing for larger config files +* `ConfigBase *parentSeparator(char sep)` : specify the character to separate parent layers from options +* `ConfigBase *section(const std::string §ionName)` : specify the section name to use to get the option values, only this section will be processed +* `ConfigBase *index(uint16_t sectionIndex)` : specify an index section to use for processing if multiple TOML sections of the same name are present `[[section]]` -For example to specify reading a configure file that used `:` to separate name and values +For example, to specify reading a configure file that used `:` to separate name and values: ```cpp auto config_base=app.get_config_formatter_base(); @@ -142,9 +171,11 @@ config_base->valueSeparator(':'); ``` The default configuration file will read INI files, but will write out files in the TOML format. To specify outputting INI formatted files use + ```cpp app.config_formatter(std::make_shared<CLI::ConfigINI>()); ``` + which makes use of a predefined modification of the ConfigBase class which TOML also uses. If a custom formatter is used that is not inheriting from the from ConfigBase class `get_config_formatter_base() will return a nullptr if RTTI is on (usually the default), or garbage if RTTI is off, so some care must be exercised in its use with custom configurations. ## Custom formats @@ -166,16 +197,39 @@ app.config_formatter(std::make_shared<NewConfig>()); See [`examples/json.cpp`](https://github.com/CLIUtils/CLI11/blob/master/examples/json.cpp) for a complete JSON config example. +### Trivial JSON configuration example + +```JSON +{ + "test": 56, + "testb": "test", + "flag": true +} +``` + +The parser can handle these structures with only a minor tweak + +```cpp +app.get_config_formatter_base()->valueSeparator(':'); +``` + +The open and close brackets must be on a separate line and the comma gets interpreted as an array separator but since no values are after the comma they get ignored as well. This will not support multiple layers or sections or any other moderately complex JSON, but can work if the input file is simple. ## Triggering Subcommands -Configuration files can be used to trigger subcommands if a subcommand is set to configure. By default configuration file just set the default values of a subcommand. But if the `configure()` option is set on a subcommand then the if the subcommand is utilized via a `[subname]` block in the configuration file it will act as if it were called from the command line. Subsubcommands can be triggered via [subname.subsubname]. Using the `[[subname]]` will be as if the subcommand were triggered multiple times from the command line. This functionality can allow the configuration file to act as a scripting file. + +Configuration files can be used to trigger subcommands if a subcommand is set to configure. By default configuration file just set the default values of a subcommand. But if the `configure()` option is set on a subcommand then the if the subcommand is utilized via a `[subname]` block in the configuration file it will act as if it were called from the command line. Subsubcommands can be triggered via `[subname.subsubname]`. Using the `[[subname]]` will be as if the subcommand were triggered multiple times from the command line. This functionality can allow the configuration file to act as a scripting file. For custom configuration files this behavior can be triggered by specifying the parent subcommands in the structure and `++` as the name to open a new subcommand scope and `--` to close it. These names trigger the different callbacks of configurable subcommands. +## Stream parsing + +In addition to the regular parse functions a `parse_from_stream(std::istream &input)` is available to directly parse a stream operator. For example to process some arguments in an already open file stream. The stream is fed directly in the config parser so bypasses the normal command line parsing. + ## Implementation Notes -The config file input works with any form of the option given: Long, short, positional, or the environment variable name. When generating a config file it will create a name in following priority. -1. First long name -1. Positional name -1. First short name -1. Environment name +The config file input works with any form of the option given: Long, short, positional, or the environment variable name. When generating a config file it will create an option name in following priority. + +1. First long name +2. Positional name +3. First short name +4. Environment name diff --git a/packages/CLI11/book/chapters/flags.md b/packages/CLI11/book/chapters/flags.md index d4f88fb6895a9d5402e14e6c65e2975311f94e53..aa920fe8572c3e70079201a033db28b8add89aff 100644 --- a/packages/CLI11/book/chapters/flags.md +++ b/packages/CLI11/book/chapters/flags.md @@ -11,8 +11,7 @@ bool my_flag{false}; app.add_flag("-f", my_flag, "Optional description"); ``` -This will bind the flag `-f` to the boolean `my_flag`. After the parsing step, `my_flag` will be `false` if the flag was not found on the command line, or `true` if it was. By default, it will be allowed any number of times, but if you explicitly[^1] request `->take_last(false)`, it will only be allowed once; passing something like `./my_app -f -f` or `./my_app -ff` will throw a `ParseError` with a nice help description. - +This will bind the flag `-f` to the boolean `my_flag`. After the parsing step, `my_flag` will be `false` if the flag was not found on the command line, or `true` if it was. By default, it will be allowed any number of times, but if you explicitly\[^1\] request `->take_last(false)`, it will only be allowed once; passing something like `./my_app -f -f` or `./my_app -ff` will throw a `ParseError` with a nice help description. A flag name may start with any character except ('-', ' ', '\n', and '!'). For long flags, after the first character all characters are allowed except ('=',':','{',' ', '\n'). Names are given as a comma separated string, with the dash or dashes. An flag can have as many names as you want, and afterward, using `count`, you can use any of the names, with dashes as needed. ## Integer flags @@ -65,12 +64,11 @@ After parsing, you can use `my_flag->count()` to count the number of times this If you want to define a callback that runs when you make a flag, you can use `add_flag_function` (C++11 or newer) or `add_flag` (C++14 or newer only) to add a callback function. The function should have the signature `void(std::size_t)`. This could be useful for a version printout, etc. -``` +```cpp auto callback = [](int count){std::cout << "This was called " << count << " times";}; app.add_flag_function("-c", callback, "Optional description"); ``` - ## Aliases The name string, the first item of every `add_` method, can contain as many short and long names as you want, separated by commas. For example, `"-a,--alpha,-b,--beta"` would allow any of those to be recognized on the command line. If you use the same name twice, or if you use the same name in multiple flags, CLI11 will immediately throw a `CLI::ConstructionError` describing your problem (it will not wait until the parsing step). @@ -122,5 +120,4 @@ Flag int: 3 Flag plain: 1 ``` - -[^1] It will not inherit this from the parent defaults, since this is often useful even if you don't want all options to allow multiple passed options. +\[^1\]: It will not inherit this from the parent defaults, since this is often useful even if you don't want all options to allow multiple passed options. diff --git a/packages/CLI11/book/chapters/formatting.md b/packages/CLI11/book/chapters/formatting.md index 66dd228da893faa11fca64c3524b7e137bb3022a..f9cd36fa49d875ec3c0741e03e2b6014f5723667 100644 --- a/packages/CLI11/book/chapters/formatting.md +++ b/packages/CLI11/book/chapters/formatting.md @@ -4,13 +4,12 @@ New in CLI11 1.6 {% endhint %} -## Customizing an existing formatter +## Customizing an existing formatter In CLI11, you can control the output of the help printout in full or in part. The default formatter was written in such a way as to be customizable. You can use `app.get_formatter()` to get the current formatter. The formatter you set will be inherited by subcommands that are created after you set the formatter. There are several configuration options that you can set: - | Set method | Description | Availability | |------------|-------------|--------------| | `column_width(width)` | The width of the columns | Both | @@ -41,7 +40,7 @@ Look at the class definitions in `FormatterFwd.hpp` or the method definitions in This is a normal printout, with `<>` indicating the methods used to produce each line. -``` +```text <make_description(app)> <make_usage(app)> <make_positionals(app)> @@ -60,7 +59,7 @@ This is a normal printout, with `<>` indicating the methods used to produce each The `make_groups` print the group name then call `make_option(o)` on the options listed in that group. The normal printout for an option looks like this: -``` +```text make_option_opts(o) ┌───┴────┐ -n,--name (REQUIRED) This is a description @@ -72,7 +71,3 @@ Notes: * `*1`: This signature depends on whether the call is from a positional or optional. * `o` is opt pointer, `p` is true if positional. - - - - diff --git a/packages/CLI11/book/chapters/internals.md b/packages/CLI11/book/chapters/internals.md index 17bc54aa34bebd52a14f09fca5afa306ec20554e..1551c79c659bb91610448b762c13d42ea1f08831 100644 --- a/packages/CLI11/book/chapters/internals.md +++ b/packages/CLI11/book/chapters/internals.md @@ -41,5 +41,3 @@ The `_process` procedure runs the following steps; each step is recursive and co ## Exceptions The library immediately returns a C++ exception when it detects a problem, such as an incorrect construction or a malformed command line. - - diff --git a/packages/CLI11/book/chapters/options.md b/packages/CLI11/book/chapters/options.md index 04bfd0bbd2f0a04e683d5f31a9df9f1bf2e79e00..8b1daa0fffe546677b3b8f40b280e7961c95dc69 100644 --- a/packages/CLI11/book/chapters/options.md +++ b/packages/CLI11/book/chapters/options.md @@ -1,37 +1,37 @@ # Options ## Simple options -The most versatile addition to a command line program is a option. This is like a flag, but it takes an argument. CLI11 handles all the details for many types of options for you, based on their type. To add an option: +The most versatile addition to a command line program is a option. This is like a flag, but it takes an argument. CLI11 handles all the details for many types of options for you, based on their type. To add an option: ```cpp int int_option{0}; app.add_option("-i", int_option, "Optional description"); ``` -This will bind the option `-i` to the integer `int_option`. On the command line, a single value that can be converted to an integer will be expected. Non-integer results will fail. If that option is not given, CLI11 will not touch the initial value. This allows you to set up defaults by simply setting your value beforehand. If you want CLI11 to display your default value, you can add the optional final argument `true` when you add the option. +This will bind the option `-i` to the integer `int_option`. On the command line, a single value that can be converted to an integer will be expected. Non-integer results will fail. If that option is not given, CLI11 will not touch the initial value. This allows you to set up defaults by simply setting your value beforehand. If you want CLI11 to display your default value, you can add `->capture_default_str()` after the option. ```cpp int int_option{0}; -app.add_option("-i", int_option, "Optional description", true); +app.add_option("-i", int_option, "Optional description")->capture_default_str(); ``` You can use any C++ int-like type, not just `int`. CLI11 understands the following categories of types: | Type | CLI11 | |-------------|-------| -| number like | Integers, floats, bools, or any type that can be constructed from an integer or floating point number | +| number like | Integers, floats, bools, or any type that can be constructed from an integer or floating point number. Accepts common numerical strings like `0xFF` as well as octal, and decimal | | string-like | std\::string, or anything that can be constructed from or assigned a std\::string | | char | For a single char, single string values are accepted, otherwise longer strings are treated as integral values and a conversion is attempted | | complex-number | std::complex or any type which has a real(), and imag() operations available, will allow 1 or 2 string definitions like "1+2j" or two arguments "1","2" | | enumeration | any enum or enum class type is supported through conversion from the underlying type(typically int, though it can be specified otherwise) | | container-like | a container(like vector) of any available types including other containers | -| wrapper | any other object with a `value_type` static definition where the type specified by `value_type` is one of type in this list | +| wrapper | any other object with a `value_type` static definition where the type specified by `value_type` is one of the type in this list, including `std::atomic<>` | | tuple | a tuple, pair, or array, or other type with a tuple size and tuple_type operations defined and the members being a type contained in this list | | function | A function that takes an array of strings and returns a string that describes the conversion failure or empty for success. May be the empty function. (`{}`) | | streamable | any other type with a `<<` operator will also work | -By default, CLI11 will assume that an option is optional, and one value is expected if you do not use a vector. You can change this on a specific option using option modifiers. +By default, CLI11 will assume that an option is optional, and one value is expected if you do not use a vector. You can change this on a specific option using option modifiers. An option name may start with any character except ('-', ' ', '\n', and '!'). For long options, after the first character all characters are allowed except ('=',':','{',' ', '\n'). Names are given as a comma separated string, with the dash or dashes. An option can have as many names as you want, and afterward, using `count`, you can use any of the names, with dashes as needed, to count the options. One of the names is allowed to be given without proceeding dash(es); if present the option is a positional option, and that name will be used on the help line for its positional form. ## Positional options and aliases @@ -72,29 +72,38 @@ Vectors will be replaced by the parsed content if the option is given on the com A definition of a container for purposes of CLI11 is a type with a `end()`, `insert(...)`, `clear()` and `value_type` definitions. This includes `vector`, `set`, `deque`, `list`, `forward_iist`, `map`, `unordered_map` and a few others from the standard library, and many other containers from the boost library. -### containers of containers +### Containers of containers + Containers of containers are also supported. + ```cpp std::vector<std::vector<int>> int_vec; app.add_option("--vec", int_vec, "My vector of vectors option"); ``` + CLI11 inserts a separator sequence at the start of each argument call to separate the vectors. So unless the separators are injected as part of the command line each call of the option on the command line will result in a separate element of the outer vector. This can be manually controlled via `inject_separator(true|false)` but in nearly all cases this should be left to the defaults. To insert of a separator from the command line add a `%%` where the separation should occur. -``` + +```bash cmd --vec_of_vec 1 2 3 4 %% 1 2 ``` + would then result in a container of size 2 with the first element containing 4 values and the second 2. This separator is also the only way to get values into something like + ```cpp std::pair<std::vector<int>,std::vector<int>> two_vecs; app.add_option("--vec", two_vecs, "pair of vectors"); ``` + without calling the argument twice. Further levels of nesting containers should compile but intermediate layers will only have a single element in the container, so is probably not that useful. ### Nested types -Types can be nested For example + +Types can be nested. For example: + ```cpp std::map<int, std::pair<int,std::string>> map; app.add_option("--dict", map, "map of pairs"); @@ -106,6 +115,7 @@ will require 3 arguments for each invocation, and multiple sets of 3 arguments c std::map<int, std::pair<int,std::vector<std::string>>> map; app.add_option("--dict", map, "map of pairs"); ``` + will result in a requirement for 2 integers on each invocation and absorb an unlimited number of strings including 0. ## Option modifiers @@ -119,8 +129,8 @@ When you call `add_option`, you get a pointer to the added option. You can use t | `->expected(Nmin,Nmax)` | Take between `Nmin` and `Nmax` values. | | `->type_size(N)` | specify that each block of values would consist of N elements | | `->type_size(Nmin,Nmax)` | specify that each block of values would consist of between Nmin and Nmax elements | -| `->needs(opt)` | This option requires another option to also be present, opt is an `Option` pointer. | -| `->excludes(opt)` | This option cannot be given with `opt` present, opt is an `Option` pointer. | +| `->needs(opt)` | This option requires another option to also be present, opt is an `Option` pointer or a string with the name of the option. Can be removed with `->remove_needs(opt)` | +| `->excludes(opt)` | This option cannot be given with `opt` present, opt is an `Option` pointer or a string with the name of the option. Can be removed with `->remove_excludes(opt)` | | `->envname(name)` | Gets the value from the environment if present and not passed on the command line. | | `->group(name)` | The help group to put the option in. No effect for positional options. Defaults to `"Options"`. `"Hidden"` will not show up in the help print. | | `->description(string)` | Set/change the description | @@ -139,8 +149,14 @@ When you call `add_option`, you get a pointer to the added option. You can use t | `->transform(Validator)` | Run a transforming validator on each value passed. See [Validators](./validators.md) for more info | | `->each(void(std::string))` | Run a function on each parsed value, *in order*. | | `->default_str(string)` | set a default string for use in the help and as a default value if no arguments are passed and a value is requested | -| `->default_function(string())` | Advanced: Change the function that `capture_default_str()` uses. | +| `->default_function(std::string())` | Advanced: Change the function that `capture_default_str()` uses. | | `->default_val(value)` | Generate the default string from a value and validate that the value is also valid. For options that assign directly to a value type the value in that type is also updated. Value must be convertible to a string(one of known types or have a stream operator). | +| `->capture_default_str()` | Store the current value attached and display it in the help string. | +| `->always_capture_default()` | Always run `capture_default_str()` when creating new options. Only useful on an App's `option_defaults`. | +| `->run_callback_for_default()` | Force the option callback to be executed or the variable set when the `default_val` is used. | +| `->force_callback()` | Force the option callback to be executed regardless of whether the option was used or not. Will use the default_str if available, if no default is given the callback will be executed with an empty string as an argument, which will translate to a default initialized value, which can be compiler dependent | +|`->trigger_on_parse()` | Have the option callback be triggered when the value is parsed vs. at the end of all parsing, the option callback can potentially be executed multiple times. Generally only useful if you have a user defined callback or validation check. Or potentially if a vector input is given multiple times as it will clear the results when a repeat option is given via command line. It will trigger the callbacks once per option call on the command line| +| `->option_text(string)` | Sets the text between the option name and description. | The `->check(...)` and `->transform(...)` modifiers can also take a callback function of the form `bool function(std::string)` that runs on every value that the option receives, and returns a value that tells CLI11 whether the check passed or failed. @@ -161,19 +177,19 @@ if(* opt) One of CLI11's systems to allow customizability without high levels of verbosity is the inheritance system. You can set default values on the parent `App`, and all options and subcommands created from it remember the default values at the point of creation. The default value for Options, specifically, are accessible through the `option_defaults()` method. There are a number of settings that can be set and inherited: -* `group`: The group name starts as "Options" -* `required`: If the option must be given. Defaults to `false`. Is ignored for flags. -* `multi_option_policy`: What to do if several copies of an option are passed and one value is expected. Defaults to `CLI::MultiOptionPolicy::Throw`. This is also used for bool flags, but they always are created with the value `CLI::MultiOptionPolicy::TakeLast` regardless of the default, so that multiple bool flags does not cause an error. But you can override that flag by flag. -* `ignore_case`: Allow any mixture of cases for the option or flag name -* `ignore_underscore`: Allow any number of underscores in the option or flag name -* `configurable`: Specify whether an option can be configured through a config file -* `disable_flag_override`: do not allow flag values to be overridden on the command line -* `always_capture_default`: specify that the default values should be automatically captured. -* `delimiter`: A delimiter to use for capturing multiple values in a single command line string (e.g. --flag="flag,-flag2,flag3") +* `group`: The group name starts as "Options" +* `required`: If the option must be given. Defaults to `false`. Is ignored for flags. +* `multi_option_policy`: What to do if several copies of an option are passed and one value is expected. Defaults to `CLI::MultiOptionPolicy::Throw`. This is also used for bool flags, but they always are created with the value `CLI::MultiOptionPolicy::TakeLast` regardless of the default, so that multiple bool flags does not cause an error. But you can override that flag by flag. +* `ignore_case`: Allow any mixture of cases for the option or flag name +* `ignore_underscore`: Allow any number of underscores in the option or flag name +* `configurable`: Specify whether an option can be configured through a config file +* `disable_flag_override`: do not allow flag values to be overridden on the command line +* `always_capture_default`: specify that the default values should be automatically captured. +* `delimiter`: A delimiter to use for capturing multiple values in a single command line string (e.g. --flag="flag,-flag2,flag3") An example of usage: -``` +```cpp app.option_defaults()->ignore_case()->group("Required"); app.add_flag("--CaSeLeSs"); @@ -182,17 +198,16 @@ app.get_group() // is "Required" Groups are mostly for visual organization, but an empty string for a group name will hide the option. - ### Windows style options You can also set the app setting `app->allow_windows_style_options()` to allow windows style options to also be recognized on the command line: -* `/a` (flag) -* `/f filename` (option) -* `/long` (long flag) -* `/file filename` (space) -* `/file:filename` (colon) -* `/long_flag:false` (long flag with : to override the default value) +* `/a` (flag) +* `/f filename` (option) +* `/long` (long flag) +* `/file filename` (space) +* `/file:filename` (colon) +* `/long_flag:false` (long flag with : to override the default value) Windows style options do not allow combining short options or values not separated from the short option like with `-` options. You still specify option names in the same manner as on Linux with single and double dashes when you use the `add_*` functions, and the Linux style on the command line will still work. If a long and a short option share the same name, the option will match on the first one defined. @@ -200,15 +215,19 @@ Windows style options do not allow combining short options or values not separat How an option and its arguments are parsed depends on a set of controls that are part of the option structure. In most circumstances these controls are set automatically based on the type or function used to create the option and the type the arguments are parsed into. The variables define the size of the underlying type (essentially how many strings make up the type), the expected size (how many groups are expected) and a flag indicating if multiple groups are allowed with a single option. And these interact with the `multi_option_policy` when it comes time to parse. -### examples -How options manage this is best illustrated through some examples +### Examples + +How options manage this is best illustrated through some examples. + ```cpp std::string val; app.add_option("--opt",val,"description"); ``` + creates an option that assigns a value to a `std::string` When this option is constructed it sets a type_size min and max of 1. Meaning that the assignment uses a single string. The Expected size is also set to 1 by default, and `allow_extra_args` is set to false. meaning that each time this option is called 1 argument is expected. This would also be the case if val were a `double`, `int` or any other single argument types. now for example + ```cpp std::pair<int, std::string> val; app.add_option("--opt",val,"description"); @@ -227,6 +246,7 @@ detects a type size of 1, since the underlying element type is a single string, std::vector<std::tuple<int, double, std::string>> val; app.add_option("--opt",val,"description"); ``` + gets into the complicated cases where the type size is now 3. and the expected max is set to a large number and `allow_extra_args` is set to true. In this case at least 3 arguments are required to follow the option, and subsequent groups must come in groups of three, otherwise an error will result. ```cpp @@ -243,11 +263,11 @@ app.add_option("--opt",val,"description"); triggers the complex number type which has a min of 1 and max of 2, so 1 or 2 strings can be passed. Complex number conversion supports arguments of the form "1+2j" or "1","2", or "1" "2i". The imaginary number symbols `i` and `j` are interchangeable in this context. - ```cpp std::vector<std::vector<int>> val; app.add_option("--opt",val,"description"); ``` + has a type size of 1 to (1<<30). ### Customization @@ -259,11 +279,13 @@ std::string val; auto opt=app.add_flag("--opt{vvv}",val,"description"); opt->expected(0,1); ``` + will create a hybrid option, that can exist on its own in which case the value "vvv" is used or if a value is given that value will be used. -There are some additional options that can be specified to modify an option for specific cases -- `->run_callback_for_default()` will specify that the callback should be executed when a default_val is set. This is set automatically when appropriate though it can be turned on or off and any user specified callback for an option will be executed when the default value for an option is set. +There are some additional options that can be specified to modify an option for specific cases: + +* `->run_callback_for_default()` will specify that the callback should be executed when a default_val is set. This is set automatically when appropriate though it can be turned on or off and any user specified callback for an option will be executed when the default value for an option is set. ## Unusual circumstances -There are a few cases where some things break down in the type system managing options and definitions. Using the `add_option` method defines a lambda function to extract a default value if required. In most cases this either straightforward or a failure is detected automatically and handled. But in a few cases a streaming template is available that several layers down may not actually be defined. The conditions in CLI11 cannot detect this circumstance automatically and will result in compile error. One specific known case is `boost::optional` if the boost optional_io header is included. This header defines a template for all boost optional values even if they do no actually have a streaming operator. For example `boost::optional<std::vector>` does not have a streaming operator but one is detected since it is part of a template. For these cases a secondary method `app->add_option_no_stream(...)` is provided that bypasses this operation completely and should compile in these cases. +There are a few cases where some things break down in the type system managing options and definitions. Using the `add_option` method defines a lambda function to extract a default value if required. In most cases this is either straightforward or a failure is detected automatically and handled. But in a few cases a streaming template is available that several layers down may not actually be defined. This results in CLI11 not being able to detect this circumstance automatically and will result in compile error. One specific known case is `boost::optional` if the boost optional_io header is included. This header defines a template for all boost optional values even if they do not actually have a streaming operator. For example `boost::optional<std::vector>` does not have a streaming operator but one is detected since it is part of a template. For these cases a secondary method `app->add_option_no_stream(...)` is provided that bypasses this operation completely and should compile in these cases. diff --git a/packages/CLI11/book/chapters/subcommands.md b/packages/CLI11/book/chapters/subcommands.md index 230cca880040931ee41a590832f58908cd6b5697..585f91c72cf26cbe0d5b101f562b507d6029f8c4 100644 --- a/packages/CLI11/book/chapters/subcommands.md +++ b/packages/CLI11/book/chapters/subcommands.md @@ -7,7 +7,6 @@ C++ application, though the system git uses to extend the main command by callin in separate executables is supported too; that's called "Prefix commands" and is included at the end of this chapter. - ## The parent App We'll start by discussing the parent `App`. You've already used it quite a bit, to create @@ -18,7 +17,6 @@ You can replace the default help print when a `ParseError` is thrown with `app.s The default is `CLI:::FailureMessage::simple`, and you can easily define a new one. Just make a (lambda) function that takes an App pointer and a reference to an error code (even if you don't use them), and returns a string. - ## Adding a subcommand Subcommands can be added just like an option: @@ -55,6 +53,7 @@ Each App has controls to set the number of subcommands you expect. This is contr ```cpp app.require_subcommand(/* min */ 0, /* max */ 1); ``` + If you set the max to 0, CLI11 will allow an unlimited number of subcommands. After the (non-unlimited) maximum is reached, CLI11 will stop trying to match subcommands. So the if you pass "`one two`" to a command, and both `one` and `two` are subcommands, it will depend on the maximum number as to whether the "`two`" is a subcommand or an argument to the diff --git a/packages/CLI11/book/chapters/toolkits.md b/packages/CLI11/book/chapters/toolkits.md index 3b8e93a1ff78f6421f287bb4ce350e36dd2ca68e..4c276387a5469fab86e957720fc7294cc2db4e4c 100644 --- a/packages/CLI11/book/chapters/toolkits.md +++ b/packages/CLI11/book/chapters/toolkits.md @@ -2,7 +2,7 @@ CLI11 was designed to be integrate into a toolkit, providing a native experience for users. This was used in GooFit to provide `GooFit::Application`, an class designed to make ROOT users feel at home. -# Custom namespace +## Custom namespace If you want to provide CLI11 in a custom namespace, you'll want to at least put `using CLI::App` in your namespace. You can also include Option, some errors, and validators. You can also put `using namespace CLI` inside your namespace to import everything. @@ -17,14 +17,12 @@ You may also want to make your own copy of the `CLI11_PARSE` macro. Something li } ``` - -# Subclassing App +## Subclassing App If you subclass `App`, you'll just need to do a few things. You'll need a constructor; calling the base `App` constructor is a good idea, but not necessary (it just sets a description and adds a help flag. You can call anything you would like to configure in the constructor, like `option_defaults()->take_last()` or `fallthrough()`, and it will be set on all user instances. You can add flags and options, as well. - -# Virtual functions provided +## Virtual functions provided You are given a few virtual functions that you can change (only on the main App). `pre_callback` runs right before the callbacks run, letting you print out custom messages at the top of your app. diff --git a/packages/CLI11/book/chapters/validators.md b/packages/CLI11/book/chapters/validators.md index 6266a9cae4e8e7394370b8877fde2679a4f05548..06e42c80a0f82a25f94a9329be65550fca92233d 100644 --- a/packages/CLI11/book/chapters/validators.md +++ b/packages/CLI11/book/chapters/validators.md @@ -49,7 +49,6 @@ The built-in validators for CLI11 are: | `NonexistentPath` | Check for an non-existing path | | `Range(min=0, max)` | Produce a range (factory). Min and max are inclusive. | - And, the protected members that you can set when you make your own are: | Type | Member | Description | @@ -60,5 +59,3 @@ And, the protected members that you can set when you make your own are: | `int` (`-1`) | `application_index_` | The element this validator applies to (-1 for all) | | `bool` (`true`) | `active_` | This can be disabled | | `bool` (`false`) | `non_modifying_` | Specify that this is a Validator instead of a Transformer | - - diff --git a/packages/CLI11/book/code/CMakeLists.txt b/packages/CLI11/book/code/CMakeLists.txt index b91a9c7bbfc95a8515868b75bc9fe22e72027efe..987d53c4988a151977ed3f409215c2a2b706af2c 100644 --- a/packages/CLI11/book/code/CMakeLists.txt +++ b/packages/CLI11/book/code/CMakeLists.txt @@ -12,27 +12,21 @@ enable_testing() # Quick function to add the base executable function(add_cli_exe NAME) - add_executable(${NAME} ${NAME}.cpp) - target_link_libraries(${NAME} CLI11::CLI11) + add_executable(${NAME} ${NAME}.cpp) + target_link_libraries(${NAME} CLI11::CLI11) endfunction() - add_cli_exe(simplest) add_test(NAME simplest COMMAND simplest) - add_cli_exe(intro) add_test(NAME intro COMMAND intro) add_test(NAME intro_p COMMAND intro -p 5) - add_cli_exe(flags) add_test(NAME flags COMMAND flags) add_test(NAME flags_bip COMMAND flags -b -i -p) - add_cli_exe(geet) -add_test(NAME geet_add COMMAND geet add) +add_test(NAME geet_add COMMAND geet add) add_test(NAME geet_commit COMMAND geet commit -m "Test") - - diff --git a/packages/CLI11/cmake/CLI11GeneratePkgConfig.cmake b/packages/CLI11/cmake/CLI11GeneratePkgConfig.cmake index 667705d2a7a53d6b78ae6d17413cc77a90b8a3cd..5abb03d165f908d73ba0c85b97dd156483100778 100644 --- a/packages/CLI11/cmake/CLI11GeneratePkgConfig.cmake +++ b/packages/CLI11/cmake/CLI11GeneratePkgConfig.cmake @@ -1,6 +1,3 @@ configure_file("cmake/CLI11.pc.in" "CLI11.pc" @ONLY) -install(FILES "${PROJECT_BINARY_DIR}/CLI11.pc" - DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig") - - +install(FILES "${PROJECT_BINARY_DIR}/CLI11.pc" DESTINATION "${CMAKE_INSTALL_DATADIR}/pkgconfig") diff --git a/packages/CLI11/cmake/CodeCoverage.cmake b/packages/CLI11/cmake/CodeCoverage.cmake index 907cf72d01094f84568dd0e214ab8942e980c24a..e011ef1342773b14b1e8dedebb3e3e83e4161f3c 100644 --- a/packages/CLI11/cmake/CodeCoverage.cmake +++ b/packages/CLI11/cmake/CodeCoverage.cmake @@ -69,57 +69,51 @@ include(CMakeParseArguments) # Check prereqs -find_program( GCOV_PATH gcov ) -find_program( LCOV_PATH NAMES lcov lcov.bat lcov.exe lcov.perl) -find_program( GENHTML_PATH NAMES genhtml genhtml.perl genhtml.bat ) -find_program( GCOVR_PATH gcovr PATHS ${CMAKE_SOURCE_DIR}/scripts/test) -find_program( SIMPLE_PYTHON_EXECUTABLE python ) +find_program(GCOV_PATH gcov) +find_program(LCOV_PATH NAMES lcov lcov.bat lcov.exe lcov.perl) +find_program(GENHTML_PATH NAMES genhtml genhtml.perl genhtml.bat) +find_program(GCOVR_PATH gcovr PATHS ${CMAKE_SOURCE_DIR}/scripts/test) +find_program(SIMPLE_PYTHON_EXECUTABLE python) if(NOT GCOV_PATH) - message(FATAL_ERROR "gcov not found! Aborting...") + message(FATAL_ERROR "gcov not found! Aborting...") endif() # NOT GCOV_PATH if("${CMAKE_CXX_COMPILER_ID}" MATCHES "(Apple)?[Cc]lang") - if("${CMAKE_CXX_COMPILER_VERSION}" VERSION_LESS 3) - message(FATAL_ERROR "Clang version must be 3.0.0 or greater! Aborting...") - endif() + if("${CMAKE_CXX_COMPILER_VERSION}" VERSION_LESS 3) + message(FATAL_ERROR "Clang version must be 3.0.0 or greater! Aborting...") + endif() elseif(NOT CMAKE_COMPILER_IS_GNUCXX) - message(FATAL_ERROR "Compiler is not GNU gcc! Aborting...") + message(FATAL_ERROR "Compiler is not GNU gcc! Aborting...") endif() -set(COVERAGE_COMPILER_FLAGS "-g -O0 --coverage -fprofile-arcs -ftest-coverage -fno-inline -fno-inline-small-functions -fno-default-inline" +set(COVERAGE_COMPILER_FLAGS + "-g -O0 --coverage -fprofile-arcs -ftest-coverage -fno-inline -fno-inline-small-functions -fno-default-inline" CACHE INTERNAL "") set(CMAKE_CXX_FLAGS_COVERAGE ${COVERAGE_COMPILER_FLAGS} - CACHE STRING "Flags used by the C++ compiler during coverage builds." - FORCE ) + CACHE STRING "Flags used by the C++ compiler during coverage builds." FORCE) set(CMAKE_C_FLAGS_COVERAGE ${COVERAGE_COMPILER_FLAGS} - CACHE STRING "Flags used by the C compiler during coverage builds." - FORCE ) + CACHE STRING "Flags used by the C compiler during coverage builds." FORCE) set(CMAKE_EXE_LINKER_FLAGS_COVERAGE "" - CACHE STRING "Flags used for linking binaries during coverage builds." - FORCE ) + CACHE STRING "Flags used for linking binaries during coverage builds." FORCE) set(CMAKE_SHARED_LINKER_FLAGS_COVERAGE "" - CACHE STRING "Flags used by the shared libraries linker during coverage builds." - FORCE ) -mark_as_advanced( - CMAKE_CXX_FLAGS_COVERAGE - CMAKE_C_FLAGS_COVERAGE - CMAKE_EXE_LINKER_FLAGS_COVERAGE - CMAKE_SHARED_LINKER_FLAGS_COVERAGE ) + CACHE STRING "Flags used by the shared libraries linker during coverage builds." FORCE) +mark_as_advanced(CMAKE_CXX_FLAGS_COVERAGE CMAKE_C_FLAGS_COVERAGE CMAKE_EXE_LINKER_FLAGS_COVERAGE + CMAKE_SHARED_LINKER_FLAGS_COVERAGE) if(NOT CMAKE_BUILD_TYPE STREQUAL "Debug") - message(WARNING "Code coverage results with an optimised (non-Debug) build may be misleading") + message(WARNING "Code coverage results with an optimised (non-Debug) build may be misleading") endif() # NOT CMAKE_BUILD_TYPE STREQUAL "Debug" if(CMAKE_C_COMPILER_ID STREQUAL "GNU") - link_libraries(gcov) + link_libraries(gcov) else() - set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --coverage") + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --coverage") endif() # Defines a target for running and collection code coverage information @@ -134,54 +128,57 @@ endif() # ) function(SETUP_TARGET_FOR_COVERAGE) - set(options NONE) - set(oneValueArgs NAME) - set(multiValueArgs EXECUTABLE EXECUTABLE_ARGS DEPENDENCIES) - cmake_parse_arguments(Coverage "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) - - if(NOT LCOV_PATH) - message(FATAL_ERROR "lcov not found! Aborting...") - endif() # NOT LCOV_PATH - - if(NOT GENHTML_PATH) - message(FATAL_ERROR "genhtml not found! Aborting...") - endif() # NOT GENHTML_PATH - - # Setup target - add_custom_target(${Coverage_NAME} - - # Cleanup lcov - COMMAND ${LCOV_PATH} --directory . --zerocounters - # Create baseline to make sure untouched files show up in the report - COMMAND ${LCOV_PATH} -c -i -d . -o ${Coverage_NAME}.base - - # Run tests - COMMAND ${Coverage_EXECUTABLE} - - # Capturing lcov counters and generating report - COMMAND ${LCOV_PATH} --directory . --capture --output-file ${Coverage_NAME}.info - # add baseline counters - COMMAND ${LCOV_PATH} -a ${Coverage_NAME}.base -a ${Coverage_NAME}.info --output-file ${Coverage_NAME}.total - COMMAND ${LCOV_PATH} --remove ${Coverage_NAME}.total ${COVERAGE_EXCLUDES} --output-file ${PROJECT_BINARY_DIR}/${Coverage_NAME}.info.cleaned - COMMAND ${GENHTML_PATH} -o ${Coverage_NAME} ${PROJECT_BINARY_DIR}/${Coverage_NAME}.info.cleaned - COMMAND ${CMAKE_COMMAND} -E remove ${Coverage_NAME}.base ${Coverage_NAME}.total ${PROJECT_BINARY_DIR}/${Coverage_NAME}.info.cleaned - - WORKING_DIRECTORY ${PROJECT_BINARY_DIR} - DEPENDS ${Coverage_DEPENDENCIES} - COMMENT "Resetting code coverage counters to zero.\nProcessing code coverage counters and generating report." - ) - - # Show where to find the lcov info report - add_custom_command(TARGET ${Coverage_NAME} POST_BUILD - COMMAND ; - COMMENT "Lcov code coverage info report saved in ${Coverage_NAME}.info." - ) - - # Show info where to find the report - add_custom_command(TARGET ${Coverage_NAME} POST_BUILD - COMMAND ; - COMMENT "Open ./${Coverage_NAME}/index.html in your browser to view the coverage report." - ) + set(options NONE) + set(oneValueArgs NAME) + set(multiValueArgs EXECUTABLE EXECUTABLE_ARGS DEPENDENCIES) + cmake_parse_arguments(Coverage "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + if(NOT LCOV_PATH) + message(FATAL_ERROR "lcov not found! Aborting...") + endif() # NOT LCOV_PATH + + if(NOT GENHTML_PATH) + message(FATAL_ERROR "genhtml not found! Aborting...") + endif() # NOT GENHTML_PATH + + # Setup target + add_custom_target( + ${Coverage_NAME} + # Cleanup lcov + COMMAND ${LCOV_PATH} --directory . --zerocounters + # Create baseline to make sure untouched files show up in the report + COMMAND ${LCOV_PATH} -c -i -d . -o ${Coverage_NAME}.base + # Run tests + COMMAND ${Coverage_EXECUTABLE} + # Capturing lcov counters and generating report + COMMAND ${LCOV_PATH} --directory . --capture --output-file ${Coverage_NAME}.info + # add baseline counters + COMMAND ${LCOV_PATH} -a ${Coverage_NAME}.base -a ${Coverage_NAME}.info --output-file + ${Coverage_NAME}.total + COMMAND ${LCOV_PATH} --remove ${Coverage_NAME}.total ${COVERAGE_EXCLUDES} --output-file + ${PROJECT_BINARY_DIR}/${Coverage_NAME}.info.cleaned + COMMAND ${GENHTML_PATH} -o ${Coverage_NAME} ${PROJECT_BINARY_DIR}/${Coverage_NAME}.info.cleaned + COMMAND ${CMAKE_COMMAND} -E remove ${Coverage_NAME}.base ${Coverage_NAME}.total + ${PROJECT_BINARY_DIR}/${Coverage_NAME}.info.cleaned + WORKING_DIRECTORY ${PROJECT_BINARY_DIR} + DEPENDS ${Coverage_DEPENDENCIES} + COMMENT + "Resetting code coverage counters to zero.\nProcessing code coverage counters and generating report." + ) + + # Show where to find the lcov info report + add_custom_command( + TARGET ${Coverage_NAME} + POST_BUILD + COMMAND ; + COMMENT "Lcov code coverage info report saved in ${Coverage_NAME}.info.") + + # Show info where to find the report + add_custom_command( + TARGET ${Coverage_NAME} + POST_BUILD + COMMAND ; + COMMENT "Open ./${Coverage_NAME}/index.html in your browser to view the coverage report.") endfunction() # SETUP_TARGET_FOR_COVERAGE @@ -197,48 +194,50 @@ endfunction() # SETUP_TARGET_FOR_COVERAGE # ) function(SETUP_TARGET_FOR_COVERAGE_COBERTURA) - set(options NONE) - set(oneValueArgs NAME) - set(multiValueArgs EXECUTABLE EXECUTABLE_ARGS DEPENDENCIES) - cmake_parse_arguments(Coverage "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) - - if(NOT SIMPLE_PYTHON_EXECUTABLE) - message(FATAL_ERROR "python not found! Aborting...") - endif() # NOT SIMPLE_PYTHON_EXECUTABLE - - if(NOT GCOVR_PATH) - message(FATAL_ERROR "gcovr not found! Aborting...") - endif() # NOT GCOVR_PATH - - # Combine excludes to several -e arguments - set(COBERTURA_EXCLUDES "") - foreach(EXCLUDE ${COVERAGE_EXCLUDES}) - set(COBERTURA_EXCLUDES "-e ${EXCLUDE} ${COBERTURA_EXCLUDES}") - endforeach() - - add_custom_target(${Coverage_NAME} - - # Run tests - ${Coverage_EXECUTABLE} - - # Running gcovr - COMMAND ${GCOVR_PATH} -x -r ${CMAKE_SOURCE_DIR} ${COBERTURA_EXCLUDES} - -o ${Coverage_NAME}.xml - WORKING_DIRECTORY ${PROJECT_BINARY_DIR} - DEPENDS ${Coverage_DEPENDENCIES} - COMMENT "Running gcovr to produce Cobertura code coverage report." - ) - - # Show info where to find the report - add_custom_command(TARGET ${Coverage_NAME} POST_BUILD - COMMAND ; - COMMENT "Cobertura code coverage report saved in ${Coverage_NAME}.xml." - ) + set(options NONE) + set(oneValueArgs NAME) + set(multiValueArgs EXECUTABLE EXECUTABLE_ARGS DEPENDENCIES) + cmake_parse_arguments(Coverage "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + if(NOT SIMPLE_PYTHON_EXECUTABLE) + message(FATAL_ERROR "python not found! Aborting...") + endif() # NOT SIMPLE_PYTHON_EXECUTABLE + + if(NOT GCOVR_PATH) + message(FATAL_ERROR "gcovr not found! Aborting...") + endif() # NOT GCOVR_PATH + + # Combine excludes to several -e arguments + set(COBERTURA_EXCLUDES "") + foreach(EXCLUDE ${COVERAGE_EXCLUDES}) + set(COBERTURA_EXCLUDES "-e ${EXCLUDE} ${COBERTURA_EXCLUDES}") + endforeach() + + add_custom_target( + ${Coverage_NAME} + # Run tests + ${Coverage_EXECUTABLE} + # Running gcovr + COMMAND ${GCOVR_PATH} -x -r ${CMAKE_SOURCE_DIR} ${COBERTURA_EXCLUDES} -o ${Coverage_NAME}.xml + WORKING_DIRECTORY ${PROJECT_BINARY_DIR} + DEPENDS ${Coverage_DEPENDENCIES} + COMMENT "Running gcovr to produce Cobertura code coverage report.") + + # Show info where to find the report + add_custom_command( + TARGET ${Coverage_NAME} + POST_BUILD + COMMAND ; + COMMENT "Cobertura code coverage report saved in ${Coverage_NAME}.xml.") endfunction() # SETUP_TARGET_FOR_COVERAGE_COBERTURA function(APPEND_COVERAGE_COMPILER_FLAGS) - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${COVERAGE_COMPILER_FLAGS}" PARENT_SCOPE) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${COVERAGE_COMPILER_FLAGS}" PARENT_SCOPE) - message(STATUS "Appending code coverage compiler flags: ${COVERAGE_COMPILER_FLAGS}") + set(CMAKE_C_FLAGS + "${CMAKE_C_FLAGS} ${COVERAGE_COMPILER_FLAGS}" + PARENT_SCOPE) + set(CMAKE_CXX_FLAGS + "${CMAKE_CXX_FLAGS} ${COVERAGE_COMPILER_FLAGS}" + PARENT_SCOPE) + message(STATUS "Appending code coverage compiler flags: ${COVERAGE_COMPILER_FLAGS}") endfunction() # APPEND_COVERAGE_COMPILER_FLAGS diff --git a/packages/CLI11/docs/CMakeLists.txt b/packages/CLI11/docs/CMakeLists.txt index f98ff35b684697c065c18bd0b56ed64fbab462ec..e7ceb1d399c8c5ceb0943040078f07f96ac95a9c 100644 --- a/packages/CLI11/docs/CMakeLists.txt +++ b/packages/CLI11/docs/CMakeLists.txt @@ -2,17 +2,10 @@ set(DOXYGEN_EXTRACT_ALL YES) set(DOXYGEN_BUILTIN_STL_SUPPORT YES) set(PROJECT_BRIEF "C++11 Command Line Interface Parser") -file(GLOB DOC_LIST - RELATIVE "${PROJECT_SOURCE_DIR}/include" - "${PROJECT_SOURCE_DIR}/include/CLI/*.hpp" - ) - -doxygen_add_docs(docs - ${DOC_LIST} - "${CMAKE_CURRENT_SOURCE_DIR}/mainpage.md" - WORKING_DIRECTORY - "${PROJECT_SOURCE_DIR}/include" -) - - +file( + GLOB DOC_LIST + RELATIVE "${PROJECT_SOURCE_DIR}/include" + "${PROJECT_SOURCE_DIR}/include/CLI/*.hpp") +doxygen_add_docs(docs ${DOC_LIST} "${CMAKE_CURRENT_SOURCE_DIR}/mainpage.md" + WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}/include") diff --git a/packages/CLI11/docs/mainpage.md b/packages/CLI11/docs/mainpage.md index b9e2d33f831c9d3754d5a0d6d5ba079a3d0fbdcb..b1f2b396709b9cddda94231d2e0cbc3e59c3f8be 100644 --- a/packages/CLI11/docs/mainpage.md +++ b/packages/CLI11/docs/mainpage.md @@ -1,6 +1,6 @@ # Introduction {#mainpage} -This is the Doxygen API documentation for CLI11 parser. There is a friendly introduction to CLI11 on the [Github page](https://github.com/CLIUtils/CLI11), and [a tutorial series](https://cliutils.github.io/CLI11/book/). +This is the Doxygen API documentation for CLI11 parser. There is a friendly introduction to CLI11 on the [GitHub page](https://github.com/CLIUtils/CLI11), and [a tutorial series](https://cliutils.github.io/CLI11/book/). The main classes are: @@ -14,13 +14,9 @@ The main classes are: |CLI::Timer | A timer class, only in CLI/Timer.hpp (not in `CLI11.hpp`) | |CLI::AutoTimer | A timer that prints on deletion | - Groups of related topics: | Name | Description | |----------------------|------------------------------------------------| | @ref error_group | Errors that can be thrown | | @ref validator_group | Common validators used in CLI::Option::check() | - - - diff --git a/packages/CLI11/examples/CMakeLists.txt b/packages/CLI11/examples/CMakeLists.txt index 8cea1581d17b3dd919f1066e0c9a5f07a7eff756..d0d45c6282ed21995bbcfa0a70bef4d190c1ef53 100644 --- a/packages/CLI11/examples/CMakeLists.txt +++ b/packages/CLI11/examples/CMakeLists.txt @@ -1,186 +1,183 @@ - function(add_cli_exe T) - add_executable(${T} ${ARGN} ${CLI11_headers}) - target_link_libraries(${T} PUBLIC CLI11) - set_property(TARGET ${T} PROPERTY FOLDER "Examples") - if(CLI11_FORCE_LIBCXX) - set_property(TARGET ${T} APPEND_STRING PROPERTY LINK_FLAGS -stdlib=libc++) - endif() - if(CLI11_CLANG_TIDY) - set_property(TARGET ${T} PROPERTY CXX_CLANG_TIDY "${DO_CLANG_TIDY}") - endif() + add_executable(${T} ${ARGN} ${CLI11_headers}) + target_link_libraries(${T} PUBLIC CLI11) + set_property(TARGET ${T} PROPERTY FOLDER "Examples") + if(CLI11_FORCE_LIBCXX) + set_property( + TARGET ${T} + APPEND_STRING + PROPERTY LINK_FLAGS -stdlib=libc++) + endif() + if(CLI11_CLANG_TIDY) + set_property(TARGET ${T} PROPERTY CXX_CLANG_TIDY "${DO_CLANG_TIDY}") + endif() endfunction() if(CLI11_BUILD_EXAMPLES_JSON) - message(STATUS "Using nlohmann/json") - FetchContent_Declare(json - URL https://github.com/nlohmann/json/releases/download/v3.7.3/include.zip - URL_HASH "SHA256=87b5884741427220d3a33df1363ae0e8b898099fbc59f1c451113f6732891014" - ) - - FetchContent_GetProperties(json) - if (NOT json_POPULATED) - FetchContent_Populate(json) - endif() - - add_cli_exe(json json.cpp) - target_include_directories(json PUBLIC SYSTEM "${json_SOURCE_DIR}/single_include") - - add_test(NAME json_config_out COMMAND json --item 2) - set_property(TEST json_config_out PROPERTY PASS_REGULAR_EXPRESSION - [[{]] - [["item": "2"]] - [["simple": false]] - [[}]] - ) - - file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/input.json" [=[{"item":3,"simple":false}]=]) - add_test(NAME json_config_in COMMAND json --config "${CMAKE_CURRENT_BINARY_DIR}/input.json") - set_property(TEST json_config_in PROPERTY PASS_REGULAR_EXPRESSION - [[{]] - [["item": "3"]] - [["simple": false]] - [[}]] -) + message(STATUS "Using nlohmann/json") + FetchContent_Declare( + json + URL https://github.com/nlohmann/json/releases/download/v3.7.3/include.zip + URL_HASH "SHA256=87b5884741427220d3a33df1363ae0e8b898099fbc59f1c451113f6732891014") + + FetchContent_GetProperties(json) + if(NOT json_POPULATED) + FetchContent_Populate(json) + endif() + + add_cli_exe(json json.cpp) + target_include_directories(json PUBLIC SYSTEM "${json_SOURCE_DIR}/single_include") + + add_test(NAME json_config_out COMMAND json --item 2) + set_property(TEST json_config_out PROPERTY PASS_REGULAR_EXPRESSION [[{]] [["item": "2"]] + [["simple": false]] [[}]]) + + file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/input.json" [=[{"item":3,"simple":false}]=]) + add_test(NAME json_config_in COMMAND json --config "${CMAKE_CURRENT_BINARY_DIR}/input.json") + set_property(TEST json_config_in PROPERTY PASS_REGULAR_EXPRESSION [[{]] [["item": "3"]] + [["simple": false]] [[}]]) endif() - add_cli_exe(simple simple.cpp) add_test(NAME simple_basic COMMAND simple) add_test(NAME simple_all COMMAND simple -f filename.txt -c 12 --flag --flag -d 1.2) -set_property(TEST simple_all PROPERTY PASS_REGULAR_EXPRESSION - "Working on file: filename.txt, direct count: 1, opt count: 1" - "Working on count: 12, direct count: 1, opt count: 1" - "Received flag: 2 (2) times" - "Some value: 1.2") +set_property( + TEST simple_all + PROPERTY PASS_REGULAR_EXPRESSION "Working on file: filename.txt, direct count: 1, opt count: 1" + "Working on count: 12, direct count: 1, opt count: 1" "Received flag: 2 (2) times" + "Some value: 1.2") add_test(NAME simple_version COMMAND simple --version) -set_property(TEST simple_version PROPERTY PASS_REGULAR_EXPRESSION - "${CLI11_VERSION}") +set_property(TEST simple_version PROPERTY PASS_REGULAR_EXPRESSION "${CLI11_VERSION}") add_cli_exe(subcommands subcommands.cpp) add_test(NAME subcommands_none COMMAND subcommands) -set_property(TEST subcommands_none PROPERTY - PASS_REGULAR_EXPRESSION "A subcommand is required") -add_test(NAME subcommands_all COMMAND subcommands --random start --file name stop --count) -set_property(TEST subcommands_all PROPERTY PASS_REGULAR_EXPRESSION - "Working on --file from start: name" - "Working on --count from stop: 1, direct count: 1" - "Count of --random flag: 1" - "Subcommand: start" - "Subcommand: stop") +set_property(TEST subcommands_none PROPERTY PASS_REGULAR_EXPRESSION "A subcommand is required") +add_test( + NAME subcommands_all + COMMAND subcommands --random start --file + name stop --count) +set_property( + TEST subcommands_all + PROPERTY PASS_REGULAR_EXPRESSION "Working on --file from start: name" + "Working on --count from stop: 1, direct count: 1" "Count of --random flag: 1" + "Subcommand: start" "Subcommand: stop") add_cli_exe(subcom_partitioned subcom_partitioned.cpp) add_test(NAME subcom_partitioned_none COMMAND subcom_partitioned) -set_property(TEST subcom_partitioned_none PROPERTY PASS_REGULAR_EXPRESSION - "This is a timer:" - "--file is required" - "Run with --help for more information.") +set_property( + TEST subcom_partitioned_none + PROPERTY PASS_REGULAR_EXPRESSION "This is a timer:" "--file is required" + "Run with --help for more information.") add_test(NAME subcom_partitioned_all COMMAND subcom_partitioned --file this --count --count -d 1.2) -set_property(TEST subcom_partitioned_all PROPERTY PASS_REGULAR_EXPRESSION - "This is a timer:" - "Working on file: this, direct count: 1, opt count: 1" - "Working on count: 2, direct count: 2, opt count: 2" - "Some value: 1.2") - # test shows that the help prints out for unnamed subcommands +set_property( + TEST subcom_partitioned_all + PROPERTY PASS_REGULAR_EXPRESSION "This is a timer:" + "Working on file: this, direct count: 1, opt count: 1" + "Working on count: 2, direct count: 2, opt count: 2" "Some value: 1.2") +# test shows that the help prints out for unnamed subcommands add_test(NAME subcom_partitioned_help COMMAND subcom_partitioned --help) set_property(TEST subcom_partitioned_help PROPERTY PASS_REGULAR_EXPRESSION - "-f,--file TEXT REQUIRED" - "-d,--double FLOAT") + "-f,--file TEXT REQUIRED" "-d,--double FLOAT") + +#################################################### +add_cli_exe(config_app config_app.cpp) +add_test(NAME config_app1 COMMAND config_app -p) +set_property(TEST config_app1 PROPERTY PASS_REGULAR_EXPRESSION "file=") + +add_test(NAME config_app2 COMMAND config_app -p -f /) +set_property(TEST config_app2 PROPERTY PASS_REGULAR_EXPRESSION "file=\"/\"") + +add_test(NAME config_app3 COMMAND config_app -f "" -p) +set_property(TEST config_app3 PROPERTY PASS_REGULAR_EXPRESSION "file=\"\"") + +add_test(NAME config_app4 COMMAND config_app -f "/" -p) +set_property(TEST config_app4 PROPERTY PASS_REGULAR_EXPRESSION "file=\"/\"") + +#################################################### add_cli_exe(option_groups option_groups.cpp) -add_test(NAME option_groups_missing COMMAND option_groups ) -set_property(TEST option_groups_missing PROPERTY PASS_REGULAR_EXPRESSION - "Exactly 1 option from" - "is required") +add_test(NAME option_groups_missing COMMAND option_groups) +set_property(TEST option_groups_missing PROPERTY PASS_REGULAR_EXPRESSION "Exactly 1 option from" + "is required") add_test(NAME option_groups_extra COMMAND option_groups --csv --binary) -set_property(TEST option_groups_extra PROPERTY PASS_REGULAR_EXPRESSION - "and 2 were given") -add_test(NAME option_groups_extra2 COMMAND option_groups --csv --address "192.168.1.1" -o "test.out") -set_property(TEST option_groups_extra2 PROPERTY PASS_REGULAR_EXPRESSION - "at most 1") +set_property(TEST option_groups_extra PROPERTY PASS_REGULAR_EXPRESSION "and 2 were given") +add_test(NAME option_groups_extra2 COMMAND option_groups --csv --address "192.168.1.1" -o + "test.out") +set_property(TEST option_groups_extra2 PROPERTY PASS_REGULAR_EXPRESSION "at most 1") add_cli_exe(positional_arity positional_arity.cpp) -add_test(NAME positional_arity1 COMMAND positional_arity one ) -set_property(TEST positional_arity1 PROPERTY PASS_REGULAR_EXPRESSION - "File 1 = one") -add_test(NAME positional_arity2 COMMAND positional_arity one two ) -set_property(TEST positional_arity2 PROPERTY PASS_REGULAR_EXPRESSION - "File 1 = one" - "File 2 = two") +add_test(NAME positional_arity1 COMMAND positional_arity one) +set_property(TEST positional_arity1 PROPERTY PASS_REGULAR_EXPRESSION "File 1 = one") +add_test(NAME positional_arity2 COMMAND positional_arity one two) +set_property(TEST positional_arity2 PROPERTY PASS_REGULAR_EXPRESSION "File 1 = one" "File 2 = two") add_test(NAME positional_arity3 COMMAND positional_arity 1 2 one) -set_property(TEST positional_arity3 PROPERTY PASS_REGULAR_EXPRESSION - "File 1 = one") +set_property(TEST positional_arity3 PROPERTY PASS_REGULAR_EXPRESSION "File 1 = one") add_test(NAME positional_arity_fail COMMAND positional_arity 1 one two) -set_property(TEST positional_arity_fail PROPERTY PASS_REGULAR_EXPRESSION - "Could not convert") - - add_cli_exe(positional_validation positional_validation.cpp) -add_test(NAME positional_validation1 COMMAND positional_validation one ) -set_property(TEST positional_validation1 PROPERTY PASS_REGULAR_EXPRESSION - "File 1 = one") -add_test(NAME positional_validation2 COMMAND positional_validation one 1 2 two ) -set_property(TEST positional_validation2 PROPERTY PASS_REGULAR_EXPRESSION - "File 1 = one" - "File 2 = two") +set_property(TEST positional_arity_fail PROPERTY PASS_REGULAR_EXPRESSION "Could not convert") + +add_cli_exe(positional_validation positional_validation.cpp) +add_test(NAME positional_validation1 COMMAND positional_validation one) +set_property(TEST positional_validation1 PROPERTY PASS_REGULAR_EXPRESSION "File 1 = one") +add_test(NAME positional_validation2 COMMAND positional_validation one 1 2 two) +set_property(TEST positional_validation2 PROPERTY PASS_REGULAR_EXPRESSION "File 1 = one" + "File 2 = two") add_test(NAME positional_validation3 COMMAND positional_validation 1 2 one) -set_property(TEST positional_validation3 PROPERTY PASS_REGULAR_EXPRESSION - "File 1 = one") +set_property(TEST positional_validation3 PROPERTY PASS_REGULAR_EXPRESSION "File 1 = one") add_test(NAME positional_validation4 COMMAND positional_validation 1 one two 2) -set_property(TEST positional_validation4 PROPERTY PASS_REGULAR_EXPRESSION - "File 1 = one" - "File 2 = two") +set_property(TEST positional_validation4 PROPERTY PASS_REGULAR_EXPRESSION "File 1 = one" + "File 2 = two") add_cli_exe(shapes shapes.cpp) -add_test(NAME shapes_all COMMAND shapes circle 4.4 circle 10.7 rectangle 4 4 circle 2.3 triangle 4.5 ++ rectangle 2.1 ++ circle 234.675) -set_property(TEST shapes_all PROPERTY PASS_REGULAR_EXPRESSION - "circle2" - "circle4" - "rectangle2 with edges [2.1,2.1]" - "triangel1 with sides [4.5]") +add_test(NAME shapes_all COMMAND shapes circle 4.4 circle 10.7 rectangle 4 4 circle 2.3 triangle + 4.5 ++ rectangle 2.1 ++ circle 234.675) +set_property( + TEST shapes_all PROPERTY PASS_REGULAR_EXPRESSION "circle2" "circle4" + "rectangle2 with edges [2.1,2.1]" "triangel1 with sides [4.5]") add_cli_exe(ranges ranges.cpp) add_test(NAME ranges_range COMMAND ranges --range 1 2 3) -set_property(TEST ranges_range PROPERTY PASS_REGULAR_EXPRESSION - "[2:1:3]") +set_property(TEST ranges_range PROPERTY PASS_REGULAR_EXPRESSION "[2:1:3]") add_test(NAME ranges_minmax COMMAND ranges --min 2 --max 3) -set_property(TEST ranges_minmax PROPERTY PASS_REGULAR_EXPRESSION - "[2:1:3]") +set_property(TEST ranges_minmax PROPERTY PASS_REGULAR_EXPRESSION "[2:1:3]") add_test(NAME ranges_error COMMAND ranges --min 2 --max 3 --step 1 --range 1 2 3) -set_property(TEST ranges_error PROPERTY PASS_REGULAR_EXPRESSION - "Exactly 1 option from") +set_property(TEST ranges_error PROPERTY PASS_REGULAR_EXPRESSION "Exactly 1 option from") add_cli_exe(validators validators.cpp) add_test(NAME validators_help COMMAND validators --help) -set_property(TEST validators_help PROPERTY PASS_REGULAR_EXPRESSION - " -f,--file TEXT:FILE[\\r\\n\\t ]+File name" - " -v,--value INT:INT in [3 - 6][\\r\\n\\t ]+Value in range") +set_property( + TEST validators_help + PROPERTY PASS_REGULAR_EXPRESSION " -f,--file TEXT:FILE[\\r\\n\\t ]+File name" + " -v,--value INT:INT in [3 - 6][\\r\\n\\t ]+Value in range") add_test(NAME validators_file COMMAND validators --file nonex.xxx) -set_property(TEST validators_file PROPERTY PASS_REGULAR_EXPRESSION - "--file: File does not exist: nonex.xxx" - "Run with --help for more information.") +set_property( + TEST validators_file PROPERTY PASS_REGULAR_EXPRESSION "--file: File does not exist: nonex.xxx" + "Run with --help for more information.") add_test(NAME validators_plain COMMAND validators --value 9) -set_property(TEST validators_plain PROPERTY PASS_REGULAR_EXPRESSION - "--value: Value 9 not in range 3 to 6" - "Run with --help for more information.") +set_property( + TEST validators_plain PROPERTY PASS_REGULAR_EXPRESSION "--value: Value 9 not in range 3 to 6" + "Run with --help for more information.") add_cli_exe(groups groups.cpp) add_test(NAME groups_none COMMAND groups) -set_property(TEST groups_none PROPERTY PASS_REGULAR_EXPRESSION - "This is a timer:" - "--file is required" - "Run with --help for more information.") +set_property( + TEST groups_none PROPERTY PASS_REGULAR_EXPRESSION "This is a timer:" "--file is required" + "Run with --help for more information.") add_test(NAME groups_all COMMAND groups --file this --count --count -d 1.2) -set_property(TEST groups_all PROPERTY PASS_REGULAR_EXPRESSION - "This is a timer:" - "Working on file: this, direct count: 1, opt count: 1" - "Working on count: 2, direct count: 2, opt count: 2" - "Some value: 1.2") +set_property( + TEST groups_all + PROPERTY PASS_REGULAR_EXPRESSION "This is a timer:" + "Working on file: this, direct count: 1, opt count: 1" + "Working on count: 2, direct count: 2, opt count: 2" "Some value: 1.2") add_cli_exe(inter_argument_order inter_argument_order.cpp) -add_test(NAME inter_argument_order COMMAND inter_argument_order --foo 1 2 3 --x --bar 4 5 6 --z --foo 7 8) -set_property(TEST inter_argument_order PROPERTY PASS_REGULAR_EXPRESSION +add_test(NAME inter_argument_order COMMAND inter_argument_order --foo 1 2 3 --x --bar 4 5 6 --z + --foo 7 8) +set_property( + TEST inter_argument_order + PROPERTY + PASS_REGULAR_EXPRESSION [=[foo : 1 foo : 2 foo : 3 @@ -192,44 +189,37 @@ foo : 8]=]) add_cli_exe(prefix_command prefix_command.cpp) add_test(NAME prefix_command COMMAND prefix_command -v 3 2 1 -- other one two 3) -set_property(TEST prefix_command PROPERTY PASS_REGULAR_EXPRESSION - "Prefix: 3 : 2 : 1" - "Remaining commands: other one two 3") +set_property(TEST prefix_command PROPERTY PASS_REGULAR_EXPRESSION "Prefix: 3 : 2 : 1" + "Remaining commands: other one two 3") add_cli_exe(callback_passthrough callback_passthrough.cpp) add_test(NAME callback_passthrough1 COMMAND callback_passthrough --argname t2 --t2 test) -set_property(TEST callback_passthrough1 PROPERTY PASS_REGULAR_EXPRESSION - "the value is now test") +set_property(TEST callback_passthrough1 PROPERTY PASS_REGULAR_EXPRESSION "the value is now test") add_test(NAME callback_passthrough2 COMMAND callback_passthrough --arg EEEK --argname arg) -set_property(TEST callback_passthrough2 PROPERTY PASS_REGULAR_EXPRESSION - "the value is now EEEK") +set_property(TEST callback_passthrough2 PROPERTY PASS_REGULAR_EXPRESSION "the value is now EEEK") add_cli_exe(enum enum.cpp) add_test(NAME enum_pass COMMAND enum -l 1) add_test(NAME enum_fail COMMAND enum -l 4) -set_property(TEST enum_fail PROPERTY PASS_REGULAR_EXPRESSION - "--level: Check 4 value in {" "FAILED") +set_property(TEST enum_fail PROPERTY PASS_REGULAR_EXPRESSION "--level: Check 4 value in {" + "FAILED") add_cli_exe(enum_ostream enum_ostream.cpp) add_test(NAME enum_ostream_pass COMMAND enum_ostream --level medium) -set_property(TEST enum_ostream_pass PROPERTY PASS_REGULAR_EXPRESSION - "Enum received: Medium") +set_property(TEST enum_ostream_pass PROPERTY PASS_REGULAR_EXPRESSION "Enum received: Medium") add_cli_exe(digit_args digit_args.cpp) add_test(NAME digit_args COMMAND digit_args -h) -set_property(TEST digit_args PROPERTY PASS_REGULAR_EXPRESSION - "-3{3}") +set_property(TEST digit_args PROPERTY PASS_REGULAR_EXPRESSION "-3{3}") add_cli_exe(modhelp modhelp.cpp) add_test(NAME modhelp COMMAND modhelp -a test -h) -set_property(TEST modhelp PROPERTY PASS_REGULAR_EXPRESSION - "Option -a string in help: test") +set_property(TEST modhelp PROPERTY PASS_REGULAR_EXPRESSION "Option -a string in help: test") add_subdirectory(subcom_in_files) add_test(NAME subcom_in_files COMMAND subcommand_main subcommand_a -f this.txt --with-foo) -set_property(TEST subcom_in_files PROPERTY PASS_REGULAR_EXPRESSION - "Working on file: this\.txt" - "Using foo!") +set_property(TEST subcom_in_files PROPERTY PASS_REGULAR_EXPRESSION "Working on file: this\.txt" + "Using foo!") add_cli_exe(formatter formatter.cpp) @@ -245,14 +235,15 @@ add_test(NAME retired_retired_test2 COMMAND retired --retired_option 567) add_test(NAME retired_retired_test3 COMMAND retired --retired_option2 567 689 789) add_test(NAME retired_deprecated COMMAND retired --deprecate 19 20) -set_property(TEST retired_retired_test PROPERTY PASS_REGULAR_EXPRESSION - "WARNING.*retired") +set_property(TEST retired_retired_test PROPERTY PASS_REGULAR_EXPRESSION "WARNING.*retired") + +set_property(TEST retired_retired_test2 PROPERTY PASS_REGULAR_EXPRESSION "WARNING.*retired") -set_property(TEST retired_retired_test2 PROPERTY PASS_REGULAR_EXPRESSION - "WARNING.*retired") +set_property(TEST retired_retired_test3 PROPERTY PASS_REGULAR_EXPRESSION "WARNING.*retired") -set_property(TEST retired_retired_test3 PROPERTY PASS_REGULAR_EXPRESSION - "WARNING.*retired") +set_property(TEST retired_deprecated PROPERTY PASS_REGULAR_EXPRESSION "deprecated.*not_deprecated") -set_property(TEST retired_deprecated PROPERTY PASS_REGULAR_EXPRESSION - "deprecated.*not_deprecated") +#-------------------------------------------- +add_cli_exe(custom_parse custom_parse.cpp) +add_test(NAME cp_test COMMAND custom_parse --dv 1.7) +set_property(TEST cp_test PROPERTY PASS_REGULAR_EXPRESSION "called correct") diff --git a/packages/CLI11/examples/callback_passthrough.cpp b/packages/CLI11/examples/callback_passthrough.cpp index 16698aafb9a3048e2b124d8726a0604a225a44f6..48a248756f49558e726d656a0451f1005a985253 100644 --- a/packages/CLI11/examples/callback_passthrough.cpp +++ b/packages/CLI11/examples/callback_passthrough.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2020, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2021, 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 new file mode 100644 index 0000000000000000000000000000000000000000..aec9fefdfaf80875dc71fc62a2701ca62ff384b4 --- /dev/null +++ b/packages/CLI11/examples/config_app.cpp @@ -0,0 +1,50 @@ +// Copyright (c) 2017-2021, 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 <iostream> +#include <string> + +int main(int argc, char **argv) { + + CLI::App app("configuration print example"); + + app.add_flag("-p,--print", "Print configuration and exit")->configurable(false); // NEW: print flag + + std::string file; + CLI::Option *opt = app.add_option("-f,--file,file", file, "File name") + ->capture_default_str() + ->run_callback_for_default(); // NEW: capture_default_str() + + int count{0}; + CLI::Option *copt = + app.add_option("-c,--count", count, "Counter")->capture_default_str(); // NEW: capture_default_str() + + int v{0}; + CLI::Option *flag = app.add_flag("--flag", v, "Some flag that can be passed multiple times") + ->capture_default_str(); // NEW: capture_default_str() + + double value{0.0}; // = 3.14; + app.add_option("-d,--double", value, "Some Value")->capture_default_str(); // NEW: capture_default_str() + + app.get_config_formatter_base()->quoteCharacter('"', '"'); + + CLI11_PARSE(app, argc, argv); + + if(app.get_option("--print")->as<bool>()) { // NEW: print configuration and exit + std::cout << app.config_to_str(true, false); + return 0; + } + + std::cout << "Working on file: " << file << ", direct count: " << app.count("--file") + << ", opt count: " << opt->count() << std::endl; + std::cout << "Working on count: " << count << ", direct count: " << app.count("--count") + << ", opt count: " << copt->count() << std::endl; + std::cout << "Received flag: " << v << " (" << flag->count() << ") times\n"; + std::cout << "Some value: " << value << std::endl; + + return 0; +} diff --git a/packages/CLI11/examples/custom_parse.cpp b/packages/CLI11/examples/custom_parse.cpp new file mode 100644 index 0000000000000000000000000000000000000000..44fc77afd1cf91379a1f73f0868a9f23eaa17260 --- /dev/null +++ b/packages/CLI11/examples/custom_parse.cpp @@ -0,0 +1,39 @@ +// Copyright (c) 2017-2021, 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 + +// from Issue #566 on github https://github.com/CLIUtils/CLI11/issues/566 + +#include <CLI/CLI.hpp> +#include <iostream> +#include <sstream> + +// example file to demonstrate a custom lexical cast function + +template <class T = int> struct Values { + T a; + T b; + T c; +}; + +// in C++20 this is constructible from a double due to the new aggregate initialization in C++20. +using DoubleValues = Values<double>; + +// the lexical cast operator should be in the same namespace as the type for ADL to work properly +bool lexical_cast(const std::string &input, Values<double> &v) { + std::cout << "called correct lexical_cast function ! val: " << input << std::endl; + return true; +} + +DoubleValues doubles; +void argparse(CLI::Option_group *group) { group->add_option("--dv", doubles)->default_str("0"); } + +int main(int argc, char **argv) { + CLI::App app; + + argparse(app.add_option_group("param")); + CLI11_PARSE(app, argc, argv); + return 0; +} diff --git a/packages/CLI11/examples/digit_args.cpp b/packages/CLI11/examples/digit_args.cpp index cf8f7d3317da7864481b031e4ff7fff37744e07f..af0891a4f81ff41f0c73db3159edda088f41106d 100644 --- a/packages/CLI11/examples/digit_args.cpp +++ b/packages/CLI11/examples/digit_args.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2020, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2021, 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 a73e56b3f847b719f8f4ddfb979c849e29b82468..09b7f043b6f53edf65fb2e590b7bf79b4bb7b2c8 100644 --- a/packages/CLI11/examples/enum.cpp +++ b/packages/CLI11/examples/enum.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2020, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2021, 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 13d33502aea4a994fc2d08b2ab79f0192d296ad0..4e3e6b297f5a4b624af1d6ccfb5d2bbac22fd530 100644 --- a/packages/CLI11/examples/enum_ostream.cpp +++ b/packages/CLI11/examples/enum_ostream.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2020, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2021, 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 81d6209f4866def06a5e300fae3dd1051791bd46..3b2e3dce5447694d285d2d99441c363d03912e0b 100644 --- a/packages/CLI11/examples/formatter.cpp +++ b/packages/CLI11/examples/formatter.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2020, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2021, 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 22d628948ecd5a6fb01871cb9217b30d8a136c99..c766d6b23bef6806eadc94e4623aa2202c3e0090 100644 --- a/packages/CLI11/examples/groups.cpp +++ b/packages/CLI11/examples/groups.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2020, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2021, 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 deecd057e699529ab0e7c7c35134778aa6b1bf52..ebee3977ac1d26a896805ef24620dcf458e82bba 100644 --- a/packages/CLI11/examples/inter_argument_order.cpp +++ b/packages/CLI11/examples/inter_argument_order.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2020, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2021, 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/json.cpp b/packages/CLI11/examples/json.cpp index d5fe35a700da611a12e1f8bccd77d0c7ded229f5..ae2595c004736353724f9f4310a39e1c26496d38 100644 --- a/packages/CLI11/examples/json.cpp +++ b/packages/CLI11/examples/json.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2020, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2021, 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 706abcae0c3f319c8218a98f6592ba0f90b7f85c..9c674300f8b7e55f050f85fb74bd0d4fbb7c009a 100644 --- a/packages/CLI11/examples/modhelp.cpp +++ b/packages/CLI11/examples/modhelp.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2020, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2021, 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 23f428514568e02b9ee24fe0b1a316aabf48677a..dc8323fe00b7e9c30ea777349abb93e969c19696 100644 --- a/packages/CLI11/examples/nested.cpp +++ b/packages/CLI11/examples/nested.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2020, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // @@ -18,7 +18,9 @@ int main(int argc, char **argv) { std::string mvcamera_config_file = "mvcamera_config.json"; CLI::App *mvcameraApp = cameraApp->add_subcommand("mvcamera", "MatrixVision Camera Configuration"); - mvcameraApp->add_option("-c,--config", mvcamera_config_file, "Config filename", true)->check(CLI::ExistingFile); + mvcameraApp->add_option("-c,--config", mvcamera_config_file, "Config filename") + ->capture_default_str() + ->check(CLI::ExistingFile); std::string mock_camera_path; CLI::App *mockcameraApp = cameraApp->add_subcommand("mock", "Mock Camera Configuration"); diff --git a/packages/CLI11/examples/option_groups.cpp b/packages/CLI11/examples/option_groups.cpp index 679cb754b83404c02c325a8073b3b7411f2ef210..acc65740d62cb66f879c9859ee936fb89b602c14 100644 --- a/packages/CLI11/examples/option_groups.cpp +++ b/packages/CLI11/examples/option_groups.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2020, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2021, 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 eb6639b04856fe90e342254dccc2cb0fd0194076..e9238fc74011a999cfbc072585ac2c0d24fe3150 100644 --- a/packages/CLI11/examples/positional_arity.cpp +++ b/packages/CLI11/examples/positional_arity.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2020, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2021, 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 421de25096cc0273cf928a9a15295b9ecd0db33d..afc03fa8042fedf49983f49b952c9490ad3941ae 100644 --- a/packages/CLI11/examples/positional_validation.cpp +++ b/packages/CLI11/examples/positional_validation.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2020, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2021, 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 8977adcbdf95459b89be82fbdef0b7b38628164c..2af1a87b3a1457b6a93b8494415662eadd79732b 100644 --- a/packages/CLI11/examples/prefix_command.cpp +++ b/packages/CLI11/examples/prefix_command.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2020, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2021, 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 085a7ad1bec99d898106efef4595f75d3b2a85ef..6b4c1e3bc35e9471379ce8d6a4a6f71c2e563669 100644 --- a/packages/CLI11/examples/ranges.cpp +++ b/packages/CLI11/examples/ranges.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2020, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // @@ -19,7 +19,7 @@ int main(int argc, char **argv) { int min{0}, max{0}, step{1}; ogroup->add_option("--min,-m", min, "The minimum")->required(); ogroup->add_option("--max,-M", max, "The maximum")->required(); - ogroup->add_option("--step,-s", step, "The step", true); + ogroup->add_option("--step,-s", step, "The step")->capture_default_str(); app.require_option(1); diff --git a/packages/CLI11/examples/retired.cpp b/packages/CLI11/examples/retired.cpp index d658e754f5474ef08f23153bb759c602003832d4..3a18db4aed5aca16c32d6d85f2254700710ea161 100644 --- a/packages/CLI11/examples/retired.cpp +++ b/packages/CLI11/examples/retired.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2020, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2021, 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 880441b85dc729657392a2e6efe485218cd9c9b9..b318df0e152b23815027c4e904e92b8b6468e9b5 100644 --- a/packages/CLI11/examples/shapes.cpp +++ b/packages/CLI11/examples/shapes.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2020, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2021, 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 c1354eaf38189f5f87c53ad4565d86d048082fef..0f904420f267b37dbbee4fbee517c94eedce3d59 100644 --- a/packages/CLI11/examples/simple.cpp +++ b/packages/CLI11/examples/simple.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2020, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2021, 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 d41d4a59b93a798f995f9b39a21788861a57981c..89af131dabc4776fdafa302c266fb703f74a028d 100644 --- a/packages/CLI11/examples/subcom_help.cpp +++ b/packages/CLI11/examples/subcom_help.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2020, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2021, 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 63474fdc3f87bf74e482f80000befe02a87f2af7..6b229839bcf38e0fea489e07aa21f52234459240 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-2020, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2021, 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 2993739ea8764dbaf2bb16650fd847e2f719609b..116160c53059d53d393a4b0ef696521614224732 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-2020, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2021, 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 76ebe7c01e2ca0410d7bf178de8c467106b01b8f..62b63806edb990c58d408ed718d2df550b005729 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-2020, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2021, 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 20dd362326901686e0263244ee7e887ce0225384..df48e2d7918266100733563d0c6be28d3bd40710 100644 --- a/packages/CLI11/examples/subcom_partitioned.cpp +++ b/packages/CLI11/examples/subcom_partitioned.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2020, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2021, 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 937eaeaf41ad0fd3026aa938d5dea617451dff20..68f163a75b3dd0e8ba0aeea8a09b9e4432952985 100644 --- a/packages/CLI11/examples/subcommands.cpp +++ b/packages/CLI11/examples/subcommands.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2020, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2021, 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 05ea045c0a54e11e47c4b15e9ac94337bb1e479a..6ca8d3896a9dd3f8b5e511aa2be3078c893d1d30 100644 --- a/packages/CLI11/examples/validators.cpp +++ b/packages/CLI11/examples/validators.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2020, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2021, 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/App.hpp b/packages/CLI11/include/CLI/App.hpp index edbe2b7386ba7be718762c034fa2c3d3bf87ce9b..03b58d9fe7cba33953435483caa031630d6a66aa 100644 --- a/packages/CLI11/include/CLI/App.hpp +++ b/packages/CLI11/include/CLI/App.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2020, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // @@ -55,7 +55,7 @@ std::string help(const App *app, const Error &e); /// enumeration of modes of how to deal with extras in config files -enum class config_extras_mode : char { error = 0, ignore, capture }; +enum class config_extras_mode : char { error = 0, ignore, ignore_all, capture }; class App; @@ -113,6 +113,7 @@ class App { /// This is a function that runs when parsing has finished. std::function<void()> parse_complete_callback_{}; + /// This is a function that runs when all processing has completed std::function<void()> final_callback_{}; @@ -367,10 +368,9 @@ class App { /// Set an alias for the app App *alias(std::string app_name) { - if(!detail::valid_name_string(app_name)) { - throw(IncorrectConstruction("alias is not a valid name string")); + if(app_name.empty() || !detail::valid_alias_name_string(app_name)) { + throw IncorrectConstruction("Aliases may not be empty or contain newlines or null characters"); } - if(parent_ != nullptr) { aliases_.push_back(app_name); auto &res = _compare_subcommand_names(*this, *_get_fallthrough_parent()); @@ -611,14 +611,13 @@ class App { enable_if_t<!std::is_const<ConvertTo>::value, detail::enabler> = detail::dummy> Option *add_option(std::string option_name, AssignTo &variable, ///< The variable to set - std::string option_description = "", - bool defaulted = false) { + std::string option_description = "") { auto fun = [&variable](const CLI::results_t &res) { // comment for spacing return detail::lexical_conversion<AssignTo, ConvertTo>(res, variable); }; - Option *opt = add_option(option_name, fun, option_description, defaulted, [&variable]() { + Option *opt = add_option(option_name, fun, option_description, false, [&variable]() { return CLI::detail::checked_to_string<AssignTo, ConvertTo>(variable); }); opt->type_name(detail::type_name<ConvertTo>()); @@ -720,7 +719,9 @@ class App { } /// Set a version flag and version display string, replace the existing one if present - Option *set_version_flag(std::string flag_name = "", const std::string &versionString = "") { + Option *set_version_flag(std::string flag_name = "", + const std::string &versionString = "", + const std::string &version_help = "Display program version information and exit") { // take flag_description by const reference otherwise add_flag tries to assign to version_description if(version_ptr_ != nullptr) { remove_option(version_ptr_); @@ -730,17 +731,16 @@ class App { // Empty name will simply remove the version flag if(!flag_name.empty()) { version_ptr_ = add_flag_callback( - flag_name, - [versionString]() { throw(CLI::CallForVersion(versionString, 0)); }, - "Display program version information and exit"); + flag_name, [versionString]() { throw(CLI::CallForVersion(versionString, 0)); }, version_help); version_ptr_->configurable(false); } return version_ptr_; } /// Generate the version string through a callback function - Option *set_version_flag(std::string flag_name, std::function<std::string()> vfunc) { - // take flag_description by const reference otherwise add_flag tries to assign to version_description + Option *set_version_flag(std::string flag_name, + std::function<std::string()> vfunc, + const std::string &version_help = "Display program version information and exit") { if(version_ptr_ != nullptr) { remove_option(version_ptr_); version_ptr_ = nullptr; @@ -749,9 +749,7 @@ class App { // Empty name will simply remove the version flag if(!flag_name.empty()) { version_ptr_ = add_flag_callback( - flag_name, - [vfunc]() { throw(CLI::CallForVersion(vfunc(), 0)); }, - "Display program version information and exit"); + flag_name, [vfunc]() { throw(CLI::CallForVersion(vfunc(), 0)); }, version_help); version_ptr_->configurable(false); } @@ -897,56 +895,6 @@ class App { } #endif - /// Add a complex number DEPRECATED --use add_option instead - template <typename T, typename XC = double> - Option *add_complex(std::string option_name, - T &variable, - std::string option_description = "", - bool defaulted = false, - std::string label = "COMPLEX") { - - CLI::callback_t fun = [&variable](const results_t &res) { - XC x, y; - bool worked; - if(res.size() >= 2 && !res[1].empty()) { - auto str1 = res[1]; - if(str1.back() == 'i' || str1.back() == 'j') - str1.pop_back(); - worked = detail::lexical_cast(res[0], x) && detail::lexical_cast(str1, y); - } else { - auto str1 = res.front(); - auto nloc = str1.find_last_of('-'); - if(nloc != std::string::npos && nloc > 0) { - worked = detail::lexical_cast(str1.substr(0, nloc), x); - str1 = str1.substr(nloc); - if(str1.back() == 'i' || str1.back() == 'j') - str1.pop_back(); - worked = worked && detail::lexical_cast(str1, y); - } else { - if(str1.back() == 'i' || str1.back() == 'j') { - str1.pop_back(); - worked = detail::lexical_cast(str1, y); - x = XC{0}; - } else { - worked = detail::lexical_cast(str1, x); - y = XC{0}; - } - } - } - if(worked) - variable = T{x, y}; - return worked; - }; - - auto default_function = [&variable]() { return CLI::detail::checked_to_string<T, T>(variable); }; - - CLI::Option *opt = - add_option(option_name, std::move(fun), std::move(option_description), defaulted, default_function); - - opt->type_name(label)->type_size(1, 2)->delimiter('+')->run_callback_for_default(); - return opt; - } - /// Set a configuration ini file option, or clear it if no name passed Option *set_config(std::string option_name = "", std::string default_filename = "", @@ -999,6 +947,9 @@ class App { /// creates an option group as part of the given app template <typename T = Option_group> T *add_option_group(std::string group_name, std::string group_description = "") { + if(!detail::valid_alias_name_string(group_name)) { + throw IncorrectConstruction("option group names may not contain newlines or null characters"); + } auto option_group = std::make_shared<T>(std::move(group_description), group_name, this); auto ptr = option_group.get(); // move to App_p for overload resolution on older gcc versions @@ -1014,7 +965,17 @@ class App { /// Add a subcommand. Inherits INHERITABLE and OptionDefaults, and help flag App *add_subcommand(std::string subcommand_name = "", std::string subcommand_description = "") { if(!subcommand_name.empty() && !detail::valid_name_string(subcommand_name)) { - throw IncorrectConstruction("subcommand name is not valid"); + if(!detail::valid_first_char(subcommand_name[0])) { + throw IncorrectConstruction( + "Subcommand name starts with invalid character, '!' and '-' are not allowed"); + } + for(auto c : subcommand_name) { + if(!detail::valid_later_char(c)) { + throw IncorrectConstruction(std::string("Subcommand name contains invalid character ('") + c + + "'), all characters are allowed except" + "'=',':','{','}', and ' '"); + } + } } CLI::App_p subcom = std::shared_ptr<App>(new App(std::move(subcommand_description), subcommand_name, this)); return add_subcommand(std::move(subcom)); @@ -1329,6 +1290,16 @@ class App { run_callback(); } + void parse_from_stream(std::istream &input) { + if(parsed_ == 0) { + _validate(); + _configure(); + // set the parent as nullptr as this object should be the top now + } + + _parse_stream(input); + run_callback(); + } /// Provide a function to print a help message. The function gets access to the App pointer and error. void failure_message(std::function<std::string(const App *, const Error &e)> function) { failure_message_ = function; @@ -1791,7 +1762,7 @@ class App { if(name_.empty()) { return std::string("[Option Group: ") + get_group() + "]"; } - if(aliases_.empty() || !with_aliases || aliases_.empty()) { + if(aliases_.empty() || !with_aliases) { return name_; } std::string dispname = name_; @@ -1963,8 +1934,9 @@ class App { app->_configure(); } } + /// Internal function to run (App) callback, bottom up - void run_callback(bool final_mode = false) { + void run_callback(bool final_mode = false, bool suppress_final_callback = false) { pre_callback(); // in the main app if immediate_callback_ is set it runs the main callback before the used subcommands if(!final_mode && parse_complete_callback_) { @@ -1972,18 +1944,18 @@ class App { } // run the callbacks for the received subcommands for(App *subc : get_subcommands()) { - subc->run_callback(true); + subc->run_callback(true, suppress_final_callback); } // now run callbacks for option_groups for(auto &subc : subcommands_) { if(subc->name_.empty() && subc->count_all() > 0) { - subc->run_callback(true); + subc->run_callback(true, suppress_final_callback); } } // finally run the main callback - if(final_callback_ && (parsed_ > 0)) { - if(!name_.empty() || count_all() > 0) { + if(final_callback_ && (parsed_ > 0) && (!suppress_final_callback)) { + if(!name_.empty() || count_all() > 0 || parent_ == nullptr) { final_callback_(); } } @@ -2110,7 +2082,7 @@ class App { } for(const Option_p &opt : options_) { - if(opt->count() > 0 && !opt->get_callback_run()) { + if((*opt) && !opt->get_callback_run()) { opt->run_callback(); } } @@ -2277,10 +2249,27 @@ class App { /// Process callbacks and such. void _process() { - _process_config_file(); - _process_env(); + CLI::FileError fe("ne"); + bool caught_error{false}; + try { + // the config file might generate a FileError but that should not be processed until later in the process + // to allow for help, version and other errors to generate first. + _process_config_file(); + // process env shouldn't throw but no reason to process it if config generated an error + _process_env(); + } catch(const CLI::FileError &fe2) { + fe = fe2; + caught_error = true; + } + // callbacks and help_flags can generate exceptions which should take priority over the config file error if one + // exists _process_callbacks(); _process_help_flags(); + + if(caught_error) { + throw CLI::FileError(std::move(fe)); + } + _process_requirements(); } @@ -2349,7 +2338,7 @@ class App { _process_callbacks(); _process_help_flags(); _process_requirements(); - run_callback(); + run_callback(false, true); } } @@ -2370,6 +2359,18 @@ class App { _process_extras(); } + /// Internal function to parse a stream + void _parse_stream(std::istream &input) { + auto values = config_formatter_->from_config(input); + _parse_config(values); + increment_parsed(); + _trigger_pre_parse(values.size()); + _process(); + + // Throw error if any items are left over (depending on settings) + _process_extras(); + } + /// Parse one config param, return false if not found in any subcommand, remove if it is /// /// If this has more than one dot.separated.name, go into the subcommand matching it @@ -2430,8 +2431,12 @@ class App { return false; } - if(!op->get_configurable()) + if(!op->get_configurable()) { + if(get_allow_config_extras() == config_extras_mode::ignore_all) { + return false; + } throw ConfigError::NotConfigurable(item.fullname()); + } if(op->empty()) { // Flag parsing @@ -2749,6 +2754,9 @@ class App { op->add_result(std::string{}); } } + if(op->get_trigger_on_parse() && op->current_option_state_ == Option::option_state::callback_run) { + op->clear(); + } int min_num = (std::min)(op->get_type_size_min(), op->get_items_expected_min()); int max_num = op->get_items_expected_max(); // check container like options to limit the argument size to a single type if the allow_extra_flags argument is @@ -2821,7 +2829,9 @@ class App { if(min_num > 0 && op->get_type_size_max() != min_num && (collected % op->get_type_size_max()) != 0) { op->add_result(std::string{}); } - + if(op->get_trigger_on_parse()) { + op->run_callback(); + } if(!rest.empty()) { rest = "-" + rest; args.push_back(rest); diff --git a/packages/CLI11/include/CLI/CLI.hpp b/packages/CLI11/include/CLI/CLI.hpp index 98fca54884b17de40198ea6719b90549eb3c9d61..990ba4078138d69c39170892616d191a77e1d333 100644 --- a/packages/CLI11/include/CLI/CLI.hpp +++ b/packages/CLI11/include/CLI/CLI.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2020, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2021, 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/Config.hpp b/packages/CLI11/include/CLI/Config.hpp index 725872dcaf786b5787e8826f78112292f3e1ad5f..57944ecae95d3a54b07b372fbd8179abf416dd01 100644 --- a/packages/CLI11/include/CLI/Config.hpp +++ b/packages/CLI11/include/CLI/Config.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2020, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // @@ -23,9 +23,9 @@ namespace CLI { // [CLI11:config_hpp:verbatim] namespace detail { -inline std::string convert_arg_for_ini(const std::string &arg) { +inline std::string convert_arg_for_ini(const std::string &arg, char stringQuote = '"', char characterQuote = '\'') { if(arg.empty()) { - return std::string(2, '"'); + return std::string(2, stringQuote); } // some specifically supported strings if(arg == "true" || arg == "false" || arg == "nan" || arg == "inf") { @@ -40,7 +40,7 @@ inline std::string convert_arg_for_ini(const std::string &arg) { } // just quote a single non numeric character if(arg.size() == 1) { - return std::string("'") + arg + '\''; + return std::string(1, characterQuote) + arg + characterQuote; } // handle hex, binary or octal arguments if(arg.front() == '0') { @@ -60,16 +60,20 @@ inline std::string convert_arg_for_ini(const std::string &arg) { } } } - if(arg.find_first_of('"') == std::string::npos) { - return std::string("\"") + arg + '"'; + if(arg.find_first_of(stringQuote) == std::string::npos) { + return std::string(1, stringQuote) + arg + stringQuote; } else { - return std::string("'") + arg + '\''; + return characterQuote + arg + characterQuote; } } /// Comma separated join, adds quotes if needed -inline std::string -ini_join(const std::vector<std::string> &args, char sepChar = ',', char arrayStart = '[', char arrayEnd = ']') { +inline std::string ini_join(const std::vector<std::string> &args, + char sepChar = ',', + char arrayStart = '[', + char arrayEnd = ']', + char stringQuote = '"', + char characterQuote = '\'') { std::string joined; if(args.size() > 1 && arrayStart != '\0') { joined.push_back(arrayStart); @@ -82,7 +86,7 @@ ini_join(const std::vector<std::string> &args, char sepChar = ',', char arraySta joined.push_back(' '); } } - joined.append(convert_arg_for_ini(arg)); + joined.append(convert_arg_for_ini(arg, stringQuote, characterQuote)); } if(args.size() > 1 && arrayEnd != '\0') { joined.push_back(arrayEnd); @@ -90,17 +94,17 @@ ini_join(const std::vector<std::string> &args, char sepChar = ',', char arraySta return joined; } -inline std::vector<std::string> generate_parents(const std::string §ion, std::string &name) { +inline std::vector<std::string> generate_parents(const std::string §ion, std::string &name, char parentSeparator) { std::vector<std::string> parents; if(detail::to_lower(section) != "default") { - if(section.find('.') != std::string::npos) { - parents = detail::split(section, '.'); + if(section.find(parentSeparator) != std::string::npos) { + parents = detail::split(section, parentSeparator); } else { parents = {section}; } } - if(name.find('.') != std::string::npos) { - std::vector<std::string> plist = detail::split(name, '.'); + if(name.find(parentSeparator) != std::string::npos) { + std::vector<std::string> plist = detail::split(name, parentSeparator); name = plist.back(); detail::remove_quotes(name); plist.pop_back(); @@ -115,10 +119,11 @@ inline std::vector<std::string> generate_parents(const std::string §ion, std } /// assuming non default segments do a check on the close and open of the segments in a configItem structure -inline void checkParentSegments(std::vector<ConfigItem> &output, const std::string ¤tSection) { +inline void +checkParentSegments(std::vector<ConfigItem> &output, const std::string ¤tSection, char parentSeparator) { std::string estring; - auto parents = detail::generate_parents(currentSection, estring); + auto parents = detail::generate_parents(currentSection, estring, parentSeparator); if(!output.empty() && output.back().name == "--") { std::size_t msize = (parents.size() > 1U) ? parents.size() : 2; while(output.back().parents.size() >= msize) { @@ -166,43 +171,53 @@ inline void checkParentSegments(std::vector<ConfigItem> &output, const std::stri inline std::vector<ConfigItem> ConfigBase::from_config(std::istream &input) const { std::string line; - std::string section = "default"; - + std::string currentSection = "default"; + std::string previousSection = "default"; std::vector<ConfigItem> output; bool isDefaultArray = (arrayStart == '[' && arrayEnd == ']' && arraySeparator == ','); bool isINIArray = (arrayStart == '\0' || arrayStart == ' ') && arrayStart == arrayEnd; + bool inSection{false}; char aStart = (isINIArray) ? '[' : arrayStart; char aEnd = (isINIArray) ? ']' : arrayEnd; char aSep = (isINIArray && arraySeparator == ' ') ? ',' : arraySeparator; - + int currentSectionIndex{0}; while(getline(input, line)) { std::vector<std::string> items_buffer; std::string name; detail::trim(line); std::size_t len = line.length(); - if(len > 1 && line.front() == '[' && line.back() == ']') { - if(section != "default") { + // lines have to be at least 3 characters to have any meaning to CLI just skip the rest + if(len < 3) { + continue; + } + if(line.front() == '[' && line.back() == ']') { + if(currentSection != "default") { // insert a section end which is just an empty items_buffer output.emplace_back(); - output.back().parents = detail::generate_parents(section, name); + output.back().parents = detail::generate_parents(currentSection, name, parentSeparatorChar); output.back().name = "--"; } - section = line.substr(1, len - 2); + currentSection = line.substr(1, len - 2); // deal with double brackets for TOML - if(section.size() > 1 && section.front() == '[' && section.back() == ']') { - section = section.substr(1, section.size() - 2); + if(currentSection.size() > 1 && currentSection.front() == '[' && currentSection.back() == ']') { + currentSection = currentSection.substr(1, currentSection.size() - 2); } - if(detail::to_lower(section) == "default") { - section = "default"; + if(detail::to_lower(currentSection) == "default") { + currentSection = "default"; } else { - detail::checkParentSegments(output, section); + detail::checkParentSegments(output, currentSection, parentSeparatorChar); + } + inSection = false; + if(currentSection == previousSection) { + ++currentSectionIndex; + } else { + currentSectionIndex = 0; + previousSection = currentSection; } continue; } - if(len == 0) { - continue; - } + // comment lines if(line.front() == ';' || line.front() == '#' || line.front() == commentChar) { continue; @@ -213,6 +228,11 @@ inline std::vector<ConfigItem> ConfigBase::from_config(std::istream &input) cons if(pos != std::string::npos) { name = detail::trim_copy(line.substr(0, pos)); std::string item = detail::trim_copy(line.substr(pos + 1)); + auto cloc = item.find(commentChar); + if(cloc != std::string::npos) { + item.erase(cloc, std::string::npos); + detail::trim(item); + } if(item.size() > 1 && item.front() == aStart) { for(std::string multiline; item.back() != aEnd && std::getline(input, multiline);) { detail::trim(multiline); @@ -228,9 +248,15 @@ inline std::vector<ConfigItem> ConfigBase::from_config(std::istream &input) cons } } else { name = detail::trim_copy(line); + auto cloc = name.find(commentChar); + if(cloc != std::string::npos) { + name.erase(cloc, std::string::npos); + detail::trim(name); + } + items_buffer = {"true"}; } - if(name.find('.') == std::string::npos) { + if(name.find(parentSeparatorChar) == std::string::npos) { detail::remove_quotes(name); } // clean up quotes on the items @@ -238,8 +264,20 @@ inline std::vector<ConfigItem> ConfigBase::from_config(std::istream &input) cons detail::remove_quotes(it); } - std::vector<std::string> parents = detail::generate_parents(section, name); - + std::vector<std::string> parents = detail::generate_parents(currentSection, name, parentSeparatorChar); + if(parents.size() > maximumLayers) { + continue; + } + if(!configSection.empty() && !inSection) { + if(parents.empty() || parents.front() != configSection) { + continue; + } + if(configIndex >= 0 && currentSectionIndex != configIndex) { + continue; + } + parents.erase(parents.begin()); + inSection = true; + } if(!output.empty() && name == output.back().name && parents == output.back().parents) { output.back().inputs.insert(output.back().inputs.end(), items_buffer.begin(), items_buffer.end()); } else { @@ -249,11 +287,11 @@ inline std::vector<ConfigItem> ConfigBase::from_config(std::istream &input) cons output.back().inputs = std::move(items_buffer); } } - if(section != "default") { + if(currentSection != "default") { // insert a section end which is just an empty items_buffer std::string ename; output.emplace_back(); - output.back().parents = detail::generate_parents(section, ename); + output.back().parents = detail::generate_parents(currentSection, ename, parentSeparatorChar); output.back().name = "--"; while(output.back().parents.size() > 1) { output.push_back(output.back()); @@ -273,8 +311,8 @@ ConfigBase::to_config(const App *app, bool default_also, bool write_description, std::vector<std::string> groups = app->get_groups(); bool defaultUsed = false; groups.insert(groups.begin(), std::string("Options")); - if(write_description) { - out << commentLead << app->get_description() << '\n'; + if(write_description && (app->get_configurable() || app->get_parent() == nullptr || app->get_name().empty())) { + out << commentLead << detail::fix_newlines(commentLead, app->get_description()) << '\n'; } for(auto &group : groups) { if(group == "Options" || group.empty()) { @@ -296,13 +334,16 @@ ConfigBase::to_config(const App *app, bool default_also, bool write_description, } } std::string name = prefix + opt->get_single_name(); - std::string value = detail::ini_join(opt->reduced_results(), arraySeparator, arrayStart, arrayEnd); + std::string value = detail::ini_join( + opt->reduced_results(), arraySeparator, arrayStart, arrayEnd, stringQuote, characterQuote); if(value.empty() && default_also) { if(!opt->get_default_str().empty()) { - value = detail::convert_arg_for_ini(opt->get_default_str()); + value = detail::convert_arg_for_ini(opt->get_default_str(), stringQuote, characterQuote); } else if(opt->get_expected_min() == 0) { value = "false"; + } else if(opt->get_run_callback_for_default()) { + value = "\"\""; // empty string default value } } @@ -332,17 +373,18 @@ ConfigBase::to_config(const App *app, bool default_also, bool write_description, if(!prefix.empty() || app->get_parent() == nullptr) { out << '[' << prefix << subcom->get_name() << "]\n"; } else { - std::string subname = app->get_name() + "." + subcom->get_name(); + std::string subname = app->get_name() + parentSeparatorChar + subcom->get_name(); auto p = app->get_parent(); while(p->get_parent() != nullptr) { - subname = p->get_name() + "." + subname; + subname = p->get_name() + parentSeparatorChar + subname; p = p->get_parent(); } out << '[' << subname << "]\n"; } out << to_config(subcom, default_also, write_description, ""); } else { - out << to_config(subcom, default_also, write_description, prefix + subcom->get_name() + "."); + out << to_config( + subcom, default_also, write_description, prefix + subcom->get_name() + parentSeparatorChar); } } } diff --git a/packages/CLI11/include/CLI/ConfigFwd.hpp b/packages/CLI11/include/CLI/ConfigFwd.hpp index 2d9f9a39c7d0609ee524bb3d881a6c836f8c0ff9..ef2ac345f87986f8888512791a0255a9e1e5f1d3 100644 --- a/packages/CLI11/include/CLI/ConfigFwd.hpp +++ b/packages/CLI11/include/CLI/ConfigFwd.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2020, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // @@ -87,6 +87,18 @@ class ConfigBase : public Config { char arraySeparator = ','; /// the character used separate the name from the value char valueDelimiter = '='; + /// the character to use around strings + char stringQuote = '"'; + /// the character to use around single characters + char characterQuote = '\''; + /// the maximum number of layers to allow + uint8_t maximumLayers{255}; + /// the separator used to separator parent layers + char parentSeparatorChar{'.'}; + /// Specify the configuration index to use for arrayed sections + int16_t configIndex{-1}; + /// Specify the configuration section that should be used + std::string configSection{}; public: std::string @@ -114,6 +126,41 @@ class ConfigBase : public Config { valueDelimiter = vSep; return this; } + /// Specify the quote characters used around strings and characters + ConfigBase *quoteCharacter(char qString, char qChar) { + stringQuote = qString; + characterQuote = qChar; + return this; + } + /// Specify the maximum number of parents + ConfigBase *maxLayers(uint8_t layers) { + maximumLayers = layers; + return this; + } + /// Specify the separator to use for parent layers + ConfigBase *parentSeparator(char sep) { + parentSeparatorChar = sep; + return this; + } + /// get a reference to the configuration section + std::string §ionRef() { return configSection; } + /// get the section + const std::string §ion() const { return configSection; } + /// specify a particular section of the configuration file to use + ConfigBase *section(const std::string §ionName) { + configSection = sectionName; + return this; + } + + /// get a reference to the configuration index + int16_t &indexRef() { return configIndex; } + /// get the section index + int16_t index() const { return configIndex; } + /// specify a particular index in the section to use (-1) for all sections to use + ConfigBase *index(int16_t sectionIndex) { + configIndex = sectionIndex; + return this; + } }; /// the default Config is the TOML file format diff --git a/packages/CLI11/include/CLI/Error.hpp b/packages/CLI11/include/CLI/Error.hpp index e6a8325730deb119e7c37989cdf469e78d951f57..de3122bb431406cf4f16d61161f413b2f2ef29f0 100644 --- a/packages/CLI11/include/CLI/Error.hpp +++ b/packages/CLI11/include/CLI/Error.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2020, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2021, 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 cb9e92a57ab5a9bc68a1b156752a9549c28af9cc..e45aa25d9739a78d60542b2cfd989199201ade27 100644 --- a/packages/CLI11/include/CLI/Formatter.hpp +++ b/packages/CLI11/include/CLI/Formatter.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2020, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2021, 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 6908d464447ae76081eb6240b7ac9bf86eadfcd2..728926c0f58909a61ffcf3af583ec28652e8fff0 100644 --- a/packages/CLI11/include/CLI/FormatterFwd.hpp +++ b/packages/CLI11/include/CLI/FormatterFwd.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2020, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2021, 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/Macros.hpp b/packages/CLI11/include/CLI/Macros.hpp index 778fec7dcbdace22952977e0feb851e5be8cf9c5..f228c918cd408ce33aeed734ca3cc2b2d40f6edf 100644 --- a/packages/CLI11/include/CLI/Macros.hpp +++ b/packages/CLI11/include/CLI/Macros.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2020, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // @@ -8,7 +8,7 @@ // [CLI11:macros_hpp:verbatim] -// The following version macro is very similar to the one in PyBind11 +// The following version macro is very similar to the one in pybind11 #if !(defined(_MSC_VER) && __cplusplus == 199711L) && !defined(__INTEL_COMPILER) #if __cplusplus >= 201402L #define CLI11_CPP14 diff --git a/packages/CLI11/include/CLI/Option.hpp b/packages/CLI11/include/CLI/Option.hpp index f09f61f9730f105383552b92a245df786a3c48c6..616cd120cb292d94f4e533e0186c70a483500521 100644 --- a/packages/CLI11/include/CLI/Option.hpp +++ b/packages/CLI11/include/CLI/Option.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2020, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // @@ -94,6 +94,9 @@ template <typename CRTP> class OptionBase { /// Changes the group membership CRTP *group(const std::string &name) { + if(!detail::valid_alias_name_string(name)) { + throw IncorrectConstruction("Group names may not contain newlines or null characters"); + } group_ = name; return static_cast<CRTP *>(this); } @@ -337,6 +340,10 @@ class Option : public OptionBase<Option> { bool run_callback_for_default_{false}; /// flag indicating a separator needs to be injected after each argument call bool inject_separator_{false}; + /// flag indicating that the option should trigger the validation and callback chain on each result when loaded + bool trigger_on_result_{false}; + /// flag indicating that the option should force the callback regardless if any results present + bool force_callback_{false}; ///@} /// Making an option by hand is not defined, it must be made by the App class @@ -358,8 +365,8 @@ class Option : public OptionBase<Option> { /// True if the option was not passed bool empty() const { return results_.empty(); } - /// This class is true if option is passed. - explicit operator bool() const { return !empty(); } + /// This bool operator returns true if any arguments were passed or the option callback is forced + explicit operator bool() const { return !empty() || force_callback_; } /// Clear the parsed results (mostly for testing) void clear() { @@ -420,6 +427,21 @@ class Option : public OptionBase<Option> { } /// Get the current value of allow extra args bool get_allow_extra_args() const { return allow_extra_args_; } + /// Set the value of trigger_on_parse which specifies that the option callback should be triggered on every parse + Option *trigger_on_parse(bool value = true) { + trigger_on_result_ = value; + return this; + } + /// The status of trigger on parse + bool get_trigger_on_parse() const { return trigger_on_result_; } + + /// Set the value of force_callback + Option *force_callback(bool value = true) { + force_callback_ = value; + return this; + } + /// The status of force_callback + bool get_force_callback() const { return force_callback_; } /// Set the value of run_callback_for_default which controls whether the callback function should be called to set /// the default This is controlled automatically but could be manipulated by the user. @@ -666,7 +688,7 @@ class Option : public OptionBase<Option> { /// The maximum number of arguments the option expects int get_type_size_max() const { return type_size_max_; } - /// The number of arguments the option expects + /// Return the inject_separator flag int get_inject_separator() const { return inject_separator_; } /// The environment variable associated to this value @@ -818,7 +840,9 @@ class Option : public OptionBase<Option> { /// Process the callback void run_callback() { - + if(force_callback_ && results_.empty()) { + add_result(default_str_); + } if(current_option_state_ == option_state::parsing) { _validate_results(results_); current_option_state_ = option_state::validated; @@ -974,10 +998,10 @@ class Option : public OptionBase<Option> { /// Puts a result at the end Option *add_result(std::vector<std::string> s) { + current_option_state_ = option_state::parsing; for(auto &str : s) { _add_result(std::move(str), results_); } - current_option_state_ = option_state::parsing; return this; } @@ -1136,7 +1160,8 @@ class Option : public OptionBase<Option> { results_.clear(); try { add_result(val_str); - if(run_callback_for_default_) { + // if trigger_on_result_ is set the callback already ran + if(run_callback_for_default_ && !trigger_on_result_) { run_callback(); // run callback sets the state we need to reset it again current_option_state_ = option_state::parsing; } else { diff --git a/packages/CLI11/include/CLI/Split.hpp b/packages/CLI11/include/CLI/Split.hpp index 36c953de8471e2b4f414c3c877ff087b494638c2..83ef572c1c42e7a575d0d22c7e2515959bf9b566 100644 --- a/packages/CLI11/include/CLI/Split.hpp +++ b/packages/CLI11/include/CLI/Split.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2020, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2021, 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 090445e5b1236ce92f3b009bd1d55cde9a5d0ebd..4718aedd9a9e301591afacc4ac22730e02e1d183 100644 --- a/packages/CLI11/include/CLI/StringTools.hpp +++ b/packages/CLI11/include/CLI/StringTools.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2020, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // @@ -157,6 +157,22 @@ inline std::string &remove_quotes(std::string &str) { return str; } +/// Add a leader to the beginning of all new lines (nothing is added +/// at the start of the first line). `"; "` would be for ini files +/// +/// Can't use Regex, or this would be a subs. +inline std::string fix_newlines(const std::string &leader, std::string input) { + std::string::size_type n = 0; + while(n != std::string::npos && n < input.size()) { + n = input.find('\n', n); + if(n != std::string::npos) { + input = input.substr(0, n + 1) + leader + input.substr(n + 1); + n += leader.size(); + } + } + return input; +} + /// Make a copy of the string and then trim it, any filter string can be used (any char in string is filtered) inline std::string trim_copy(const std::string &str, const std::string &filter) { std::string s = str; @@ -191,7 +207,7 @@ inline std::ostream &format_aliases(std::ostream &out, const std::vector<std::st } else { front = false; } - out << alias; + out << detail::fix_newlines(" ", alias); } out << "\n"; } @@ -199,23 +215,35 @@ inline std::ostream &format_aliases(std::ostream &out, const std::vector<std::st } /// Verify the first character of an option -template <typename T> bool valid_first_char(T c) { - return std::isalnum(c, std::locale()) || c == '_' || c == '?' || c == '@'; -} +/// - is a trigger character, ! has special meaning and new lines would just be annoying to deal with +template <typename T> bool valid_first_char(T c) { return ((c != '-') && (c != '!') && (c != ' ') && c != '\n'); } /// Verify following characters of an option -template <typename T> bool valid_later_char(T c) { return valid_first_char(c) || c == '.' || c == '-'; } +template <typename T> bool valid_later_char(T c) { + // = and : are value separators, { has special meaning for option defaults, + // and \n would just be annoying to deal with in many places allowing space here has too much potential for + // inadvertent entry errors and bugs + return ((c != '=') && (c != ':') && (c != '{') && (c != ' ') && c != '\n'); +} -/// Verify an option name +/// Verify an option/subcommand name inline bool valid_name_string(const std::string &str) { - if(str.empty() || !valid_first_char(str[0])) + if(str.empty() || !valid_first_char(str[0])) { return false; - for(auto c : str.substr(1)) - if(!valid_later_char(c)) + } + auto e = str.end(); + for(auto c = str.begin() + 1; c != e; ++c) + if(!valid_later_char(*c)) return false; return true; } +/// Verify an app name +inline bool valid_alias_name_string(const std::string &str) { + static const std::string badChars(std::string("\n") + '\0'); + return (str.find_first_of(badChars) == std::string::npos); +} + /// check if a string is a container segment separator (empty or "%%") inline bool is_separator(const std::string &str) { static const std::string sep("%%"); @@ -260,7 +288,7 @@ inline bool has_default_flag_values(const std::string &flags) { } inline void remove_default_flag_values(std::string &flags) { - auto loc = flags.find_first_of('{'); + auto loc = flags.find_first_of('{', 2); while(loc != std::string::npos) { auto finish = flags.find_first_of("},", loc + 1); if((finish != std::string::npos) && (flags[finish] == '}')) { @@ -367,22 +395,6 @@ inline std::vector<std::string> split_up(std::string str, char delimiter = '\0') return output; } -/// Add a leader to the beginning of all new lines (nothing is added -/// at the start of the first line). `"; "` would be for ini files -/// -/// Can't use Regex, or this would be a subs. -inline std::string fix_newlines(const std::string &leader, std::string input) { - std::string::size_type n = 0; - while(n != std::string::npos && n < input.size()) { - n = input.find('\n', n); - if(n != std::string::npos) { - input = input.substr(0, n + 1) + leader + input.substr(n + 1); - n += leader.size(); - } - } - return input; -} - /// This function detects an equal or colon followed by an escaped quote after an argument /// then modifies the string to replace the equality with a space. This is needed /// to allow the split up function to work properly and is intended to be used with the find_and_modify function diff --git a/packages/CLI11/include/CLI/Timer.hpp b/packages/CLI11/include/CLI/Timer.hpp index 69728b7dc89a4df2bed42755730a1b9f9184465a..429ca026c5ea3d66a2dfe786d7965f231cf36b7f 100644 --- a/packages/CLI11/include/CLI/Timer.hpp +++ b/packages/CLI11/include/CLI/Timer.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2020, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2021, 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 be0e89ab6f55a304e7171e26010fcb1b0b4dd983..2b87ec60a84dbec5cc0ea6b8d05649e8c8f9d605 100644 --- a/packages/CLI11/include/CLI/TypeTools.hpp +++ b/packages/CLI11/include/CLI/TypeTools.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2020, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // @@ -1059,7 +1059,7 @@ bool lexical_cast(const std::string &input, T &output) { } // LCOV_EXCL_START // This version of cast is only used for odd cases in an older compilers the fail over - // from_stream is tested elsewhere an not relevent for coverage here + // from_stream is tested elsewhere an not relevant for coverage here return from_stream(input, output); // LCOV_EXCL_STOP } diff --git a/packages/CLI11/include/CLI/Validators.hpp b/packages/CLI11/include/CLI/Validators.hpp index 90b590f6b6f99cf3067683284146e849f673ec28..3c8b2f42036a39ed8e1bd7b3bc2e631555072250 100644 --- a/packages/CLI11/include/CLI/Validators.hpp +++ b/packages/CLI11/include/CLI/Validators.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2020, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // @@ -467,35 +467,35 @@ class Range : public Validator { /// Note that the constructor is templated, but the struct is not, so C++17 is not /// needed to provide nice syntax for Range(a,b). template <typename T> - Range(T min, T max, const std::string &validator_name = std::string{}) : Validator(validator_name) { + Range(T min_val, T max_val, const std::string &validator_name = std::string{}) : Validator(validator_name) { if(validator_name.empty()) { std::stringstream out; - out << detail::type_name<T>() << " in [" << min << " - " << max << "]"; + out << detail::type_name<T>() << " in [" << min_val << " - " << max_val << "]"; description(out.str()); } - func_ = [min, max](std::string &input) { + func_ = [min_val, max_val](std::string &input) { T val; bool converted = detail::lexical_cast(input, val); - if((!converted) || (val < min || val > max)) - return std::string("Value ") + input + " not in range " + std::to_string(min) + " to " + - std::to_string(max); + if((!converted) || (val < min_val || val > max_val)) + return std::string("Value ") + input + " not in range " + std::to_string(min_val) + " to " + + std::to_string(max_val); - return std::string(); + return std::string{}; }; } /// Range of one value is 0 to value template <typename T> - explicit Range(T max, const std::string &validator_name = std::string{}) - : Range(static_cast<T>(0), max, validator_name) {} + explicit Range(T max_val, const std::string &validator_name = std::string{}) + : Range(static_cast<T>(0), max_val, validator_name) {} }; /// Check for a non negative number -const Range NonNegativeNumber(std::numeric_limits<double>::max(), "NONNEGATIVE"); +const Range NonNegativeNumber((std::numeric_limits<double>::max)(), "NONNEGATIVE"); /// Check for a positive valued number (val>0.0), min() her is the smallest positive number -const Range PositiveNumber(std::numeric_limits<double>::min(), std::numeric_limits<double>::max(), "POSITIVE"); +const Range PositiveNumber((std::numeric_limits<double>::min)(), (std::numeric_limits<double>::max)(), "POSITIVE"); /// Produce a bounded range (factory). Min and max are inclusive. class Bound : public Validator { @@ -504,28 +504,28 @@ class Bound : public Validator { /// /// Note that the constructor is templated, but the struct is not, so C++17 is not /// needed to provide nice syntax for Range(a,b). - template <typename T> Bound(T min, T max) { + template <typename T> Bound(T min_val, T max_val) { std::stringstream out; - out << detail::type_name<T>() << " bounded to [" << min << " - " << max << "]"; + out << detail::type_name<T>() << " bounded to [" << min_val << " - " << max_val << "]"; description(out.str()); - func_ = [min, max](std::string &input) { + func_ = [min_val, max_val](std::string &input) { T val; bool converted = detail::lexical_cast(input, val); if(!converted) { return std::string("Value ") + input + " could not be converted"; } - if(val < min) - input = detail::to_string(min); - else if(val > max) - input = detail::to_string(max); + if(val < min_val) + input = detail::to_string(min_val); + else if(val > max_val) + input = detail::to_string(max_val); return std::string{}; }; } /// Range of one value is 0 to value - template <typename T> explicit Bound(T max) : Bound(static_cast<T>(0), max) {} + template <typename T> explicit Bound(T max_val) : Bound(static_cast<T>(0), max_val) {} }; namespace detail { @@ -1100,12 +1100,35 @@ inline std::pair<std::string, std::string> split_program_name(std::string comman if(esp == std::string::npos) { // if we have reached the end and haven't found a valid file just assume the first argument is the // program name - esp = commandline.find_first_of(' ', 1); + if(commandline[0] == '"' || commandline[0] == '\'' || commandline[0] == '`') { + bool embeddedQuote = false; + auto keyChar = commandline[0]; + auto end = commandline.find_first_of(keyChar, 1); + while((end != std::string::npos) && (commandline[end - 1] == '\\')) { // deal with escaped quotes + end = commandline.find_first_of(keyChar, end + 1); + embeddedQuote = true; + } + if(end != std::string::npos) { + vals.first = commandline.substr(1, end - 1); + esp = end + 1; + if(embeddedQuote) { + vals.first = find_and_replace(vals.first, std::string("\\") + keyChar, std::string(1, keyChar)); + } + } else { + esp = commandline.find_first_of(' ', 1); + } + } else { + esp = commandline.find_first_of(' ', 1); + } + break; } } - vals.first = commandline.substr(0, esp); - rtrim(vals.first); + if(vals.first.empty()) { + vals.first = commandline.substr(0, esp); + rtrim(vals.first); + } + // strip the program name vals.second = (esp != std::string::npos) ? commandline.substr(esp + 1) : std::string{}; ltrim(vals.second); diff --git a/packages/CLI11/include/CLI/Version.hpp b/packages/CLI11/include/CLI/Version.hpp index dde959890da59b6640e9a16eefdc60b5a74e925f..c989ae86d4cc559f0491ce4a88c71e26d87fbf7b 100644 --- a/packages/CLI11/include/CLI/Version.hpp +++ b/packages/CLI11/include/CLI/Version.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2020, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // @@ -8,9 +8,9 @@ // [CLI11:version_hpp:verbatim] -#define CLI11_VERSION_MAJOR 1 -#define CLI11_VERSION_MINOR 9 +#define CLI11_VERSION_MAJOR 2 +#define CLI11_VERSION_MINOR 1 #define CLI11_VERSION_PATCH 1 -#define CLI11_VERSION "1.9.1" +#define CLI11_VERSION "2.1.1" // [CLI11:version_hpp:end] diff --git a/packages/CLI11/scripts/MakeSingleHeader.py b/packages/CLI11/scripts/MakeSingleHeader.py index 78714ffb5038a6a825de32b9a2d64f1314dd7da9..42e0ee7a9c590ae27614d5e77db2c9459170438b 100755 --- a/packages/CLI11/scripts/MakeSingleHeader.py +++ b/packages/CLI11/scripts/MakeSingleHeader.py @@ -39,12 +39,20 @@ DIR = os.path.dirname(os.path.abspath(__file__)) class HeaderGroups(dict): def __init__(self, tag): + """ + A dictionary that also can read headers given a tag expression. + + TODO: might have gone overboard on this one, could maybe be two functions. + """ self.re_matcher = re.compile( tag_str.format(tag=tag), re.MULTILINE | re.DOTALL | re.VERBOSE ) super(HeaderGroups, self).__init__() def read_header(self, filename): + """ + Read a header file in and add items to the dict, based on the item's action. + """ with open(filename) as f: inner = f.read() @@ -67,12 +75,18 @@ class HeaderGroups(dict): raise RuntimeError("Action not understood, must be verbatim or set") def post_process(self): + """ + Turn sets into multiple line strings. + """ for key in self: if isinstance(self[key], set): self[key] = "\n".join(self[key]) -def MakeHeader(output, main_header, files, tag, namespace, macro=None, version=None): +def make_header(output, main_header, files, tag, namespace, macro=None, version=None): + """ + Makes a single header given a main header template and a list of files. + """ groups = HeaderGroups(tag) # Set tag if possible to class variable @@ -129,7 +143,7 @@ if __name__ == "__main__": parser.add_argument("--version", help="Include this version in the generated file") args = parser.parse_args() - MakeHeader( + make_header( args.output, args.main, args.files, diff --git a/packages/CLI11/scripts/UpdateDownloadProj.py b/packages/CLI11/scripts/UpdateDownloadProj.py deleted file mode 100755 index e20797f4f6c942d9dcca25a2ae8afe6069e56269..0000000000000000000000000000000000000000 --- a/packages/CLI11/scripts/UpdateDownloadProj.py +++ /dev/null @@ -1,29 +0,0 @@ -#!/usr/bin/env python - -from __future__ import print_function, division - -from plumbum import local, cli, FG -from plumbum.cmd import curl - -FILES = [ - "https://raw.githubusercontent.com/Crascit/DownloadProject/master/DownloadProject.cmake", - "https://raw.githubusercontent.com/Crascit/DownloadProject/master/DownloadProject.CMakeLists.cmake.in", -] - -DIR = local.path(__file__).dirname - - -def download_file(path): - name = path.split("/")[-1] - (curl[path] > name) & FG - - -class UpdateDownloadProj(cli.Application): - def main(self): - with local.cwd(DIR / "../cmake"): - for f in FILES: - download_file(f) - - -if __name__ == "__main__": - UpdateDownloadProj() diff --git a/packages/CLI11/scripts/mdlint_style.rb b/packages/CLI11/scripts/mdlint_style.rb new file mode 100644 index 0000000000000000000000000000000000000000..6fca85b1a3d05a43aba1fca2e3aa256676e1f11d --- /dev/null +++ b/packages/CLI11/scripts/mdlint_style.rb @@ -0,0 +1,8 @@ +all + +exclude_rule 'MD013' # Line length +exclude_rule 'MD033' # Inline HTML +exclude_rule 'MD034' # Bare URL (for now) + +rule 'MD026', punctuation: '.,;:!' # Trailing punctuation in header (& in this case) +rule 'MD029', style: :ordered diff --git a/packages/CLI11/test_package/example.cpp b/packages/CLI11/test_package/example.cpp index beb97fe7fafec9e224cc58e97843ed5d8dff6f23..464cf751896f183fa1bba19d965ac521c8b60a52 100644 --- a/packages/CLI11/test_package/example.cpp +++ b/packages/CLI11/test_package/example.cpp @@ -9,7 +9,7 @@ int main(int argc, char **argv) { CLI::App app("Some nice description"); int x = 0; - app.add_option("-x", x, "an integer value", true /* show default */); + app.add_option("-x", x, "an integer value")->capture_default_str(); bool flag; app.add_flag("-f,--flag", flag, "a flag option"); diff --git a/packages/CLI11/tests/AppTest.cpp b/packages/CLI11/tests/AppTest.cpp index 6c3e71ced8ee59cbe4409698b86b44b56430a881..d425b414c08fcd287f07a9030810614f7b8292f5 100644 --- a/packages/CLI11/tests/AppTest.cpp +++ b/packages/CLI11/tests/AppTest.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2020, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // @@ -127,6 +127,17 @@ TEST_CASE_METHOD(TApp, "DashedOptionsSingleString", "[app]") { CHECK(app.count("--that") == 2u); } +TEST_CASE_METHOD(TApp, "StrangeFlagNames", "[app]") { + app.add_flag("-="); + app.add_flag("--t\tt"); + app.add_flag("-{"); + CHECK_THROWS_AS(app.add_flag("--t t"), CLI::ConstructionError); + args = {"-=", "--t\tt"}; + run(); + CHECK(app.count("-=") == 1u); + CHECK(app.count("--t\tt") == 1u); +} + TEST_CASE_METHOD(TApp, "RequireOptionsError", "[app]") { using Catch::Matchers::Contains; @@ -582,6 +593,20 @@ TEST_CASE_METHOD(TApp, "SingleArgVector", "[app]") { CHECK("happy" == path); } +TEST_CASE_METHOD(TApp, "StrangeOptionNames", "[app]") { + app.add_option("-:"); + app.add_option("--t\tt"); + app.add_option("--{}"); + app.add_option("--:)"); + CHECK_THROWS_AS(app.add_option("--t t"), CLI::ConstructionError); + args = {"-:)", "--{}", "5"}; + run(); + CHECK(app.count("-:") == 1u); + CHECK(app.count("--{}") == 1u); + CHECK(app["-:"]->as<char>() == ')'); + CHECK(app["--{}"]->as<int>() == 5); +} + TEST_CASE_METHOD(TApp, "FlagLikeOption", "[app]") { bool val{false}; auto opt = app.add_option("--flag", val)->type_size(0)->default_str("true"); @@ -809,7 +834,7 @@ TEST_CASE_METHOD(TApp, "TakeFirstOptMulti", "[app]") { TEST_CASE_METHOD(TApp, "ComplexOptMulti", "[app]") { std::complex<double> val; - app.add_complex("--long", val)->take_first()->allow_extra_args(); + app.add_option("--long", val)->take_first()->allow_extra_args(); args = {"--long", "1", "2", "3", "4"}; @@ -1933,7 +1958,6 @@ TEST_CASE_METHOD(TApp, "AllowExtrasArgModify", "[app]") { app.allow_extras(); app.add_option("-f", v2); args = {"27", "-f", "45", "-x"}; - auto cargs = args; app.parse(args); CHECK(std::vector<std::string>({"45", "-x"}) == args); @@ -2219,7 +2243,7 @@ TEST_CASE_METHOD(TApp, "CustomUserSepParse3", "[app]") { CHECK(std::vector<int>({1, 2}) == vals); app.remove_option(opt); - app.add_option("--idx", vals, "", false)->delimiter(','); + app.add_option("--idx", vals)->delimiter(','); run(); CHECK(std::vector<int>({1, 2}) == vals); } @@ -2255,3 +2279,32 @@ TEST_CASE_METHOD(TApp, "CustomUserSepParse5", "[app]") { run(); CHECK(std::vector<std::string>({"this", "is", "a", "test"}) == bar); } + +// #218 +TEST_CASE_METHOD(TApp, "logFormSingleDash", "[app]") { + bool verbose{false}; + bool veryverbose{false}; + bool veryveryverbose{false}; + app.name("testargs"); + app.allow_extras(); + args = {"-v", "-vv", "-vvv"}; + app.final_callback([&]() { + auto rem = app.remaining(); + for(auto &arg : rem) { + if(arg == "-v") { + verbose = true; + } + if(arg == "-vv") { + veryverbose = true; + } + if(arg == "-vvv") { + veryveryverbose = true; + } + } + }); + run(); + CHECK(app.remaining().size() == 3U); + CHECK(verbose); + CHECK(veryverbose); + CHECK(veryveryverbose); +} diff --git a/packages/CLI11/tests/BoostOptionTypeTest.cpp b/packages/CLI11/tests/BoostOptionTypeTest.cpp index 2110bcc6540bd881bb4474b0d00df3e21f0582c0..7ed096cd19f473cdb05ca1ec47ab586d93b272b4 100644 --- a/packages/CLI11/tests/BoostOptionTypeTest.cpp +++ b/packages/CLI11/tests/BoostOptionTypeTest.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2020, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2021, 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 4a86923f6dd7b74a9f51d02785e9f060e0617129..c322615f638715b988d17ea0addf102595b4b890 100644 --- a/packages/CLI11/tests/CMakeLists.txt +++ b/packages/CLI11/tests/CMakeLists.txt @@ -1,34 +1,35 @@ list(APPEND CMAKE_MODULE_PATH "${CLI11_SOURCE_DIR}/cmake") if(CLI11_SANITIZERS) - message(STATUS "Using arsenm/sanitizers-cmake") - FetchContent_Declare(sanitizers - GIT_REPOSITORY https://github.com/arsenm/sanitizers-cmake.git - GIT_SHALLOW 1 - GIT_TAG 99e159e) + message(STATUS "Using arsenm/sanitizers-cmake") + FetchContent_Declare( + sanitizers + GIT_REPOSITORY https://github.com/arsenm/sanitizers-cmake.git + GIT_SHALLOW 1 + GIT_TAG 99e159e) - FetchContent_GetProperties(sanitizers) + FetchContent_GetProperties(sanitizers) - if (NOT sanitizers_POPULATED) - FetchContent_Populate(sanitizers) - endif() + if(NOT sanitizers_POPULATED) + FetchContent_Populate(sanitizers) + endif() - list(APPEND CMAKE_MODULE_PATH "${sanitizers_SOURCE_DIR}/cmake") + list(APPEND CMAKE_MODULE_PATH "${sanitizers_SOURCE_DIR}/cmake") - find_package(Sanitizers) - if(SANITIZE_ADDRESS) - message(STATUS "You might want to use \"${ASan_WRAPPER}\" to run your program") - endif() + find_package(Sanitizers) + if(SANITIZE_ADDRESS) + message(STATUS "You might want to use \"${ASan_WRAPPER}\" to run your program") + endif() else() - macro(add_sanitizers) - endmacro() -endif() + macro(add_sanitizers) + endmacro() +endif() # Add boost to test boost::optional (currently explicitly requested)" option(CLI11_BOOST "Turn on boost test (currently may fail with Boost 1.70)" OFF) if(CLI11_BOOST) - find_package(Boost 1.61 REQUIRED) + find_package(Boost 1.61 REQUIRED) endif() set(boost-optional-def $<$<BOOL:${Boost_FOUND}>:CLI11_BOOST_OPTIONAL>) @@ -50,15 +51,14 @@ set(CLI11_TESTS StringParseTest ComplexTypeTest TrueFalseTest - OptionGroupTest - ) + OptionGroupTest) if(WIN32) - list(APPEND CLI11_TESTS WindowsTest) + list(APPEND CLI11_TESTS WindowsTest) endif() -if (Boost_FOUND) - list(APPEND CLI11_TESTS BoostOptionTypeTest) +if(Boost_FOUND) + list(APPEND CLI11_TESTS BoostOptionTypeTest) endif() set(CLI11_MULTIONLY_TESTS TimerTest) @@ -70,68 +70,64 @@ target_include_directories(catch_main PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}") # that would require changing the includes. FetchContent would be better, but # requires newer CMake. -set(url https://github.com/philsquared/Catch/releases/download/v2.13.4/catch.hpp) -file(DOWNLOAD ${url} "${CMAKE_CURRENT_BINARY_DIR}/catch.hpp" STATUS status EXPECTED_HASH SHA256=6e0fa3dd160891a01c1f3b34e8bcd6e0140abe08eca022e390027f27dec2050b) +set(url https://github.com/philsquared/Catch/releases/download/v2.13.6/catch.hpp) +file( + DOWNLOAD ${url} "${CMAKE_CURRENT_BINARY_DIR}/catch.hpp" + STATUS status + EXPECTED_HASH SHA256=681e7505a50887c9085539e5135794fc8f66d8e5de28eadf13a30978627b0f47) list(GET status 0 error) if(error) - message(FATAL_ERROR "Could not download ${url}") + message(FATAL_ERROR "Could not download ${url}") endif() target_include_directories(catch_main PUBLIC "${CMAKE_CURRENT_BINARY_DIR}") # Target must already exist macro(add_catch_test TESTNAME) - target_link_libraries(${TESTNAME} PUBLIC catch_main) - - add_test(${TESTNAME} ${TESTNAME}) - set_target_properties(${TESTNAME} PROPERTIES FOLDER "Tests") - if (CLI11_FORCE_LIBCXX) - set_property(TARGET ${T} APPEND_STRING - PROPERTY LINK_FLAGS -stdlib=libc++) - endif() + target_link_libraries(${TESTNAME} PUBLIC catch_main) + + add_test(${TESTNAME} ${TESTNAME}) + set_target_properties(${TESTNAME} PROPERTIES FOLDER "Tests") + if(CLI11_FORCE_LIBCXX) + set_property( + TARGET ${T} + APPEND_STRING + PROPERTY LINK_FLAGS -stdlib=libc++) + endif() endmacro() foreach(T IN LISTS CLI11_TESTS) - if(CLI11_CUDA_TESTS) - set_property( - SOURCE ${T}.cpp - PROPERTY LANGUAGE CUDA - ) - endif() - add_executable(${T} ${T}.cpp ${CLI11_headers}) - add_sanitizers(${T}) - if(NOT CLI11_CUDA_TESTS) - target_link_libraries(${T} PRIVATE CLI11_warnings) - endif() - target_link_libraries(${T} PRIVATE CLI11) - add_catch_test(${T}) - - if(CLI11_SINGLE_FILE AND CLI11_SINGLE_FILE_TESTS) - add_executable(${T}_Single ${T}.cpp) - target_link_libraries(${T}_Single PRIVATE CLI11_SINGLE) - add_catch_test(${T}_Single) - set_property(TARGET ${T}_Single PROPERTY FOLDER "Tests Single File") - endif() + if(CLI11_CUDA_TESTS) + set_property(SOURCE ${T}.cpp PROPERTY LANGUAGE CUDA) + endif() + add_executable(${T} ${T}.cpp ${CLI11_headers}) + add_sanitizers(${T}) + if(NOT CLI11_CUDA_TESTS) + target_link_libraries(${T} PRIVATE CLI11_warnings) + endif() + target_link_libraries(${T} PRIVATE CLI11) + add_catch_test(${T}) + + if(CLI11_SINGLE_FILE AND CLI11_SINGLE_FILE_TESTS) + add_executable(${T}_Single ${T}.cpp) + target_link_libraries(${T}_Single PRIVATE CLI11_SINGLE) + add_catch_test(${T}_Single) + set_property(TARGET ${T}_Single PROPERTY FOLDER "Tests Single File") + endif() endforeach() foreach(T IN LISTS CLI11_MULTIONLY_TESTS) - add_executable(${T} ${T}.cpp ${CLI11_headers}) - add_sanitizers(${T}) - target_link_libraries(${T} PUBLIC CLI11) - add_catch_test(${T}) + add_executable(${T} ${T}.cpp ${CLI11_headers}) + add_sanitizers(${T}) + target_link_libraries(${T} PUBLIC CLI11) + add_catch_test(${T}) endforeach() # Add -Wno-deprecated-declarations to DeprecatedTest -set(no-deprecated-declarations - $<$<CXX_COMPILER_ID:MSVC>:/wd4996> - $<$<NOT:$<CXX_COMPILER_ID:MSVC>>:-Wno-deprecated-declarations> - ) -target_compile_options(DeprecatedTest - PRIVATE - ${no-deprecated-declarations}) -if (TARGET DeprecatedTest_Single) - target_compile_options(DeprecatedTest_Single - PRIVATE - ${no-deprecated-declarations}) +set(no-deprecated-declarations $<$<CXX_COMPILER_ID:MSVC>:/wd4996> + $<$<NOT:$<CXX_COMPILER_ID:MSVC>>:-Wno-deprecated-declarations>) +target_compile_options(DeprecatedTest PRIVATE ${no-deprecated-declarations}) +if(TARGET DeprecatedTest_Single) + target_compile_options(DeprecatedTest_Single PRIVATE ${no-deprecated-declarations}) endif() # Link test (build error if inlines missing) @@ -142,33 +138,38 @@ add_executable(link_test_2 link_test_2.cpp) target_link_libraries(link_test_2 PUBLIC CLI11 link_test_1) add_catch_test(link_test_2) if(CLI11_FORCE_LIBCXX) - set_property(TARGET link_test_1 APPEND_STRING - PROPERTY LINK_FLAGS -stdlib=libc++) - set_property(TARGET link_test_2 APPEND_STRING - PROPERTY LINK_FLAGS -stdlib=libc++) + set_property( + TARGET link_test_1 + APPEND_STRING + PROPERTY LINK_FLAGS -stdlib=libc++) + set_property( + TARGET link_test_2 + APPEND_STRING + PROPERTY LINK_FLAGS -stdlib=libc++) endif() # Add informational printout add_executable(informational informational.cpp) target_link_libraries(informational PUBLIC CLI11) if(CLI11_FORCE_LIBCXX) - set_property(TARGET informational APPEND_STRING - PROPERTY LINK_FLAGS -stdlib=libc++) + set_property( + TARGET informational + APPEND_STRING + PROPERTY LINK_FLAGS -stdlib=libc++) endif() # Force this to be in a standard location so CTest can find it -set_target_properties(informational PROPERTIES - RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}" - RUNTIME_OUTPUT_DIRECTORY_DEBUG "${CMAKE_BINARY_DIR}" - RUNTIME_OUTPUT_DIRECTORY_RELEASE "${CMAKE_BINARY_DIR}" - RUNTIME_OUTPUT_DIRECTORY_RELWITHDEBINFO "${CMAKE_BINARY_DIR}" - RUNTIME_OUTPUT_DIRECTORY_MINSIZEREL "${CMAKE_BINARY_DIR}" - ) +set_target_properties( + informational + PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}" + RUNTIME_OUTPUT_DIRECTORY_DEBUG "${CMAKE_BINARY_DIR}" + RUNTIME_OUTPUT_DIRECTORY_RELEASE "${CMAKE_BINARY_DIR}" + RUNTIME_OUTPUT_DIRECTORY_RELWITHDEBINFO "${CMAKE_BINARY_DIR}" + RUNTIME_OUTPUT_DIRECTORY_MINSIZEREL "${CMAKE_BINARY_DIR}") # Adding this printout to CTest file(WRITE "${PROJECT_BINARY_DIR}/CTestCustom.cmake" - "set(CTEST_CUSTOM_PRE_TEST \"${CMAKE_BINARY_DIR}/informational\")" -) + "set(CTEST_CUSTOM_PRE_TEST \"${CMAKE_BINARY_DIR}/informational\")") target_compile_definitions(informational PRIVATE ${boost-optional-def}) target_compile_definitions(OptionalTest PRIVATE ${boost-optional-def}) @@ -176,33 +177,33 @@ target_compile_definitions(OptionalTest PRIVATE ${boost-optional-def}) message(STATUS "Boost libs=${Boost_INCLUDE_DIRS}") if(TARGET Boost::boost) - message(STATUS "including boost target") - target_link_libraries(informational PRIVATE Boost::boost) - target_link_libraries(OptionalTest PRIVATE Boost::boost) - target_link_libraries(BoostOptionTypeTest PRIVATE Boost::boost) - if(CLI11_SINGLE_FILE AND CLI11_SINGLE_FILE_TESTS) - target_link_libraries(OptionalTest_Single PRIVATE Boost::boost) - target_link_libraries(BoostOptionTypeTest_Single PRIVATE Boost::boost) - endif() + message(STATUS "including boost target") + target_link_libraries(informational PRIVATE Boost::boost) + target_link_libraries(OptionalTest PRIVATE Boost::boost) + target_link_libraries(BoostOptionTypeTest PRIVATE Boost::boost) + if(CLI11_SINGLE_FILE AND CLI11_SINGLE_FILE_TESTS) + target_link_libraries(OptionalTest_Single PRIVATE Boost::boost) + target_link_libraries(BoostOptionTypeTest_Single PRIVATE Boost::boost) + endif() elseif(BOOST_FOUND) -message(STATUS "no boost target") - target_include_directories(informational PRIVATE ${Boost_INCLUDE_DIRS}) - target_include_directories(OptionalTest PRIVATE ${Boost_INCLUDE_DIRS}) - target_include_directories(BoostOptionTypeTest PRIVATE ${Boost_INCLUDE_DIRS}) - if(CLI11_SINGLE_FILE AND CLI11_SINGLE_FILE_TESTS) - target_include_directories(OptionalTest_Single PRIVATE ${Boost_INCLUDE_DIRS}) - target_include_directories(BoostOptionTypeTest_Single PRIVATE ${Boost_INCLUDE_DIRS}) - endif() + message(STATUS "no boost target") + target_include_directories(informational PRIVATE ${Boost_INCLUDE_DIRS}) + target_include_directories(OptionalTest PRIVATE ${Boost_INCLUDE_DIRS}) + target_include_directories(BoostOptionTypeTest PRIVATE ${Boost_INCLUDE_DIRS}) + if(CLI11_SINGLE_FILE AND CLI11_SINGLE_FILE_TESTS) + target_include_directories(OptionalTest_Single PRIVATE ${Boost_INCLUDE_DIRS}) + target_include_directories(BoostOptionTypeTest_Single PRIVATE ${Boost_INCLUDE_DIRS}) + endif() endif() if(CMAKE_BUILD_TYPE STREQUAL Coverage) - include(CodeCoverage) - setup_target_for_coverage( - NAME CLI11_coverage - EXECUTABLE ctest - DEPENDENCIES - ${CLI11_TESTS} - ${CLI11_MULTIONLY_TESTS} - ) + include(CodeCoverage) + setup_target_for_coverage( + NAME + CLI11_coverage + EXECUTABLE + ctest + DEPENDENCIES + ${CLI11_TESTS} + ${CLI11_MULTIONLY_TESTS}) endif() - diff --git a/packages/CLI11/tests/ComplexTypeTest.cpp b/packages/CLI11/tests/ComplexTypeTest.cpp index b9a5d4e51cfc2584b8f199ef9ced5c8be245f8a4..4806c544e8a5764f29e4ffe1745e2b694eeb4eb9 100644 --- a/packages/CLI11/tests/ComplexTypeTest.cpp +++ b/packages/CLI11/tests/ComplexTypeTest.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2020, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2021, 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/ConfigFileTest.cpp b/packages/CLI11/tests/ConfigFileTest.cpp index 12fa88c10a3949c218b246c1b6a9ddd429f9f309..61c066eedaac5f0f2950b5a92101e681c20debaf 100644 --- a/packages/CLI11/tests/ConfigFileTest.cpp +++ b/packages/CLI11/tests/ConfigFileTest.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2020, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // @@ -535,6 +535,10 @@ TEST_CASE_METHOD(TApp, "IniRequiredNoDefault", "[config]") { int two{0}; app.add_option("--two", two); REQUIRE_THROWS_AS(run(), CLI::FileError); + // test to make sure help still gets called correctly + // GitHub issue #533 https://github.com/CLIUtils/CLI11/issues/553 + args = {"--help"}; + REQUIRE_THROWS_AS(run(), CLI::CallForHelp); } TEST_CASE_METHOD(TApp, "IniNotRequiredNoDefault", "[config]") { @@ -794,6 +798,114 @@ TEST_CASE_METHOD(TApp, "IniRequired", "[config]") { CHECK_THROWS_AS(run(), CLI::RequiredError); } +TEST_CASE_METHOD(TApp, "IniInlineComment", "[config]") { + + TempFile tmpini{"TestIniTmp.ini"}; + + app.set_config("--config", tmpini, "", true); + app.config_formatter(std::make_shared<CLI::ConfigINI>()); + + { + std::ofstream out{tmpini}; + out << "[default]" << std::endl; + out << "two=99 ; this is a two" << std::endl; + out << "three=3; this is a three" << std::endl; + } + + int one{0}, two{0}, three{0}; + app.add_option("--one", one)->required(); + app.add_option("--two", two)->required(); + app.add_option("--three", three)->required(); + + args = {"--one=1"}; + + run(); + CHECK(1 == one); + CHECK(99 == two); + CHECK(3 == three); + + one = two = three = 0; + args = {"--one=1", "--two=2"}; + + CHECK_NOTHROW(run()); + CHECK(1 == one); + CHECK(2 == two); + CHECK(3 == three); + + args = {}; + + CHECK_THROWS_AS(run(), CLI::RequiredError); + + args = {"--two=2"}; + + CHECK_THROWS_AS(run(), CLI::RequiredError); +} + +TEST_CASE_METHOD(TApp, "TomlInlineComment", "[config]") { + + TempFile tmpini{"TestIniTmp.ini"}; + + app.set_config("--config", tmpini, "", true); + + { + std::ofstream out{tmpini}; + out << "[default]" << std::endl; + out << "two=99 # this is a two" << std::endl; + out << "three=3# this is a three" << std::endl; + } + + int one{0}, two{0}, three{0}; + app.add_option("--one", one)->required(); + app.add_option("--two", two)->required(); + app.add_option("--three", three)->required(); + + args = {"--one=1"}; + + run(); + CHECK(1 == one); + CHECK(99 == two); + CHECK(3 == three); + + one = two = three = 0; + args = {"--one=1", "--two=2"}; + + CHECK_NOTHROW(run()); + CHECK(1 == one); + CHECK(2 == two); + CHECK(3 == three); + + args = {}; + + CHECK_THROWS_AS(run(), CLI::RequiredError); + + args = {"--two=2"}; + + CHECK_THROWS_AS(run(), CLI::RequiredError); +} + +TEST_CASE_METHOD(TApp, "ConfigModifiers", "[config]") { + + app.set_config("--config", "test.ini", "", true); + + auto cfgptr = app.get_config_formatter_base(); + + cfgptr->section("test"); + CHECK(cfgptr->section() == "test"); + + CHECK(cfgptr->sectionRef() == "test"); + auto &sref = cfgptr->sectionRef(); + sref = "this"; + CHECK(cfgptr->section() == "this"); + + cfgptr->index(5); + CHECK(cfgptr->index() == 5); + + CHECK(cfgptr->indexRef() == 5); + auto &iref = cfgptr->indexRef(); + iref = 7; + CHECK(cfgptr->index() == 7); +} + TEST_CASE_METHOD(TApp, "IniVector", "[config]") { TempFile tmpini{"TestIniTmp.ini"}; @@ -999,6 +1111,39 @@ TEST_CASE_METHOD(TApp, "IniLayered", "[config]") { CHECK(!*subcom); } +TEST_CASE_METHOD(TApp, "IniLayeredStream", "[config]") { + + TempFile tmpini{"TestIniTmp.ini"}; + + app.set_config("--config", tmpini); + + { + std::ofstream out{tmpini}; + out << "[default]" << std::endl; + out << "val=1" << std::endl; + out << "[subcom]" << std::endl; + out << "val=2" << std::endl; + out << "subsubcom.val=3" << std::endl; + } + + int one{0}, two{0}, three{0}; + app.add_option("--val", one); + auto subcom = app.add_subcommand("subcom"); + subcom->add_option("--val", two); + auto subsubcom = subcom->add_subcommand("subsubcom"); + subsubcom->add_option("--val", three); + + std::ifstream in{tmpini}; + app.parse_from_stream(in); + + CHECK(one == 1); + CHECK(two == 2); + CHECK(three == 3); + + CHECK(0U == subcom->count()); + CHECK(!*subcom); +} + TEST_CASE_METHOD(TApp, "IniLayeredDotSection", "[config]") { TempFile tmpini{"TestIniTmp.ini"}; @@ -1030,6 +1175,45 @@ TEST_CASE_METHOD(TApp, "IniLayeredDotSection", "[config]") { CHECK(0U == subcom->count()); CHECK(!*subcom); + + three = 0; + // check maxlayers + app.get_config_formatter_base()->maxLayers(1); + run(); + CHECK(three == 0); +} + +TEST_CASE_METHOD(TApp, "IniLayeredCustomSectionSeparator", "[config]") { + + TempFile tmpini{"TestIniTmp.ini"}; + + app.set_config("--config", tmpini); + + { + std::ofstream out{tmpini}; + out << "[default]" << std::endl; + out << "val=1" << std::endl; + out << "[subcom]" << std::endl; + out << "val=2" << std::endl; + out << "[subcom|subsubcom]" << std::endl; + out << "val=3" << std::endl; + } + app.get_config_formatter_base()->parentSeparator('|'); + int one{0}, two{0}, three{0}; + app.add_option("--val", one); + auto subcom = app.add_subcommand("subcom"); + subcom->add_option("--val", two); + auto subsubcom = subcom->add_subcommand("subsubcom"); + subsubcom->add_option("--val", three); + + run(); + + CHECK(one == 1); + CHECK(two == 2); + CHECK(three == 3); + + CHECK(0U == subcom->count()); + CHECK(!*subcom); } TEST_CASE_METHOD(TApp, "IniSubcommandConfigurable", "[config]") { @@ -1107,6 +1291,130 @@ TEST_CASE_METHOD(TApp, "IniSubcommandConfigurablePreParse", "[config]") { CHECK(0U == subcom2->count()); } +TEST_CASE_METHOD(TApp, "IniSection", "[config]") { + + TempFile tmpini{"TestIniTmp.ini"}; + + app.set_config("--config", tmpini); + app.get_config_formatter_base()->section("config"); + + { + std::ofstream out{tmpini}; + out << "[config]" << std::endl; + out << "val=2" << std::endl; + out << "subsubcom.val=3" << std::endl; + out << "[default]" << std::endl; + out << "val=1" << std::endl; + } + + int val{0}; + app.add_option("--val", val); + + run(); + + CHECK(2 == val); +} + +TEST_CASE_METHOD(TApp, "IniSection2", "[config]") { + + TempFile tmpini{"TestIniTmp.ini"}; + + app.set_config("--config", tmpini); + app.get_config_formatter_base()->section("config"); + + { + std::ofstream out{tmpini}; + out << "[default]" << std::endl; + out << "val=1" << std::endl; + out << "[config]" << std::endl; + out << "val=2" << std::endl; + out << "subsubcom.val=3" << std::endl; + } + + int val{0}; + app.add_option("--val", val); + + run(); + + CHECK(2 == val); +} + +TEST_CASE_METHOD(TApp, "jsonLikeParsing", "[config]") { + + TempFile tmpjson{"TestJsonTmp.json"}; + + app.set_config("--config", tmpjson); + app.get_config_formatter_base()->valueSeparator(':'); + + { + std::ofstream out{tmpjson}; + out << "{" << std::endl; + out << "\"val\":1," << std::endl; + out << "\"val2\":\"test\"," << std::endl; + out << "\"flag\":true" << std::endl; + out << "}" << std::endl; + } + + int val{0}; + app.add_option("--val", val); + std::string val2{0}; + app.add_option("--val2", val2); + + bool flag{false}; + app.add_flag("--flag", flag); + + run(); + + CHECK(1 == val); + CHECK(val2 == "test"); + CHECK(flag); +} + +TEST_CASE_METHOD(TApp, "TomlSectionNumber", "[config]") { + + TempFile tmpini{"TestTomlTmp.toml"}; + + app.set_config("--config", tmpini); + app.get_config_formatter_base()->section("config")->index(0); + + { + std::ofstream out{tmpini}; + out << "[default]" << std::endl; + out << "val=1" << std::endl; + out << "[[config]]" << std::endl; + out << "val=2" << std::endl; + out << "subsubcom.val=3" << std::endl; + out << "[[config]]" << std::endl; + out << "val=4" << std::endl; + out << "subsubcom.val=3" << std::endl; + out << "[[config]]" << std::endl; + out << "val=6" << std::endl; + out << "subsubcom.val=3" << std::endl; + } + + int val{0}; + app.add_option("--val", val); + + run(); + + CHECK(2 == val); + + auto &index = app.get_config_formatter_base()->indexRef(); + index = 1; + run(); + + CHECK(4 == val); + + index = -1; + run(); + // Take the first section in this case + CHECK(2 == val); + index = 2; + run(); + + CHECK(6 == val); +} + TEST_CASE_METHOD(TApp, "IniSubcommandConfigurableParseComplete", "[config]") { TempFile tmpini{"TestIniTmp.ini"}; @@ -1685,7 +1993,7 @@ TEST_CASE_METHOD(TApp, "TomlOutputHiddenOptions", "[config]") { const std::string description2 = "Second description."; app.add_flag("--" + flag1, description1)->group("group1"); app.add_flag("--" + flag2, description2)->group("group2"); - app.add_option("--dval", val, "", true)->group(""); + app.add_option("--dval", val)->capture_default_str()->group(""); run(); @@ -1701,6 +2009,16 @@ TEST_CASE_METHOD(TApp, "TomlOutputHiddenOptions", "[config]") { CHECK(std::string::npos == loc); } +TEST_CASE_METHOD(TApp, "TomlOutputAppMultiLineDescription", "[config]") { + app.description("Some short app description.\n" + "That has multiple lines."); + run(); + + std::string str = app.config_to_str(true, true); + CHECK_THAT(str, Contains("# Some short app description.\n")); + CHECK_THAT(str, Contains("# That has multiple lines.\n")); +} + TEST_CASE_METHOD(TApp, "TomlOutputMultiLineDescription", "[config]") { std::string flag = "some_flag"; const std::string description = "Some short description.\nThat has lines."; @@ -1714,6 +2032,35 @@ TEST_CASE_METHOD(TApp, "TomlOutputMultiLineDescription", "[config]") { CHECK_THAT(str, Contains(flag + "=false\n")); } +TEST_CASE_METHOD(TApp, "TomlOutputOptionGroupMultiLineDescription", "[config]") { + std::string flag = "flag"; + const std::string description = "Short flag description.\n"; + auto og = app.add_option_group("group"); + og->description("Option group description.\n" + "That has multiple lines."); + og->add_flag("--" + flag, description); + run(); + + std::string str = app.config_to_str(true, true); + CHECK_THAT(str, Contains("# Option group description.\n")); + CHECK_THAT(str, Contains("# That has multiple lines.\n")); +} + +TEST_CASE_METHOD(TApp, "TomlOutputSubcommandMultiLineDescription", "[config]") { + std::string flag = "flag"; + const std::string description = "Short flag description.\n"; + auto subcom = app.add_subcommand("subcommand"); + subcom->configurable(); + subcom->description("Subcommand description.\n" + "That has multiple lines."); + subcom->add_flag("--" + flag, description); + run(); + + std::string str = app.config_to_str(true, true); + CHECK_THAT(str, Contains("# Subcommand description.\n")); + CHECK_THAT(str, Contains("# That has multiple lines.\n")); +} + TEST_CASE_METHOD(TApp, "TomlOutputOptionGroup", "[config]") { std::string flag1 = "flagnr1"; std::string flag2 = "flagnr2"; @@ -1723,7 +2070,7 @@ TEST_CASE_METHOD(TApp, "TomlOutputOptionGroup", "[config]") { app.add_flag("--" + flag1, description1)->group("group1"); app.add_flag("--" + flag2, description2)->group("group2"); auto og = app.add_option_group("group3", "g3 desc"); - og->add_option("--dval", val, "", true)->group(""); + og->add_option("--dval", val)->capture_default_str()->group(""); run(); @@ -1809,7 +2156,7 @@ TEST_CASE_METHOD(TApp, "TomlOutputSet", "[config]") { TEST_CASE_METHOD(TApp, "TomlOutputDefault", "[config]") { int v{7}; - app.add_option("--simple", v, "", true); + app.add_option("--simple", v)->capture_default_str(); run(); @@ -1888,6 +2235,27 @@ TEST_CASE_METHOD(TApp, "TomlOutputSubsubcomConfigurable", "[config]") { CHECK(std::string::npos == str.find("sub2.newest=true")); } +TEST_CASE_METHOD(TApp, "TomlOutputSubcomNonConfigurable", "[config]") { + + app.add_flag("--simple"); + auto subcom = app.add_subcommand("other", "other_descriptor")->configurable(); + subcom->add_flag("--newer"); + + auto subcom2 = app.add_subcommand("sub2", "descriptor2"); + subcom2->add_flag("--newest")->configurable(false); + + args = {"--simple", "other", "--newer", "sub2", "--newest"}; + run(); + + std::string str = app.config_to_str(true, true); + CHECK_THAT(str, Contains("other_descriptor")); + CHECK_THAT(str, Contains("simple=true")); + CHECK_THAT(str, Contains("[other]")); + CHECK_THAT(str, Contains("newer=true")); + CHECK_THAT(str, !Contains("newest")); + CHECK_THAT(str, !Contains("descriptor2")); +} + TEST_CASE_METHOD(TApp, "TomlOutputSubsubcomConfigurableDeep", "[config]") { app.add_flag("--simple"); @@ -1934,10 +2302,10 @@ TEST_CASE_METHOD(TApp, "TomlOutputQuoted", "[config]") { TEST_CASE_METHOD(TApp, "DefaultsTomlOutputQuoted", "[config]") { std::string val1{"I am a string"}; - app.add_option("--val1", val1, "", true); + app.add_option("--val1", val1)->capture_default_str(); std::string val2{R"(I am a "confusing" string)"}; - app.add_option("--val2", val2, "", true); + app.add_option("--val2", val2)->capture_default_str(); run(); @@ -2068,7 +2436,7 @@ TEST_CASE_METHOD(TApp, "IniOutputHiddenOptions", "[config]") { const std::string description2 = "Second description."; app.add_flag("--" + flag1, description1)->group("group1"); app.add_flag("--" + flag2, description2)->group("group2"); - app.add_option("--dval", val, "", true)->group(""); + app.add_option("--dval", val)->capture_default_str()->group(""); app.config_formatter(std::make_shared<CLI::ConfigINI>()); run(); @@ -2084,6 +2452,17 @@ TEST_CASE_METHOD(TApp, "IniOutputHiddenOptions", "[config]") { CHECK(std::string::npos == loc); } +TEST_CASE_METHOD(TApp, "IniOutputAppMultiLineDescription", "[config]") { + app.description("Some short app description.\n" + "That has multiple lines."); + app.config_formatter(std::make_shared<CLI::ConfigINI>()); + run(); + + std::string str = app.config_to_str(true, true); + CHECK_THAT(str, Contains("; Some short app description.\n")); + CHECK_THAT(str, Contains("; That has multiple lines.\n")); +} + TEST_CASE_METHOD(TApp, "IniOutputMultiLineDescription", "[config]") { std::string flag = "some_flag"; const std::string description = "Some short description.\nThat has lines."; @@ -2097,6 +2476,37 @@ TEST_CASE_METHOD(TApp, "IniOutputMultiLineDescription", "[config]") { CHECK_THAT(str, Contains(flag + "=false\n")); } +TEST_CASE_METHOD(TApp, "IniOutputOptionGroupMultiLineDescription", "[config]") { + std::string flag = "flag"; + const std::string description = "Short flag description.\n"; + auto og = app.add_option_group("group"); + og->description("Option group description.\n" + "That has multiple lines."); + og->add_flag("--" + flag, description); + app.config_formatter(std::make_shared<CLI::ConfigINI>()); + run(); + + std::string str = app.config_to_str(true, true); + CHECK_THAT(str, Contains("; Option group description.\n")); + CHECK_THAT(str, Contains("; That has multiple lines.\n")); +} + +TEST_CASE_METHOD(TApp, "IniOutputSubcommandMultiLineDescription", "[config]") { + std::string flag = "flag"; + const std::string description = "Short flag description.\n"; + auto subcom = app.add_subcommand("subcommand"); + subcom->configurable(); + subcom->description("Subcommand description.\n" + "That has multiple lines."); + subcom->add_flag("--" + flag, description); + app.config_formatter(std::make_shared<CLI::ConfigINI>()); + run(); + + std::string str = app.config_to_str(true, true); + CHECK_THAT(str, Contains("; Subcommand description.\n")); + CHECK_THAT(str, Contains("; That has multiple lines.\n")); +} + TEST_CASE_METHOD(TApp, "IniOutputOptionGroup", "[config]") { std::string flag1 = "flagnr1"; std::string flag2 = "flagnr2"; @@ -2106,7 +2516,7 @@ TEST_CASE_METHOD(TApp, "IniOutputOptionGroup", "[config]") { app.add_flag("--" + flag1, description1)->group("group1"); app.add_flag("--" + flag2, description2)->group("group2"); auto og = app.add_option_group("group3", "g3 desc"); - og->add_option("--dval", val, "", true)->group(""); + og->add_option("--dval", val)->capture_default_str()->group(""); app.config_formatter(std::make_shared<CLI::ConfigINI>()); run(); @@ -2177,7 +2587,7 @@ TEST_CASE_METHOD(TApp, "IniOutputSet", "[config]") { TEST_CASE_METHOD(TApp, "IniOutputDefault", "[config]") { int v{7}; - app.add_option("--simple", v, "", true); + app.add_option("--simple", v)->capture_default_str(); app.config_formatter(std::make_shared<CLI::ConfigINI>()); run(); @@ -2202,6 +2612,21 @@ TEST_CASE_METHOD(TApp, "IniOutputSubcom", "[config]") { CHECK_THAT(str, Contains("other.newer=true")); } +TEST_CASE_METHOD(TApp, "IniOutputSubcomCustomSep", "[config]") { + + app.add_flag("--simple"); + auto subcom = app.add_subcommand("other"); + subcom->add_flag("--newer"); + app.config_formatter(std::make_shared<CLI::ConfigINI>()); + app.get_config_formatter_base()->parentSeparator(':'); + args = {"--simple", "other", "--newer"}; + run(); + + std::string str = app.config_to_str(); + CHECK_THAT(str, Contains("simple=true")); + CHECK_THAT(str, Contains("other:newer=true")); +} + TEST_CASE_METHOD(TApp, "IniOutputSubcomConfigurable", "[config]") { app.add_flag("--simple"); @@ -2235,6 +2660,24 @@ TEST_CASE_METHOD(TApp, "IniOutputSubsubcom", "[config]") { CHECK_THAT(str, Contains("other.sub2.newest=true")); } +TEST_CASE_METHOD(TApp, "IniOutputSubsubcomCustomSep", "[config]") { + + app.add_flag("--simple"); + auto subcom = app.add_subcommand("other"); + subcom->add_flag("--newer"); + auto subsubcom = subcom->add_subcommand("sub2"); + subsubcom->add_flag("--newest"); + app.config_formatter(std::make_shared<CLI::ConfigINI>()); + app.get_config_formatter_base()->parentSeparator('|'); + args = {"--simple", "other", "--newer", "sub2", "--newest"}; + run(); + + std::string str = app.config_to_str(); + CHECK_THAT(str, Contains("simple=true")); + CHECK_THAT(str, Contains("other|newer=true")); + CHECK_THAT(str, Contains("other|sub2|newest=true")); +} + TEST_CASE_METHOD(TApp, "IniOutputSubsubcomConfigurable", "[config]") { app.add_flag("--simple"); @@ -2302,10 +2745,10 @@ TEST_CASE_METHOD(TApp, "IniOutputQuoted", "[config]") { TEST_CASE_METHOD(TApp, "DefaultsIniOutputQuoted", "[config]") { std::string val1{"I am a string"}; - app.add_option("--val1", val1, "", true); + app.add_option("--val1", val1)->capture_default_str(); std::string val2{R"(I am a "confusing" string)"}; - app.add_option("--val2", val2, "", true); + app.add_option("--val2", val2)->capture_default_str(); app.config_formatter(std::make_shared<CLI::ConfigINI>()); run(); diff --git a/packages/CLI11/tests/CreationTest.cpp b/packages/CLI11/tests/CreationTest.cpp index 2a70f70d419552e96e13e0cf3a3c6f524a64ce68..47900570744aaa55a49555ef9a40054c4655d03e 100644 --- a/packages/CLI11/tests/CreationTest.cpp +++ b/packages/CLI11/tests/CreationTest.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2020, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // @@ -739,13 +739,13 @@ TEST_CASE_METHOD(TApp, "MakeUnstreamableOptions", "[creation]") { app.add_option("--value", value); // This used to fail to build, since it tries to stream from Unstreamable - app.add_option("--value2", value, "", false); + app.add_option("--value2", value); std::vector<Unstreamable> values; app.add_option("--values", values); // This used to fail to build, since it tries to stream from Unstreamable - app.add_option("--values2", values, "", false); + app.add_option("--values2", values); args = {"--value", "45"}; run(); diff --git a/packages/CLI11/tests/DeprecatedTest.cpp b/packages/CLI11/tests/DeprecatedTest.cpp index cf9987c6d97b6a8251630f60deff8e5466138e28..f763a89d4ab86209851789deff57702a955e03d3 100644 --- a/packages/CLI11/tests/DeprecatedTest.cpp +++ b/packages/CLI11/tests/DeprecatedTest.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2020, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // @@ -12,214 +12,3 @@ TEST_CASE("Deprecated: Empty", "[deprecated]") { // No deprecated features at this time. CHECK(true); } - -// Classic sets - -TEST_CASE("THelp: Defaults", "[deprecated]") { - CLI::App app{"My prog"}; - - int one{1}, two{2}; - app.add_option("--one", one, "Help for one", true); - app.add_option("--set", two, "Help for set", true)->check(CLI::IsMember({2, 3, 4})); - - std::string help = app.help(); - - CHECK_THAT(help, Contains("--one")); - CHECK_THAT(help, Contains("--set")); - CHECK_THAT(help, Contains("1")); - CHECK_THAT(help, Contains("=2")); - CHECK_THAT(help, Contains("2,3,4")); -} - -TEST_CASE("THelp: VectorOpts", "[deprecated]") { - CLI::App app{"My prog"}; - std::vector<int> x = {1, 2}; - app.add_option("-q,--quick", x, "", true); - - std::string help = app.help(); - - CHECK_THAT(help, Contains("INT=[1,2] ...")); -} - -TEST_CASE("THelp: SetLower", "[deprecated]") { - CLI::App app{"My prog"}; - - std::string def{"One"}; - app.add_option("--set", def, "Help for set", true)->check(CLI::IsMember({"oNe", "twO", "THREE"})); - - std::string help = app.help(); - - CHECK_THAT(help, Contains("--set")); - CHECK_THAT(help, Contains("=One")); - CHECK_THAT(help, Contains("oNe")); - CHECK_THAT(help, Contains("twO")); - CHECK_THAT(help, Contains("THREE")); -} - -TEST_CASE("THelp: ChangingSetDefaulted", "[deprecated]") { - CLI::App app; - - std::set<int> vals{1, 2, 3}; - int val = 2; - app.add_option("--val", val, "", true)->check(CLI::IsMember(&vals)); - - std::string help = app.help(); - - CHECK_THAT(help, Contains("1")); - CHECK_THAT(help, !Contains("4")); - - vals.insert(4); - vals.erase(1); - - help = app.help(); - - CHECK_THAT(help, !Contains("1")); - CHECK_THAT(help, Contains("4")); -} - -TEST_CASE("THelp: ChangingCaselessSetDefaulted", "[deprecated]") { - CLI::App app; - - std::set<std::string> vals{"1", "2", "3"}; - std::string val = "2"; - app.add_option("--val", val, "", true)->check(CLI::IsMember(&vals, CLI::ignore_case)); - - std::string help = app.help(); - - CHECK_THAT(help, Contains("1")); - CHECK_THAT(help, !Contains("4")); - - vals.insert("4"); - vals.erase("1"); - - help = app.help(); - - CHECK_THAT(help, !Contains("1")); - CHECK_THAT(help, Contains("4")); -} - -TEST_CASE_METHOD(TApp, "DefaultOpts", "[deprecated]") { - - int i = 3; - std::string s = "HI"; - - app.add_option("-i,i", i, "", false); - app.add_option("-s,s", s, "", true); - - args = {"-i2", "9"}; - - run(); - - CHECK(app.count("i") == 1u); - CHECK(app.count("-s") == 1u); - CHECK(i == 2); - CHECK(s == "9"); -} - -TEST_CASE_METHOD(TApp, "VectorDefaultedFixedString", "[deprecated]") { - std::vector<std::string> strvec{"one"}; - std::vector<std::string> answer{"mystring", "mystring2", "mystring3"}; - - CLI::Option *opt = app.add_option("-s,--string", strvec, "", true)->expected(3); - CHECK(opt->get_expected() == 3); - - args = {"--string", "mystring", "mystring2", "mystring3"}; - run(); - CHECK(app.count("--string") == 3u); - CHECK(strvec == answer); -} - -TEST_CASE_METHOD(TApp, "DefaultedResult", "[deprecated]") { - std::string sval = "NA"; - int ival; - auto opts = app.add_option("--string", sval, "", true); - auto optv = app.add_option("--val", ival); - args = {}; - run(); - CHECK("NA" == sval); - std::string nString; - opts->results(nString); - CHECK("NA" == nString); - int newIval; - // CHECK_THROWS_AS (optv->results(newIval), CLI::ConversionError); - optv->default_str("442"); - optv->results(newIval); - CHECK(442 == newIval); -} - -TEST_CASE_METHOD(TApp, "OptionWithDefaults", "[deprecated]") { - int someint = 2; - app.add_option("-a", someint, "", true); - - args = {"-a1", "-a2"}; - - CHECK_THROWS_AS(run(), CLI::ArgumentMismatch); -} - -// #209 -TEST_CASE_METHOD(TApp, "CustomUserSepParse", "[deprecated]") { - - std::vector<int> vals = {1, 2, 3}; - args = {"--idx", "1,2,3"}; - auto opt = app.add_option("--idx", vals)->delimiter(','); - run(); - CHECK(std::vector<int>({1, 2, 3}) == vals); - std::vector<int> vals2; - // check that the results vector gets the results in the same way - opt->results(vals2); - CHECK(vals == vals2); - - app.remove_option(opt); - - app.add_option("--idx", vals, "", true)->delimiter(','); - run(); - CHECK(std::vector<int>({1, 2, 3}) == vals); -} - -// #209 -TEST_CASE_METHOD(TApp, "CustomUserSepParse2", "[deprecated]") { - - std::vector<int> vals = {1, 2, 3}; - args = {"--idx", "1,2,"}; - auto opt = app.add_option("--idx", vals)->delimiter(','); - run(); - CHECK(std::vector<int>({1, 2}) == vals); - - app.remove_option(opt); - - app.add_option("--idx", vals, "", true)->delimiter(','); - run(); - CHECK(std::vector<int>({1, 2}) == vals); -} -// -// #209 -TEST_CASE_METHOD(TApp, "CustomUserSepParse4", "[deprecated]") { - - std::vector<int> vals; - args = {"--idx", "1, 2"}; - auto opt = app.add_option("--idx", vals, "", true)->delimiter(','); - run(); - CHECK(std::vector<int>({1, 2}) == vals); - - app.remove_option(opt); - - app.add_option("--idx", vals)->delimiter(','); - run(); - CHECK(std::vector<int>({1, 2}) == vals); -} - -// #218 -TEST_CASE_METHOD(TApp, "CustomUserSepParse5", "[deprecated]") { - - std::vector<std::string> bar; - args = {"this", "is", "a", "test"}; - auto opt = app.add_option("bar", bar, "bar"); - run(); - CHECK(std::vector<std::string>({"this", "is", "a", "test"}) == bar); - - app.remove_option(opt); - args = {"this", "is", "a", "test"}; - app.add_option("bar", bar, "bar", true); - run(); - CHECK(std::vector<std::string>({"this", "is", "a", "test"}) == bar); -} diff --git a/packages/CLI11/tests/FormatterTest.cpp b/packages/CLI11/tests/FormatterTest.cpp index 22da56f0e543af444e9caadd0c60a2932fe35018..1516945de96458ef905e1a301561152d3c49f8d4 100644 --- a/packages/CLI11/tests/FormatterTest.cpp +++ b/packages/CLI11/tests/FormatterTest.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2020, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2021, 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/HelpTest.cpp b/packages/CLI11/tests/HelpTest.cpp index 48089f6fc76ea10a7446447ebf8114fdd9797553..cb8c81ab810ec78563dacaf9ec19a98e70c5853f 100644 --- a/packages/CLI11/tests/HelpTest.cpp +++ b/packages/CLI11/tests/HelpTest.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2020, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // @@ -1155,6 +1155,7 @@ TEST_CASE("THelp: ChangingDefaults", "[help]") { std::vector<int> x = {1, 2}; CLI::Option *opt = app.add_option("-q,--quick", x); x = {3, 4}; + CHECK(x[0] == 3); opt->capture_default_str(); @@ -1162,6 +1163,7 @@ TEST_CASE("THelp: ChangingDefaults", "[help]") { std::string help = app.help(); CHECK_THAT(help, Contains("INT=[3,4] ...")); + CHECK(x[0] == 5); } TEST_CASE("THelp: ChangingDefaultsWithAutoCapture", "[help]") { @@ -1170,8 +1172,10 @@ TEST_CASE("THelp: ChangingDefaultsWithAutoCapture", "[help]") { app.option_defaults()->always_capture_default(); std::vector<int> x = {1, 2}; + CHECK(x[0] == 1); app.add_option("-q,--quick", x); x = {3, 4}; + CHECK(x[0] == 3); std::string help = app.help(); @@ -1220,6 +1224,21 @@ TEST_CASE("TVersion: callback_flag", "[help]") { CHECK_THAT(vers, Contains("VERSION")); } +TEST_CASE("TVersion: help", "[help]") { + + CLI::App app; + + app.set_version_flag("-v,--version", "version_string", "help_for_version"); + + auto hvers = app.help(); + CHECK_THAT(hvers, Contains("help_for_version")); + + app.set_version_flag( + "-v", []() { return std::string("VERSION2 " CLI11_VERSION); }, "help_for_version2"); + hvers = app.help(); + CHECK_THAT(hvers, Contains("help_for_version2")); +} + TEST_CASE("TVersion: parse_throw", "[help]") { CLI::App app; diff --git a/packages/CLI11/tests/HelpersTest.cpp b/packages/CLI11/tests/HelpersTest.cpp index 0c032fbce389899324025f4c8759e328e5ffaea2..7a497aa1cb05c0af6b77e5668063f57f9525c9bf 100644 --- a/packages/CLI11/tests/HelpersTest.cpp +++ b/packages/CLI11/tests/HelpersTest.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2020, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // @@ -155,13 +155,14 @@ TEST_CASE("String: InvalidName", "[helpers]") { CHECK(CLI::detail::valid_name_string("valid")); CHECK_FALSE(CLI::detail::valid_name_string("-invalid")); CHECK(CLI::detail::valid_name_string("va-li-d")); - CHECK_FALSE(CLI::detail::valid_name_string("vali&d")); + CHECK_FALSE(CLI::detail::valid_name_string("valid{}")); CHECK(CLI::detail::valid_name_string("_valid")); - CHECK_FALSE(CLI::detail::valid_name_string("/valid")); + CHECK(CLI::detail::valid_name_string("/valid")); CHECK(CLI::detail::valid_name_string("vali?d")); CHECK(CLI::detail::valid_name_string("@@@@")); CHECK(CLI::detail::valid_name_string("b@d2?")); CHECK(CLI::detail::valid_name_string("2vali?d")); + CHECK_FALSE(CLI::detail::valid_name_string("!valid")); } TEST_CASE("StringTools: Modify", "[helpers]") { @@ -997,7 +998,12 @@ TEST_CASE("Types: TypeName", "[helpers]") { std::string umapName = CLI::detail::type_name<std::unordered_map<int, std::tuple<std::string, double>>>(); CHECK(umapName == "[INT,[TEXT,FLOAT]]"); + // On older compilers, this may show up as other/TEXT vclass = CLI::detail::classify_object<std::atomic<int>>::value; + CHECK((CLI::detail::object_category::wrapper_value == vclass || CLI::detail::object_category::other == vclass)); + + std::string atomic_name = CLI::detail::type_name<std::atomic<int>>(); + CHECK((atomic_name == "INT" || atomic_name == "TEXT")); } TEST_CASE("Types: OverflowSmall", "[helpers]") { @@ -1128,8 +1134,8 @@ TEST_CASE("Types: LexicalConversionDouble", "[helpers]") { CHECK((float)x == Approx((float)9.12)); CLI::results_t bad_input = {"hello"}; - res = CLI::detail::lexical_conversion<long double, double>(input, x); - CHECK(res); + res = CLI::detail::lexical_conversion<long double, double>(bad_input, x); + CHECK_FALSE(res); } TEST_CASE("Types: LexicalConversionDoubleTuple", "[helpers]") { @@ -1140,8 +1146,8 @@ TEST_CASE("Types: LexicalConversionDoubleTuple", "[helpers]") { CHECK(std::get<0>(x) == Approx(9.12)); CLI::results_t bad_input = {"hello"}; - res = CLI::detail::lexical_conversion<decltype(x), decltype(x)>(input, x); - CHECK(res); + res = CLI::detail::lexical_conversion<decltype(x), decltype(x)>(bad_input, x); + CHECK_FALSE(res); } TEST_CASE("Types: LexicalConversionVectorDouble", "[helpers]") { diff --git a/packages/CLI11/tests/NewParseTest.cpp b/packages/CLI11/tests/NewParseTest.cpp index d9d9dbf91ca777d8c70fa2f289316e535acec0f0..30a0c9f41239ebc619dab762206437ce4b883096 100644 --- a/packages/CLI11/tests/NewParseTest.cpp +++ b/packages/CLI11/tests/NewParseTest.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2020, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // @@ -13,49 +13,9 @@ using Catch::Matchers::Contains; using cx = std::complex<double>; -TEST_CASE_METHOD(TApp, "Complex", "[newparse]") { - cx comp{1, 2}; - app.add_complex("-c,--complex", comp, "", true); - - args = {"-c", "4", "3"}; - - std::string help = app.help(); - CHECK_THAT(help, Contains("1")); - CHECK_THAT(help, Contains("2")); - CHECK_THAT(help, Contains("COMPLEX")); - - CHECK(comp.real() == Approx(1)); - CHECK(comp.imag() == Approx(2)); - - run(); - - CHECK(comp.real() == Approx(4)); - CHECK(comp.imag() == Approx(3)); -} - TEST_CASE_METHOD(TApp, "ComplexOption", "[newparse]") { cx comp{1, 2}; - app.add_option("-c,--complex", comp, "", true); - - args = {"-c", "4", "3"}; - - std::string help = app.help(); - CHECK_THAT(help, Contains("1")); - CHECK_THAT(help, Contains("2")); - CHECK_THAT(help, Contains("COMPLEX")); - - CHECK(comp.real() == Approx(1)); - CHECK(comp.imag() == Approx(2)); - - run(); - - CHECK(comp.real() == Approx(4)); - CHECK(comp.imag() == Approx(3)); -} - -TEST_CASE_METHOD(TApp, "ComplexFloat", "[newparse]") { - std::complex<float> comp{1, 2}; - app.add_complex<std::complex<float>, float>("-c,--complex", comp, "", true); + app.add_option("-c,--complex", comp)->capture_default_str(); args = {"-c", "4", "3"}; @@ -75,7 +35,7 @@ TEST_CASE_METHOD(TApp, "ComplexFloat", "[newparse]") { TEST_CASE_METHOD(TApp, "ComplexFloatOption", "[newparse]") { std::complex<float> comp{1, 2}; - app.add_option("-c,--complex", comp, "", true); + app.add_option("-c,--complex", comp)->capture_default_str(); args = {"-c", "4", "3"}; @@ -93,41 +53,9 @@ TEST_CASE_METHOD(TApp, "ComplexFloatOption", "[newparse]") { CHECK(comp.imag() == Approx(3)); } -TEST_CASE_METHOD(TApp, "ComplexWithDelimiter", "[newparse]") { - cx comp{1, 2}; - app.add_complex("-c,--complex", comp, "", true)->delimiter('+'); - - args = {"-c", "4+3i"}; - - std::string help = app.help(); - CHECK_THAT(help, Contains("1")); - CHECK_THAT(help, Contains("2")); - CHECK_THAT(help, Contains("COMPLEX")); - - CHECK(comp.real() == Approx(1)); - CHECK(comp.imag() == Approx(2)); - - run(); - - CHECK(comp.real() == Approx(4)); - CHECK(comp.imag() == Approx(3)); - - args = {"-c", "5+-3i"}; - run(); - - CHECK(comp.real() == Approx(5)); - CHECK(comp.imag() == Approx(-3)); - - args = {"-c", "6", "-4i"}; - run(); - - CHECK(comp.real() == Approx(6)); - CHECK(comp.imag() == Approx(-4)); -} - TEST_CASE_METHOD(TApp, "ComplexWithDelimiterOption", "[newparse]") { cx comp{1, 2}; - app.add_option("-c,--complex", comp, "", true)->delimiter('+'); + app.add_option("-c,--complex", comp)->capture_default_str()->delimiter('+'); args = {"-c", "4+3i"}; @@ -157,18 +85,6 @@ TEST_CASE_METHOD(TApp, "ComplexWithDelimiterOption", "[newparse]") { CHECK(comp.imag() == Approx(-4)); } -TEST_CASE_METHOD(TApp, "ComplexIgnoreI", "[newparse]") { - cx comp{1, 2}; - app.add_complex("-c,--complex", comp); - - args = {"-c", "4", "3i"}; - - run(); - - CHECK(comp.real() == Approx(4)); - CHECK(comp.imag() == Approx(3)); -} - TEST_CASE_METHOD(TApp, "ComplexIgnoreIOption", "[newparse]") { cx comp{1, 2}; app.add_option("-c,--complex", comp); @@ -181,40 +97,6 @@ TEST_CASE_METHOD(TApp, "ComplexIgnoreIOption", "[newparse]") { CHECK(comp.imag() == Approx(3)); } -TEST_CASE_METHOD(TApp, "ComplexSingleArg", "[newparse]") { - cx comp{1, 2}; - app.add_complex("-c,--complex", comp); - - args = {"-c", "4"}; - run(); - CHECK(comp.real() == Approx(4)); - CHECK(comp.imag() == Approx(0)); - - args = {"-c", "4-2i"}; - run(); - CHECK(comp.real() == Approx(4)); - CHECK(comp.imag() == Approx(-2)); - args = {"-c", "4+2i"}; - run(); - CHECK(comp.real() == Approx(4)); - CHECK(comp.imag() == Approx(2)); - - args = {"-c", "-4+2j"}; - run(); - CHECK(comp.real() == Approx(-4)); - CHECK(comp.imag() == Approx(2)); - - args = {"-c", "-4.2-2j"}; - run(); - CHECK(comp.real() == Approx(-4.2)); - CHECK(comp.imag() == Approx(-2)); - - args = {"-c", "-4.2-2.7i"}; - run(); - CHECK(comp.real() == Approx(-4.2)); - CHECK(comp.imag() == Approx(-2.7)); -} - TEST_CASE_METHOD(TApp, "ComplexSingleArgOption", "[newparse]") { cx comp{1, 2}; app.add_option("-c,--complex", comp); @@ -249,29 +131,6 @@ TEST_CASE_METHOD(TApp, "ComplexSingleArgOption", "[newparse]") { CHECK(comp.imag() == Approx(-2.7)); } -TEST_CASE_METHOD(TApp, "ComplexSingleImag", "[newparse]") { - cx comp{1, 2}; - app.add_complex("-c,--complex", comp); - - args = {"-c", "4j"}; - run(); - CHECK(comp.real() == Approx(0)); - CHECK(comp.imag() == Approx(4)); - - args = {"-c", "-4j"}; - run(); - CHECK(comp.real() == Approx(0)); - CHECK(comp.imag() == Approx(-4)); - args = {"-c", "-4"}; - run(); - CHECK(comp.real() == Approx(-4)); - CHECK(comp.imag() == Approx(0)); - args = {"-c", "+4"}; - run(); - CHECK(comp.real() == Approx(4)); - CHECK(comp.imag() == Approx(0)); -} - TEST_CASE_METHOD(TApp, "ComplexSingleImagOption", "[newparse]") { cx comp{1, 2}; app.add_option("-c,--complex", comp); diff --git a/packages/CLI11/tests/OptionGroupTest.cpp b/packages/CLI11/tests/OptionGroupTest.cpp index 175f31dc3daf5de4da456c7a304a235d31aec42e..ac6684f49407ff490a5878759ea8421449c3560d 100644 --- a/packages/CLI11/tests/OptionGroupTest.cpp +++ b/packages/CLI11/tests/OptionGroupTest.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2020, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // @@ -23,6 +23,16 @@ TEST_CASE_METHOD(TApp, "BasicOptionGroup", "[optiongroup]") { CHECK(1u == app.count_all()); } +TEST_CASE_METHOD(TApp, "OptionGroupInvalidNames", "[optiongroup]") { + CHECK_THROWS_AS(app.add_option_group("clusters\ncluster2", "description"), CLI::IncorrectConstruction); + + std::string groupName("group1"); + groupName += '\0'; + groupName.append("group2"); + + CHECK_THROWS_AS(app.add_option_group(groupName), CLI::IncorrectConstruction); +} + TEST_CASE_METHOD(TApp, "BasicOptionGroupExact", "[optiongroup]") { auto ogroup = app.add_option_group("clusters"); int res{0}; @@ -593,7 +603,7 @@ TEST_CASE_METHOD(ManyGroups, "SameSubcommand", "[optiongroup]") { // so when the subcommands are disabled they can have the same name sub1->disabled(false); sub2->disabled(false); - // if they are reenabled they are not checked for overlap on enabling so they can have the same name + // if they are re-enabled they are not checked for overlap on enabling so they can have the same name args = {"sub1", "sub1", "sub1"}; run(); diff --git a/packages/CLI11/tests/OptionTypeTest.cpp b/packages/CLI11/tests/OptionTypeTest.cpp index a2f893bec598d56cbf17bd76c1de5c62b83eb19d..19b69746c825871713be3e0d5f14be005ba236ee 100644 --- a/packages/CLI11/tests/OptionTypeTest.cpp +++ b/packages/CLI11/tests/OptionTypeTest.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2020, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // @@ -240,7 +240,7 @@ TEST_CASE_METHOD(TApp, "CharOption", "[optiontype]") { TEST_CASE_METHOD(TApp, "vectorDefaults", "[optiontype]") { std::vector<int> vals{4, 5}; - auto opt = app.add_option("--long", vals, "", true); + auto opt = app.add_option("--long", vals)->capture_default_str(); args = {"--long", "[1,2,3]"}; @@ -269,6 +269,17 @@ TEST_CASE_METHOD(TApp, "vectorDefaults", "[optiontype]") { CHECK(std::vector<int>({5}) == res); } +TEST_CASE_METHOD(TApp, "mapInput", "[optiontype]") { + std::map<int, std::string> vals{}; + app.add_option("--long", vals); + + args = {"--long", "5", "test"}; + + run(); + + CHECK(vals.at(5) == "test"); +} + TEST_CASE_METHOD(TApp, "CallbackBoolFlags", "[optiontype]") { bool value{false}; @@ -320,8 +331,6 @@ TEST_CASE_METHOD(TApp, "pair_check", "[optiontype]") { CHECK_THROWS_AS(run(), CLI::ValidationError); } -// this will require that modifying the multi-option policy for tuples be allowed which it isn't at present - 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 @@ -915,3 +924,76 @@ TEST_CASE_METHOD(TApp, "vectorDoubleArg", "[optiontype]") { CHECK(2U == cv.size()); CHECK(2U == extras.size()); } + +TEST_CASE_METHOD(TApp, "OnParseCall", "[optiontype]") { + + int cnt{0}; + + auto *opt = app.add_option("-c", + [&cnt](const CLI::results_t &) { + ++cnt; + return true; + }) + ->expected(1, 20) + ->trigger_on_parse(); + std::vector<std::string> extras; + app.add_option("args", extras); + args = {"-c", "1", "-c", "2", "-c", "3"}; + CHECK(opt->get_trigger_on_parse()); + run(); + CHECK(3 == cnt); +} + +TEST_CASE_METHOD(TApp, "OnParseCallVector", "[optiontype]") { + + std::vector<std::string> vec; + + app.add_option("-c", vec)->trigger_on_parse(); + args = {"-c", "1", "2", "3", "-c", "2", "-c", "3", "4", "5"}; + run(); + CHECK(vec.size() == 3U); +} + +TEST_CASE_METHOD(TApp, "force_callback", "[optiontype]") { + + int cnt{0}; + + auto *opt = app.add_option("-c", + [&cnt](const CLI::results_t &) { + ++cnt; + return true; + }) + ->expected(1, 20) + ->force_callback() + ->default_str("5"); + std::vector<std::string> extras; + app.add_option("args", extras); + args = {}; + CHECK(opt->get_force_callback()); + run(); + CHECK(1 == cnt); + cnt = 0; + args = {"-c", "10"}; + run(); + CHECK(1 == cnt); +} + +TEST_CASE_METHOD(TApp, "force_callback2", "[optiontype]") { + + int cnt{0}; + + app.add_option("-c", cnt)->force_callback()->default_val(5); + args = {}; + run(); + CHECK(5 == cnt); +} + +TEST_CASE_METHOD(TApp, "force_callback3", "[optiontype]") { + + int cnt{10}; + + app.add_option("-c", cnt)->force_callback(); + args = {}; + run(); + CHECK(0 == cnt); +} diff --git a/packages/CLI11/tests/OptionalTest.cpp b/packages/CLI11/tests/OptionalTest.cpp index 4d8da020b67b1f3531cce758d0eaa0e238bec745..6b07f01c703e7f26d46d5cabfc2520e6fdb0364e 100644 --- a/packages/CLI11/tests/OptionalTest.cpp +++ b/packages/CLI11/tests/OptionalTest.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2020, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2021, 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 d34a67a64f2163a7e1704fbc99a1952188611e37..5d225ff63ff1fa8692dbdd240da50fdd505d0104 100644 --- a/packages/CLI11/tests/SetTest.cpp +++ b/packages/CLI11/tests/SetTest.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2020, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // @@ -373,7 +373,7 @@ TEST_CASE_METHOD(TApp, "NumericalSets", "[set]") { TEST_CASE_METHOD(TApp, "SetWithDefaults", "[set]") { int someint{2}; - app.add_option("-a", someint, "", true)->check(CLI::IsMember({1, 2, 3, 4})); + app.add_option("-a", someint)->capture_default_str()->check(CLI::IsMember({1, 2, 3, 4})); args = {"-a1", "-a2"}; @@ -382,7 +382,7 @@ TEST_CASE_METHOD(TApp, "SetWithDefaults", "[set]") { TEST_CASE_METHOD(TApp, "SetWithDefaultsConversion", "[set]") { int someint{2}; - app.add_option("-a", someint, "", true)->check(CLI::IsMember({1, 2, 3, 4})); + app.add_option("-a", someint)->capture_default_str()->check(CLI::IsMember({1, 2, 3, 4})); args = {"-a", "hi"}; @@ -391,7 +391,7 @@ TEST_CASE_METHOD(TApp, "SetWithDefaultsConversion", "[set]") { TEST_CASE_METHOD(TApp, "SetWithDefaultsIC", "[set]") { std::string someint = "ho"; - app.add_option("-a", someint, "", true)->check(CLI::IsMember({"Hi", "Ho"})); + app.add_option("-a", someint)->capture_default_str()->check(CLI::IsMember({"Hi", "Ho"})); args = {"-aHi", "-aHo"}; @@ -415,7 +415,7 @@ TEST_CASE_METHOD(TApp, "InSet", "[set]") { TEST_CASE_METHOD(TApp, "InSetWithDefault", "[set]") { std::string choice = "one"; - app.add_option("-q,--quick", choice, "", true)->check(CLI::IsMember({"one", "two", "three"})); + app.add_option("-q,--quick", choice)->capture_default_str()->check(CLI::IsMember({"one", "two", "three"})); run(); CHECK(choice == "one"); @@ -432,7 +432,9 @@ TEST_CASE_METHOD(TApp, "InSetWithDefault", "[set]") { TEST_CASE_METHOD(TApp, "InCaselessSetWithDefault", "[set]") { std::string choice = "one"; - app.add_option("-q,--quick", choice, "", true)->transform(CLI::IsMember({"one", "two", "three"}, CLI::ignore_case)); + app.add_option("-q,--quick", choice) + ->capture_default_str() + ->transform(CLI::IsMember({"one", "two", "three"}, CLI::ignore_case)); run(); CHECK(choice == "one"); @@ -494,7 +496,7 @@ TEST_CASE_METHOD(TApp, "FailMutableSet", "[set]") { int choice{0}; auto vals = std::shared_ptr<std::set<int>>(new std::set<int>({1, 2, 3})); app.add_option("-q,--quick", choice)->check(CLI::IsMember(vals)); - app.add_option("-s,--slow", choice, "", true)->check(CLI::IsMember(vals)); + app.add_option("-s,--slow", choice)->capture_default_str()->check(CLI::IsMember(vals)); args = {"--quick=hello"}; CHECK_THROWS_AS(run(), CLI::ValidationError); @@ -651,7 +653,7 @@ TEST_CASE_METHOD(TApp, "AddRemoveSetItems", "[set]") { std::string type1, type2; app.add_option("--type1", type1)->check(CLI::IsMember(&items)); - app.add_option("--type2", type2, "", true)->check(CLI::IsMember(&items)); + app.add_option("--type2", type2)->capture_default_str()->check(CLI::IsMember(&items)); args = {"--type1", "TYPE1", "--type2", "TYPE2"}; @@ -682,7 +684,7 @@ TEST_CASE_METHOD(TApp, "AddRemoveSetItemsNoCase", "[set]") { std::string type1, type2; app.add_option("--type1", type1)->transform(CLI::IsMember(&items, CLI::ignore_case)); - app.add_option("--type2", type2, "", true)->transform(CLI::IsMember(&items, CLI::ignore_case)); + app.add_option("--type2", type2)->capture_default_str()->transform(CLI::IsMember(&items, CLI::ignore_case)); args = {"--type1", "TYPe1", "--type2", "TyPE2"}; diff --git a/packages/CLI11/tests/SimpleTest.cpp b/packages/CLI11/tests/SimpleTest.cpp index 92262b5e64ef7f30a65fe50d7a38592d33ac1809..3051a46d4c1c7924f357688da59a9e372ac8ba70 100644 --- a/packages/CLI11/tests/SimpleTest.cpp +++ b/packages/CLI11/tests/SimpleTest.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2020, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2021, 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 3a9f7371d87bd0412554eb49d2fe559ea060c95c..a9842481139218f4ef47e9f6cda8dedc6ba5e50f 100644 --- a/packages/CLI11/tests/StringParseTest.cpp +++ b/packages/CLI11/tests/StringParseTest.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2020, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // @@ -78,3 +78,31 @@ TEST_CASE_METHOD(TApp, "ExistingExeCheckWithLotsOfSpace", "[stringparse]") { CHECK(std::string("./") + std::string(tmpexe) == app.get_name()); } + +// From GitHub issue #591 https://github.com/CLIUtils/CLI11/issues/591 +TEST_CASE_METHOD(TApp, "ProgNameWithSpace", "[stringparse]") { + + app.add_flag("--foo"); + CHECK_NOTHROW(app.parse("\"Foo Bar\" --foo", true)); + + CHECK(app["--foo"]->as<bool>()); + CHECK(app.get_name() == "Foo Bar"); +} + +TEST_CASE_METHOD(TApp, "ProgNameWithSpaceEmbeddedQuote", "[stringparse]") { + + app.add_flag("--foo"); + CHECK_NOTHROW(app.parse("\"Foo\\\" Bar\" --foo", true)); + + CHECK(app["--foo"]->as<bool>()); + CHECK(app.get_name() == "Foo\" Bar"); +} + +TEST_CASE_METHOD(TApp, "ProgNameWithSpaceSingleQuote", "[stringparse]") { + + app.add_flag("--foo"); + CHECK_NOTHROW(app.parse(R"('Foo\' Bar' --foo)", true)); + + CHECK(app["--foo"]->as<bool>()); + CHECK(app.get_name() == "Foo' Bar"); +} diff --git a/packages/CLI11/tests/SubcommandTest.cpp b/packages/CLI11/tests/SubcommandTest.cpp index eef67bca7d44994678b3534dd8c3d155171a7479..e1ffded6b9371c13d68e7f9c800c87fadd89e207 100644 --- a/packages/CLI11/tests/SubcommandTest.cpp +++ b/packages/CLI11/tests/SubcommandTest.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2020, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // @@ -109,7 +109,7 @@ TEST_CASE_METHOD(TApp, "CrazyNameSubcommand", "[subcom]") { TEST_CASE_METHOD(TApp, "RequiredAndSubcommands", "[subcom]") { std::string baz; - app.add_option("baz", baz, "Baz Description", true)->required(); + app.add_option("baz", baz, "Baz Description")->required()->capture_default_str(); auto foo = app.add_subcommand("foo"); auto bar = app.add_subcommand("bar"); @@ -811,6 +811,18 @@ TEST_CASE_METHOD(TApp, "RequiredPosInSubcommand", "[subcom]") { CHECK_THROWS_AS(run(), CLI::RequiredError); } +TEST_CASE_METHOD(TApp, "invalidSubcommandName", "[subcom]") { + + bool gotError{false}; + try { + app.add_subcommand("!foo/foo", "Foo a bar"); + } catch(const CLI::IncorrectConstruction &e) { + gotError = true; + CHECK_THAT(e.what(), Contains("!")); + } + CHECK(gotError); +} + struct SubcommandProgram : public TApp { CLI::App *start{nullptr}; @@ -1633,6 +1645,28 @@ TEST_CASE_METHOD(TApp, "OptionGroupAlias", "[subcom]") { CHECK(-3 == val); } +TEST_CASE_METHOD(TApp, "OptionGroupAliasWithSpaces", "[subcom]") { + double val{0.0}; + auto sub = app.add_option_group("sub1"); + sub->alias("sub2 bb"); + sub->alias("sub3/b"); + sub->add_option("-v,--value", val); + args = {"sub1", "-v", "-3"}; + CHECK_THROWS_AS(run(), CLI::ExtrasError); + + args = {"sub2 bb", "--value", "-5"}; + run(); + CHECK(-5.0 == val); + + args = {"sub3/b", "-v", "7"}; + run(); + CHECK(7 == val); + + args = {"-v", "-3"}; + run(); + CHECK(-3 == val); +} + TEST_CASE_METHOD(TApp, "subcommand_help", "[subcom]") { auto sub1 = app.add_subcommand("help")->silent(); bool flag{false}; @@ -1654,9 +1688,8 @@ TEST_CASE_METHOD(TApp, "AliasErrors", "[subcom]") { auto sub1 = app.add_subcommand("sub1"); auto sub2 = app.add_subcommand("sub2"); - CHECK_THROWS_AS(sub2->alias("this is a not a valid alias"), CLI::IncorrectConstruction); - CHECK_THROWS_AS(sub2->alias("-alias"), CLI::IncorrectConstruction); - CHECK_THROWS_AS(sub2->alias("alia$"), CLI::IncorrectConstruction); + CHECK_THROWS_AS(sub2->alias("this is a not\n a valid alias"), CLI::IncorrectConstruction); + CHECK_NOTHROW(sub2->alias("-alias")); // this is allowed but would be unusable on command line parsers CHECK_THROWS_AS(app.add_subcommand("--bad_subcommand_name", "documenting the bad subcommand"), CLI::IncorrectConstruction); @@ -1862,3 +1895,63 @@ TEST_CASE_METHOD(ManySubcommands, "defaultEnabledSubcommand", "[subcom]") { CHECK(sub2->get_enabled_by_default()); CHECK(!sub2->get_disabled()); } + +// #572 +TEST_CASE_METHOD(TApp, "MultiFinalCallbackCounts", "[subcom]") { + + int app_compl = 0; + int sub_compl = 0; + int subsub_compl = 0; + int app_final = 0; + int sub_final = 0; + int subsub_final = 0; + + app.parse_complete_callback([&app_compl]() { app_compl++; }); + app.final_callback([&app_final]() { app_final++; }); + + auto *sub = app.add_subcommand("sub"); + + sub->parse_complete_callback([&sub_compl]() { sub_compl++; }); + sub->final_callback([&sub_final]() { sub_final++; }); + + auto *subsub = sub->add_subcommand("subsub"); + + subsub->parse_complete_callback([&subsub_compl]() { subsub_compl++; }); + subsub->final_callback([&subsub_final]() { subsub_final++; }); + + SECTION("No specified subcommands") { + args = {}; + run(); + + CHECK(app_compl == 1); + CHECK(app_final == 1); + CHECK(sub_compl == 0); + CHECK(sub_final == 0); + CHECK(subsub_compl == 0); + CHECK(subsub_final == 0); + } + + SECTION("One layer of subcommands") { + args = {"sub"}; + run(); + + CHECK(app_compl == 1); + CHECK(app_final == 1); + CHECK(sub_compl == 1); + CHECK(sub_final == 1); + CHECK(subsub_compl == 0); + CHECK(subsub_final == 0); + } + + SECTION("Fully specified subcommands") { + args = {"sub", "subsub"}; + run(); + + CHECK(app_compl == 1); + CHECK(app_final == 1); + CHECK(sub_compl == 1); + CHECK(sub_final == 1); + CHECK(subsub_compl == 1); + CHECK(subsub_final == 1); + } +} diff --git a/packages/CLI11/tests/TimerTest.cpp b/packages/CLI11/tests/TimerTest.cpp index 8c88f478ace08a3e192c0439717ec44ad32b9ab9..11e6e12177815c0d18e228aedd3736927b47d79e 100644 --- a/packages/CLI11/tests/TimerTest.cpp +++ b/packages/CLI11/tests/TimerTest.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2020, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2021, 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/TransformTest.cpp b/packages/CLI11/tests/TransformTest.cpp index 84ac544a4d003778cbcd73797034eb70c5612d98..76a1eff83012ae33b2b3c20ea37102796acf9b55 100644 --- a/packages/CLI11/tests/TransformTest.cpp +++ b/packages/CLI11/tests/TransformTest.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2020, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2021, 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 aa8886c092ab21c1b464ec833c3fd00924378924..c3b556c3fdd89c252474e03fd8f4f0f6a120ebc3 100644 --- a/packages/CLI11/tests/TrueFalseTest.cpp +++ b/packages/CLI11/tests/TrueFalseTest.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2020, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2021, 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 847266649965e6fc03176cda5862d46d07dbe103..5a75908c20f95f9d4089cbcae72b68caa6338416 100644 --- a/packages/CLI11/tests/WindowsTest.cpp +++ b/packages/CLI11/tests/WindowsTest.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2020, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2021, 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 0f72adda27064e17d3cb97f242526e5b1b2b4501..91991682544dc465c44c989fe1a4e0e4dd2c56e9 100644 --- a/packages/CLI11/tests/app_helper.hpp +++ b/packages/CLI11/tests/app_helper.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2020, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2021, 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/informational.cpp b/packages/CLI11/tests/informational.cpp index 92f7dc4c8d597e729ae62efadf0eb226aa66c31f..f0fc70ba1778262d1b6888075382181e919c246f 100644 --- a/packages/CLI11/tests/informational.cpp +++ b/packages/CLI11/tests/informational.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2020, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2021, 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 be91037607cc1d090ca572a93803c527a8242975..447afbf4e94a62cb3980b6e5bf5f178d6852f6a6 100644 --- a/packages/CLI11/tests/link_test_1.cpp +++ b/packages/CLI11/tests/link_test_1.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2020, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2021, 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 b8544ab47a8104b831b66cd409490c200261bcb7..abce5493b75cf4619f08d41daa83c2e997b5647d 100644 --- a/packages/CLI11/tests/link_test_2.cpp +++ b/packages/CLI11/tests/link_test_2.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2020, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2021, 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 f1c2725f53e260069f3cc867bb1398ac21cb4b2f..f5fbebd81dad107729cb1e239e846f3a05d610f9 100644 --- a/packages/CLI11/tests/main.cpp +++ b/packages/CLI11/tests/main.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2020, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2021, 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/mesonTest/main.cpp b/packages/CLI11/tests/mesonTest/main.cpp index 69813782ad87dd6c6d4b4725cc6700ad79a4da14..64d45eb6f1e7e909c64a5540f7bcd60ebe878327 100644 --- a/packages/CLI11/tests/mesonTest/main.cpp +++ b/packages/CLI11/tests/mesonTest/main.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2020, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. //