diff --git a/packages/CLI11/.all-contributorsrc b/packages/CLI11/.all-contributorsrc index 7de67853e063fa7ea75d853506c59ea571d265ef..14ba0211e135005b41ac3ea91451afd3ee599f3f 100644 --- a/packages/CLI11/.all-contributorsrc +++ b/packages/CLI11/.all-contributorsrc @@ -476,8 +476,226 @@ "contributions": [ "code" ] + }, + { + "login": "trokhymchuk", + "name": "Artem Trokhymchuk ", + "avatar_url": "https://avatars.githubusercontent.com/u/66204814?v=4", + "profile": "https://github.com/trokhymchuk", + "contributions": [ + "code" + ] + }, + { + "login": "dherrera-fb", + "name": "dherrera-fb", + "avatar_url": "https://avatars.githubusercontent.com/u/89840711?v=4", + "profile": "https://github.com/dherrera-fb", + "contributions": [ + "code" + ] + }, + { + "login": "VolkerChristian", + "name": "Volker Christian", + "avatar_url": "https://avatars.githubusercontent.com/u/18554540?v=4", + "profile": "https://github.com/VolkerChristian", + "contributions": [ + "code" + ] + }, + { + "login": "thewtex", + "name": "Matt McCormick", + "avatar_url": "https://avatars.githubusercontent.com/u/25432?v=4", + "profile": "https://www.mmmccormick.com/", + "contributions": [ + "code" + ] + }, + { + "login": "polistern", + "name": "polistern", + "avatar_url": "https://avatars.githubusercontent.com/u/55511995?v=4", + "profile": "http://polistern.i2p/", + "contributions": [ + "code" + ] + }, + { + "login": "andreasxp", + "name": "Andrey Zhukov", + "avatar_url": "https://avatars.githubusercontent.com/u/28830446?v=4", + "profile": "https://github.com/andreasxp", + "contributions": [ + "code" + ] + }, + { + "login": "SherlockInSpace", + "name": "Ryan Sherlock", + "avatar_url": "https://avatars.githubusercontent.com/u/5507786?v=4", + "profile": "https://github.com/SherlockInSpace", + "contributions": [ + "code" + ] + }, + { + "login": "Krzmbrzl", + "name": "Robert Adam", + "avatar_url": "https://avatars.githubusercontent.com/u/12751591?v=4", + "profile": "https://github.com/Krzmbrzl", + "contributions": [ + "code" + ] + }, + { + "login": "RangeMachine", + "name": "RangeMachine", + "avatar_url": "https://avatars.githubusercontent.com/u/11577601?v=4", + "profile": "https://github.com/RangeMachine", + "contributions": [ + "code" + ] + }, + { + "login": "ptheywood", + "name": "Peter Heywood", + "avatar_url": "https://avatars.githubusercontent.com/u/628937?v=4", + "profile": "http://ptheywood.uk/", + "contributions": [ + "code" + ] + }, + { + "login": "peterh", + "name": "Peter Harris", + "avatar_url": "https://avatars.githubusercontent.com/u/79339?v=4", + "profile": "https://github.com/peterh", + "contributions": [ + "code" + ] + }, + { + "login": "PeteAudinate", + "name": "PeteAudinate", + "avatar_url": "https://avatars.githubusercontent.com/u/99274874?v=4", + "profile": "https://github.com/PeteAudinate", + "contributions": [ + "code" + ] + }, + { + "login": "captainurist", + "name": "captainurist", + "avatar_url": "https://avatars.githubusercontent.com/u/73941350?v=4", + "profile": "https://github.com/captainurist", + "contributions": [ + "code" + ] + }, + { + "login": "djerius", + "name": "djerius", + "avatar_url": "https://avatars.githubusercontent.com/u/196875?v=4", + "profile": "https://github.com/djerius", + "contributions": [ + "code" + ] + }, + { + "login": "shameekganguly", + "name": "shameekganguly", + "avatar_url": "https://avatars.githubusercontent.com/u/2412842?v=4", + "profile": "https://github.com/shameekganguly", + "contributions": [ + "code" + ] + }, + { + "login": "ayum", + "name": "ayum", + "avatar_url": "https://avatars.githubusercontent.com/u/6747040?v=4", + "profile": "https://github.com/ayum", + "contributions": [ + "code" + ] + }, + { + "login": "BenjaminBeichler", + "name": "Benjamin Beichler", + "avatar_url": "https://avatars.githubusercontent.com/u/1441492?v=4", + "profile": "https://github.com/BenjaminBeichler", + "contributions": [ + "code" + ] + }, + { + "login": "DarkWingMcQuack", + "name": "DarkWingMcQuack", + "avatar_url": "https://avatars.githubusercontent.com/u/38857302?v=4", + "profile": "https://github.com/DarkWingMcQuack", + "contributions": [ + "code" + ] + }, + { + "login": "eli-schwartz", + "name": "Eli Schwartz", + "avatar_url": "https://avatars.githubusercontent.com/u/6551424?v=4", + "profile": "https://github.com/eli-schwartz", + "contributions": [ + "code" + ] + }, + { + "login": "bruxisma", + "name": "Izzy Muerte", + "avatar_url": "https://avatars.githubusercontent.com/u/63051?v=4", + "profile": "https://izzys.casa/", + "contributions": [ + "code" + ] + }, + { + "login": "j-rivero", + "name": "Jose Luis Rivero", + "avatar_url": "https://avatars.githubusercontent.com/u/2098802?v=4", + "profile": "https://github.com/j-rivero", + "contributions": [ + "code" + ] + }, + { + "login": "looopTools", + "name": "Lars Nielsen", + "avatar_url": "https://avatars.githubusercontent.com/u/1943536?v=4", + "profile": "https://github.com/looopTools", + "contributions": [ + "code" + ] + }, + { + "login": "cetius", + "name": "Marcin Ropa", + "avatar_url": "https://avatars.githubusercontent.com/u/6552472?v=4", + "profile": "https://github.com/cetius", + "contributions": [ + "code" + ] + }, + { + "login": "nathanielhourt", + "name": "Nathaniel Hourt", + "avatar_url": "https://avatars.githubusercontent.com/u/271977?v=4", + "profile": "https://github.com/nathanielhourt", + "contributions": [ + "code" + ] } ], + "contributorsSortAlphabetically": true, "contributorsPerLine": 7, - "skipCi": true + "skipCi": true, + "commitType": "docs" } diff --git a/packages/CLI11/.ci/azure-cmake-new.yml b/packages/CLI11/.ci/azure-cmake-new.yml new file mode 100644 index 0000000000000000000000000000000000000000..56a2fb4d99627c1fa37ffc453586d7b030eb2e4f --- /dev/null +++ b/packages/CLI11/.ci/azure-cmake-new.yml @@ -0,0 +1,17 @@ +steps: + # Note that silkeh/clang does not include ca-certificates, so check the shasum for verification + - bash: | + wget --no-check-certificate "https://cmake.org/files/v3.28/cmake-3.28.0-linux-x86_64.tar.gz" + echo "898f0b5ca6e2ea5286998e97bd33f030d7d09f18ca4b88be661fdfbad5dadd88 cmake-3.28.0-linux-x86_64.tar.gz" | shasum -sca 256 + displayName: Download CMake + + - task: ExtractFiles@1 + inputs: + archiveFilePatterns: "cmake*.tar.gz" + destinationFolder: "cmake_program" + displayName: Extract CMake + + - bash: + echo + "##vso[task.prependpath]$(Build.SourcesDirectory)/cmake_program/cmake-3.28.0-linux-x86_64/bin" + displayName: Add CMake to PATH diff --git a/packages/CLI11/.clang-tidy b/packages/CLI11/.clang-tidy index 82450d1b5a1047190bb6a7f02db561c582542fd8..727b76525e3bf579e222bd7945799ab07338ae86 100644 --- a/packages/CLI11/.clang-tidy +++ b/packages/CLI11/.clang-tidy @@ -6,6 +6,7 @@ # modernize-avoid-c-arrays trips up in TEMPLATE_TEST_CASE catch macro # modernize-return-braced-init-list triggers on lambdas ? # modernize-make-unique requires C++14 +# modernize-type_traits requires C++17 # readability-avoid-const-params-in-decls Affected by the pre-compile split Checks: | @@ -37,6 +38,8 @@ Checks: | -modernize-concat-nested-namespaces, -modernize-return-braced-init-list, -modernize-make-unique, + -modernize-type-traits, + -modernize-macro-to-enum, *performance*, -performance-unnecessary-value-param, -performance-inefficient-string-concatenation, diff --git a/packages/CLI11/.codacy.yml b/packages/CLI11/.codacy.yml new file mode 100644 index 0000000000000000000000000000000000000000..03a1e522b2fbd30ea61dd173265d74cfbc0ff71e --- /dev/null +++ b/packages/CLI11/.codacy.yml @@ -0,0 +1,18 @@ +--- +engines: + rubocop: + enabled: true + duplication: + enabled: true + metrics: + enabled: true + coverage: + enabled: false +languages: + +exclude_paths: + - "fuzz/**/*" + - "fuzz/*" + - "scripts/**/*" + - "scripts/*" + - "**.md" diff --git a/packages/CLI11/.github/actions/quick_cmake/action.yml b/packages/CLI11/.github/actions/quick_cmake/action.yml index 8359fb0d01575c5225ab9bc09e657e58e76e4099..d2b3825fe5331e63b232dbe45750ef7c31410373 100644 --- a/packages/CLI11/.github/actions/quick_cmake/action.yml +++ b/packages/CLI11/.github/actions/quick_cmake/action.yml @@ -1,5 +1,5 @@ name: Quick CMake config -description: "Runs CMake 3.4+ (if already setup)" +description: "Runs CMake 3.5+ (if already setup)" inputs: args: description: "Other arguments" @@ -13,7 +13,7 @@ runs: using: composite steps: - name: CMake ${{ inputs.cmake-version }} - uses: jwlawson/actions-setup-cmake@v1.13 + uses: jwlawson/actions-setup-cmake@v1.14 with: cmake-version: "${{ inputs.cmake-version }}" - run: | diff --git a/packages/CLI11/.github/dependabot.yml b/packages/CLI11/.github/dependabot.yml index f265d88d94951cbeb868e505d142cfe6232955c4..40aaf098ff5d105969fe3bb9323e238acc20b3e4 100644 --- a/packages/CLI11/.github/dependabot.yml +++ b/packages/CLI11/.github/dependabot.yml @@ -7,3 +7,7 @@ updates: interval: "weekly" target-branch: "main" open-pull-requests-limit: 10 + groups: + actions: + patterns: + - "*" diff --git a/packages/CLI11/.github/workflows/build.yml b/packages/CLI11/.github/workflows/build.yml index 625502c6b33751730894729cabff3dff86f23e2f..57d54dc16694a3a0e51aa144343d8edb334bbea2 100644 --- a/packages/CLI11/.github/workflows/build.yml +++ b/packages/CLI11/.github/workflows/build.yml @@ -13,11 +13,11 @@ jobs: name: Single header runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: submodules: true - - uses: actions/setup-python@v4 + - uses: actions/setup-python@v5 with: python-version: "3.x" @@ -39,12 +39,12 @@ jobs: - name: Copy file to main folder run: cp build/include/CLI11.hpp CLI11.hpp - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 with: name: CLI11.hpp path: CLI11.hpp - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 with: name: CLI11-Source path: CLI11-Source diff --git a/packages/CLI11/.github/workflows/docs.yml b/packages/CLI11/.github/workflows/docs.yml new file mode 100644 index 0000000000000000000000000000000000000000..6b79b352fa297d26ee77103e039d1fed4ed6c4d8 --- /dev/null +++ b/packages/CLI11/.github/workflows/docs.yml @@ -0,0 +1,91 @@ +name: Docs + +on: + workflow_dispatch: + pull_request: + push: + branches: + - main + +permissions: + contents: read + pages: write + id-token: write + +concurrency: + group: "pages" + cancel-in-progress: false + +jobs: + apidocs: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: mattnotmitt/doxygen-action@v1 + with: + doxyfile-path: ./docs/Doxyfile + + - uses: actions/upload-artifact@v4 + with: + name: api-docs + path: html + + gitbook: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version: 16 + + - uses: awalsh128/cache-apt-pkgs-action@v1 + with: + packages: calibre calibre-bin libxss1 libasound2 + version: 1 + + - name: Install JS requirements + working-directory: book + run: | + npm install + + - name: Build book + working-directory: book + run: | + npx gitbook build . public + npx gitbook pdf . public/cli11.pdf + + - uses: actions/upload-artifact@v4 + with: + name: gitbook + path: book/public + + pages: + runs-on: ubuntu-latest + needs: [apidocs, gitbook] + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + if: > + success() + && github.ref == 'refs/heads/main' + && github.repository == 'CLIUtils/CLI11' + steps: + - uses: actions/configure-pages@v4 + id: pages + + - uses: actions/download-artifact@v4 + with: + name: api-docs + path: _site + + - uses: actions/download-artifact@v4 + with: + name: gitbook + path: _site/book + + - uses: actions/upload-pages-artifact@v3 + + - uses: actions/deploy-pages@v4 + id: deployment diff --git a/packages/CLI11/.github/workflows/fuzz.yml b/packages/CLI11/.github/workflows/fuzz.yml index 75d161b38a1f06cb91752e7b02e4f81e58071cca..413f150fbb80ac78d7832a1538d377f5251c7c56 100644 --- a/packages/CLI11/.github/workflows/fuzz.yml +++ b/packages/CLI11/.github/workflows/fuzz.yml @@ -16,7 +16,7 @@ jobs: name: quickfuzz1 runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: 0 @@ -35,12 +35,12 @@ jobs: - name: Build run: cmake --build build -j4 - - name: Test + - name: Test_app run: | cd build make QUICK_CLI11_APP_FUZZ - - name: Test2 + - name: Test_file run: | cd build make QUICK_CLI11_FILE_FUZZ @@ -48,7 +48,7 @@ jobs: - name: artifacts if: failure() - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: file_failure path: ./build/fuzz/cli11_*_fail_artifact.txt diff --git a/packages/CLI11/.github/workflows/tests.yml b/packages/CLI11/.github/workflows/tests.yml index 2ab5a66692d9f433ba88aa12fcfff05c679a1e99..460d2ebd36469c75d4b8bedc5398d38314138bdd 100644 --- a/packages/CLI11/.github/workflows/tests.yml +++ b/packages/CLI11/.github/workflows/tests.yml @@ -10,6 +10,9 @@ concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true +env: + CTEST_OUTPUT_ON_FAILURE: "1" + jobs: coverage: name: Coverage @@ -19,7 +22,7 @@ jobs: std: ["11", "14", "17", "20"] precompile: ["ON", "OFF"] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: 0 @@ -35,7 +38,7 @@ jobs: cmake -S . -B build \ -DCMAKE_CXX_STANDARD=${{matrix.std}} \ -DCLI11_SINGLE_FILE_TESTS=OFF \ - -DCLI11_EXAMPLES=OFF \ + -DCLI11_BUILD_EXAMPLES=OFF \ -DCLI11_PRECOMPILED=${{matrix.precompile}} \ -DCMAKE_BUILD_TYPE=Coverage @@ -55,15 +58,38 @@ jobs: - uses: codecov/codecov-action@v3 with: files: build/coverage.info - fail_ci_if_error: true functionalities: fixes + catch2-3: + name: Catch 2 3.x + runs-on: macos-latest + steps: + - uses: actions/checkout@v4 + + - name: Get Catch 2 + run: brew install catch2 + + - name: Configure + run: | + cmake -S . -B build \ + -DCMAKE_CXX_STANDARD=14 \ + -DCLI11_SINGLE_FILE_TESTS=OFF \ + -DCLI11_BUILD_EXAMPLES=OFF \ + -DCLI11_PRECOMPILED=ON + + - name: Build + run: cmake --build build -j4 + + - name: Test + run: cmake --build build --target test + + clang-tidy: name: Clang-Tidy runs-on: ubuntu-latest - container: silkeh/clang:14 + container: silkeh/clang:17 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Configure run: > @@ -81,7 +107,7 @@ jobs: steps: - name: Add build tools run: apt-get update && apt-get install -y wget git cmake - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: submodules: true - name: Configure @@ -92,11 +118,11 @@ jobs: cuda12-build: name: CUDA 12 build only runs-on: ubuntu-latest - container: nvidia/cuda:12.1.0-devel-ubuntu22.04 + container: nvidia/cuda:12.3.1-devel-ubuntu22.04 steps: - name: Add build tools run: apt-get update && apt-get install -y wget git cmake - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: submodules: true - name: Configure @@ -106,9 +132,9 @@ jobs: boost-build: name: Boost build - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: submodules: true - name: Add boost @@ -127,7 +153,7 @@ jobs: name: Meson build runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Prepare commands run: | @@ -140,16 +166,62 @@ jobs: - name: Build run: meson compile -C build-meson - cmake-config-ubuntu-1804: - name: CMake config check (Ubuntu 18.04) - runs-on: ubuntu-18.04 + install: + name: install tests + runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 + with: + submodules: true + - name: Configure + run: cmake -S . -B build -DCLI11_INSTALL_PACKAGE_TESTS=ON -DCMAKE_INSTALL_PREFIX=/home/runner/work/install + - name: Build + run: cmake --build build -j2 + - name: install + run: cmake --install build + - name: Run tests + run: ctest --output-on-failure -L Packaging + working-directory: build - - name: Check CMake 3.4 + install-precompiled: + name: install tests precompiled + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 with: - cmake-version: "3.4" - uses: ./.github/actions/quick_cmake + submodules: true + - name: Configure + run: cmake -S . -B build -DCLI11_INSTALL_PACKAGE_TESTS=ON -DCMAKE_INSTALL_PREFIX=/home/runner/work/install -DCLI11_PRECOMPILED=ON + - name: Build + run: cmake --build build -j2 + - name: install + run: cmake --install build + - name: Run tests + run: ctest --output-on-failure -L Packaging + working-directory: build + + install-single_file: + name: install tests single file + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + submodules: true + - name: Configure + run: cmake -S . -B build -DCLI11_INSTALL_PACKAGE_TESTS=ON -DCMAKE_INSTALL_PREFIX=/home/runner/work/install -DCLI11_SINGLE_FILE=ON + - name: Build + run: cmake --build build -j2 + - name: install + run: cmake --install build + - name: Run tests + run: ctest --output-on-failure -L Packaging + working-directory: build + + cmake-config-ubuntu-2004: + name: CMake config check (Ubuntu 20.04) + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v4 - name: Check CMake 3.5 uses: ./.github/actions/quick_cmake @@ -187,17 +259,10 @@ jobs: cmake-version: "3.10" if: success() || failure() - cmake-config-ubuntu-2004: - name: CMake config check (Ubuntu 20.04) - runs-on: ubuntu-20.04 - steps: - - uses: actions/checkout@v3 - - - name: Check CMake 3.11 (full) + - name: Check CMake 3.11 uses: ./.github/actions/quick_cmake with: cmake-version: "3.11" - args: -DCLI11_SANITIZERS=ON -DCLI11_BUILD_EXAMPLES_JSON=ON if: success() || failure() - name: Check CMake 3.12 @@ -216,6 +281,7 @@ jobs: uses: ./.github/actions/quick_cmake with: cmake-version: "3.14" + args: -DCLI11_SANITIZERS=ON -DCLI11_BUILD_EXAMPLES_JSON=ON if: success() || failure() - name: Check CMake 3.15 @@ -234,7 +300,7 @@ jobs: name: CMake config check (Ubuntu 22.04) runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Check CMake 3.17 uses: ./.github/actions/quick_cmake @@ -272,22 +338,40 @@ jobs: cmake-version: "3.22" if: success() || failure() - - name: Check CMake 3.23 + - name: Check CMake 3.23 uses: ./.github/actions/quick_cmake with: cmake-version: "3.23" if: success() || failure() - - name: Check CMake 3.24 (full) + - name: Check CMake 3.24 uses: ./.github/actions/quick_cmake with: cmake-version: "3.24" - args: -DCLI11_SANITIZERS=ON -DCLI11_BUILD_EXAMPLES_JSON=ON if: success() || failure() - - name: Check CMake 3.25 (full) + - name: Check CMake 3.25 uses: ./.github/actions/quick_cmake with: cmake-version: "3.25" + if: success() || failure() + + - name: Check CMake 3.26 (full) + uses: ./.github/actions/quick_cmake + with: + cmake-version: "3.26" + args: -DCLI11_SANITIZERS=ON -DCLI11_BUILD_EXAMPLES_JSON=ON + if: success() || failure() + + - name: Check CMake 3.27 + uses: ./.github/actions/quick_cmake + with: + cmake-version: "3.27" + if: success() || failure() + + - name: Check CMake 3.28 (full) + uses: ./.github/actions/quick_cmake + with: + cmake-version: "3.28" args: -DCLI11_SANITIZERS=ON -DCLI11_BUILD_EXAMPLES_JSON=ON if: success() || failure() diff --git a/packages/CLI11/.gitrepo b/packages/CLI11/.gitrepo index 925d8dccefb3a338b66e4a57300b3d1e983bf56a..3c6f249cfbead662dc9b5c1abfb35929ace55549 100644 --- a/packages/CLI11/.gitrepo +++ b/packages/CLI11/.gitrepo @@ -6,7 +6,7 @@ [subrepo] remote = git@github.com:CLIUtils/CLI11.git branch = main - commit = 784fa3ebd387e63feef41d174f587bbe4cfec4da - parent = 164bbb3a73dc902c29aa7ccabfaba50cefa6345d + commit = 20de8b73bbbabaf2f94dd07c4ece8ff3590af531 + parent = 65932507ed66da9e0ada0d9294a336690069148a method = merge cmdver = 0.4.6 diff --git a/packages/CLI11/.pre-commit-config.yaml b/packages/CLI11/.pre-commit-config.yaml index febf04dd23f4f524d646fefc2b093a35792dfdbe..0d271c913ccb1f4b1c13118ac7a4bcb48dfc56a5 100644 --- a/packages/CLI11/.pre-commit-config.yaml +++ b/packages/CLI11/.pre-commit-config.yaml @@ -5,12 +5,12 @@ ci: repos: - repo: https://github.com/psf/black - rev: 23.3.0 + rev: 23.12.1 hooks: - id: black - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.4.0 + rev: v4.5.0 hooks: - id: check-added-large-files - id: check-case-conflict @@ -26,7 +26,7 @@ repos: - id: debug-statements - repo: https://github.com/pre-commit/mirrors-clang-format - rev: v16.0.3 + rev: v17.0.6 hooks: - id: clang-format types_or: [c++, c, cuda] @@ -38,7 +38,7 @@ repos: additional_dependencies: [pyyaml] - repo: https://github.com/pre-commit/mirrors-prettier - rev: "v3.0.0-alpha.9-for-vscode" + rev: "v4.0.0-alpha.8" hooks: - id: prettier types_or: [yaml, markdown, html, css, scss, javascript, json] @@ -87,7 +87,7 @@ repos: exclude: .pre-commit-config.yaml - repo: https://github.com/codespell-project/codespell - rev: v2.2.4 + rev: v2.2.6 hooks: - id: codespell args: ["-L", "atleast,ans,doub,inout"] diff --git a/packages/CLI11/CLI11.hpp.in b/packages/CLI11/CLI11.hpp.in index edc16bb15f6468287a4410d9c14fa258e0356003..e84a20aac305056b0bc9cdc3ccc0a64bc8912f2c 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-2023 University of Cincinnati, developed by Henry +// CLI11 {version} Copyright (c) 2017-2024 University of Cincinnati, developed by Henry // Schreiner under NSF AWARD 1414736. All rights reserved. // // Redistribution and use in source and binary forms of CLI11, with or without diff --git a/packages/CLI11/CMakeLists.txt b/packages/CLI11/CMakeLists.txt index cdc2011d8800199f8c752523446391905eba808a..512553f89e2e30e764d280a35e50ffcd088d7c68 100644 --- a/packages/CLI11/CMakeLists.txt +++ b/packages/CLI11/CMakeLists.txt @@ -1,15 +1,15 @@ -cmake_minimum_required(VERSION 3.4) -# Note: this is a header only library. If you have an older CMake than 3.4, +cmake_minimum_required(VERSION 3.5) +# Note: this is a header only library. If you have an older CMake than 3.5, # 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.24) version +# Make sure users don't get warnings on a tested (3.5 to 3.28) version # of CMake. For most of the policies, the new version is better (hence the change). -# We don't use the 3.4...3.24 syntax because of a bug in an older MSVC's +# We don't use the 3.5...3.28 syntax because of a bug in an older MSVC's # built-in and modified CMake 3.11 -if(${CMAKE_VERSION} VERSION_LESS 3.25) +if(${CMAKE_VERSION} VERSION_LESS 3.28) cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}) else() - cmake_policy(VERSION 3.25) + cmake_policy(VERSION 3.28) endif() set(VERSION_REGEX "#define CLI11_VERSION[ \t]+\"(.+)\"") @@ -81,7 +81,7 @@ option(CLI11_WARNINGS_AS_ERRORS "Turn all warnings into errors (for CI)") option(CLI11_SINGLE_FILE "Generate a single header file") option(CLI11_PRECOMPILED "Generate a precompiled static library instead of a header-only" OFF) cmake_dependent_option(CLI11_SANITIZERS "Download the sanitizers CMake config" OFF - "NOT CMAKE_VERSION VERSION_LESS 3.11" OFF) + "NOT CMAKE_VERSION VERSION_LESS 3.13" OFF) cmake_dependent_option(CLI11_BUILD_DOCS "Build CLI11 documentation" ON "${build-docs}" OFF) @@ -198,39 +198,41 @@ if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME AND EXISTS "${CMAKE_CURRENT_SOURCE_D endif() # Packaging support -set(CPACK_PACKAGE_VENDOR "github.com/CLIUtils/CLI11") -set(CPACK_PACKAGE_CONTACT "https://${CPACK_PACKAGE_VENDOR}") -set(CPACK_PACKAGE_VERSION_MAJOR ${PROJECT_VERSION_MAJOR}) # Automatic in CMake 3.12+ -set(CPACK_PACKAGE_VERSION_MINOR ${PROJECT_VERSION_MINOR}) # Automatic in CMake 3.12+ -set(CPACK_PACKAGE_VERSION_PATCH ${PROJECT_VERSION_PATCH}) # Automatic in CMake 3.12+ -set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "Command line parser with simple and intuitive interface") -set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE") -set(CPACK_RESOURCE_FILE_README "${CMAKE_CURRENT_SOURCE_DIR}/README.md") -set(CPACK_PACKAGE_DESCRIPTION_FILE "${CMAKE_CURRENT_SOURCE_DIR}/CLI11.CPack.Description.txt") -set(CPACK_SOURCE_GENERATOR "TGZ;ZIP") - -# CPack collects *everything* except what's listed here. -set(CPACK_SOURCE_IGNORE_FILES - /.git - /dist - /.*build.* - /\\\\.DS_Store - /.*\\\\.egg-info - /var - /azure-pipelines.yml - /.ci - /docs - /examples - /test_package - /book - /.travis.yml - .swp - /.all-contributorsrc - /.appveyor.yml - /.pre-commit.*yaml) - -set(CPACK_DEBIAN_PACKAGE_ARCHITECTURE "all") -set(CPACK_DEBIAN_COMPRESSION_TYPE "xz") -set(CPACK_DEBIAN_PACKAGE_NAME "libcli11-dev") - -include(CPack) +if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME) + set(CPACK_PACKAGE_VENDOR "github.com/CLIUtils/CLI11") + set(CPACK_PACKAGE_CONTACT "https://${CPACK_PACKAGE_VENDOR}") + set(CPACK_PACKAGE_VERSION_MAJOR ${PROJECT_VERSION_MAJOR}) # Automatic in CMake 3.12+ + set(CPACK_PACKAGE_VERSION_MINOR ${PROJECT_VERSION_MINOR}) # Automatic in CMake 3.12+ + set(CPACK_PACKAGE_VERSION_PATCH ${PROJECT_VERSION_PATCH}) # Automatic in CMake 3.12+ + set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "Command line parser with simple and intuitive interface") + set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE") + set(CPACK_RESOURCE_FILE_README "${CMAKE_CURRENT_SOURCE_DIR}/README.md") + set(CPACK_PACKAGE_DESCRIPTION_FILE "${CMAKE_CURRENT_SOURCE_DIR}/CLI11.CPack.Description.txt") + set(CPACK_SOURCE_GENERATOR "TGZ;ZIP") + + # CPack collects *everything* except what's listed here. + set(CPACK_SOURCE_IGNORE_FILES + /.git + /dist + /.*build.* + /\\\\.DS_Store + /.*\\\\.egg-info + /var + /azure-pipelines.yml + /.ci + /docs + /examples + /test_package + /book + /.travis.yml + .swp + /.all-contributorsrc + /.appveyor.yml + /.pre-commit.*yaml) + + set(CPACK_DEBIAN_PACKAGE_ARCHITECTURE "all") + set(CPACK_DEBIAN_COMPRESSION_TYPE "xz") + set(CPACK_DEBIAN_PACKAGE_NAME "libcli11-dev") + + include(CPack) +endif() diff --git a/packages/CLI11/CPPLINT.cfg b/packages/CLI11/CPPLINT.cfg index 40bec3714ed59c7101b1d168b9ccdb087a611132..e1d27d9f5f770900c0b6c7fd48744f01cf2ddceb 100644 --- a/packages/CLI11/CPPLINT.cfg +++ b/packages/CLI11/CPPLINT.cfg @@ -9,6 +9,7 @@ filter=-readability/nolint # Conflicts with clang-tidy filter=-readability/check # Catch uses CHECK(a == b) (Tests only) filter=-build/namespaces # Currently using it for one test (Tests only) filter=-runtime/references # Requires fundamental change of API, don't see need for this +filter=-runtime/string # Requires not using static const strings which makes thing really annoying 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/LICENSE b/packages/CLI11/LICENSE index aae15855ecf563d8e62cd6458a0c99b96be4e5b1..715be0bb493bea2a438781b4e9b4dfeaf4fc7634 100644 --- a/packages/CLI11/LICENSE +++ b/packages/CLI11/LICENSE @@ -1,4 +1,4 @@ -CLI11 2.2 Copyright (c) 2017-2023 University of Cincinnati, developed by Henry +CLI11 2.2 Copyright (c) 2017-2024 University of Cincinnati, developed by Henry Schreiner under NSF AWARD 1414736. All rights reserved. Redistribution and use in source and binary forms of CLI11, with or without diff --git a/packages/CLI11/README.md b/packages/CLI11/README.md index bd9e58db71e02dfe4a479c283783b37bf281193f..bb75fd4928b405f87cbd60c29cf50b7a8befec3b 100644 --- a/packages/CLI11/README.md +++ b/packages/CLI11/README.md @@ -24,39 +24,42 @@ set with a simple and intuitive interface. ## 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) - - [Unicode support](#unicode-support) - - [Utilities](#utilities) - - [Other libraries](#other-libraries) -- [API](#api) -- [Examples](#Examples) -- [Contribute](#contribute) -- [License](#license) +- [CLI11: Command line parser for C++11](#cli11-command-line-parser-for-c11) + - [Table of Contents](#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) + - [Callbacks](#callbacks) + - [Option groups](#option-groups) + - [Configuration file](#configuration-file) + - [Inheriting defaults](#inheriting-defaults) + - [Formatting](#formatting) + - [Subclassing](#subclassing) + - [How it works](#how-it-works) + - [Unicode support](#unicode-support) + - [Note on using Unicode paths](#note-on-using-unicode-paths) + - [Utilities](#utilities) + - [Other libraries](#other-libraries) + - [API](#api) + - [Examples](#examples) + - [Contribute](#contribute) + - [License](#license) Features that were added in the last released minor version are marked with "🆕". Features only available in main are marked with "🚧". @@ -168,7 +171,8 @@ this library: ## Install -To use, there are several methods: +To use, the most common methods are described here additional methods and +details are available at [installation][]: - All-in-one local header: Copy `CLI11.hpp` from the [most recent release][github releases] into your include directory, and you are set. This @@ -178,95 +182,9 @@ To use, there are several methods: 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 to 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" -``` - -- 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.) -- Via FetchContent in CMake 3.14+ (or 3.11+ with more work): you can add this - with fetch-content, then use the `CLI11::CLI11` target as above, and CMake - will download the project in the configure stage: - -```cmake -include(FetchContent) -FetchContent_Declare( - cli11 - GIT_REPOSITORY https://github.com/CLIUtils/CLI11 - GIT_TAG v2.2.0 -) - -FetchContent_MakeAvailable(cli11) -``` - -It is highly recommended that you use the git hash for `GIT_TAG` instead of a -tag or branch, as that will both be more secure, as well as faster to -reconfigure - CMake will not have to reach out to the internet to see if the tag -moved. You can also download just the single header file from the releases using -`file(DOWNLOAD`. - -To build the tests, checkout the repository and use CMake: - -```bash -cmake -S . -B build -cmake --build build -CTEST_OUTPUT_ON_FAILURE=1 cmake --build build -t test -``` - -<details><summary>Note: Special instructions for GCC 8</summary><p> - -If you are using GCC 8 and using it in C++17 mode with CLI11. CLI11 makes use of -the `<filesystem>` header if available, but specifically for this compiler, the -`filesystem` library is separate from the standard library and needs to be -linked separately. So it is available but CLI11 doesn't use it by default. - -Specifically `libstdc++fs` needs to be added to the linking list and -`CLI11_HAS_FILESYSTEM=1` has to be defined. Then the filesystem variant of the -Validators could be used on GCC 8. GCC 9+ does not have this issue so the -`<filesystem>` is used by default. - -There may also be other cases where a specific library needs to be linked. - -Defining `CLI11_HAS_FILESYSTEM=0` which will remove the usage and hence any -linking issue. - -In some cases certain clang compilations may require linking against `libc++fs`. -These situations have not been encountered so the specific situations requiring -them are unknown yet. - -</p></details> -</br> + point at this folder. With CMake 3.5+, use `include_directories(/opt/CLI11)` +- For other methods including using CMake or vcpkg and some specific + instructions for GCC 8 or WASI see [installation][]. ## Usage @@ -276,22 +194,27 @@ To set up, add options, and run, your main function will look something like this: ```cpp -int main() { +int main(int argc, char** argv) { CLI::App app{"App description"}; + argv = app.ensure_utf8(argv); std::string filename = "default"; app.add_option("-f,--file", filename, "A help string"); - CLI11_PARSE(app); + CLI11_PARSE(app, argc, argv); return 0; } ``` +For more information about 🚧`ensure_utf8` the section on +[Unicode support](#unicode-support) below. The 🚧`ensure_utf8` function is only +available in main currently and not in a release. + <details><summary>Note: If you don't like macros, this is what that macro expands to: (click to expand)</summary><p> ```cpp try { - app.parse(); + app.parse(argc, argv); } catch (const CLI::ParseError &e) { return app.exit(e); } @@ -304,25 +227,6 @@ inside the catch block; for example, help flags intentionally short-circuit all other processing for speed and to ensure required options and the like do not interfere. -</p></details> - -<details><summary>Note: Why are argc and argv not used? (click to expand)</summary><p> - -`argc` and `argv` may contain incorrect information on Windows when unicode text -is passed in. Check out a section on [unicode support](#unicode-support) below. - -If this is not a concern, you can explicitly pass `argc` and `argv` from main or -from an external preprocessor of CLI arguments to `parse`: - -```cpp -int main(int argc, char** argv) { - // ... - - CLI11_PARSE(app, argc, argv); - return 0; -} -``` - </p></details> </br> @@ -512,10 +416,11 @@ Before parsing, you can set the following options: 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. + passed on the command line. 🚧 The value must also pass any validators to be + used. - `->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). + options. Defaults to `"Options"`. Options given an empty string 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 @@ -546,8 +451,8 @@ Before parsing, you can set the following options: This equivalent to calling `->delimiter(delim)` and `->join()`. Valid values are `CLI::MultiOptionPolicy::Throw`, `CLI::MultiOptionPolicy::Throw`, `CLI::MultiOptionPolicy::TakeLast`, `CLI::MultiOptionPolicy::TakeFirst`, - `CLI::MultiOptionPolicy::Join`, `CLI::MultiOptionPolicy::TakeAll`, and - `CLI::MultiOptionPolicy::Sum` 🆕. + `CLI::MultiOptionPolicy::Join`, `CLI::MultiOptionPolicy::TakeAll`, + `CLI::MultiOptionPolicy::Sum` 🆕, and `CLI::MultiOptionPolicy::Reverse` 🚧. - `->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 @@ -797,6 +702,17 @@ filters on the key values is performed. `CLI::FileOnDefaultPath(default_path, false)`. This allows multiple paths to be chained using multiple transform calls. +- `CLI::EscapedString`: 🚧 can be used to process an escaped string. The + processing is equivalent to that used for TOML config files, see + [TOML strings](https://toml.io/en/v1.0.0#string). With 2 notable exceptions. + \` can also be used as a literal string notation, and it also allows binary + string notation see + [binary strings](https://cliutils.github.io/CLI11/book/chapters/config.html). + The escaped string processing will remove outer quotes if present, `"` will + indicate a string with potential escape sequences, `'` and \` will indicate a + literal string and the quotes removed but no escape sequences will be + processed. This is the same escape processing as used in config files. + ##### Validator operations Validators are copyable and have a few operations that can be performed on them @@ -901,12 +817,15 @@ not used in performance critical code: ### Subcommands -Subcommands are supported, and can be nested infinitely. To add a subcommand, -call the `add_subcommand` method with a name and an optional description. This -gives a pointer to an `App` that behaves just like the main app, and can take -options or further subcommands. Add `->ignore_case()` to a subcommand to allow -any variation of caps to also be accepted. `->ignore_underscore()` is similar, -but for underscores. Children inherit the current setting from the parent. You +Subcommands are keywords that invoke a new set of options and features. For +example, the `git` command has a long series of subcommands, like `add` and +`commit`. Each can have its own options and implementations. Subcommands are +supported in CLI11, and can be nested infinitely. To add a subcommand, call the +`add_subcommand` method with a name and an optional description. This gives a +pointer to an `App` that behaves just like the main app, and can take options or +further subcommands. Add `->ignore_case()` to a subcommand to allow any +variation of caps to also be accepted. `->ignore_underscore()` is similar, but +for underscores. Children inherit the current setting from the parent. You cannot add multiple matching subcommand names at the same level (including `ignore_case` and `ignore_underscore`). @@ -965,9 +884,11 @@ through the `add_subcommand` method have the same restrictions as option names. - `--subcommand1.subsub.f val` (short form nested subcommand option) The use of dot notation in this form is equivalent `--subcommand.long <args>` => -`subcommand --long <args> ++`. Nested subcommands also work `"sub1.subsub"` -would trigger the subsub subcommand in `sub1`. This is equivalent to "sub1 -subsub" +`subcommand --long <args> ++`. Nested subcommands also work `sub1.subsub` would +trigger the subsub subcommand in `sub1`. This is equivalent to "sub1 subsub". +Quotes around the subcommand names are permitted 🚧 following the TOML standard +for such specification. This includes allowing escape sequences. For example +`"subcommand".'f'` or `"subcommand.with.dots".arg1 = value`. #### Subcommand options @@ -1093,10 +1014,10 @@ option_groups. These are: - `.prefix_command()`: Like `allow_extras`, but stop immediately on the first unrecognized item. It is ideal for allowing your app or subcommand to be a "prefix" to calling another app. -- `.usage(message)`: Replace text to appear at the start of the help string +- `.usage(message)`: 🚧 Replace text to appear at the start of the help string after description. -- `.usage(std::string())`: Set a callback to generate a string that will appear - at the start of the help string after description. +- `.usage(std::string())`: 🚧 Set a callback to generate a string that will + appear at the start of the help string after description. - `.footer(message)`: Set text to appear at the bottom of the help string. - `.footer(std::string())`: Set a callback to generate a string that will appear at the end of the help string. @@ -1109,17 +1030,19 @@ option_groups. These are: 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. +- `.group(name)`: Set a group name, defaults to `"Subcommands"`. Setting an + empty string for the name 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. +> [!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 @@ -1299,18 +1222,22 @@ 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: +default reader can also accept files in INI format as well. The config reader +can read most aspects of TOML files including strings both literal 🚧 and with +potential escape sequences 🚧, digit separators 🚧, and multi-line strings 🚧, +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 # # The default section is [default], case insensitive value = 1 +value2 = 123_456 # a string with separators str = "A string" +str2 = "A string\nwith new lines" +str3 = 'A literal "string"' vector = [1,2,3] str_vector = ["one","two","and three"] @@ -1318,6 +1245,7 @@ str_vector = ["one","two","and three"] [subcommand] in_subcommand = Wow sub.subcommand = true +"sub"."subcommand2" = "string_value" ``` or equivalently in INI format @@ -1476,8 +1404,6 @@ 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 - ```cpp app.add_option("--fancy-count", [](std::vector<std::string> val){ std::cout << "This option was given " << val.size() << " times." << std::endl; @@ -1501,49 +1427,76 @@ CLI11 supports Unicode and wide strings as defined in the When using the command line on Windows with unicode arguments, your `main` function may already receive broken Unicode. Parsing `argv` at that point will -not give you a correct string. To fix this, you have three options: - -1. If you pass unmodified command-line arguments to CLI11, call `app.parse()` - instead of `app.parse(argc, argv)` (or `CLI11_PARSE(app)` instead of - `CLI11_PARSE(app, argc, argv)`). The library will find correct arguments - itself. - - ```cpp - int main() { - CLI::App app; - // ... - CLI11_PARSE(app); - } - ``` - -2. Get correct arguments with which the program was originally executed using - provided functions: `CLI::argc()` and `CLI::argv()`. These two methods are - the only cross-platform ways of handling unicode correctly. - - ```cpp - int main() { - CLI::App app; - // ... - CLI11_PARSE(app, CLI::argc(), CLI::argv()); - } - ``` - -3. Use the Windows-only non-standard `wmain` function, which accepts - `wchar_t *argv[]` instead of `char* argv[]`. Parsing this will allow CLI to - convert wide strings to UTF-8 without losing information. - - ```cpp - int wmain(int argc, wchar_t *argv[]) { - CLI::App app; - // ... - CLI11_PARSE(app, argc, argv); - } - ``` - -4. Retrieve arguments yourself by using Windows APIs like - [`CommandLineToArgvW`](https://learn.microsoft.com/en-us/windows/win32/api/shellapi/nf-shellapi-commandlinetoargvw) - and pass them to CLI. This is what the library is doing under the hood in - `CLI::argv()`. +not give you a correct string. To fix this, you have three good options and two +bad ones: + +1\. Replace `argv` with `app.ensure_utf8(argv)` before any arguments are parsed. +`ensure_utf8` will do nothing on systems where `argv` is already in UTF-8 (Such +as Linux or macOS) and return `argv` unmodified. On Windows, it will discard +`argv` and replace it with a correctly decoded array or arguments from win32 +API. + +```cpp +int main(int argc, char** argv) { + CLI::App app; + argv = app.ensure_utf8(argv); // new argv memory is held by app + // ... + CLI11_PARSE(app, argc, argv); +} +``` + +2\. If you pass unmodified command-line arguments to CLI11, call `app.parse()` +instead of `app.parse(argc, argv)` (or `CLI11_PARSE(app)` instead of +`CLI11_PARSE(app, argc, argv)`). The library will find correct arguments by +itself. + +> [!NOTE] +> +> This approach may not work on weird OS configurations, such as when the +> `/proc` dir is missing on Linux systems (see also +> [#845](https://github.com/CLIUtils/CLI11/issues/845)). +> +> ```cpp +> int main() { +> CLI::App app; +> // ... +> CLI11_PARSE(app); +> } +> ``` + +3\. Get correct arguments with which the program was originally executed using +provided functions: `CLI::argc()` and `CLI::argv()`. These three methods are the +only cross-platform ways of handling unicode correctly. + +```cpp +int main() { + CLI::App app; + // ... + CLI11_PARSE(app, CLI::argc(), CLI::argv()); +} +``` + +<details><summary>Bad options (click to expand)</summary><p> + +4\. Use the Windows-only non-standard `wmain` function, which accepts +`wchar_t *argv[]` instead of `char* argv[]`. Parsing this will allow CLI to +convert wide strings to UTF-8 without losing information. + +```cpp +int wmain(int argc, wchar_t *argv[]) { + CLI::App app; + // ... + CLI11_PARSE(app, argc, argv); +} +``` + +5\. Retrieve arguments yourself by using Windows APIs like +[`CommandLineToArgvW`](https://learn.microsoft.com/en-us/windows/win32/api/shellapi/nf-shellapi-commandlinetoargvw) +and pass them to CLI. This is what the library is doing under the hood in +`CLI::argv()`. + +</p></details> +</br> The library provides functions to convert between UTF-8 and wide strings: @@ -1648,6 +1601,9 @@ GitBook][gitbook]. Several short examples of different features are included in the repository. A brief description of each is included here +- [arg_capture](https://github.com/CLIUtils/CLI11/blob/main/examples/arg_capture.cpp): + Example of capturing all remaining arguments after a specific option, using + subcommand and prefix_command() with an alias. - [callback_passthrough](https://github.com/CLIUtils/CLI11/blob/main/examples/callback_passthrough.cpp): Example of directly passing remaining arguments through to a callback function which generates a CLI11 application based on existing arguments. @@ -1727,75 +1683,107 @@ thanks to all the contributors <!-- prettier-ignore-start --> <!-- markdownlint-disable --> <table> - <tr> - <td align="center"><a href="http://iscinumpy.gitlab.io"><img src="https://avatars1.githubusercontent.com/u/4616906?v=4" width="100px;" alt=""/><br /><sub><b>Henry Schreiner</b></sub></a><br /><a href="https://github.com/CLIUtils/CLI11/issues?q=author%3Ahenryiii" title="Bug reports">🐛</a> <a href="https://github.com/CLIUtils/CLI11/commits?author=henryiii" title="Documentation">📖</a> <a href="https://github.com/CLIUtils/CLI11/commits?author=henryiii" title="Code">💻</a></td> - <td align="center"><a href="https://github.com/phlptp"><img src="https://avatars0.githubusercontent.com/u/20667153?v=4" width="100px;" alt=""/><br /><sub><b>Philip Top</b></sub></a><br /><a href="https://github.com/CLIUtils/CLI11/issues?q=author%3Aphlptp" title="Bug reports">🐛</a> <a href="https://github.com/CLIUtils/CLI11/commits?author=phlptp" title="Documentation">📖</a> <a href="https://github.com/CLIUtils/CLI11/commits?author=phlptp" title="Code">💻</a></td> - <td align="center"><a href="https://www.linkedin.com/in/cbachhuber/"><img src="https://avatars0.githubusercontent.com/u/27212661?v=4" width="100px;" alt=""/><br /><sub><b>Christoph Bachhuber</b></sub></a><br /><a href="#example-cbachhuber" title="Examples">💡</a> <a href="https://github.com/CLIUtils/CLI11/commits?author=cbachhuber" title="Code">💻</a></td> - <td align="center"><a href="https://lambdafu.net/"><img src="https://avatars1.githubusercontent.com/u/1138455?v=4" width="100px;" alt=""/><br /><sub><b>Marcus Brinkmann</b></sub></a><br /><a href="https://github.com/CLIUtils/CLI11/issues?q=author%3Alambdafu" title="Bug reports">🐛</a> <a href="https://github.com/CLIUtils/CLI11/commits?author=lambdafu" title="Code">💻</a></td> - <td align="center"><a href="https://github.com/SkyToGround"><img src="https://avatars1.githubusercontent.com/u/58835?v=4" width="100px;" alt=""/><br /><sub><b>Jonas Nilsson</b></sub></a><br /><a href="https://github.com/CLIUtils/CLI11/issues?q=author%3ASkyToGround" title="Bug reports">🐛</a> <a href="https://github.com/CLIUtils/CLI11/commits?author=SkyToGround" title="Code">💻</a></td> - <td align="center"><a href="https://github.com/dvj"><img src="https://avatars2.githubusercontent.com/u/77217?v=4" width="100px;" alt=""/><br /><sub><b>Doug Johnston</b></sub></a><br /><a href="https://github.com/CLIUtils/CLI11/issues?q=author%3Advj" title="Bug reports">🐛</a> <a href="https://github.com/CLIUtils/CLI11/commits?author=dvj" title="Code">💻</a></td> - <td align="center"><a href="http://lucas-czech.de"><img src="https://avatars0.githubusercontent.com/u/4741887?v=4" width="100px;" alt=""/><br /><sub><b>Lucas Czech</b></sub></a><br /><a href="https://github.com/CLIUtils/CLI11/issues?q=author%3Alczech" title="Bug reports">🐛</a> <a href="https://github.com/CLIUtils/CLI11/commits?author=lczech" title="Code">💻</a></td> - </tr> - <tr> - <td align="center"><a href="https://github.com/rafiw"><img src="https://avatars3.githubusercontent.com/u/3034707?v=4" width="100px;" alt=""/><br /><sub><b>Rafi Wiener</b></sub></a><br /><a href="https://github.com/CLIUtils/CLI11/issues?q=author%3Arafiw" title="Bug reports">🐛</a> <a href="https://github.com/CLIUtils/CLI11/commits?author=rafiw" title="Code">💻</a></td> - <td align="center"><a href="https://github.com/mensinda"><img src="https://avatars3.githubusercontent.com/u/3407462?v=4" width="100px;" alt=""/><br /><sub><b>Daniel Mensinger</b></sub></a><br /><a href="#platform-mensinda" title="Packaging/porting to new platform">📦</a></td> - <td align="center"><a href="https://github.com/jbriales"><img src="https://avatars1.githubusercontent.com/u/6850478?v=4" width="100px;" alt=""/><br /><sub><b>Jesus Briales</b></sub></a><br /><a href="https://github.com/CLIUtils/CLI11/commits?author=jbriales" title="Code">💻</a> <a href="https://github.com/CLIUtils/CLI11/issues?q=author%3Ajbriales" title="Bug reports">🐛</a></td> - <td align="center"><a href="https://seanfisk.com/"><img src="https://avatars0.githubusercontent.com/u/410322?v=4" width="100px;" alt=""/><br /><sub><b>Sean Fisk</b></sub></a><br /><a href="https://github.com/CLIUtils/CLI11/issues?q=author%3Aseanfisk" title="Bug reports">🐛</a> <a href="https://github.com/CLIUtils/CLI11/commits?author=seanfisk" title="Code">💻</a></td> - <td align="center"><a href="https://github.com/fpeng1985"><img src="https://avatars1.githubusercontent.com/u/87981?v=4" width="100px;" alt=""/><br /><sub><b>fpeng1985</b></sub></a><br /><a href="https://github.com/CLIUtils/CLI11/commits?author=fpeng1985" title="Code">💻</a></td> - <td align="center"><a href="https://github.com/almikhayl"><img src="https://avatars2.githubusercontent.com/u/6747040?v=4" width="100px;" alt=""/><br /><sub><b>almikhayl</b></sub></a><br /><a href="https://github.com/CLIUtils/CLI11/commits?author=almikhayl" title="Code">💻</a> <a href="#platform-almikhayl" title="Packaging/porting to new platform">📦</a></td> - <td align="center"><a href="https://github.com/andrew-hardin"><img src="https://avatars0.githubusercontent.com/u/16496326?v=4" width="100px;" alt=""/><br /><sub><b>Andrew Hardin</b></sub></a><br /><a href="https://github.com/CLIUtils/CLI11/commits?author=andrew-hardin" title="Code">💻</a></td> - </tr> - <tr> - <td align="center"><a href="https://github.com/SX91"><img src="https://avatars2.githubusercontent.com/u/754754?v=4" width="100px;" alt=""/><br /><sub><b>Anton</b></sub></a><br /><a href="https://github.com/CLIUtils/CLI11/commits?author=SX91" title="Code">💻</a></td> - <td align="center"><a href="https://github.com/helmesjo"><img src="https://avatars0.githubusercontent.com/u/2501070?v=4" width="100px;" alt=""/><br /><sub><b>Fred Helmesjö</b></sub></a><br /><a href="https://github.com/CLIUtils/CLI11/issues?q=author%3Ahelmesjo" title="Bug reports">🐛</a> <a href="https://github.com/CLIUtils/CLI11/commits?author=helmesjo" title="Code">💻</a></td> - <td align="center"><a href="https://github.com/skannan89"><img src="https://avatars0.githubusercontent.com/u/11918764?v=4" width="100px;" alt=""/><br /><sub><b>Kannan</b></sub></a><br /><a href="https://github.com/CLIUtils/CLI11/issues?q=author%3Askannan89" title="Bug reports">🐛</a> <a href="https://github.com/CLIUtils/CLI11/commits?author=skannan89" title="Code">💻</a></td> - <td align="center"><a href="http://himvis.com"><img src="https://avatars3.githubusercontent.com/u/465279?v=4" width="100px;" alt=""/><br /><sub><b>Khem Raj</b></sub></a><br /><a href="https://github.com/CLIUtils/CLI11/commits?author=kraj" title="Code">💻</a></td> - <td align="center"><a href="https://www.mogigoma.com/"><img src="https://avatars2.githubusercontent.com/u/130862?v=4" width="100px;" alt=""/><br /><sub><b>Mak Kolybabi</b></sub></a><br /><a href="https://github.com/CLIUtils/CLI11/commits?author=mogigoma" title="Documentation">📖</a></td> - <td align="center"><a href="http://msoeken.github.io"><img src="https://avatars0.githubusercontent.com/u/1998245?v=4" width="100px;" alt=""/><br /><sub><b>Mathias Soeken</b></sub></a><br /><a href="https://github.com/CLIUtils/CLI11/commits?author=msoeken" title="Documentation">📖</a></td> - <td align="center"><a href="https://github.com/nathanhourt"><img src="https://avatars2.githubusercontent.com/u/271977?v=4" width="100px;" alt=""/><br /><sub><b>Nathan Hourt</b></sub></a><br /><a href="https://github.com/CLIUtils/CLI11/issues?q=author%3Anathanhourt" title="Bug reports">🐛</a> <a href="https://github.com/CLIUtils/CLI11/commits?author=nathanhourt" title="Code">💻</a></td> - </tr> - <tr> - <td align="center"><a href="https://github.com/pleroux0"><img src="https://avatars2.githubusercontent.com/u/39619854?v=4" width="100px;" alt=""/><br /><sub><b>Paul le Roux</b></sub></a><br /><a href="https://github.com/CLIUtils/CLI11/commits?author=pleroux0" title="Code">💻</a> <a href="#platform-pleroux0" title="Packaging/porting to new platform">📦</a></td> - <td align="center"><a href="https://github.com/chfast"><img src="https://avatars1.githubusercontent.com/u/573380?v=4" width="100px;" alt=""/><br /><sub><b>Paweł Bylica</b></sub></a><br /><a href="#platform-chfast" title="Packaging/porting to new platform">📦</a></td> - <td align="center"><a href="https://github.com/peterazmanov"><img src="https://avatars0.githubusercontent.com/u/15322318?v=4" width="100px;" alt=""/><br /><sub><b>Peter Azmanov</b></sub></a><br /><a href="https://github.com/CLIUtils/CLI11/commits?author=peterazmanov" title="Code">💻</a></td> - <td align="center"><a href="https://github.com/delpinux"><img src="https://avatars0.githubusercontent.com/u/35096584?v=4" width="100px;" alt=""/><br /><sub><b>Stéphane Del Pino</b></sub></a><br /><a href="https://github.com/CLIUtils/CLI11/commits?author=delpinux" title="Code">💻</a></td> - <td align="center"><a href="https://github.com/metopa"><img src="https://avatars2.githubusercontent.com/u/3974178?v=4" width="100px;" alt=""/><br /><sub><b>Viacheslav Kroilov</b></sub></a><br /><a href="https://github.com/CLIUtils/CLI11/commits?author=metopa" title="Code">💻</a></td> - <td align="center"><a href="http://cs.odu.edu/~ctsolakis"><img src="https://avatars0.githubusercontent.com/u/6725596?v=4" width="100px;" alt=""/><br /><sub><b>christos</b></sub></a><br /><a href="https://github.com/CLIUtils/CLI11/commits?author=ChristosT" title="Code">💻</a></td> - <td align="center"><a href="https://github.com/deining"><img src="https://avatars3.githubusercontent.com/u/18169566?v=4" width="100px;" alt=""/><br /><sub><b>deining</b></sub></a><br /><a href="https://github.com/CLIUtils/CLI11/commits?author=deining" title="Documentation">📖</a></td> - </tr> - <tr> - <td align="center"><a href="https://github.com/elszon"><img src="https://avatars0.githubusercontent.com/u/2971495?v=4" width="100px;" alt=""/><br /><sub><b>elszon</b></sub></a><br /><a href="https://github.com/CLIUtils/CLI11/commits?author=elszon" title="Code">💻</a></td> - <td align="center"><a href="https://github.com/ncihnegn"><img src="https://avatars3.githubusercontent.com/u/12021721?v=4" width="100px;" alt=""/><br /><sub><b>ncihnegn</b></sub></a><br /><a href="https://github.com/CLIUtils/CLI11/commits?author=ncihnegn" title="Code">💻</a></td> - <td align="center"><a href="https://github.com/nurelin"><img src="https://avatars3.githubusercontent.com/u/5276274?v=4" width="100px;" alt=""/><br /><sub><b>nurelin</b></sub></a><br /><a href="https://github.com/CLIUtils/CLI11/commits?author=nurelin" title="Code">💻</a></td> - <td align="center"><a href="https://github.com/ryan4729"><img src="https://avatars3.githubusercontent.com/u/40183301?v=4" width="100px;" alt=""/><br /><sub><b>ryan4729</b></sub></a><br /><a href="https://github.com/CLIUtils/CLI11/commits?author=ryan4729" title="Tests">⚠️</a></td> - <td align="center"><a href="https://izzys.casa"><img src="https://avatars0.githubusercontent.com/u/63051?v=4" width="100px;" alt=""/><br /><sub><b>Isabella Muerte</b></sub></a><br /><a href="#platform-slurps-mad-rips" title="Packaging/porting to new platform">📦</a></td> - <td align="center"><a href="https://github.com/KOLANICH"><img src="https://avatars1.githubusercontent.com/u/240344?v=4" width="100px;" alt=""/><br /><sub><b>KOLANICH</b></sub></a><br /><a href="#platform-KOLANICH" title="Packaging/porting to new platform">📦</a></td> - <td align="center"><a href="https://github.com/jgerityneurala"><img src="https://avatars2.githubusercontent.com/u/57360646?v=4" width="100px;" alt=""/><br /><sub><b>James Gerity</b></sub></a><br /><a href="https://github.com/CLIUtils/CLI11/commits?author=jgerityneurala" title="Documentation">📖</a></td> - </tr> - <tr> - <td align="center"><a href="https://github.com/jsoref"><img src="https://avatars0.githubusercontent.com/u/2119212?v=4" width="100px;" alt=""/><br /><sub><b>Josh Soref</b></sub></a><br /><a href="#tool-jsoref" title="Tools">🔧</a></td> - <td align="center"><a href="https://github.com/geir-t"><img src="https://avatars3.githubusercontent.com/u/35292136?v=4" width="100px;" alt=""/><br /><sub><b>geir-t</b></sub></a><br /><a href="#platform-geir-t" title="Packaging/porting to new platform">📦</a></td> - <td align="center"><a href="https://ondrejcertik.com/"><img src="https://avatars3.githubusercontent.com/u/20568?v=4" width="100px;" alt=""/><br /><sub><b>Ondřej Čertík</b></sub></a><br /><a href="https://github.com/CLIUtils/CLI11/issues?q=author%3Acertik" title="Bug reports">🐛</a></td> - <td align="center"><a href="http://sam.hocevar.net/"><img src="https://avatars2.githubusercontent.com/u/245089?v=4" width="100px;" alt=""/><br /><sub><b>Sam Hocevar</b></sub></a><br /><a href="https://github.com/CLIUtils/CLI11/commits?author=samhocevar" title="Code">💻</a></td> - <td align="center"><a href="http://www.ratml.org/"><img src="https://avatars0.githubusercontent.com/u/1845039?v=4" width="100px;" alt=""/><br /><sub><b>Ryan Curtin</b></sub></a><br /><a href="https://github.com/CLIUtils/CLI11/commits?author=rcurtin" title="Documentation">📖</a></td> - <td align="center"><a href="https://mbh.sh"><img src="https://avatars3.githubusercontent.com/u/20403931?v=4" width="100px;" alt=""/><br /><sub><b>Michael Hall</b></sub></a><br /><a href="https://github.com/CLIUtils/CLI11/commits?author=mbhall88" title="Documentation">📖</a></td> - <td align="center"><a href="https://github.com/ferdymercury"><img src="https://avatars3.githubusercontent.com/u/10653970?v=4" width="100px;" alt=""/><br /><sub><b>ferdymercury</b></sub></a><br /><a href="https://github.com/CLIUtils/CLI11/commits?author=ferdymercury" title="Documentation">📖</a></td> - </tr> - <tr> - <td align="center"><a href="https://github.com/jakoblover"><img src="https://avatars0.githubusercontent.com/u/14160441?v=4" width="100px;" alt=""/><br /><sub><b>Jakob Lover</b></sub></a><br /><a href="https://github.com/CLIUtils/CLI11/commits?author=jakoblover" title="Code">💻</a></td> - <td align="center"><a href="https://github.com/ZeeD26"><img src="https://avatars2.githubusercontent.com/u/2487468?v=4" width="100px;" alt=""/><br /><sub><b>Dominik Steinberger</b></sub></a><br /><a href="https://github.com/CLIUtils/CLI11/commits?author=ZeeD26" title="Code">💻</a></td> - <td align="center"><a href="https://github.com/dfleury2"><img src="https://avatars1.githubusercontent.com/u/4805384?v=4" width="100px;" alt=""/><br /><sub><b>D. Fleury</b></sub></a><br /><a href="https://github.com/CLIUtils/CLI11/commits?author=dfleury2" title="Code">💻</a></td> - <td align="center"><a href="https://github.com/dbarowy"><img src="https://avatars3.githubusercontent.com/u/573142?v=4" width="100px;" alt=""/><br /><sub><b>Dan Barowy</b></sub></a><br /><a href="https://github.com/CLIUtils/CLI11/commits?author=dbarowy" title="Documentation">📖</a></td> - <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> + <tbody> + <tr> + <td align="center" valign="top" width="14.28%"><a href="https://profiles.sussex.ac.uk/p281168-alex-dewar/publications"><img src="https://avatars.githubusercontent.com/u/23149834?v=4?s=100" width="100px;" alt="Alex Dewar"/><br /><sub><b>Alex Dewar</b></sub></a><br /><a href="https://github.com/CLIUtils/CLI11/commits?author=alexdewar" title="Code">💻</a></td> + <td align="center" valign="top" width="14.28%"><a href="https://github.com/andrew-hardin"><img src="https://avatars0.githubusercontent.com/u/16496326?v=4?s=100" width="100px;" alt="Andrew Hardin"/><br /><sub><b>Andrew Hardin</b></sub></a><br /><a href="https://github.com/CLIUtils/CLI11/commits?author=andrew-hardin" title="Code">💻</a></td> + <td align="center" valign="top" width="14.28%"><a href="https://github.com/andreasxp"><img src="https://avatars.githubusercontent.com/u/28830446?v=4?s=100" width="100px;" alt="Andrey Zhukov"/><br /><sub><b>Andrey Zhukov</b></sub></a><br /><a href="https://github.com/CLIUtils/CLI11/commits?author=andreasxp" title="Code">💻</a></td> + <td align="center" valign="top" width="14.28%"><a href="https://github.com/SX91"><img src="https://avatars2.githubusercontent.com/u/754754?v=4?s=100" width="100px;" alt="Anton"/><br /><sub><b>Anton</b></sub></a><br /><a href="https://github.com/CLIUtils/CLI11/commits?author=SX91" title="Code">💻</a></td> + <td align="center" valign="top" width="14.28%"><a href="https://github.com/trokhymchuk"><img src="https://avatars.githubusercontent.com/u/66204814?v=4?s=100" width="100px;" alt="Artem Trokhymchuk "/><br /><sub><b>Artem Trokhymchuk </b></sub></a><br /><a href="https://github.com/CLIUtils/CLI11/commits?author=trokhymchuk" title="Code">💻</a></td> + <td align="center" valign="top" width="14.28%"><a href="https://github.com/BenjaminBeichler"><img src="https://avatars.githubusercontent.com/u/1441492?v=4?s=100" width="100px;" alt="Benjamin Beichler"/><br /><sub><b>Benjamin Beichler</b></sub></a><br /><a href="https://github.com/CLIUtils/CLI11/commits?author=BenjaminBeichler" title="Code">💻</a></td> + <td align="center" valign="top" width="14.28%"><a href="https://www.linkedin.com/in/cbachhuber/"><img src="https://avatars0.githubusercontent.com/u/27212661?v=4?s=100" width="100px;" alt="Christoph Bachhuber"/><br /><sub><b>Christoph Bachhuber</b></sub></a><br /><a href="#example-cbachhuber" title="Examples">💡</a> <a href="https://github.com/CLIUtils/CLI11/commits?author=cbachhuber" title="Code">💻</a></td> + </tr> + <tr> + <td align="center" valign="top" width="14.28%"><a href="https://github.com/dfleury2"><img src="https://avatars1.githubusercontent.com/u/4805384?v=4?s=100" width="100px;" alt="D. Fleury"/><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" valign="top" width="14.28%"><a href="https://github.com/dbarowy"><img src="https://avatars3.githubusercontent.com/u/573142?v=4?s=100" width="100px;" alt="Dan Barowy"/><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" valign="top" width="14.28%"><a href="https://github.com/mensinda"><img src="https://avatars3.githubusercontent.com/u/3407462?v=4?s=100" width="100px;" alt="Daniel Mensinger"/><br /><sub><b>Daniel Mensinger</b></sub></a><br /><a href="#platform-mensinda" title="Packaging/porting to new platform">📦</a></td> + <td align="center" valign="top" width="14.28%"><a href="https://github.com/DarkWingMcQuack"><img src="https://avatars.githubusercontent.com/u/38857302?v=4?s=100" width="100px;" alt="DarkWingMcQuack"/><br /><sub><b>DarkWingMcQuack</b></sub></a><br /><a href="https://github.com/CLIUtils/CLI11/commits?author=DarkWingMcQuack" title="Code">💻</a></td> + <td align="center" valign="top" width="14.28%"><a href="https://github.com/ZeeD26"><img src="https://avatars2.githubusercontent.com/u/2487468?v=4?s=100" width="100px;" alt="Dominik Steinberger"/><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" valign="top" width="14.28%"><a href="https://github.com/dvj"><img src="https://avatars2.githubusercontent.com/u/77217?v=4?s=100" width="100px;" alt="Doug Johnston"/><br /><sub><b>Doug Johnston</b></sub></a><br /><a href="https://github.com/CLIUtils/CLI11/issues?q=author%3Advj" title="Bug reports">🐛</a> <a href="https://github.com/CLIUtils/CLI11/commits?author=dvj" title="Code">💻</a></td> + <td align="center" valign="top" width="14.28%"><a href="https://github.com/eli-schwartz"><img src="https://avatars.githubusercontent.com/u/6551424?v=4?s=100" width="100px;" alt="Eli Schwartz"/><br /><sub><b>Eli Schwartz</b></sub></a><br /><a href="https://github.com/CLIUtils/CLI11/commits?author=eli-schwartz" title="Code">💻</a></td> + </tr> + <tr> + <td align="center" valign="top" width="14.28%"><a href="https://github.com/helmesjo"><img src="https://avatars0.githubusercontent.com/u/2501070?v=4?s=100" width="100px;" alt="Fred Helmesjö"/><br /><sub><b>Fred Helmesjö</b></sub></a><br /><a href="https://github.com/CLIUtils/CLI11/issues?q=author%3Ahelmesjo" title="Bug reports">🐛</a> <a href="https://github.com/CLIUtils/CLI11/commits?author=helmesjo" title="Code">💻</a></td> + <td align="center" valign="top" width="14.28%"><a href="http://iscinumpy.gitlab.io"><img src="https://avatars1.githubusercontent.com/u/4616906?v=4?s=100" width="100px;" alt="Henry Schreiner"/><br /><sub><b>Henry Schreiner</b></sub></a><br /><a href="https://github.com/CLIUtils/CLI11/issues?q=author%3Ahenryiii" title="Bug reports">🐛</a> <a href="https://github.com/CLIUtils/CLI11/commits?author=henryiii" title="Documentation">📖</a> <a href="https://github.com/CLIUtils/CLI11/commits?author=henryiii" title="Code">💻</a></td> + <td align="center" valign="top" width="14.28%"><a href="https://izzys.casa"><img src="https://avatars0.githubusercontent.com/u/63051?v=4?s=100" width="100px;" alt="Isabella Muerte"/><br /><sub><b>Isabella Muerte</b></sub></a><br /><a href="#platform-slurps-mad-rips" title="Packaging/porting to new platform">📦</a></td> + <td align="center" valign="top" width="14.28%"><a href="https://izzys.casa/"><img src="https://avatars.githubusercontent.com/u/63051?v=4?s=100" width="100px;" alt="Izzy Muerte"/><br /><sub><b>Izzy Muerte</b></sub></a><br /><a href="https://github.com/CLIUtils/CLI11/commits?author=bruxisma" title="Code">💻</a></td> + <td align="center" valign="top" width="14.28%"><a href="https://github.com/jakoblover"><img src="https://avatars0.githubusercontent.com/u/14160441?v=4?s=100" width="100px;" alt="Jakob Lover"/><br /><sub><b>Jakob Lover</b></sub></a><br /><a href="https://github.com/CLIUtils/CLI11/commits?author=jakoblover" title="Code">💻</a></td> + <td align="center" valign="top" width="14.28%"><a href="https://github.com/jgerityneurala"><img src="https://avatars2.githubusercontent.com/u/57360646?v=4?s=100" width="100px;" alt="James Gerity"/><br /><sub><b>James Gerity</b></sub></a><br /><a href="https://github.com/CLIUtils/CLI11/commits?author=jgerityneurala" title="Documentation">📖</a></td> + <td align="center" valign="top" width="14.28%"><a href="https://github.com/jbriales"><img src="https://avatars1.githubusercontent.com/u/6850478?v=4?s=100" width="100px;" alt="Jesus Briales"/><br /><sub><b>Jesus Briales</b></sub></a><br /><a href="https://github.com/CLIUtils/CLI11/commits?author=jbriales" title="Code">💻</a> <a href="https://github.com/CLIUtils/CLI11/issues?q=author%3Ajbriales" title="Bug reports">🐛</a></td> + </tr> + <tr> + <td align="center" valign="top" width="14.28%"><a href="https://github.com/SkyToGround"><img src="https://avatars1.githubusercontent.com/u/58835?v=4?s=100" width="100px;" alt="Jonas Nilsson"/><br /><sub><b>Jonas Nilsson</b></sub></a><br /><a href="https://github.com/CLIUtils/CLI11/issues?q=author%3ASkyToGround" title="Bug reports">🐛</a> <a href="https://github.com/CLIUtils/CLI11/commits?author=SkyToGround" title="Code">💻</a></td> + <td align="center" valign="top" width="14.28%"><a href="https://github.com/j-rivero"><img src="https://avatars.githubusercontent.com/u/2098802?v=4?s=100" width="100px;" alt="Jose Luis Rivero"/><br /><sub><b>Jose Luis Rivero</b></sub></a><br /><a href="https://github.com/CLIUtils/CLI11/commits?author=j-rivero" title="Code">💻</a></td> + <td align="center" valign="top" width="14.28%"><a href="https://github.com/jsoref"><img src="https://avatars0.githubusercontent.com/u/2119212?v=4?s=100" width="100px;" alt="Josh Soref"/><br /><sub><b>Josh Soref</b></sub></a><br /><a href="#tool-jsoref" title="Tools">🔧</a></td> + <td align="center" valign="top" width="14.28%"><a href="https://github.com/KOLANICH"><img src="https://avatars1.githubusercontent.com/u/240344?v=4?s=100" width="100px;" alt="KOLANICH"/><br /><sub><b>KOLANICH</b></sub></a><br /><a href="#platform-KOLANICH" title="Packaging/porting to new platform">📦</a></td> + <td align="center" valign="top" width="14.28%"><a href="https://github.com/skannan89"><img src="https://avatars0.githubusercontent.com/u/11918764?v=4?s=100" width="100px;" alt="Kannan"/><br /><sub><b>Kannan</b></sub></a><br /><a href="https://github.com/CLIUtils/CLI11/issues?q=author%3Askannan89" title="Bug reports">🐛</a> <a href="https://github.com/CLIUtils/CLI11/commits?author=skannan89" title="Code">💻</a></td> + <td align="center" valign="top" width="14.28%"><a href="http://himvis.com"><img src="https://avatars3.githubusercontent.com/u/465279?v=4?s=100" width="100px;" alt="Khem Raj"/><br /><sub><b>Khem Raj</b></sub></a><br /><a href="https://github.com/CLIUtils/CLI11/commits?author=kraj" title="Code">💻</a></td> + <td align="center" valign="top" width="14.28%"><a href="https://github.com/looopTools"><img src="https://avatars.githubusercontent.com/u/1943536?v=4?s=100" width="100px;" alt="Lars Nielsen"/><br /><sub><b>Lars Nielsen</b></sub></a><br /><a href="https://github.com/CLIUtils/CLI11/commits?author=looopTools" title="Code">💻</a></td> + </tr> + <tr> + <td align="center" valign="top" width="14.28%"><a href="http://lucas-czech.de"><img src="https://avatars0.githubusercontent.com/u/4741887?v=4?s=100" width="100px;" alt="Lucas Czech"/><br /><sub><b>Lucas Czech</b></sub></a><br /><a href="https://github.com/CLIUtils/CLI11/issues?q=author%3Alczech" title="Bug reports">🐛</a> <a href="https://github.com/CLIUtils/CLI11/commits?author=lczech" title="Code">💻</a></td> + <td align="center" valign="top" width="14.28%"><a href="https://www.mogigoma.com/"><img src="https://avatars2.githubusercontent.com/u/130862?v=4?s=100" width="100px;" alt="Mak Kolybabi"/><br /><sub><b>Mak Kolybabi</b></sub></a><br /><a href="https://github.com/CLIUtils/CLI11/commits?author=mogigoma" title="Documentation">📖</a></td> + <td align="center" valign="top" width="14.28%"><a href="https://github.com/cetius"><img src="https://avatars.githubusercontent.com/u/6552472?v=4?s=100" width="100px;" alt="Marcin Ropa"/><br /><sub><b>Marcin Ropa</b></sub></a><br /><a href="https://github.com/CLIUtils/CLI11/commits?author=cetius" title="Code">💻</a></td> + <td align="center" valign="top" width="14.28%"><a href="https://lambdafu.net/"><img src="https://avatars1.githubusercontent.com/u/1138455?v=4?s=100" width="100px;" alt="Marcus Brinkmann"/><br /><sub><b>Marcus Brinkmann</b></sub></a><br /><a href="https://github.com/CLIUtils/CLI11/issues?q=author%3Alambdafu" title="Bug reports">🐛</a> <a href="https://github.com/CLIUtils/CLI11/commits?author=lambdafu" title="Code">💻</a></td> + <td align="center" valign="top" width="14.28%"><a href="http://msoeken.github.io"><img src="https://avatars0.githubusercontent.com/u/1998245?v=4?s=100" width="100px;" alt="Mathias Soeken"/><br /><sub><b>Mathias Soeken</b></sub></a><br /><a href="https://github.com/CLIUtils/CLI11/commits?author=msoeken" title="Documentation">📖</a></td> + <td align="center" valign="top" width="14.28%"><a href="https://www.mmmccormick.com/"><img src="https://avatars.githubusercontent.com/u/25432?v=4?s=100" width="100px;" alt="Matt McCormick"/><br /><sub><b>Matt McCormick</b></sub></a><br /><a href="https://github.com/CLIUtils/CLI11/commits?author=thewtex" title="Code">💻</a></td> + <td align="center" valign="top" width="14.28%"><a href="https://github.com/AnticliMaxtic"><img src="https://avatars.githubusercontent.com/u/43995389?v=4?s=100" width="100px;" alt="Max"/><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" valign="top" width="14.28%"><a href="https://mbh.sh"><img src="https://avatars3.githubusercontent.com/u/20403931?v=4?s=100" width="100px;" alt="Michael Hall"/><br /><sub><b>Michael Hall</b></sub></a><br /><a href="https://github.com/CLIUtils/CLI11/commits?author=mbhall88" title="Documentation">📖</a></td> + <td align="center" valign="top" width="14.28%"><a href="https://github.com/nathanhourt"><img src="https://avatars2.githubusercontent.com/u/271977?v=4?s=100" width="100px;" alt="Nathan Hourt"/><br /><sub><b>Nathan Hourt</b></sub></a><br /><a href="https://github.com/CLIUtils/CLI11/issues?q=author%3Anathanhourt" title="Bug reports">🐛</a> <a href="https://github.com/CLIUtils/CLI11/commits?author=nathanhourt" title="Code">💻</a></td> + <td align="center" valign="top" width="14.28%"><a href="https://github.com/nathanielhourt"><img src="https://avatars.githubusercontent.com/u/271977?v=4?s=100" width="100px;" alt="Nathaniel Hourt"/><br /><sub><b>Nathaniel Hourt</b></sub></a><br /><a href="https://github.com/CLIUtils/CLI11/commits?author=nathanielhourt" title="Code">💻</a></td> + <td align="center" valign="top" width="14.28%"><a href="https://github.com/paddy-hack"><img src="https://avatars.githubusercontent.com/u/6804372?v=4?s=100" width="100px;" alt="Olaf Meeuwissen"/><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" valign="top" width="14.28%"><a href="https://ondrejcertik.com/"><img src="https://avatars3.githubusercontent.com/u/20568?v=4?s=100" width="100px;" alt="Ondřej Čertík"/><br /><sub><b>Ondřej Čertík</b></sub></a><br /><a href="https://github.com/CLIUtils/CLI11/issues?q=author%3Acertik" title="Bug reports">🐛</a></td> + <td align="center" valign="top" width="14.28%"><a href="https://github.com/pleroux0"><img src="https://avatars2.githubusercontent.com/u/39619854?v=4?s=100" width="100px;" alt="Paul le Roux"/><br /><sub><b>Paul le Roux</b></sub></a><br /><a href="https://github.com/CLIUtils/CLI11/commits?author=pleroux0" title="Code">💻</a> <a href="#platform-pleroux0" title="Packaging/porting to new platform">📦</a></td> + <td align="center" valign="top" width="14.28%"><a href="https://github.com/chfast"><img src="https://avatars1.githubusercontent.com/u/573380?v=4?s=100" width="100px;" alt="Paweł Bylica"/><br /><sub><b>Paweł Bylica</b></sub></a><br /><a href="#platform-chfast" title="Packaging/porting to new platform">📦</a></td> + </tr> + <tr> + <td align="center" valign="top" width="14.28%"><a href="https://github.com/PeteAudinate"><img src="https://avatars.githubusercontent.com/u/99274874?v=4?s=100" width="100px;" alt="PeteAudinate"/><br /><sub><b>PeteAudinate</b></sub></a><br /><a href="https://github.com/CLIUtils/CLI11/commits?author=PeteAudinate" title="Code">💻</a></td> + <td align="center" valign="top" width="14.28%"><a href="https://github.com/peterazmanov"><img src="https://avatars0.githubusercontent.com/u/15322318?v=4?s=100" width="100px;" alt="Peter Azmanov"/><br /><sub><b>Peter Azmanov</b></sub></a><br /><a href="https://github.com/CLIUtils/CLI11/commits?author=peterazmanov" title="Code">💻</a></td> + <td align="center" valign="top" width="14.28%"><a href="https://github.com/peterh"><img src="https://avatars.githubusercontent.com/u/79339?v=4?s=100" width="100px;" alt="Peter Harris"/><br /><sub><b>Peter Harris</b></sub></a><br /><a href="https://github.com/CLIUtils/CLI11/commits?author=peterh" title="Code">💻</a></td> + <td align="center" valign="top" width="14.28%"><a href="http://ptheywood.uk/"><img src="https://avatars.githubusercontent.com/u/628937?v=4?s=100" width="100px;" alt="Peter Heywood"/><br /><sub><b>Peter Heywood</b></sub></a><br /><a href="https://github.com/CLIUtils/CLI11/commits?author=ptheywood" title="Code">💻</a></td> + <td align="center" valign="top" width="14.28%"><a href="https://github.com/phlptp"><img src="https://avatars0.githubusercontent.com/u/20667153?v=4?s=100" width="100px;" alt="Philip Top"/><br /><sub><b>Philip Top</b></sub></a><br /><a href="https://github.com/CLIUtils/CLI11/issues?q=author%3Aphlptp" title="Bug reports">🐛</a> <a href="https://github.com/CLIUtils/CLI11/commits?author=phlptp" title="Documentation">📖</a> <a href="https://github.com/CLIUtils/CLI11/commits?author=phlptp" title="Code">💻</a></td> + <td align="center" valign="top" width="14.28%"><a href="https://github.com/rafiw"><img src="https://avatars3.githubusercontent.com/u/3034707?v=4?s=100" width="100px;" alt="Rafi Wiener"/><br /><sub><b>Rafi Wiener</b></sub></a><br /><a href="https://github.com/CLIUtils/CLI11/issues?q=author%3Arafiw" title="Bug reports">🐛</a> <a href="https://github.com/CLIUtils/CLI11/commits?author=rafiw" title="Code">💻</a></td> + <td align="center" valign="top" width="14.28%"><a href="https://github.com/RangeMachine"><img src="https://avatars.githubusercontent.com/u/11577601?v=4?s=100" width="100px;" alt="RangeMachine"/><br /><sub><b>RangeMachine</b></sub></a><br /><a href="https://github.com/CLIUtils/CLI11/commits?author=RangeMachine" title="Code">💻</a></td> + </tr> + <tr> + <td align="center" valign="top" width="14.28%"><a href="https://github.com/Krzmbrzl"><img src="https://avatars.githubusercontent.com/u/12751591?v=4?s=100" width="100px;" alt="Robert Adam"/><br /><sub><b>Robert Adam</b></sub></a><br /><a href="https://github.com/CLIUtils/CLI11/commits?author=Krzmbrzl" title="Code">💻</a></td> + <td align="center" valign="top" width="14.28%"><a href="http://www.ratml.org/"><img src="https://avatars0.githubusercontent.com/u/1845039?v=4?s=100" width="100px;" alt="Ryan Curtin"/><br /><sub><b>Ryan Curtin</b></sub></a><br /><a href="https://github.com/CLIUtils/CLI11/commits?author=rcurtin" title="Documentation">📖</a></td> + <td align="center" valign="top" width="14.28%"><a href="https://github.com/SherlockInSpace"><img src="https://avatars.githubusercontent.com/u/5507786?v=4?s=100" width="100px;" alt="Ryan Sherlock"/><br /><sub><b>Ryan Sherlock</b></sub></a><br /><a href="https://github.com/CLIUtils/CLI11/commits?author=SherlockInSpace" title="Code">💻</a></td> + <td align="center" valign="top" width="14.28%"><a href="http://sam.hocevar.net/"><img src="https://avatars2.githubusercontent.com/u/245089?v=4?s=100" width="100px;" alt="Sam Hocevar"/><br /><sub><b>Sam Hocevar</b></sub></a><br /><a href="https://github.com/CLIUtils/CLI11/commits?author=samhocevar" title="Code">💻</a></td> + <td align="center" valign="top" width="14.28%"><a href="https://seanfisk.com/"><img src="https://avatars0.githubusercontent.com/u/410322?v=4?s=100" width="100px;" alt="Sean Fisk"/><br /><sub><b>Sean Fisk</b></sub></a><br /><a href="https://github.com/CLIUtils/CLI11/issues?q=author%3Aseanfisk" title="Bug reports">🐛</a> <a href="https://github.com/CLIUtils/CLI11/commits?author=seanfisk" title="Code">💻</a></td> + <td align="center" valign="top" width="14.28%"><a href="https://github.com/delpinux"><img src="https://avatars0.githubusercontent.com/u/35096584?v=4?s=100" width="100px;" alt="Stéphane Del Pino"/><br /><sub><b>Stéphane Del Pino</b></sub></a><br /><a href="https://github.com/CLIUtils/CLI11/commits?author=delpinux" title="Code">💻</a></td> + <td align="center" valign="top" width="14.28%"><a href="https://github.com/metopa"><img src="https://avatars2.githubusercontent.com/u/3974178?v=4?s=100" width="100px;" alt="Viacheslav Kroilov"/><br /><sub><b>Viacheslav Kroilov</b></sub></a><br /><a href="https://github.com/CLIUtils/CLI11/commits?author=metopa" title="Code">💻</a></td> + </tr> + <tr> + <td align="center" valign="top" width="14.28%"><a href="https://github.com/VolkerChristian"><img src="https://avatars.githubusercontent.com/u/18554540?v=4?s=100" width="100px;" alt="Volker Christian"/><br /><sub><b>Volker Christian</b></sub></a><br /><a href="https://github.com/CLIUtils/CLI11/commits?author=VolkerChristian" title="Code">💻</a></td> + <td align="center" valign="top" width="14.28%"><a href="https://github.com/almikhayl"><img src="https://avatars2.githubusercontent.com/u/6747040?v=4?s=100" width="100px;" alt="almikhayl"/><br /><sub><b>almikhayl</b></sub></a><br /><a href="https://github.com/CLIUtils/CLI11/commits?author=almikhayl" title="Code">💻</a> <a href="#platform-almikhayl" title="Packaging/porting to new platform">📦</a></td> + <td align="center" valign="top" width="14.28%"><a href="https://github.com/ayum"><img src="https://avatars.githubusercontent.com/u/6747040?v=4?s=100" width="100px;" alt="ayum"/><br /><sub><b>ayum</b></sub></a><br /><a href="https://github.com/CLIUtils/CLI11/commits?author=ayum" title="Code">💻</a></td> + <td align="center" valign="top" width="14.28%"><a href="https://github.com/captainurist"><img src="https://avatars.githubusercontent.com/u/73941350?v=4?s=100" width="100px;" alt="captainurist"/><br /><sub><b>captainurist</b></sub></a><br /><a href="https://github.com/CLIUtils/CLI11/commits?author=captainurist" title="Code">💻</a></td> + <td align="center" valign="top" width="14.28%"><a href="http://cs.odu.edu/~ctsolakis"><img src="https://avatars0.githubusercontent.com/u/6725596?v=4?s=100" width="100px;" alt="christos"/><br /><sub><b>christos</b></sub></a><br /><a href="https://github.com/CLIUtils/CLI11/commits?author=ChristosT" title="Code">💻</a></td> + <td align="center" valign="top" width="14.28%"><a href="https://github.com/deining"><img src="https://avatars3.githubusercontent.com/u/18169566?v=4?s=100" width="100px;" alt="deining"/><br /><sub><b>deining</b></sub></a><br /><a href="https://github.com/CLIUtils/CLI11/commits?author=deining" title="Documentation">📖</a></td> + <td align="center" valign="top" width="14.28%"><a href="https://github.com/dherrera-fb"><img src="https://avatars.githubusercontent.com/u/89840711?v=4?s=100" width="100px;" alt="dherrera-fb"/><br /><sub><b>dherrera-fb</b></sub></a><br /><a href="https://github.com/CLIUtils/CLI11/commits?author=dherrera-fb" title="Code">💻</a></td> + </tr> + <tr> + <td align="center" valign="top" width="14.28%"><a href="https://github.com/djerius"><img src="https://avatars.githubusercontent.com/u/196875?v=4?s=100" width="100px;" alt="djerius"/><br /><sub><b>djerius</b></sub></a><br /><a href="https://github.com/CLIUtils/CLI11/commits?author=djerius" title="Code">💻</a></td> + <td align="center" valign="top" width="14.28%"><a href="https://github.com/dryleev"><img src="https://avatars.githubusercontent.com/u/83670813?v=4?s=100" width="100px;" alt="dryleev"/><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" valign="top" width="14.28%"><a href="https://github.com/elszon"><img src="https://avatars0.githubusercontent.com/u/2971495?v=4?s=100" width="100px;" alt="elszon"/><br /><sub><b>elszon</b></sub></a><br /><a href="https://github.com/CLIUtils/CLI11/commits?author=elszon" title="Code">💻</a></td> + <td align="center" valign="top" width="14.28%"><a href="https://github.com/ferdymercury"><img src="https://avatars3.githubusercontent.com/u/10653970?v=4?s=100" width="100px;" alt="ferdymercury"/><br /><sub><b>ferdymercury</b></sub></a><br /><a href="https://github.com/CLIUtils/CLI11/commits?author=ferdymercury" title="Documentation">📖</a></td> + <td align="center" valign="top" width="14.28%"><a href="https://github.com/fpeng1985"><img src="https://avatars1.githubusercontent.com/u/87981?v=4?s=100" width="100px;" alt="fpeng1985"/><br /><sub><b>fpeng1985</b></sub></a><br /><a href="https://github.com/CLIUtils/CLI11/commits?author=fpeng1985" title="Code">💻</a></td> + <td align="center" valign="top" width="14.28%"><a href="https://github.com/geir-t"><img src="https://avatars3.githubusercontent.com/u/35292136?v=4?s=100" width="100px;" alt="geir-t"/><br /><sub><b>geir-t</b></sub></a><br /><a href="#platform-geir-t" title="Packaging/porting to new platform">📦</a></td> + <td align="center" valign="top" width="14.28%"><a href="https://github.com/ncihnegn"><img src="https://avatars3.githubusercontent.com/u/12021721?v=4?s=100" width="100px;" alt="ncihnegn"/><br /><sub><b>ncihnegn</b></sub></a><br /><a href="https://github.com/CLIUtils/CLI11/commits?author=ncihnegn" title="Code">💻</a></td> + </tr> + <tr> + <td align="center" valign="top" width="14.28%"><a href="https://github.com/nurelin"><img src="https://avatars3.githubusercontent.com/u/5276274?v=4?s=100" width="100px;" alt="nurelin"/><br /><sub><b>nurelin</b></sub></a><br /><a href="https://github.com/CLIUtils/CLI11/commits?author=nurelin" title="Code">💻</a></td> + <td align="center" valign="top" width="14.28%"><a href="http://polistern.i2p/"><img src="https://avatars.githubusercontent.com/u/55511995?v=4?s=100" width="100px;" alt="polistern"/><br /><sub><b>polistern</b></sub></a><br /><a href="https://github.com/CLIUtils/CLI11/commits?author=polistern" title="Code">💻</a></td> + <td align="center" valign="top" width="14.28%"><a href="https://github.com/ryan4729"><img src="https://avatars3.githubusercontent.com/u/40183301?v=4?s=100" width="100px;" alt="ryan4729"/><br /><sub><b>ryan4729</b></sub></a><br /><a href="https://github.com/CLIUtils/CLI11/commits?author=ryan4729" title="Tests">⚠️</a></td> + <td align="center" valign="top" width="14.28%"><a href="https://github.com/shameekganguly"><img src="https://avatars.githubusercontent.com/u/2412842?v=4?s=100" width="100px;" alt="shameekganguly"/><br /><sub><b>shameekganguly</b></sub></a><br /><a href="https://github.com/CLIUtils/CLI11/commits?author=shameekganguly" title="Code">💻</a></td> + </tr> + </tbody> </table> -<!-- markdownlint-enable --> +<!-- markdownlint-restore --> <!-- prettier-ignore-end --> <!-- ALL-CONTRIBUTORS-LIST:END --> @@ -1890,3 +1878,4 @@ try! Feedback is always welcome. [argparse]: https://github.com/p-ranav/argparse [toml]: https://toml.io [lyra]: https://github.com/bfgroup/Lyra +[installation]: https://cliutils.github.io/CLI11/book/chapters/installation.html diff --git a/packages/CLI11/azure-pipelines.yml b/packages/CLI11/azure-pipelines.yml index 1bb4d0771abf016d442eaa33300dc31698ca9825..647c7982b62a7cdff5e24f74c7acc57ae804d3b4 100644 --- a/packages/CLI11/azure-pipelines.yml +++ b/packages/CLI11/azure-pipelines.yml @@ -9,7 +9,6 @@ trigger: pr: - main - - "v*" variables: cli11.single: ON @@ -28,6 +27,20 @@ jobs: - bash: cpplint --counting=detailed --recursive examples include/CLI tests displayName: Checking against google style guide + - job: build_only + strategy: + matrix: + visual_studio_arm64: + vmImage: "windows-2022" + cli11.std: 17 + cli11.build_type: Debug + cli11.options: -G "Visual Studio 17 2022" -A ARM64 + pool: + vmImage: $(vmImage) + + steps: + - template: .ci/azure-build.yml + - job: Native strategy: matrix: @@ -137,3 +150,28 @@ jobs: - template: .ci/azure-cmake.yml - template: .ci/azure-build.yml - template: .ci/azure-test.yml + + - job: Docker_new + variables: + cli11.single: OFF + pool: + vmImage: "ubuntu-latest" + strategy: + matrix: + gcc13: + containerImage: gcc:13 + cli11.std: 17 + cli11.options: -DCMAKE_CXX_FLAGS="-Wstrict-overflow=5" + gcc12: + containerImage: gcc:12 + cli11.std: 20 + cli11.options: -DCMAKE_CXX_FLAGS="-Wredundant-decls -Wconversion" + clang17_20: + containerImage: silkeh/clang:17 + cli11.std: 23 + cli11.options: -DCMAKE_CXX_FLAGS=-std=c++23 + container: $[ variables['containerImage'] ] + steps: + - template: .ci/azure-cmake-new.yml + - template: .ci/azure-build.yml + - template: .ci/azure-test.yml diff --git a/packages/CLI11/book/chapters/config.md b/packages/CLI11/book/chapters/config.md index 30ca48effb6a5370bd6bdba1fc73e521b5737339..54f8661390c139ee02fb23dd4d1b5ad047233ae6 100644 --- a/packages/CLI11/book/chapters/config.md +++ b/packages/CLI11/book/chapters/config.md @@ -8,7 +8,9 @@ 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. +the file is not found and this is set to true. The option pointer returned by +`set_config` is the same type as returned by `add_option` and all modifiers +including validators, and checks are valid. ### Adding a default path @@ -98,12 +100,29 @@ If it is needed to get the configuration file name used this can be obtained via `app["--config"]->as<std::string>()` assuming `--config` was the configuration option name. +### Order of precedence + +By default if multiple configuration files are given they are read in reverse +order. With the last one given taking precedence over the earlier ones. This +behavior can be changed through the `multi_option_policy`. For example: + +```cpp +app.set_config("--config") + ->multi_option_policy(CLI::MultiOptionPolicy::TakeAll); +``` + +will read the files in the order given, which may be useful in some +circumstances. Using `CLI::MultiOptionPolicy::TakeLast` would work similarly +getting the last `N` files given. The default policy for config options is +`CLI::MultiOptionPolicy::Reverse` which takes the last expected `N` and reverses +them so the last option given is given precedence. + ## Configure file format Here is an example configuration file, in [TOML](https://github.com/toml-lang/toml) format: -```ini +```toml # Comments are supported, using a # # The default section is [default], case insensitive @@ -148,6 +167,61 @@ The main differences are in vector notation and comment character. Note: CLI11 is not a full TOML parser as it just reads values as strings. It is possible (but not recommended) to mix notation. +### Multi-line strings + +The default config file parser supports multi-line strings like the toml +standard [TOML](https://toml.io/en/). It also supports multiline comments like +python doc strings. + +```toml +""" +this is a multine +comment +""" + +""" this is also +a multiline comment""" + +''' and so is +this +''' + +value = 1 +str = """ +this is a multiline string value +the first \n is removed and so is the last +""" + +str2 = ''' this is also a mu- +ltiline value ''' + +str3 = """\ + a line continuation \ + will skip \ + all white space between the '\' \ + and the next non-whitespace character \ + making this into a single line +""" + +``` + +The key is that the closing of the multiline string must be at the end of a line +and match the starting 3 quote sequence. Multiline sequences using `"""` allow +escape sequences. Following [TOML](https://toml.io/en/v1.0.0#string) with the +addition of allowing '\0' for a null character, and binary Strings described in +the next section. This same formatting also applies to single line strings. +Multiline strings are not allowed as part of an array. + +### Binary Strings + +Config files have a binary conversion capability, this is mainly to support +writing config files but can be used by user generated files as well. Strings +with the form `B"(XXXXX)"` will convert any characters inside the parenthesis +with the form `\xHH` to the equivalent binary value. The HH are hexadecimal +characters. Characters not in this form will be translated as given. If argument +values with unprintable characters are used to generate a config file this +binary form will be used in the output string. + ## Multiple configuration files If it is desired that multiple configuration be allowed. Use @@ -206,8 +280,8 @@ char arraySeparator = ','; char valueDelimiter = '='; /// the character to use around strings char stringQuote = '"'; -/// the character to use around single characters -char characterQuote = '\''; +/// the character to use around single characters and literal strings +char literalQuote = '\''; /// the maximum number of layers to allow uint8_t maximumLayers{255}; /// the separator used to separator parent layers @@ -228,8 +302,8 @@ These can be modified via setter functions 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 *quoteCharacter(char qString, char literalChar)` :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 @@ -342,3 +416,6 @@ will create an option name in following priority. 2. Positional name 3. First short name 4. Environment name + +In config files the name will be enclosed in quotes if there is any potential +ambiguities in parsing the name. diff --git a/packages/CLI11/book/chapters/flags.md b/packages/CLI11/book/chapters/flags.md index 16134b26a450b2df13a798bdecd2e8fdabcd5976..c1318025394552cb88af42422d84a1991fbf43fe 100644 --- a/packages/CLI11/book/chapters/flags.md +++ b/packages/CLI11/book/chapters/flags.md @@ -21,7 +21,7 @@ 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 +given as a comma separated string, with the dash or dashes. A flag can have as many names as you want, and afterward, using `count`, you can use any of the names, with dashes as needed. diff --git a/packages/CLI11/book/chapters/installation.md b/packages/CLI11/book/chapters/installation.md index c8af7dfa2fcb46ff66c174ba1f7ceb80b0bdf491..e1678a52306b63fa169429a25c5c8ec9fcdfd46b 100644 --- a/packages/CLI11/book/chapters/installation.md +++ b/packages/CLI11/book/chapters/installation.md @@ -9,7 +9,8 @@ This example uses the single file edition of CLI11. You can download `CLI11.hpp` from the latest release and put it into the same folder as your source code, then compile this with C++ enabled. For a larger project, you can just put this -in an include folder and you are set. +in an include folder and you are set. This is the simplest and most +straightforward means of including CLI11 with a project. ## Full edition @@ -24,7 +25,7 @@ include shown above. ### CMake support for the full edition -If you use CMake 3.4+ for your project (highly recommended), CLI11 comes with a +If you use CMake 3.5+ for your project (highly recommended), CLI11 comes with a powerful CMakeLists.txt file that was designed to also be used with `add_subproject`. You can add the repository to your code (preferably as a git submodule), then add the following line to your project (assuming your folder is @@ -43,7 +44,83 @@ You can also configure and optionally install CLI11, and CMake will create the necessary `lib/cmake/CLI11/CLI11Config.cmake` files, so `find_package(CLI11 CONFIG REQUIRED)` also works. -If you use conan.io, CLI11 supports that too. +If you use conan.io, CLI11 supports that too. CLI11 also supports Meson and +pkg-config if you are not using CMake. + +If the CMake option `CLI11_PRECOMPILED` is set then the library is compiled into +a static library. This can be used to improve compile times if CLI11 is included +in many different parts of a project. + +### Global Headers + +Use `CLI/*.hpp` files stored in a shared folder. You could check out the git +repository to 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" +``` + +#### Global Headers with 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](https://conan.io/center/cli11) or +[Hunter](https://docs.hunter.sh/en/latest/packages/pkg/CLI11.html). (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.) + +#### Using Fetchcontent + +If you do not want to add cmake as a submodule or include it with your code the +project can be added using `FetchContent`. This capability requires CMake 3.14+ +(or 3.11+ with more work). + +An example CMake file would include: + +```cmake +include(FetchContent) +FetchContent_Declare( + cli11_proj + QUIET + GIT_REPOSITORY https://github.com/CLIUtils/CLI11.git + GIT_TAG v2.3.2 +) + +FetchContent_MakeAvailable(cli11_proj) + +# And now you can use it +target_link_libraries(<your project> PRIVATE CLI11::CLI11) +``` + +And use + +```c++ +#include <CLI/CLI.hpp> +``` + +in your project. It is highly recommended that you use the git hash for +`GIT_TAG` instead of a tag or branch, as that will both be more secure, as well +as faster to reconfigure - CMake will not have to reach out to the internet to +see if the tag moved. You can also download just the single header file from the +releases using `file(DOWNLOAD)`. ### Running tests on the full edition @@ -99,16 +176,63 @@ Total Test time (real) = 0.34 sec For the curious, the CMake options and defaults are listed below. Most options default to off if CLI11 is used as a subdirectory in another project. -| Option | Description | -| ----------------------------- | ----------------------------------------------------------------------------------------------- | -| `CLI11_SINGLE_FILE=ON` | Build the `CLI11.hpp` file from the sources. Requires Python (version 3 or 2.7). | -| `CLI11_SINGLE_FILE_TESTS=OFF` | Run the tests on the generated single file version as well | -| `CLI11_EXAMPLES=ON` | Build the example programs. | -| `CLI11_TESTING=ON` | Build the tests. | -| `CLI11_CLANG_TIDY=OFF` | Run `clang-tidy` on the examples and headers. Requires CMake 3.6+. | -| `CLI11_CLANG_TIDY_OPTIONS=""` | Options to pass to `clang-tidy`, such as `-fix` (single threaded build only if applying fixes!) | +| Option | Description | +| ------------------------------ | -------------------------------------------------------------------------------- | +| `CLI11_SINGLE_FILE=ON` | Build the `CLI11.hpp` file from the sources. Requires Python (version 3 or 2.7). | +| `CLI11_PRECOMPILED=OFF` | generate a precompiled static library instead of header-only | +| `CLI11_SINGLE_FILE_TESTS=OFF` | Run the tests on the generated single file version as well | +| `CLI11_BUILD_DOCS=ON` | build CLI11 documentation and book | +| `CLI11_BUILD_EXAMPLES=ON` | Build the example programs. | +| `CLI11_BUILD_EXAMPLES_JSON=ON` | Build some additional example using json libraries | +| `CLI11_INSTALL=ON` | install CLI11 to the install folder during the install process | +| `CLI11_FORCE_LIBCXX=OFF` | use libc++ instead of libstdc++ if building with clang on linux | +| `CLI11_CUDA_TESTS=OFF` | build the tests with NVCC | +| `CLI11_BUILD_TESTS=ON` | Build the tests. | [^1]: Docker is being used to create a pristine disposable environment; there is nothing special about this container. Alpine is being used because it is small, modern, and fast. Commands are similar on any other platform. + +## Installing cli11 using vcpkg + +You can download and install cli11 using the +[vcpkg](https://github.com/Microsoft/vcpkg) dependency manager: + +```bash +git clone https://github.com/Microsoft/vcpkg.git +cd vcpkg +./bootstrap-vcpkg.sh +./vcpkg integrate install +./vcpkg install cli11 +``` + +The cli11 port in vcpkg is kept up to date by Microsoft team members and +community contributors. If the version is out of date, please +[create an issue or pull request](https://github.com/Microsoft/vcpkg) on the +vcpkg repository. + +## Special instructions for GCC 8, Some clang, and WASI + +If you are using GCC 8 and using it in C++17 mode with CLI11. CLI11 makes use of +the `<filesystem>` header if available, but specifically for this compiler, the +`filesystem` library is separate from the standard library and needs to be +linked separately. So it is available but CLI11 doesn't use it by default. + +Specifically `libstdc++fs` needs to be added to the linking list and +`CLI11_HAS_FILESYSTEM=1` has to be defined. Then the filesystem variant of the +Validators could be used on GCC 8. GCC 9+ does not have this issue so the +`<filesystem>` is used by default. + +There may also be other cases where a specific library needs to be linked. + +Defining `CLI11_HAS_FILESYSTEM=0` which will remove the usage and hence any +linking issue. + +In some cases certain clang compilations may require linking against `libc++fs`. +These situations have not been encountered so the specific situations requiring +them are unknown yet. + +If building with WASI it is necessary to add the flag +`-lc-printscan-long-double` to the build to allow long double support. See #841 +for more details. diff --git a/packages/CLI11/book/chapters/options.md b/packages/CLI11/book/chapters/options.md index 39447113b4a7fde4660139f6b11b673e8e75351f..67fb9e5546d9718e35cdd85c29bb399c3abd0ca8 100644 --- a/packages/CLI11/book/chapters/options.md +++ b/packages/CLI11/book/chapters/options.md @@ -26,18 +26,18 @@ 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. 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 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 | +| Type | CLI11 | +| -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| 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[\0755, or \o755], decimal, and binary(0b011111100), supports value separators including `_` and `'` | +| 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 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 @@ -214,15 +214,15 @@ that to add option modifiers. A full listing of the option modifiers: | `->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 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. | +| `->envname(name)` | Gets the value from the environment if present and not passed on the command line and passes any validators. | +| `->group(name)` | The help group to put the option in. No effect for positional options. Defaults to `"Options"`. Options given an empty string for the group name will not show up in the help print. | | `->description(string)` | Set/change the description | | `->ignore_case()` | Ignore the case on the command line (also works on subcommands, does not affect arguments). | | `->ignore_underscore()` | Ignore any underscores on the command line (also works on subcommands, does not affect arguments). | | `->allow_extra_args()` | Allow extra argument values to be included when an option is passed. Enabled by default for vector options. | | `->disable_flag_override()` | specify that flag options cannot be overridden on the command line use `=<newval>` | | `->delimiter('<CH>')` | specify a character that can be used to separate elements in a command line argument, default is <none>, common values are ',', and ';' | -| `->multi_option_policy( CLI::MultiOptionPolicy::Throw)` | Sets the policy for handling multiple arguments if the option was received on the command line several times. `Throw`ing an error is the default, but `TakeLast`, `TakeFirst`, `TakeAll`, `Join`, and `Sum` are also available. See the next four lines for shortcuts to set this more easily. | +| `->multi_option_policy( CLI::MultiOptionPolicy::Throw)` | Sets the policy for handling multiple arguments if the option was received on the command line several times. `Throw`ing an error is the default, but `TakeLast`, `TakeFirst`, `TakeAll`, `Join`, `Reverse`, and `Sum` are also available. See the next four lines for shortcuts to set this more easily. | | `->take_last()` | Only use the last option if passed several times. This is always true by default for bool options, regardless of the app default, but can be set to false explicitly with `->multi_option_policy()`. | | `->take_first()` | sets `->multi_option_policy(CLI::MultiOptionPolicy::TakeFirst)` | | `->take_all()` | sets `->multi_option_policy(CLI::MultiOptionPolicy::TakeAll)` | @@ -246,6 +246,28 @@ 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. +### Multi Option policy + +The Multi option policy can be used to instruct CLI11 what to do when an option +is called multiple times and how to return those values in a meaningful way. +There are several options can be set through the +`->multi_option_policy( CLI::MultiOptionPolicy::Throw)` option modifier. +`Throw`ing an error is the default, but `TakeLast`, `TakeFirst`, `TakeAll`, +`Join`, `Reverse`, and `Sum` + +| Value | Description | +| --------- | --------------------------------------------------------------------------------- | +| Throw | Throws an error if more values are given then expected | +| TakeLast | Selects the last expected number of values given | +| TakeFirst | Selects the first expected number of of values given | +| Join | Joins the strings together using the `delimiter` given | +| TakeAll | Takes all the values | +| Sum | If the values are numeric, it sums them and returns the result | +| Reverse | Selects the last expected number of values given and return them in reverse order | + +NOTE: For reverse, the index used for an indexed validator is also applied in +reverse order index 1 will be the last element and 2 second from last and so on. + ## Using the `CLI::Option` pointer Each of the option creation mechanisms returns a pointer to the internally @@ -261,7 +283,7 @@ CLI::Option* opt = app.add_flag("--opt"); CLI11_PARSE(app, argv, argc); if(* opt) - std::cout << "Flag received " << opt->count() << " times." << std::endl; + std::cout << "Flag received " << opt->count() << " times." << '\n'; ``` ## Inheritance of defaults diff --git a/packages/CLI11/book/package.json b/packages/CLI11/book/package.json new file mode 100644 index 0000000000000000000000000000000000000000..493b5db9acab34c5273173f3fef2d2b29e854d02 --- /dev/null +++ b/packages/CLI11/book/package.json @@ -0,0 +1,14 @@ +{ + "name": "cli11-gitbook", + "version": "1.0.0", + "dependencies": { + "gitbook-cli": "2.2.0", + "gitbook-plugin-hints": "^1.0.2", + "gitbook-plugin-include-codeblock": "^3.2.2", + "gitbook-plugin-term": "^0.5.1", + "svgexport": ">=0.4.2" + }, + "scripts": { + "postinstall": "npx gitbook fetch 3.2.3 && npx gitbook install" + } +} diff --git a/packages/CLI11/cmake/CLI11GeneratePkgConfig.cmake b/packages/CLI11/cmake/CLI11GeneratePkgConfig.cmake index 5abb03d165f908d73ba0c85b97dd156483100778..a9c5eb88525bd236f07adca8797669e93b7f9331 100644 --- a/packages/CLI11/cmake/CLI11GeneratePkgConfig.cmake +++ b/packages/CLI11/cmake/CLI11GeneratePkgConfig.cmake @@ -1,3 +1,7 @@ -configure_file("cmake/CLI11.pc.in" "CLI11.pc" @ONLY) +if(CLI11_PRECOMPILED) + configure_file("cmake/CLI11precompiled.pc.in" "CLI11.pc" @ONLY) +else() + configure_file("cmake/CLI11.pc.in" "CLI11.pc" @ONLY) +endif() -install(FILES "${PROJECT_BINARY_DIR}/CLI11.pc" DESTINATION "${CMAKE_INSTALL_DATADIR}/pkgconfig") +install(FILES "${PROJECT_BINARY_DIR}/CLI11.pc" DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig") diff --git a/packages/CLI11/cmake/CLI11precompiled.pc.in b/packages/CLI11/cmake/CLI11precompiled.pc.in new file mode 100644 index 0000000000000000000000000000000000000000..df73d7780c02767b29807f9f333b1568e65d1a4f --- /dev/null +++ b/packages/CLI11/cmake/CLI11precompiled.pc.in @@ -0,0 +1,11 @@ +prefix=@CMAKE_INSTALL_PREFIX@ +exec_prefix=${prefix} +includedir=${prefix}/include +libdir=${exec_prefix}/lib + +Name: CLI11 +Description: C++ command line parser +Version: @PROJECT_VERSION@ + +Cflags: -I${includedir} -DCLI11_COMPILE +Libs: -L${libdir} -lCLI11 diff --git a/packages/CLI11/cmake/CLIsingle.hpp.in b/packages/CLI11/cmake/CLIsingle.hpp.in new file mode 100644 index 0000000000000000000000000000000000000000..a2d783c3006382a6c0d1f310d93d3f7f9796889e --- /dev/null +++ b/packages/CLI11/cmake/CLIsingle.hpp.in @@ -0,0 +1,10 @@ +// Copyright (c) 2017-2024, University of Cincinnati, developed by Henry Schreiner +// under NSF AWARD 1414736 and by the respective contributors. +// All rights reserved. +// +// SPDX-License-Identifier: BSD-3-Clause + +#pragma once + +//single file header +#include "../CLI11.hpp" diff --git a/packages/CLI11/cmake/CodeCoverage.cmake b/packages/CLI11/cmake/CodeCoverage.cmake index e011ef1342773b14b1e8dedebb3e3e83e4161f3c..1867b174270b0c9c246a952e5f3160cbb5800f29 100644 --- a/packages/CLI11/cmake/CodeCoverage.cmake +++ b/packages/CLI11/cmake/CodeCoverage.cmake @@ -88,7 +88,7 @@ elseif(NOT CMAKE_COMPILER_IS_GNUCXX) endif() set(COVERAGE_COMPILER_FLAGS - "-g -O0 --coverage -fprofile-arcs -ftest-coverage -fno-inline -fno-inline-small-functions -fno-default-inline" + "-g -O0 --coverage -fprofile-arcs -ftest-coverage -fno-inline -fno-inline-small-functions -fno-default-inline -fno-elide-constructors" CACHE INTERNAL "") set(CMAKE_CXX_FLAGS_COVERAGE diff --git a/packages/CLI11/examples/CMakeLists.txt b/packages/CLI11/examples/CMakeLists.txt index 131a9fd02514e1aa835b1d613698f0550f7a8799..0ed2f8ec8f77f4aa8b146983a61d17ca16875f85 100644 --- a/packages/CLI11/examples/CMakeLists.txt +++ b/packages/CLI11/examples/CMakeLists.txt @@ -189,6 +189,12 @@ 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") +add_cli_exe(arg_capture arg_capture.cpp) +add_test(NAME arg_capture COMMAND arg_capture -v 27 --sub -v 13 --val prefix) +set_property(TEST arg_capture PROPERTY PASS_REGULAR_EXPRESSION "value=27") +add_test(NAME arg_capture2 COMMAND arg_capture -v 27 --sub -v 13 --val prefix) +set_property(TEST arg_capture2 PROPERTY PASS_REGULAR_EXPRESSION "after Args:-v 13 --val prefix") + 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") diff --git a/packages/CLI11/examples/arg_capture.cpp b/packages/CLI11/examples/arg_capture.cpp new file mode 100644 index 0000000000000000000000000000000000000000..bdf3afebcdf7eb17de8608ac39cb215ab0e79c21 --- /dev/null +++ b/packages/CLI11/examples/arg_capture.cpp @@ -0,0 +1,34 @@ +// Copyright (c) 2017-2024, 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 + +// Code modified from https://github.com/CLIUtils/CLI11/issues/559 + +#include <CLI/CLI.hpp> +#include <iostream> +#include <string> + +/** This example demonstrates the use of `prefix_command` on a subcommand +to capture all subsequent arguments along with an alias to make it appear as a regular options. + +All the values after the "sub" or "--sub" are available in the remaining() method. +*/ +int main(int argc, const char *argv[]) { + + int value{0}; + CLI::App app{"Test App"}; + app.add_option("-v", value, "value"); + + auto *subcom = app.add_subcommand("sub", "")->prefix_command(); + subcom->alias("--sub"); + CLI11_PARSE(app, argc, argv); + + std::cout << "value=" << value << '\n'; + std::cout << "after Args:"; + for(const auto &aarg : subcom->remaining()) { + std::cout << aarg << " "; + } + std::cout << '\n'; +} diff --git a/packages/CLI11/examples/callback_passthrough.cpp b/packages/CLI11/examples/callback_passthrough.cpp index 1aac0df6beef22f1431159bda5099e9c6142ad6b..234ed894b7d7cc232cda5e062358615f1a6ebd64 100644 --- a/packages/CLI11/examples/callback_passthrough.cpp +++ b/packages/CLI11/examples/callback_passthrough.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2024, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // diff --git a/packages/CLI11/examples/config_app.cpp b/packages/CLI11/examples/config_app.cpp index a0426ad616b7221533047ae16c1ae4a0f734712d..ccd9824259fd304f231928b3ea350f85307c705b 100644 --- a/packages/CLI11/examples/config_app.cpp +++ b/packages/CLI11/examples/config_app.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2024, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // @@ -40,11 +40,11 @@ int main(int argc, char **argv) { } std::cout << "Working on file: " << file << ", direct count: " << app.count("--file") - << ", opt count: " << opt->count() << std::endl; + << ", opt count: " << opt->count() << '\n'; std::cout << "Working on count: " << count << ", direct count: " << app.count("--count") - << ", opt count: " << copt->count() << std::endl; + << ", opt count: " << copt->count() << '\n'; std::cout << "Received flag: " << v << " (" << flag->count() << ") times\n"; - std::cout << "Some value: " << value << std::endl; + std::cout << "Some value: " << value << '\n'; return 0; } diff --git a/packages/CLI11/examples/custom_parse.cpp b/packages/CLI11/examples/custom_parse.cpp index eaaedd552f65af734eec6c9d1a2e048cbfa53369..2f9c2a08f4ef3a5a731142462fc3d0fab5f884ad 100644 --- a/packages/CLI11/examples/custom_parse.cpp +++ b/packages/CLI11/examples/custom_parse.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2024, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // @@ -23,7 +23,7 @@ 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; + std::cout << "called correct lexical_cast function ! val: " << input << '\n'; return true; } diff --git a/packages/CLI11/examples/digit_args.cpp b/packages/CLI11/examples/digit_args.cpp index a0785ddbdf7c1df5c8123e7cb2b24241b0b4be66..2144f22d327bb8a54098d27e9ba046a2b8a67b29 100644 --- a/packages/CLI11/examples/digit_args.cpp +++ b/packages/CLI11/examples/digit_args.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2024, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // @@ -16,6 +16,6 @@ int main(int argc, char **argv) { CLI11_PARSE(app, argc, argv); - std::cout << "value = " << val << std::endl; + std::cout << "value = " << val << '\n'; return 0; } diff --git a/packages/CLI11/examples/enum.cpp b/packages/CLI11/examples/enum.cpp index 133adde9aca4c62e755b3597e6e299af2c0b7e54..863eda4dc1a8fbf73beedb1b4e87baead4496d07 100644 --- a/packages/CLI11/examples/enum.cpp +++ b/packages/CLI11/examples/enum.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2024, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // @@ -27,7 +27,7 @@ int main(int argc, char **argv) { // CLI11's built in enum streaming can be used outside CLI11 like this: using CLI::enums::operator<<; - std::cout << "Enum received: " << level << std::endl; + std::cout << "Enum received: " << level << '\n'; return 0; } diff --git a/packages/CLI11/examples/enum_ostream.cpp b/packages/CLI11/examples/enum_ostream.cpp index 1f8ac57e409d637f1533b4adbadf649d5976a134..939a3fa7d81834c677a41b9896351cd2acb02490 100644 --- a/packages/CLI11/examples/enum_ostream.cpp +++ b/packages/CLI11/examples/enum_ostream.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2024, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // @@ -44,7 +44,7 @@ int main(int argc, char **argv) { // CLI11's built in enum streaming can be used outside CLI11 like this: using CLI::enums::operator<<; - std::cout << "Enum received: " << level << std::endl; + std::cout << "Enum received: " << level << '\n'; return 0; } diff --git a/packages/CLI11/examples/formatter.cpp b/packages/CLI11/examples/formatter.cpp index 4973cf95f9ec27334fda2a02fe3aef461d84da43..b9afb1f96af2e19faf4dcdd0cb079de13484d01e 100644 --- a/packages/CLI11/examples/formatter.cpp +++ b/packages/CLI11/examples/formatter.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2024, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // @@ -31,7 +31,7 @@ int main(int argc, char **argv) { CLI11_PARSE(app, argc, argv); - std::cout << "This app was meant to show off the formatter, run with -h" << std::endl; + std::cout << "This app was meant to show off the formatter, run with -h" << '\n'; return 0; } diff --git a/packages/CLI11/examples/groups.cpp b/packages/CLI11/examples/groups.cpp index 09c5d6ba2bdfea9108ad9cbc4bbaaf092e4be4c0..8084f75186dfa805a6cbbb77651654c6b28017c9 100644 --- a/packages/CLI11/examples/groups.cpp +++ b/packages/CLI11/examples/groups.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2024, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // @@ -30,10 +30,10 @@ int main(int argc, char **argv) { } std::cout << "Working on file: " << file << ", direct count: " << app.count("--file") - << ", opt count: " << opt->count() << std::endl; + << ", opt count: " << opt->count() << '\n'; std::cout << "Working on count: " << count << ", direct count: " << app.count("--count") - << ", opt count: " << copt->count() << std::endl; - std::cout << "Some value: " << value << std::endl; + << ", opt count: " << copt->count() << '\n'; + std::cout << "Some value: " << value << '\n'; return 0; } diff --git a/packages/CLI11/examples/inter_argument_order.cpp b/packages/CLI11/examples/inter_argument_order.cpp index e8c489c2ad34e7d349655dca9ab9ef5bf2cfb3b1..d0a8ba55bf503c7f51e097edd7b2ca65c9ed2132 100644 --- a/packages/CLI11/examples/inter_argument_order.cpp +++ b/packages/CLI11/examples/inter_argument_order.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2024, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // @@ -46,6 +46,6 @@ int main(int argc, char **argv) { // Prove the vector is correct for(auto &pair : keyval) { - std::cout << pair.first << " : " << pair.second << std::endl; + std::cout << pair.first << " : " << pair.second << '\n'; } } diff --git a/packages/CLI11/examples/modhelp.cpp b/packages/CLI11/examples/modhelp.cpp index d0f8cf87525194e9f3ac69b8b9c80ea5c6efecac..472e302414fc3c577bea6e13b8aae2c78b8f2885 100644 --- a/packages/CLI11/examples/modhelp.cpp +++ b/packages/CLI11/examples/modhelp.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2024, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // @@ -26,10 +26,10 @@ Note that this will not shortcut `->required` and other similar options.)raw"}; if(*help) throw CLI::CallForHelp(); } catch(const CLI::Error &e) { - std::cout << "Option -a string in help: " << some_option << std::endl; + std::cout << "Option -a string in help: " << some_option << '\n'; return test.exit(e); } - std::cout << "Option -a string: " << some_option << std::endl; + std::cout << "Option -a string: " << some_option << '\n'; return 0; } diff --git a/packages/CLI11/examples/nested.cpp b/packages/CLI11/examples/nested.cpp index 3587023ac3b813413d033b7ba1b3d80178bcc5dc..cfdb5736f94f56d3c5663ea0b4c0e6ff4bbe5e1a 100644 --- a/packages/CLI11/examples/nested.cpp +++ b/packages/CLI11/examples/nested.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2024, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // diff --git a/packages/CLI11/examples/option_groups.cpp b/packages/CLI11/examples/option_groups.cpp index 3a282536bc00ee11e97a15dbde36f6b846cf1e09..f95671a1dfb1a1e37981dbeca63842d5687c673f 100644 --- a/packages/CLI11/examples/option_groups.cpp +++ b/packages/CLI11/examples/option_groups.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2024, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // @@ -33,13 +33,13 @@ int main(int argc, char **argv) { CLI11_PARSE(app, argc, argv); std::string format_type = (csv) ? std::string("CSV") : ((human) ? "human readable" : "binary"); - std::cout << "Selected " << format_type << " format" << std::endl; + std::cout << "Selected " << format_type << " format\n"; if(!fileLoc.empty()) { - std::cout << " sent to file " << fileLoc << std::endl; + std::cout << " sent to file " << fileLoc << '\n'; } else if(!networkAddress.empty()) { - std::cout << " sent over network to " << networkAddress << std::endl; + std::cout << " sent over network to " << networkAddress << '\n'; } else { - std::cout << " sent to std::cout" << std::endl; + std::cout << " sent to std::cout\n"; } return 0; diff --git a/packages/CLI11/examples/positional_arity.cpp b/packages/CLI11/examples/positional_arity.cpp index d2d9b9c89a108cd82a04ae86f1375962889a90c9..0db7ce3be7d17a42614eb7c605eafcfef80fe8b2 100644 --- a/packages/CLI11/examples/positional_arity.cpp +++ b/packages/CLI11/examples/positional_arity.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2024, 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 6b552daa536a4842b6600ced176ad37d30a3f0c1..ad283e98c633f53d0aeccd61324dad2d671a88e5 100644 --- a/packages/CLI11/examples/positional_validation.cpp +++ b/packages/CLI11/examples/positional_validation.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2024, 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 843f40374616283f9208004237f8e95fdf651bb8..f681a04630a3576ce001107061dee546b747b9ca 100644 --- a/packages/CLI11/examples/prefix_command.cpp +++ b/packages/CLI11/examples/prefix_command.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2024, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // @@ -25,11 +25,11 @@ int main(int argc, char **argv) { for(int v : vals) std::cout << ": " << v << " "; - std::cout << std::endl << "Remaining commands: "; + std::cout << '\n' << "Remaining commands: "; for(const auto &com : more_comms) std::cout << com << " "; - std::cout << std::endl; + std::cout << '\n'; return 0; } diff --git a/packages/CLI11/examples/ranges.cpp b/packages/CLI11/examples/ranges.cpp index ec14905bf4be51920b6aee7df10cdb3dea3909aa..63ad06ab9e5a5022661552c67a3eb8bd91c4ad4b 100644 --- a/packages/CLI11/examples/ranges.cpp +++ b/packages/CLI11/examples/ranges.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2024, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // diff --git a/packages/CLI11/examples/retired.cpp b/packages/CLI11/examples/retired.cpp index 28f61da04dfc5f867e5f6c79cd67cf4c1fc35c2c..24f9585c11b756f8d43af1643cec4e7f055b18d0 100644 --- a/packages/CLI11/examples/retired.cpp +++ b/packages/CLI11/examples/retired.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2024, 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 d3f48ac73c40bb395321fbc88532b341af33eddc..39ea579285f7673464908d510f352a46557575ca 100644 --- a/packages/CLI11/examples/shapes.cpp +++ b/packages/CLI11/examples/shapes.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2024, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // @@ -18,7 +18,7 @@ int main(int argc, char **argv) { int circle_counter{0}; circle->callback([&radius, &circle_counter] { ++circle_counter; - std::cout << "circle" << circle_counter << " with radius " << radius << std::endl; + std::cout << "circle" << circle_counter << " with radius " << radius << '\n'; }); circle->add_option("radius", radius, "the radius of the circle")->required(); @@ -32,7 +32,7 @@ int main(int argc, char **argv) { if(edge2 == 0) { edge2 = edge1; } - std::cout << "rectangle" << rect_counter << " with edges [" << edge1 << ',' << edge2 << "]" << std::endl; + std::cout << "rectangle" << rect_counter << " with edges [" << edge1 << ',' << edge2 << "]\n"; edge2 = 0; }); @@ -45,7 +45,7 @@ int main(int argc, char **argv) { tri->callback([&sides, &tri_counter] { ++tri_counter; - std::cout << "triangle" << tri_counter << " with sides [" << CLI::detail::join(sides) << "]" << std::endl; + std::cout << "triangle" << tri_counter << " with sides [" << CLI::detail::join(sides) << "]\n"; }); tri->add_option("sides", sides, "the side lengths of the triangle"); diff --git a/packages/CLI11/examples/simple.cpp b/packages/CLI11/examples/simple.cpp index b7095dd2cc703648668e8921efa43b01c6308ad2..c33037d20451e4ab9f6d32ccb93d808c282769d9 100644 --- a/packages/CLI11/examples/simple.cpp +++ b/packages/CLI11/examples/simple.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2024, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // @@ -28,11 +28,11 @@ int main(int argc, char **argv) { CLI11_PARSE(app, argc, argv); std::cout << "Working on file: " << file << ", direct count: " << app.count("--file") - << ", opt count: " << opt->count() << std::endl; + << ", opt count: " << opt->count() << '\n'; std::cout << "Working on count: " << count << ", direct count: " << app.count("--count") - << ", opt count: " << copt->count() << std::endl; + << ", opt count: " << copt->count() << '\n'; std::cout << "Received flag: " << v << " (" << flag->count() << ") times\n"; - std::cout << "Some value: " << value << std::endl; + std::cout << "Some value: " << value << '\n'; return 0; } diff --git a/packages/CLI11/examples/subcom_help.cpp b/packages/CLI11/examples/subcom_help.cpp index 65030eb86aa0bae0f9c6a29fe0fdc71ccc967adc..d7cfadaa33c9382c021f28ebe5f9b346654e043a 100644 --- a/packages/CLI11/examples/subcom_help.cpp +++ b/packages/CLI11/examples/subcom_help.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2024, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // @@ -15,7 +15,7 @@ int main(int argc, char *argv[]) { cli_sub.add_option("sub_arg", sub_arg, "Argument for subcommand")->required(); CLI11_PARSE(cli_global, argc, argv); if(cli_sub) { - std::cout << "Got: " << sub_arg << std::endl; + std::cout << "Got: " << sub_arg << '\n'; } return 0; } diff --git a/packages/CLI11/examples/subcom_in_files/subcommand_a.cpp b/packages/CLI11/examples/subcom_in_files/subcommand_a.cpp index bb1a6a13d4bc1c6a7d2acffc2998caae8c8bb2f0..19d309148ab63100f82178b3a756d1ead6992a71 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-2023, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2024, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // @@ -30,8 +30,8 @@ void setup_subcommand_a(CLI::App &app) { /// but having a separate function is cleaner. void run_subcommand_a(SubcommandAOptions const &opt) { // Do stuff... - std::cout << "Working on file: " << opt.file << std::endl; + std::cout << "Working on file: " << opt.file << '\n'; if(opt.with_foo) { - std::cout << "Using foo!" << std::endl; + std::cout << "Using foo!" << '\n'; } } diff --git a/packages/CLI11/examples/subcom_in_files/subcommand_a.hpp b/packages/CLI11/examples/subcom_in_files/subcommand_a.hpp index 6a8395d1a3219efc67417b563ecf75da3eea1943..ae08464afbebd25cd5d09a4f03ba2685bacffce5 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-2023, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2024, 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 e65339c901c52689c3e3b1b045d6d8ca463a70f0..795dfd486b238fa93082402d09c02aec9c824c20 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-2023, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2024, 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 b6273eaed6fdd1737e0a92008958a81237a457d2..a46eea1ce6dba31e7ae774357ff3addae94ff647 100644 --- a/packages/CLI11/examples/subcom_partitioned.cpp +++ b/packages/CLI11/examples/subcom_partitioned.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2024, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // @@ -37,10 +37,10 @@ int main(int argc, char **argv) { } std::cout << "Working on file: " << file << ", direct count: " << impOpt->count("--file") - << ", opt count: " << opt->count() << std::endl; + << ", opt count: " << opt->count() << '\n'; std::cout << "Working on count: " << count << ", direct count: " << impOpt->count("--count") - << ", opt count: " << copt->count() << std::endl; - std::cout << "Some value: " << value << std::endl; + << ", opt count: " << copt->count() << '\n'; + std::cout << "Some value: " << value << '\n'; return 0; } diff --git a/packages/CLI11/examples/subcommands.cpp b/packages/CLI11/examples/subcommands.cpp index e69c04eed06971905c53d0b4490028d373fd704b..fe93edc2f42ebfb2de3519e49c964d5a8e8be79b 100644 --- a/packages/CLI11/examples/subcommands.cpp +++ b/packages/CLI11/examples/subcommands.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2024, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // @@ -24,12 +24,11 @@ int main(int argc, char **argv) { CLI11_PARSE(app, argc, argv); - std::cout << "Working on --file from start: " << file << std::endl; - std::cout << "Working on --count from stop: " << s->count() << ", direct count: " << stop->count("--count") - << std::endl; - std::cout << "Count of --random flag: " << app.count("--random") << std::endl; + std::cout << "Working on --file from start: " << file << '\n'; + std::cout << "Working on --count from stop: " << s->count() << ", direct count: " << stop->count("--count") << '\n'; + std::cout << "Count of --random flag: " << app.count("--random") << '\n'; for(auto *subcom : app.get_subcommands()) - std::cout << "Subcommand: " << subcom->get_name() << std::endl; + std::cout << "Subcommand: " << subcom->get_name() << '\n'; return 0; } diff --git a/packages/CLI11/examples/testEXE.cpp b/packages/CLI11/examples/testEXE.cpp index b2cac7fbae1d93ef91eeb09a593c8bf8ee1e0e35..b42c60f2a73e2249ea27243bd8416869e5b46b21 100644 --- a/packages/CLI11/examples/testEXE.cpp +++ b/packages/CLI11/examples/testEXE.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2024, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // @@ -12,15 +12,17 @@ int main(int argc, const char *argv[]) { - int logLevel{0}; + int value{0}; CLI::App app{"Test App"}; + app.add_option("-v", value, "value"); - app.add_option("-v", logLevel, "level"); - - auto *subcom = app.add_subcommand("sub", "")->fallthrough(); - subcom->preparse_callback([&app](size_t) { app.get_subcommand("sub")->add_option_group("group"); }); - + auto *subcom = app.add_subcommand("sub", "")->prefix_command(); CLI11_PARSE(app, argc, argv); - std::cout << "level: " << logLevel << std::endl; + std::cout << "value =" << value << '\n'; + std::cout << "after Args:"; + for(const auto &aarg : subcom->remaining()) { + std::cout << aarg << " "; + } + std::cout << '\n'; } diff --git a/packages/CLI11/examples/validators.cpp b/packages/CLI11/examples/validators.cpp index 87eb07ab20e09fbe3558c122406aa03ec52ca7e0..44ff15546c3392f7bd0bc09ff78332359750ecb3 100644 --- a/packages/CLI11/examples/validators.cpp +++ b/packages/CLI11/examples/validators.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2024, 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) { app.add_option("-v,--value", count, "Value in range")->check(CLI::Range(3, 6)); CLI11_PARSE(app, argc, argv); - std::cout << "Try printing help or failing the validator" << std::endl; + std::cout << "Try printing help or failing the validator" << '\n'; return 0; } diff --git a/packages/CLI11/fuzz/CMakeLists.txt b/packages/CLI11/fuzz/CMakeLists.txt index 21df4028f53bcb747c1716260238863dfb2c60b8..5f5cff5dc032b35cb69f848820741f92527f7ba4 100644 --- a/packages/CLI11/fuzz/CMakeLists.txt +++ b/packages/CLI11/fuzz/CMakeLists.txt @@ -1,4 +1,4 @@ -# Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner +# Copyright (c) 2017-2024, University of Cincinnati, developed by Henry Schreiner # under NSF AWARD 1414736 and by the respective contributors. # All rights reserved. # @@ -19,14 +19,17 @@ if(CMAKE_CXX_STANDARD GREATER 16) set(CLI11_FUZZ_ARTIFACT_PATH ${PROJECT_BINARY_DIR}/fuzz) endif() - if(NOT CLI11_FUZZ_TIME) - set(CLI11_FUZZ_TIME 360) + if(NOT CLI11_FUZZ_TIME_APP) + set(CLI11_FUZZ_TIME_APP 600) + endif() + if(NOT CLI11_FUZZ_TIME_FILE) + set(CLI11_FUZZ_TIME_FILE 240) endif() add_custom_target( QUICK_CLI11_APP_FUZZ COMMAND ${CMAKE_COMMAND} -E make_directory corp COMMAND - cli11_app_fuzzer corp -max_total_time=${CLI11_FUZZ_TIME} -max_len=2048 + cli11_app_fuzzer corp -max_total_time=${CLI11_FUZZ_TIME_APP} -max_len=2148 -dict=${CMAKE_CURRENT_SOURCE_DIR}/fuzz_dictionary1.txt -exact_artifact_path=${CLI11_FUZZ_ARTIFACT_PATH}/cli11_app_fail_artifact.txt) @@ -34,7 +37,7 @@ if(CMAKE_CXX_STANDARD GREATER 16) QUICK_CLI11_FILE_FUZZ COMMAND ${CMAKE_COMMAND} -E make_directory corp COMMAND - cli11_file_fuzzer corp -max_total_time=${CLI11_FUZZ_TIME} -max_len=2048 + cli11_file_fuzzer corp -max_total_time=${CLI11_FUZZ_TIME_FILE} -max_len=2048 -dict=${CMAKE_CURRENT_SOURCE_DIR}/fuzz_dictionary2.txt -exact_artifact_path=${CLI11_FUZZ_ARTIFACT_PATH}/cli11_file_fail_artifact.txt) diff --git a/packages/CLI11/fuzz/cli11_app_fuzz.cpp b/packages/CLI11/fuzz/cli11_app_fuzz.cpp index 7cd10b889899141bd09d7811ea8c0106fb95b8ae..e6acc35b9f1cdbbc31c431b0c2aa82e31d5174ab 100644 --- a/packages/CLI11/fuzz/cli11_app_fuzz.cpp +++ b/packages/CLI11/fuzz/cli11_app_fuzz.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2024, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // @@ -15,16 +15,41 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) { return 0; } std::string parseString(reinterpret_cast<const char *>(Data), Size); - + std::string optionString; + std::string flagString; + if(parseString.size() > 25) { + optionString = parseString.substr(0, 25); + parseString.erase(0, 25); + } + if(parseString.size() > 25) { + flagString = parseString.substr(0, 25); + parseString.erase(0, 25); + } CLI::FuzzApp fuzzdata; - auto app = fuzzdata.generateApp(); + try { + if(!optionString.empty()) { + app->add_option(optionString, fuzzdata.buffer); + } + if(!flagString.empty()) { + app->add_flag(flagString, fuzzdata.intbuffer); + } + } catch(const CLI::ConstructionError &e) { + return 0; // Non-zero return values are reserved for future use. + } + try { app->parse(parseString); + } catch(const CLI::ParseError &e) { //(app)->exit(e); // this just indicates we caught an error known by CLI + return 0; // Non-zero return values are reserved for future use. } - - return 0; // Non-zero return values are reserved for future use. + // should be able to write the config to a file and read from it again + std::string configOut = app->config_to_str(); + app->clear(); + std::stringstream out(configOut); + app->parse_from_stream(out); + return 0; } diff --git a/packages/CLI11/fuzz/cli11_file_fuzz.cpp b/packages/CLI11/fuzz/cli11_file_fuzz.cpp index e769114eb9b1ab5ee2fcd8ffc60fed9b29f1710f..754108d17b2efb3c768f907621b9630d6b2b7ad9 100644 --- a/packages/CLI11/fuzz/cli11_file_fuzz.cpp +++ b/packages/CLI11/fuzz/cli11_file_fuzz.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2024, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // @@ -22,8 +22,15 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) { auto app = fuzzdata.generateApp(); try { app->parse_from_stream(out); + // should be able to write the config to a file and read from it again + std::string configOut = app->config_to_str(); + + app->clear(); + std::stringstream out(configOut); + app->parse_from_stream(out); + } catch(const CLI::ParseError &e) { - (app)->exit(e); + // (app)->exit(e); // this just indicates we caught an error known by CLI } diff --git a/packages/CLI11/fuzz/fuzzApp.cpp b/packages/CLI11/fuzz/fuzzApp.cpp index dc401f933a56921c37a45201e767c60fadbfde00..21d510126097bc4d364cac2e1e90419d4326d87c 100644 --- a/packages/CLI11/fuzz/fuzzApp.cpp +++ b/packages/CLI11/fuzz/fuzzApp.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2024, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // @@ -44,9 +44,10 @@ std::shared_ptr<CLI::App> FuzzApp::generateApp() { auto fApp = std::make_shared<CLI::App>("fuzzing App", "fuzzer"); fApp->set_config("--config"); fApp->add_flag("-a,--flag"); - fApp->add_flag("-b,--flag2", flag1); + fApp->add_flag("-b,--flag2,!--nflag2", flag1); fApp->add_flag("-c{34},--flag3{1}", flagCnt)->disable_flag_override(); fApp->add_flag("-e,--flagA", flagAtomic); + fApp->add_flag("--atd", doubleAtomic); fApp->add_option("-d,--opt1", val8); fApp->add_option("--opt2", val16); @@ -64,10 +65,12 @@ std::shared_ptr<CLI::App> FuzzApp::generateApp() { fApp->add_option("--dopt1", v1); fApp->add_option("--dopt2", v2); - fApp->add_option("--vopt1", vv1); - fApp->add_option("--vopt2", vvs); - fApp->add_option("--vopt3", vstr); - fApp->add_option("--vopt4", vecvecd); + auto *vgroup = fApp->add_option_group("vectors"); + + vgroup->add_option("--vopt1", vv1); + vgroup->add_option("--vopt2", vvs)->inject_separator(); + vgroup->add_option("--vopt3", vstr); + vgroup->add_option("--vopt4", vecvecd)->inject_separator(); fApp->add_option("--oopt1", od1); fApp->add_option("--oopt2", ods); @@ -75,10 +78,73 @@ std::shared_ptr<CLI::App> FuzzApp::generateApp() { fApp->add_option("--tup1", p1); fApp->add_option("--tup2", t1); fApp->add_option("--tup4", tcomplex); + vgroup->add_option("--vtup", vectup); fApp->add_option("--dwrap", dwrap); fApp->add_option("--iwrap", iwrap); - + fApp->add_option("--swrap", swrap); + // file checks + fApp->add_option("--dexists")->check(ExistingDirectory); + fApp->add_option("--fexists")->check(ExistingFile); + fApp->add_option("--fnexists")->check(NonexistentPath); + + auto *sub = fApp->add_subcommand("sub1"); + + sub->add_option("--sopt2", val16)->check(Range(1, 10)); + sub->add_option("--sopt3", val32)->check(PositiveNumber); + sub->add_option("--sopt4", val64)->check(NonNegativeNumber); + + sub->add_option("--sopt5", uval8)->transform(Bound(6, 20)); + sub->add_option("--sopt6", uval16); + sub->add_option("--sopt7", uval32); + sub->add_option("--sopt8", uval64); + + sub->add_option("--saopt1", atomicval64); + sub->add_option("--saopt2", atomicuval64); + + sub->add_option("--sdopt1", v1); + sub->add_option("--sdopt2", v2); + + sub->add_option("--svopt1", vv1); + sub->add_option("--svopt2", vvs); + sub->add_option("--svopt3", vstr); + sub->add_option("--svopt4", vecvecd); + + sub->add_option("--soopt1", od1); + sub->add_option("--soopt2", ods); + + sub->add_option("--stup1", p1); + sub->add_option("--stup2", t1); + sub->add_option("--stup4", tcomplex); + sub->add_option("--svtup", vectup); + + sub->add_option("--sdwrap", dwrap); + sub->add_option("--siwrap", iwrap); + + auto *resgroup = fApp->add_option_group("outputOrder"); + + resgroup->add_option("--vA", vstrA)->expected(0, 2)->multi_option_policy(CLI::MultiOptionPolicy::TakeAll); + resgroup->add_option("--vB", vstrB)->expected(0, 2)->multi_option_policy(CLI::MultiOptionPolicy::TakeLast); + resgroup->add_option("--vC", vstrC)->expected(0, 2)->multi_option_policy(CLI::MultiOptionPolicy::TakeFirst); + resgroup->add_option("--vD", vstrD)->expected(0, 2)->multi_option_policy(CLI::MultiOptionPolicy::Reverse); + resgroup->add_option("--vS", val32)->expected(0, 2)->multi_option_policy(CLI::MultiOptionPolicy::Sum); + resgroup->add_option("--vM", mergeBuffer)->expected(0, 2)->multi_option_policy(CLI::MultiOptionPolicy::Join); + resgroup->add_option("--vE", vstrE)->expected(2, 4)->delimiter(','); + + auto *vldtr = fApp->add_option_group("validators"); + + validator_strings.resize(10); + vldtr->add_option("--vdtr1", validator_strings[0])->join()->check(CLI::PositiveNumber); + vldtr->add_option("--vdtr2", validator_strings[1])->join()->check(CLI::NonNegativeNumber); + vldtr->add_option("--vdtr3", validator_strings[2])->join()->check(CLI::NonexistentPath); + vldtr->add_option("--vdtr4", validator_strings[3])->join()->check(CLI::Range(7, 3456)); + vldtr->add_option("--vdtr5", validator_strings[4]) + ->join() + ->check(CLI::Range(std::string("aa"), std::string("zz"), "string range")); + vldtr->add_option("--vdtr6", validator_strings[5])->join()->check(CLI::TypeValidator<double>()); + vldtr->add_option("--vdtr7", validator_strings[6])->join()->check(CLI::TypeValidator<bool>()); + vldtr->add_option("--vdtr8", validator_strings[7])->join()->check(CLI::ValidIPV4); + vldtr->add_option("--vdtr9", validator_strings[8])->join()->transform(CLI::Bound(2, 255)); return fApp; } diff --git a/packages/CLI11/fuzz/fuzzApp.hpp b/packages/CLI11/fuzz/fuzzApp.hpp index 01600cc259dce0697c5202bc30a321920a9c36e9..73c8f7e9add3111a19d9b201bbb7d227e08fa963 100644 --- a/packages/CLI11/fuzz/fuzzApp.hpp +++ b/packages/CLI11/fuzz/fuzzApp.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2024, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // @@ -41,6 +41,16 @@ class doubleWrapper { double val{0.0}; }; +class stringWrapper { + public: + stringWrapper() = default; + explicit stringWrapper(std::string_view v) : val(v){}; + CLI11_NODISCARD std::string value() const { return val; } + + private: + std::string val{}; +}; + class FuzzApp { public: FuzzApp() = default; @@ -65,6 +75,7 @@ class FuzzApp { std::vector<double> vv1{}; std::vector<std::string> vstr{}; + std::vector<std::vector<double>> vecvecd{}; std::vector<std::vector<std::string>> vvs{}; std::optional<double> od1{}; @@ -80,6 +91,7 @@ class FuzzApp { std::vector<int>, std::optional<std::string>> tcomplex2{}; + std::vector<std::tuple<std::string, double, char, std::vector<std::string>>> vectup{}; std::string_view vstrv = ""; bool flag1{false}; @@ -88,5 +100,19 @@ class FuzzApp { intWrapper64 iwrap{0}; doubleWrapper dwrap{0.0}; + stringWrapper swrap{}; + std::string buffer{}; + int intbuffer{0}; + std::atomic<double> doubleAtomic{0.0}; + + // for testing restrictions and reduction methods + std::vector<std::string> vstrA{}; + std::vector<std::string> vstrB{}; + std::vector<std::string> vstrC{}; + std::vector<std::string> vstrD{}; + std::vector<std::string> vstrE{}; + std::vector<std::string> vstrF{}; + std::string mergeBuffer{}; + std::vector<std::string> validator_strings{}; }; } // namespace CLI diff --git a/packages/CLI11/fuzz/fuzzCommand.cpp b/packages/CLI11/fuzz/fuzzCommand.cpp index 07ab0df2e24d8390ef490fa09597cc991430d8a8..2ab9805c816c7f4018fadeddff985ce8f7a8f444 100644 --- a/packages/CLI11/fuzz/fuzzCommand.cpp +++ b/packages/CLI11/fuzz/fuzzCommand.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2024, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // @@ -20,5 +20,6 @@ int main(int argc, char **argv) { (app)->exit(e); // this just indicates we caught an error known by CLI } + return 0; } diff --git a/packages/CLI11/fuzz/fuzz_dictionary1.txt b/packages/CLI11/fuzz/fuzz_dictionary1.txt index c044eecd4b56e798960acdbfb87220203fa43031..327f658ba17c7f96055382be0c3a2357efc8bb6b 100644 --- a/packages/CLI11/fuzz/fuzz_dictionary1.txt +++ b/packages/CLI11/fuzz/fuzz_dictionary1.txt @@ -4,6 +4,7 @@ "-c" "-d" "-e" +" " "--flag1" "--flag" "--flag2" @@ -32,3 +33,142 @@ "--tup4" "--dwrap" "--iwrap" +"--vtup" +"--atd" +"sub1" +"--sflag1" +"--sflag" +"--sflag2" +"--sflagA" +"--sopt1" +"--sopt2" +"--sopt3" +"--sopt4" +"--sopt5" +"--sopt6" +"--sopt7" +"--sopt8" +"--sopt9" +"--saopt1" +"--saopt2" +"--sdopt1" +"--sdopt2" +"--svopt1" +"--svopt2" +"--svopt3" +"--svopt4" +"--soopt1" +"--soopt2" +"--stup1" +"--stup2" +"--stup4" +"--sdwrap" +"--siwrap" +"--svtup" +"--satd" +"--vA" +"--vB" +"--vC" +"--vD" +"--vS" +"--vM" +"--vE" +"--vdtr" +"nflag2" +"stup1" +"svtup" +"sdwrap" +"siwrap" +"++" +"=" +"vtup" +"soopt2" +"--" +"svopt4" +"opt8" +"config" +"dwrap" +"soptneg" +"flag1" +"flag" +"flag2" +"flag3" +"enable" +"help" +"flagA" +"opt1" +"opt2" +"opt3" +"opt4" +"opt5" +"opt6" +"opt7" +"opt8" +"opt9" +"aopt1" +"aopt2" +"dopt1" +"dopt2" +"vopt1" +"vopt2" +"vopt3" +"vopt4" +"oopt1" +"oopt2" +"tup1" +"tup2" +"tup4" +"dwrap" +"iwrap" +"swrap" +"vtup" +"atd" +"sflag1" +"sflag" +"sflag2" +"sflagA" +"sopt1" +"sopt2" +"sopt3" +"sopt4" +"sopt5" +"sopt6" +"sopt7" +"sopt8" +"sopt9" +"saopt1" +"saopt2" +"sdopt1" +"sdopt2" +"svopt1" +"svopt2" +"svopt3" +"svopt4" +"soopt1" +"soopt2" +"stup1" +"stup2" +"stup4" +"sdwrap" +"siwrap" +"vdtr" +"svtup" +"satd" +"%%" +"dexists" +"fexists" +"fnexists" +",-" +",--" +"{false}" +"{4}" +"!" +"{" +"}" +"vA" +"vB" +"vC" +"vD" +"vS" +"vM" +"vE" diff --git a/packages/CLI11/fuzz/fuzz_dictionary2.txt b/packages/CLI11/fuzz/fuzz_dictionary2.txt index 12dd8f1f6c8fb74cb207817ebd0ea5169354b512..828be3a3c19eedc84af592d68bcc6cd657126034 100644 --- a/packages/CLI11/fuzz/fuzz_dictionary2.txt +++ b/packages/CLI11/fuzz/fuzz_dictionary2.txt @@ -35,3 +35,58 @@ "tup4" "dwrap" "iwrap" +"vtup" +"atd" +"sub1" +"soopt1" +"soopt2" +"stup1" +"stup2" +"stup4" +"stup2" +"stup4" +"sdwrap" +"siwrap" +"svtup" +"satd" +"sflag1" +"sflag" +"sflag2" +"sflagA" +"sopt1" +"sopt2" +"sopt3" +"sopt4" +"sopt5" +"sopt6" +"sopt7" +"sopt8" +"sopt9" +"saopt1" +"saopt2" +"sdopt1" +"sdopt2" +"svopt1" +"svopt2" +"svopt3" +"svopt4" +"config" +"nflag2" +"vdtr" +"--" +"fuzzer" +"t-" +"++" +"su" +"%%" +"swrap" +"dexists" +"fexists" +"fnexists" +"vA" +"vB" +"vC" +"vD" +"vS" +"vM" +"vE" diff --git a/packages/CLI11/include/CLI/App.hpp b/packages/CLI11/include/CLI/App.hpp index 2676445d155562f77aee64738768c64d024e5ec3..b63be47e78270d5a3a139205b1308aa981b514fb 100644 --- a/packages/CLI11/include/CLI/App.hpp +++ b/packages/CLI11/include/CLI/App.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2024, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // @@ -290,6 +290,14 @@ class App { ///@} +#ifdef _WIN32 + /// When normalizing argv to UTF-8 on Windows, this is the storage for normalized args. + std::vector<std::string> normalized_argv_{}; + + /// When normalizing argv to UTF-8 on Windows, this is the `char**` value returned to the user. + std::vector<char *> normalized_argv_view_{}; +#endif + /// Special private constructor for subcommand App(std::string app_description, std::string app_name, App *parent); @@ -309,6 +317,9 @@ class App { /// virtual destructor virtual ~App() = default; + /// Convert the contents of argv to UTF-8. Only does something on Windows, does nothing elsewhere. + CLI11_NODISCARD char **ensure_utf8(char **argv); + /// Set a callback for execution when all parsing and processing has completed /// /// Due to a bug in c++11, @@ -1223,6 +1234,9 @@ class App { /// Read and process a configuration file (main app only) void _process_config_file(); + /// Read and process a particular configuration file + void _process_config_file(const std::string &config_file, bool throw_error); + /// Get envname options if not yet passed. Runs on *all* subcommands. void _process_env(); diff --git a/packages/CLI11/include/CLI/Argv.hpp b/packages/CLI11/include/CLI/Argv.hpp index 35d81a6eaf3d888ccd245469bce2386800053b95..545bd58d1a8b9775be01d93dcf316b3f0f1e2533 100644 --- a/packages/CLI11/include/CLI/Argv.hpp +++ b/packages/CLI11/include/CLI/Argv.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2024, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // @@ -6,10 +6,21 @@ #pragma once +// [CLI11:public_includes:set] +#include <string> +#include <vector> +// [CLI11:public_includes:end] + #include <CLI/Macros.hpp> namespace CLI { // [CLI11:argv_hpp:verbatim] +namespace detail { +#ifdef _WIN32 +/// Decode and return UTF-8 argv from GetCommandLineW. +CLI11_INLINE std::vector<std::string> compute_win32_argv(); +#endif +} // namespace detail /// argc as passed in to this executable. CLI11_INLINE int argc(); diff --git a/packages/CLI11/include/CLI/CLI.hpp b/packages/CLI11/include/CLI/CLI.hpp index fa9d4bb5394e6a88f146ffde82b01bcfc2e26f75..df401e0039d17d3c516c970991a5753af28f8bcb 100644 --- a/packages/CLI11/include/CLI/CLI.hpp +++ b/packages/CLI11/include/CLI/CLI.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2024, 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 a91f0da6e8d371d6002066020df9f396260f32dd..942c43f4024e8f1048dd7e951e2adb2f61eedf9a 100644 --- a/packages/CLI11/include/CLI/Config.hpp +++ b/packages/CLI11/include/CLI/Config.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2024, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // @@ -24,7 +24,10 @@ namespace CLI { // [CLI11:config_hpp:verbatim] namespace detail { -std::string convert_arg_for_ini(const std::string &arg, char stringQuote = '"', char characterQuote = '\''); +std::string convert_arg_for_ini(const std::string &arg, + char stringQuote = '"', + char literalQuote = '\'', + bool disable_multi_line = false); /// Comma separated join, adds quotes if needed std::string ini_join(const std::vector<std::string> &args, @@ -32,7 +35,9 @@ std::string ini_join(const std::vector<std::string> &args, char arrayStart = '[', char arrayEnd = ']', char stringQuote = '"', - char characterQuote = '\''); + char literalQuote = '\''); + +void clean_name_string(std::string &name, const std::string &keyChars); std::vector<std::string> generate_parents(const std::string §ion, std::string &name, char parentSeparator); diff --git a/packages/CLI11/include/CLI/ConfigFwd.hpp b/packages/CLI11/include/CLI/ConfigFwd.hpp index a9ae2176a953c40cd0f41cc5eb85c408f6174721..fabf84dcef33dfcd3f1a55ecb0b89f28f18ecc66 100644 --- a/packages/CLI11/include/CLI/ConfigFwd.hpp +++ b/packages/CLI11/include/CLI/ConfigFwd.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2024, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // @@ -10,6 +10,7 @@ #include <algorithm> #include <fstream> #include <iostream> +#include <memory> #include <string> #include <vector> // [CLI11:public_includes:end] @@ -29,7 +30,6 @@ struct ConfigItem { /// This is the name std::string name{}; - /// Listing of inputs std::vector<std::string> inputs{}; @@ -92,8 +92,8 @@ class ConfigBase : public Config { char valueDelimiter = '='; /// the character to use around strings char stringQuote = '"'; - /// the character to use around single characters - char characterQuote = '\''; + /// the character to use around single characters and literal strings + char literalQuote = '\''; /// the maximum number of layers to allow uint8_t maximumLayers{255}; /// the separator used to separator parent layers @@ -129,10 +129,10 @@ class ConfigBase : public Config { valueDelimiter = vSep; return this; } - /// Specify the quote characters used around strings and characters - ConfigBase *quoteCharacter(char qString, char qChar) { + /// Specify the quote characters used around strings and literal strings + ConfigBase *quoteCharacter(char qString, char literalChar) { stringQuote = qString; - characterQuote = qChar; + literalQuote = literalChar; return this; } /// Specify the maximum number of parents diff --git a/packages/CLI11/include/CLI/Encoding.hpp b/packages/CLI11/include/CLI/Encoding.hpp index 379e33b20ce9e4ae58b49f15ce1f06eee4427107..d723878f372437677c3eade145b58aeecdd9d78f 100644 --- a/packages/CLI11/include/CLI/Encoding.hpp +++ b/packages/CLI11/include/CLI/Encoding.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2024, 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/Error.hpp b/packages/CLI11/include/CLI/Error.hpp index 0900da53ce374d07fa1cceaa23a55bf4e19b2875..2d6f673e943090fa0e9f285cd3592271370f71fb 100644 --- a/packages/CLI11/include/CLI/Error.hpp +++ b/packages/CLI11/include/CLI/Error.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2024, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // @@ -123,7 +123,13 @@ class BadNameString : public ConstructionError { CLI11_ERROR_DEF(ConstructionError, BadNameString) CLI11_ERROR_SIMPLE(BadNameString) static BadNameString OneCharName(std::string name) { return BadNameString("Invalid one char name: " + name); } + static BadNameString MissingDash(std::string name) { + return BadNameString("Long names strings require 2 dashes " + name); + } static BadNameString BadLongName(std::string name) { return BadNameString("Bad long name: " + name); } + static BadNameString BadPositionalName(std::string name) { + return BadNameString("Invalid positional Name: " + name); + } static BadNameString DashesOnly(std::string name) { return BadNameString("Must have a name, not just dashes: " + name); } diff --git a/packages/CLI11/include/CLI/Formatter.hpp b/packages/CLI11/include/CLI/Formatter.hpp index f58058f27add2842b57d776b54e91b7ea930e62a..bc54caf5529cc1afc2d29f83ac0e1f0045179b01 100644 --- a/packages/CLI11/include/CLI/Formatter.hpp +++ b/packages/CLI11/include/CLI/Formatter.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2024, 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 5ef0a5b585e377554c036ea5e7f41994e476105a..a0949b49d1f1e10dd6ae7844cc73c517d9473a27 100644 --- a/packages/CLI11/include/CLI/FormatterFwd.hpp +++ b/packages/CLI11/include/CLI/FormatterFwd.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2024, 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 c7ac94e8741370f3a1633dfcf8568af8ac8339d8..3fd26475e5d644265029352a8ea34daa39fa46d3 100644 --- a/packages/CLI11/include/CLI/Macros.hpp +++ b/packages/CLI11/include/CLI/Macros.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2024, 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/Option.hpp b/packages/CLI11/include/CLI/Option.hpp index d32350738e34031a14c3be68e59d75a09b20831b..a0fa7ceac3aa3c4fe3b21bafb0b920952afec067 100644 --- a/packages/CLI11/include/CLI/Option.hpp +++ b/packages/CLI11/include/CLI/Option.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2024, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // @@ -41,7 +41,8 @@ enum class MultiOptionPolicy : char { TakeFirst, //!< take only the first Expected number of arguments Join, //!< merge all the arguments together into a single string via the delimiter character default('\n') TakeAll, //!< just get all the passed argument regardless - Sum //!< sum all the arguments together if numerical or concatenate directly without delimiter + Sum, //!< sum all the arguments together if numerical or concatenate directly without delimiter + Reverse, //!< take only the last Expected number of arguments in reverse order }; /// This is the CRTP base class for Option and OptionDefaults. It was designed this way @@ -549,12 +550,12 @@ class Option : public OptionBase<Option> { if(!lnames_.empty()) { return lnames_[0]; } - if(!pname_.empty()) { - return pname_; - } if(!snames_.empty()) { return snames_[0]; } + if(!pname_.empty()) { + return pname_; + } return envname_; } /// The number of times the option expects to be included @@ -577,13 +578,13 @@ class Option : public OptionBase<Option> { CLI11_NODISCARD int get_items_expected() const { return get_items_expected_min(); } /// True if the argument can be given directly - CLI11_NODISCARD bool get_positional() const { return pname_.length() > 0; } + CLI11_NODISCARD bool get_positional() const { return !pname_.empty(); } /// True if option has at least one non-positional name - CLI11_NODISCARD bool nonpositional() const { return (snames_.size() + lnames_.size()) > 0; } + CLI11_NODISCARD bool nonpositional() const { return (!lnames_.empty() || !snames_.empty()); } /// True if option has description - CLI11_NODISCARD bool has_description() const { return description_.length() > 0; } + CLI11_NODISCARD bool has_description() const { return !description_.empty(); } /// Get the description CLI11_NODISCARD const std::string &get_description() const { return description_; } diff --git a/packages/CLI11/include/CLI/Split.hpp b/packages/CLI11/include/CLI/Split.hpp index d00e7f8cbe6b5bd5f0c395de7b96164e575e20e1..165575393a6c36d2be97fc2dc535d7140a27ab14 100644 --- a/packages/CLI11/include/CLI/Split.hpp +++ b/packages/CLI11/include/CLI/Split.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2024, 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 2a31005c858a00a71f489cf4c485d7805cb410cb..fb0069b7161e8cda5d041ff4b8a1b8016c9f93db 100644 --- a/packages/CLI11/include/CLI/StringTools.hpp +++ b/packages/CLI11/include/CLI/StringTools.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2024, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // @@ -120,6 +120,9 @@ inline std::string trim_copy(const std::string &str) { /// remove quotes at the front and back of a string either '"' or '\'' CLI11_INLINE std::string &remove_quotes(std::string &str); +/// remove quotes from all elements of a string vector and process escaped components +CLI11_INLINE void remove_quotes(std::vector<std::string> &args); + /// 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 /// @@ -140,14 +143,16 @@ CLI11_INLINE std::ostream &format_aliases(std::ostream &out, const std::vector<s /// Verify the first character of an option /// - 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'); } +template <typename T> bool valid_first_char(T c) { + return ((c != '-') && (static_cast<unsigned char>(c) > 33)); // space and '!' not allowed +} /// Verify following characters of an option 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'); + // and control codes other than tab 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 != '{') && ((static_cast<unsigned char>(c) > 32) || c == '\t')); } /// Verify an option/subcommand name @@ -210,18 +215,46 @@ template <typename Callable> inline std::string find_and_modify(std::string str, return str; } +/// close a sequence of characters indicated by a closure character. Brackets allows sub sequences +/// recognized bracket sequences include "'`[(<{ other closure characters are assumed to be literal strings +CLI11_INLINE std::size_t close_sequence(const std::string &str, std::size_t start, char closure_char); + /// Split a string '"one two" "three"' into 'one two', 'three' -/// Quote characters can be ` ' or " +/// Quote characters can be ` ' or " or bracket characters [{(< with matching to the matching bracket CLI11_INLINE std::vector<std::string> split_up(std::string str, char delimiter = '\0'); +/// get the value of an environmental variable or empty string if empty +CLI11_INLINE std::string get_environment_value(const std::string &env_name); + /// 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 /// the return value is the offset+1 which is required by the find_and_modify function. CLI11_INLINE std::size_t escape_detect(std::string &str, std::size_t offset); -/// Add quotes if the string contains spaces -CLI11_INLINE std::string &add_quotes_if_needed(std::string &str); +/// @brief detect if a string has escapable characters +/// @param str the string to do the detection on +/// @return true if the string has escapable characters +CLI11_INLINE bool has_escapable_character(const std::string &str); + +/// @brief escape all escapable characters +/// @param str the string to escape +/// @return a string with the escapble characters escaped with '\' +CLI11_INLINE std::string add_escaped_characters(const std::string &str); + +/// @brief replace the escaped characters with their equivalent +CLI11_INLINE std::string remove_escaped_characters(const std::string &str); + +/// generate a string with all non printable characters escaped to hex codes +CLI11_INLINE std::string binary_escape_string(const std::string &string_to_escape); + +CLI11_INLINE bool is_binary_escaped_string(const std::string &escaped_string); + +/// extract an escaped binary_string +CLI11_INLINE std::string extract_binary_string(const std::string &escaped_string); + +/// process a quoted string, remove the quotes and if appropriate handle escaped characters +CLI11_INLINE bool process_quoted_string(std::string &str, char string_char = '\"', char literal_char = '\''); } // namespace detail diff --git a/packages/CLI11/include/CLI/Timer.hpp b/packages/CLI11/include/CLI/Timer.hpp index b185d3302a7ef82703bc642a635f5224c6dcdf6d..7ffc2d9b2e5a62551f3c4a98947b498e9d454446 100644 --- a/packages/CLI11/include/CLI/Timer.hpp +++ b/packages/CLI11/include/CLI/Timer.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2024, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // @@ -126,7 +126,7 @@ class AutoTimer : public Timer { // GCC 4.7 does not support using inheriting constructors. /// This destructor prints the string - ~AutoTimer() { std::cout << to_string() << std::endl; } + ~AutoTimer() { std::cout << to_string() << '\n'; } }; } // namespace CLI diff --git a/packages/CLI11/include/CLI/TypeTools.hpp b/packages/CLI11/include/CLI/TypeTools.hpp index 9d43ea3614011db9e59f9827676b5a34ed976294..7e66c6adc7c668f91762e076a06cf150073fb03e 100644 --- a/packages/CLI11/include/CLI/TypeTools.hpp +++ b/packages/CLI11/include/CLI/TypeTools.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2024, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // @@ -7,6 +7,7 @@ #pragma once // [CLI11:public_includes:set] +#include <algorithm> #include <cmath> #include <cstdint> #include <exception> @@ -607,12 +608,23 @@ template <typename T> struct classify_object<T, typename std::enable_if<is_bool< template <typename T> struct classify_object<T, typename std::enable_if<std::is_floating_point<T>::value>::type> { static constexpr object_category value{object_category::floating_point}; }; +#if defined _MSC_VER +// in MSVC wstring should take precedence if available this isn't as useful on other compilers due to the broader use of +// utf-8 encoding +#define WIDE_STRING_CHECK \ + !std::is_assignable<T &, std::wstring>::value && !std::is_constructible<T, std::wstring>::value +#define STRING_CHECK true +#else +#define WIDE_STRING_CHECK true +#define STRING_CHECK !std::is_assignable<T &, std::string>::value && !std::is_constructible<T, std::string>::value +#endif /// String and similar direct assignment template <typename T> -struct classify_object<T, - typename std::enable_if<!std::is_floating_point<T>::value && !std::is_integral<T>::value && - std::is_assignable<T &, std::string>::value>::type> { +struct classify_object< + T, + typename std::enable_if<!std::is_floating_point<T>::value && !std::is_integral<T>::value && WIDE_STRING_CHECK && + std::is_assignable<T &, std::string>::value>::type> { static constexpr object_category value{object_category::string_assignable}; }; @@ -622,7 +634,7 @@ struct classify_object< T, typename std::enable_if<!std::is_floating_point<T>::value && !std::is_integral<T>::value && !std::is_assignable<T &, std::string>::value && (type_count<T>::value == 1) && - std::is_constructible<T, std::string>::value>::type> { + WIDE_STRING_CHECK && std::is_constructible<T, std::string>::value>::type> { static constexpr object_category value{object_category::string_constructible}; }; @@ -630,9 +642,7 @@ struct classify_object< template <typename T> struct classify_object<T, typename std::enable_if<!std::is_floating_point<T>::value && !std::is_integral<T>::value && - !std::is_assignable<T &, std::string>::value && - !std::is_constructible<T, std::string>::value && - std::is_assignable<T &, std::wstring>::value>::type> { + STRING_CHECK && std::is_assignable<T &, std::wstring>::value>::type> { static constexpr object_category value{object_category::wstring_assignable}; }; @@ -640,10 +650,8 @@ template <typename T> struct classify_object< T, typename std::enable_if<!std::is_floating_point<T>::value && !std::is_integral<T>::value && - !std::is_assignable<T &, std::string>::value && - !std::is_constructible<T, std::string>::value && !std::is_assignable<T &, std::wstring>::value && (type_count<T>::value == 1) && - std::is_constructible<T, std::wstring>::value>::type> { + STRING_CHECK && std::is_constructible<T, std::wstring>::value>::type> { static constexpr object_category value{object_category::wstring_constructible}; }; @@ -854,7 +862,7 @@ bool integral_conversion(const std::string &input, T &output) noexcept { if(input.empty() || input.front() == '-') { return false; } - char *val = nullptr; + char *val{nullptr}; errno = 0; std::uint64_t output_ll = std::strtoull(input.c_str(), &val, 0); if(errno == ERANGE) { @@ -870,6 +878,33 @@ bool integral_conversion(const std::string &input, T &output) noexcept { output = (output_sll < 0) ? static_cast<T>(0) : static_cast<T>(output_sll); return (static_cast<std::int64_t>(output) == output_sll); } + // remove separators + if(input.find_first_of("_'") != std::string::npos) { + std::string nstring = input; + nstring.erase(std::remove(nstring.begin(), nstring.end(), '_'), nstring.end()); + nstring.erase(std::remove(nstring.begin(), nstring.end(), '\''), nstring.end()); + return integral_conversion(nstring, output); + } + if(input.compare(0, 2, "0o") == 0) { + val = nullptr; + errno = 0; + output_ll = std::strtoull(input.c_str() + 2, &val, 8); + if(errno == ERANGE) { + return false; + } + output = static_cast<T>(output_ll); + return (val == (input.c_str() + input.size()) && static_cast<std::uint64_t>(output) == output_ll); + } + if(input.compare(0, 2, "0b") == 0) { + val = nullptr; + errno = 0; + output_ll = std::strtoull(input.c_str() + 2, &val, 2); + if(errno == ERANGE) { + return false; + } + output = static_cast<T>(output_ll); + return (val == (input.c_str() + input.size()) && static_cast<std::uint64_t>(output) == output_ll); + } return false; } @@ -894,11 +929,38 @@ bool integral_conversion(const std::string &input, T &output) noexcept { output = static_cast<T>(1); return true; } + // remove separators + if(input.find_first_of("_'") != std::string::npos) { + std::string nstring = input; + nstring.erase(std::remove(nstring.begin(), nstring.end(), '_'), nstring.end()); + nstring.erase(std::remove(nstring.begin(), nstring.end(), '\''), nstring.end()); + return integral_conversion(nstring, output); + } + if(input.compare(0, 2, "0o") == 0) { + val = nullptr; + errno = 0; + output_ll = std::strtoll(input.c_str() + 2, &val, 8); + if(errno == ERANGE) { + return false; + } + output = static_cast<T>(output_ll); + return (val == (input.c_str() + input.size()) && static_cast<std::int64_t>(output) == output_ll); + } + if(input.compare(0, 2, "0b") == 0) { + val = nullptr; + errno = 0; + output_ll = std::strtoll(input.c_str() + 2, &val, 2); + if(errno == ERANGE) { + return false; + } + output = static_cast<T>(output_ll); + return (val == (input.c_str() + input.size()) && static_cast<std::int64_t>(output) == output_ll); + } return false; } -/// Convert a flag into an integer value typically binary flags -inline std::int64_t to_flag_value(std::string val) { +/// Convert a flag into an integer value typically binary flags sets errno to nonzero if conversion failed +inline std::int64_t to_flag_value(std::string val) noexcept { static const std::string trueString("true"); static const std::string falseString("false"); if(val == trueString) { @@ -926,7 +988,8 @@ inline std::int64_t to_flag_value(std::string val) { ret = 1; break; default: - throw std::invalid_argument("unrecognized character"); + errno = EINVAL; + return -1; } return ret; } @@ -935,7 +998,11 @@ inline std::int64_t to_flag_value(std::string val) { } else if(val == falseString || val == "off" || val == "no" || val == "disable") { ret = -1; } else { - ret = std::stoll(val); + char *loc_ptr{nullptr}; + ret = std::strtoll(val.c_str(), &loc_ptr, 0); + if(loc_ptr != (val.c_str() + val.size()) && errno == 0) { + errno = EINVAL; + } } return ret; } @@ -964,18 +1031,16 @@ bool lexical_cast(const std::string &input, T &output) { template <typename T, enable_if_t<classify_object<T>::value == object_category::boolean_value, detail::enabler> = detail::dummy> bool lexical_cast(const std::string &input, T &output) { - try { - auto out = to_flag_value(input); + errno = 0; + auto out = to_flag_value(input); + if(errno == 0) { output = (out > 0); - return true; - } catch(const std::invalid_argument &) { - return false; - } catch(const std::out_of_range &) { - // if the number is out of the range of a 64 bit value then it is still a number and for this purpose is still - // valid all we care about the sign + } else if(errno == ERANGE) { output = (input[0] != '-'); - return true; + } else { + return false; } + return true; } /// Floats @@ -988,7 +1053,17 @@ bool lexical_cast(const std::string &input, T &output) { char *val = nullptr; auto output_ld = std::strtold(input.c_str(), &val); output = static_cast<T>(output_ld); - return val == (input.c_str() + input.size()); + if(val == (input.c_str() + input.size())) { + return true; + } + // remove separators + if(input.find_first_of("_'") != std::string::npos) { + std::string nstring = input; + nstring.erase(std::remove(nstring.begin(), nstring.end(), '_'), nstring.end()); + nstring.erase(std::remove(nstring.begin(), nstring.end(), '\''), nstring.end()); + return lexical_cast(nstring, output); + } + return false; } /// complex @@ -1309,9 +1384,7 @@ bool lexical_conversion(const std::vector<std ::string> &strings, AssignTo &outp FirstType v1; SecondType v2; bool retval = lexical_assign<FirstType, FirstType>(strings[0], v1); - if(strings.size() > 1) { - retval = retval && lexical_assign<SecondType, SecondType>(strings[1], v2); - } + retval = retval && lexical_assign<SecondType, SecondType>((strings.size() > 1) ? strings[1] : std::string{}, v2); if(retval) { output = AssignTo{v1, v2}; } @@ -1326,6 +1399,9 @@ template <class AssignTo, detail::enabler> = detail::dummy> bool lexical_conversion(const std::vector<std ::string> &strings, AssignTo &output) { output.erase(output.begin(), output.end()); + if(strings.empty()) { + return true; + } if(strings.size() == 1 && strings[0] == "{}") { return true; } @@ -1628,12 +1704,13 @@ inline std::string sum_string_vector(const std::vector<std::string> &values) { double tv{0.0}; auto comp = lexical_cast(arg, tv); if(!comp) { - try { - tv = static_cast<double>(detail::to_flag_value(arg)); - } catch(const std::exception &) { - fail = true; + errno = 0; + auto fv = detail::to_flag_value(arg); + fail = (errno != 0); + if(fail) { break; } + tv = static_cast<double>(fv); } val += tv; } @@ -1642,13 +1719,10 @@ inline std::string sum_string_vector(const std::vector<std::string> &values) { output.append(arg); } } else { - if(val <= static_cast<double>((std::numeric_limits<std::int64_t>::min)()) || - val >= static_cast<double>((std::numeric_limits<std::int64_t>::max)()) || - std::ceil(val) == std::floor(val)) { - output = detail::value_string(static_cast<int64_t>(val)); - } else { - output = detail::value_string(val); - } + std::ostringstream out; + out.precision(16); + out << val; + output = out.str(); } return output; } diff --git a/packages/CLI11/include/CLI/Validators.hpp b/packages/CLI11/include/CLI/Validators.hpp index 59d800de860e70b98005038038d77729cc895d33..bdddeb84f2ac95a303bb3a74b0b01df3e0c363bf 100644 --- a/packages/CLI11/include/CLI/Validators.hpp +++ b/packages/CLI11/include/CLI/Validators.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2024, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // @@ -218,6 +218,11 @@ class IPV4Validator : public Validator { IPV4Validator(); }; +class EscapedStringTransformer : public Validator { + public: + EscapedStringTransformer(); +}; + } // namespace detail // Static is not needed here, because global const implies static. @@ -237,6 +242,9 @@ const detail::NonexistentPathValidator NonexistentPath; /// Check for an IP4 address const detail::IPV4Validator ValidIPV4; +/// convert escaped characters into their associated values +const detail::EscapedStringTransformer EscapedString; + /// Validate the input as a particular type template <typename DesiredType> class TypeValidator : public Validator { public: diff --git a/packages/CLI11/include/CLI/Version.hpp b/packages/CLI11/include/CLI/Version.hpp index d5c817a9c3d84c2b17d40e3d61e20d57e2697bf4..7e9db02a68f722f015724b57ff42e6e1bf76d7fc 100644 --- a/packages/CLI11/include/CLI/Version.hpp +++ b/packages/CLI11/include/CLI/Version.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2024, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // @@ -9,8 +9,8 @@ // [CLI11:version_hpp:verbatim] #define CLI11_VERSION_MAJOR 2 -#define CLI11_VERSION_MINOR 3 -#define CLI11_VERSION_PATCH 2 -#define CLI11_VERSION "2.3.2" +#define CLI11_VERSION_MINOR 4 +#define CLI11_VERSION_PATCH 0 +#define CLI11_VERSION "2.4.0" // [CLI11:version_hpp:end] diff --git a/packages/CLI11/include/CLI/impl/App_inl.hpp b/packages/CLI11/include/CLI/impl/App_inl.hpp index 7d487442f924d56758e68d8d88eae1767a26daf0..ae8b5f33960021fc000c31d0f9d45a1525944836 100644 --- a/packages/CLI11/include/CLI/impl/App_inl.hpp +++ b/packages/CLI11/include/CLI/impl/App_inl.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2024, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // @@ -57,10 +57,32 @@ CLI11_INLINE App::App(std::string app_description, std::string app_name, App *pa } } +CLI11_NODISCARD CLI11_INLINE char **App::ensure_utf8(char **argv) { +#ifdef _WIN32 + (void)argv; + + normalized_argv_ = detail::compute_win32_argv(); + + if(!normalized_argv_view_.empty()) { + normalized_argv_view_.clear(); + } + + normalized_argv_view_.reserve(normalized_argv_.size()); + for(auto &arg : normalized_argv_) { + // using const_cast is well-defined, string is known to not be const. + normalized_argv_view_.push_back(const_cast<char *>(arg.data())); + } + + return normalized_argv_view_.data(); +#else + return argv; +#endif +} + CLI11_INLINE App *App::name(std::string app_name) { if(parent_ != nullptr) { - auto oname = name_; + std::string oname = name_; name_ = app_name; const auto &res = _compare_subcommand_names(*this, *_get_fallthrough_parent()); if(!res.empty()) { @@ -141,6 +163,32 @@ CLI11_INLINE Option *App::add_option(std::string option_name, if(std::find_if(std::begin(options_), std::end(options_), [&myopt](const Option_p &v) { return *v == myopt; }) == std::end(options_)) { + if(myopt.lnames_.empty() && myopt.snames_.empty()) { + // if the option is positional only there is additional potential for ambiguities in config files and needs + // to be checked + std::string test_name = "--" + myopt.get_single_name(); + if(test_name.size() == 3) { + test_name.erase(0, 1); + } + + auto *op = get_option_no_throw(test_name); + if(op != nullptr) { + throw(OptionAlreadyAdded("added option positional name matches existing option: " + test_name)); + } + } else if(parent_ != nullptr) { + for(auto &ln : myopt.lnames_) { + auto *op = parent_->get_option_no_throw(ln); + if(op != nullptr) { + throw(OptionAlreadyAdded("added option matches existing positional option: " + ln)); + } + } + for(auto &sn : myopt.snames_) { + auto *op = parent_->get_option_no_throw(sn); + if(op != nullptr) { + throw(OptionAlreadyAdded("added option matches existing positional option: " + sn)); + } + } + } options_.emplace_back(); Option_p &option = options_.back(); option.reset(new Option(option_name, option_description, option_callback, this)); @@ -315,8 +363,11 @@ CLI11_INLINE Option *App::set_config(std::string option_name, } if(!default_filename.empty()) { config_ptr_->default_str(std::move(default_filename)); + config_ptr_->force_callback_ = true; } config_ptr_->configurable(false); + // set the option to take the last value and reverse given by default + config_ptr_->multi_option_policy(MultiOptionPolicy::Reverse); } return config_ptr_; @@ -346,13 +397,14 @@ CLI11_INLINE bool App::remove_option(Option *opt) { CLI11_INLINE App *App::add_subcommand(std::string subcommand_name, std::string subcommand_description) { if(!subcommand_name.empty() && !detail::valid_name_string(subcommand_name)) { if(!detail::valid_first_char(subcommand_name[0])) { - throw IncorrectConstruction("Subcommand name starts with invalid character, '!' and '-' are not allowed"); + throw IncorrectConstruction( + "Subcommand name starts with invalid character, '!' and '-' and control characters"); } 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 ' '"); + "'=',':','{','}', ' ', and control characters"); } } } @@ -527,8 +579,12 @@ CLI11_INLINE void App::parse(std::string commandline, bool program_name_included auto args = detail::split_up(std::move(commandline)); // remove all empty strings args.erase(std::remove(args.begin(), args.end(), std::string{}), args.end()); + try { + detail::remove_quotes(args); + } catch(const std::invalid_argument &arg) { + throw CLI::ParseError(arg.what(), CLI::ExitCodes::InvalidError); + } std::reverse(args.begin(), args.end()); - parse(std::move(args)); } @@ -602,7 +658,7 @@ CLI11_INLINE int App::exit(const Error &e, std::ostream &out, std::ostream &err) } if(e.get_name() == "CallForVersion") { - out << e.what() << std::endl; + out << e.what() << '\n'; return e.get_exit_code(); } @@ -698,7 +754,8 @@ CLI11_NODISCARD CLI11_INLINE std::string App::help(std::string prev, AppFormatMo CLI11_NODISCARD CLI11_INLINE std::string App::version() const { std::string val; if(version_ptr_ != nullptr) { - auto rv = version_ptr_->results(); + // copy the results for reuse later + results_t rv = version_ptr_->results(); version_ptr_->clear(); version_ptr_->add_result("true"); try { @@ -807,7 +864,7 @@ CLI11_NODISCARD CLI11_INLINE bool App::check_name(std::string name_to_check) con if(local_name == name_to_check) { return true; } - for(auto les : aliases_) { // NOLINT(performance-for-range-copy) + for(std::string les : aliases_) { // NOLINT(performance-for-range-copy) if(ignore_underscore_) { les = detail::remove_underscore(les); } @@ -1010,34 +1067,42 @@ CLI11_NODISCARD CLI11_INLINE detail::Classifier App::_recognize(const std::strin return detail::Classifier::NONE; } +CLI11_INLINE void App::_process_config_file(const std::string &config_file, bool throw_error) { + auto path_result = detail::check_path(config_file.c_str()); + if(path_result == detail::path_type::file) { + try { + std::vector<ConfigItem> values = config_formatter_->from_file(config_file); + _parse_config(values); + } catch(const FileError &) { + if(throw_error) + throw; + } + } else if(throw_error) { + throw FileError::Missing(config_file); + } +} + CLI11_INLINE void App::_process_config_file() { if(config_ptr_ != nullptr) { bool config_required = config_ptr_->get_required(); auto file_given = config_ptr_->count() > 0; + if(!(file_given || config_ptr_->envname_.empty())) { + std::string ename_string = detail::get_environment_value(config_ptr_->envname_); + if(!ename_string.empty()) { + config_ptr_->add_result(ename_string); + } + } + config_ptr_->run_callback(); + auto config_files = config_ptr_->as<std::vector<std::string>>(); if(config_files.empty() || config_files.front().empty()) { if(config_required) { - throw FileError::Missing("no specified config file"); + throw FileError("config file is required but none was given"); } return; } - for(auto rit = config_files.rbegin(); rit != config_files.rend(); ++rit) { - const auto &config_file = *rit; - auto path_result = detail::check_path(config_file.c_str()); - if(path_result == detail::path_type::file) { - try { - std::vector<ConfigItem> values = config_formatter_->from_file(config_file); - _parse_config(values); - if(!file_given) { - config_ptr_->add_result(config_file); - } - } catch(const FileError &) { - if(config_required || file_given) - throw; - } - } else if(config_required || file_given) { - throw FileError::Missing(config_file); - } + for(const auto &config_file : config_files) { + _process_config_file(config_file, config_required || file_given); } } } @@ -1045,32 +1110,24 @@ CLI11_INLINE void App::_process_config_file() { CLI11_INLINE void App::_process_env() { for(const Option_p &opt : options_) { if(opt->count() == 0 && !opt->envname_.empty()) { - char *buffer = nullptr; - std::string ename_string; - -#ifdef _MSC_VER - // Windows version - std::size_t sz = 0; - if(_dupenv_s(&buffer, &sz, opt->envname_.c_str()) == 0 && buffer != nullptr) { - ename_string = std::string(buffer); - free(buffer); - } -#else - // This also works on Windows, but gives a warning - buffer = std::getenv(opt->envname_.c_str()); - if(buffer != nullptr) - ename_string = std::string(buffer); -#endif - + std::string ename_string = detail::get_environment_value(opt->envname_); if(!ename_string.empty()) { - opt->add_result(ename_string); + std::string result = ename_string; + result = opt->_validate(result, 0); + if(result.empty()) { + opt->add_result(ename_string); + } } } } for(App_p &sub : subcommands_) { - if(sub->get_name().empty() || !sub->parse_complete_callback_) - sub->_process_env(); + if(sub->get_name().empty() || !sub->parse_complete_callback_) { + if(sub->count_all() > 0) { + // only process environment variables if the callback has actually been triggered already + sub->_process_env(); + } + } } } @@ -1370,12 +1427,11 @@ CLI11_INLINE void App::_parse_config(const std::vector<ConfigItem> &args) { } CLI11_INLINE bool App::_parse_single_config(const ConfigItem &item, std::size_t level) { + if(level < item.parents.size()) { try { auto *subcom = get_subcommand(item.parents.at(level)); - auto result = subcom->_parse_single_config(item, level + 1); - - return result; + return subcom->_parse_single_config(item, level + 1); } catch(const OptionNotFound &) { return false; } @@ -1405,10 +1461,11 @@ CLI11_INLINE bool App::_parse_single_config(const ConfigItem &item, std::size_t if(item.name.size() == 1) { op = get_option_no_throw("-" + item.name); } + if(op == nullptr) { + op = get_option_no_throw(item.name); + } } - if(op == nullptr) { - op = get_option_no_throw(item.name); - } + if(op == nullptr) { // If the option was not present if(get_allow_config_extras() == config_extras_mode::capture) @@ -1435,29 +1492,54 @@ CLI11_INLINE bool App::_parse_single_config(const ConfigItem &item, std::size_t auto res = config_formatter_->to_flag(item); bool converted{false}; if(op->get_disable_flag_override()) { - - try { - auto val = detail::to_flag_value(res); - if(val == 1) { - res = op->get_flag_value(item.name, "{}"); - converted = true; - } - } catch(...) { + auto val = detail::to_flag_value(res); + if(val == 1) { + res = op->get_flag_value(item.name, "{}"); + converted = true; } } if(!converted) { + errno = 0; res = op->get_flag_value(item.name, res); } op->add_result(res); return true; } - if(static_cast<int>(item.inputs.size()) > op->get_items_expected_max()) { + if(static_cast<int>(item.inputs.size()) > op->get_items_expected_max() && + op->get_multi_option_policy() != MultiOptionPolicy::TakeAll) { if(op->get_items_expected_max() > 1) { throw ArgumentMismatch::AtMost(item.fullname(), op->get_items_expected_max(), item.inputs.size()); } - throw ConversionError::TooManyInputsFlag(item.fullname()); + + if(!op->get_disable_flag_override()) { + throw ConversionError::TooManyInputsFlag(item.fullname()); + } + // if the disable flag override is set then we must have the flag values match a known flag value + // this is true regardless of the output value, so an array input is possible and must be accounted for + for(const auto &res : item.inputs) { + bool valid_value{false}; + if(op->default_flag_values_.empty()) { + if(res == "true" || res == "false" || res == "1" || res == "0") { + valid_value = true; + } + } else { + for(const auto &valid_res : op->default_flag_values_) { + if(valid_res.second == res) { + valid_value = true; + break; + } + } + } + + if(valid_value) { + op->add_result(res); + } else { + throw InvalidError("invalid flag argument given"); + } + } + return true; } } op->add_result(item.inputs); @@ -1492,7 +1574,7 @@ CLI11_INLINE bool App::_parse_single(std::vector<std::string> &args, bool &posit case detail::Classifier::SHORT: case detail::Classifier::WINDOWS_STYLE: // If already parsed a subcommand, don't accept options_ - _parse_arg(args, classifier, false); + retval = _parse_arg(args, classifier, false); break; case detail::Classifier::NONE: // Probably a positional or something for a parent (sub)command @@ -1534,6 +1616,7 @@ CLI11_NODISCARD CLI11_INLINE bool App::_has_remaining_positionals() const { CLI11_INLINE bool App::_parse_positional(std::vector<std::string> &args, bool haltOnSubcommand) { const std::string &positional = args.back(); + Option *posOpt{nullptr}; if(positionals_at_end_) { // deal with the case of required arguments at the end which should take precedence over other arguments @@ -1550,56 +1633,47 @@ CLI11_INLINE bool App::_parse_positional(std::vector<std::string> &args, bool ha continue; } } - - parse_order_.push_back(opt.get()); - /// if we require a separator add it here - if(opt->get_inject_separator()) { - if(!opt->results().empty() && !opt->results().back().empty()) { - opt->add_result(std::string{}); - } - } - if(opt->get_trigger_on_parse() && - opt->current_option_state_ == Option::option_state::callback_run) { - opt->clear(); - } - opt->add_result(positional); - if(opt->get_trigger_on_parse()) { - opt->run_callback(); - } - args.pop_back(); - return true; + posOpt = opt.get(); + break; } } } } } - for(const Option_p &opt : options_) { - // Eat options, one by one, until done - if(opt->get_positional() && - (static_cast<int>(opt->count()) < opt->get_items_expected_min() || opt->get_allow_extra_args())) { - if(validate_positionals_) { - std::string pos = positional; - pos = opt->_validate(pos, 0); - if(!pos.empty()) { - continue; - } - } - if(opt->get_inject_separator()) { - if(!opt->results().empty() && !opt->results().back().empty()) { - opt->add_result(std::string{}); + if(posOpt == nullptr) { + for(const Option_p &opt : options_) { + // Eat options, one by one, until done + if(opt->get_positional() && + (static_cast<int>(opt->count()) < opt->get_items_expected_min() || opt->get_allow_extra_args())) { + if(validate_positionals_) { + std::string pos = positional; + pos = opt->_validate(pos, 0); + if(!pos.empty()) { + continue; + } } + posOpt = opt.get(); + break; } - if(opt->get_trigger_on_parse() && opt->current_option_state_ == Option::option_state::callback_run) { - opt->clear(); - } - opt->add_result(positional); - if(opt->get_trigger_on_parse()) { - opt->run_callback(); + } + } + if(posOpt != nullptr) { + parse_order_.push_back(posOpt); + if(posOpt->get_inject_separator()) { + if(!posOpt->results().empty() && !posOpt->results().back().empty()) { + posOpt->add_result(std::string{}); } - parse_order_.push_back(opt.get()); - args.pop_back(); - return true; } + if(posOpt->get_trigger_on_parse() && posOpt->current_option_state_ == Option::option_state::callback_run) { + posOpt->clear(); + } + posOpt->add_result(positional); + if(posOpt->get_trigger_on_parse()) { + posOpt->run_callback(); + } + + args.pop_back(); + return true; } for(auto &subc : subcommands_) { @@ -1942,7 +2016,7 @@ CLI11_INLINE void App::_trigger_pre_parse(std::size_t remaining_args) { } else if(immediate_callback_) { if(!name_.empty()) { auto pcnt = parsed_; - auto extras = std::move(missing_); + missing_t extras = std::move(missing_); clear(); parsed_ = pcnt; pre_parse_called_ = true; @@ -2128,12 +2202,12 @@ CLI11_INLINE void retire_option(App *app, Option *opt) { ->allow_extra_args(opt->get_allow_extra_args()); app->remove_option(opt); - auto *opt2 = app->add_option(option_copy->get_name(false, true), "option has been retired and has no effect") - ->type_name("RETIRED") - ->default_str("RETIRED") - ->type_size(option_copy->get_type_size_min(), option_copy->get_type_size_max()) - ->expected(option_copy->get_expected_min(), option_copy->get_expected_max()) - ->allow_extra_args(option_copy->get_allow_extra_args()); + auto *opt2 = app->add_option(option_copy->get_name(false, true), "option has been retired and has no effect"); + opt2->type_name("RETIRED") + ->default_str("RETIRED") + ->type_size(option_copy->get_type_size_min(), option_copy->get_type_size_max()) + ->expected(option_copy->get_expected_min(), option_copy->get_expected_max()) + ->allow_extra_args(option_copy->get_allow_extra_args()); Validator retired_warning{[opt2](std::string &) { std::cout << "WARNING " << opt2->get_name() << " is retired and has no effect\n"; diff --git a/packages/CLI11/include/CLI/impl/Argv_inl.hpp b/packages/CLI11/include/CLI/impl/Argv_inl.hpp index 3d00a570d1e5032ed72bd4a255c8e60be50934d2..620f1fb73faa35590f43cb9045605ccd54745aef 100644 --- a/packages/CLI11/include/CLI/impl/Argv_inl.hpp +++ b/packages/CLI11/include/CLI/impl/Argv_inl.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2024, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // @@ -29,6 +29,10 @@ #define _X86_ #elif defined(__arm__) || defined(_M_ARM) || defined(_M_ARMT) #define _ARM_ +#elif defined(__aarch64__) || defined(_M_ARM64) +#define _ARM64_ +#elif defined(_M_ARM64EC) +#define _ARM64EC_ #endif #endif @@ -85,6 +89,29 @@ static const std::vector<const char *> static_args = [] { }(); #endif +#ifdef _WIN32 +CLI11_INLINE std::vector<std::string> compute_win32_argv() { + std::vector<std::string> result; + int argc = 0; + + auto deleter = [](wchar_t **ptr) { LocalFree(ptr); }; + // NOLINTBEGIN(*-avoid-c-arrays) + auto wargv = std::unique_ptr<wchar_t *[], decltype(deleter)>(CommandLineToArgvW(GetCommandLineW(), &argc), deleter); + // NOLINTEND(*-avoid-c-arrays) + + if(wargv == nullptr) { + throw std::runtime_error("CommandLineToArgvW failed with code " + std::to_string(GetLastError())); + } + + result.reserve(static_cast<size_t>(argc)); + for(size_t i = 0; i < static_cast<size_t>(argc); ++i) { + result.push_back(narrow(wargv[i])); + } + + return result; +} +#endif + /// Command-line arguments, as passed in to this executable, converted to utf-8 on Windows. CLI11_INLINE const std::vector<const char *> &args() { // This function uses initialization via lambdas extensively to take advantage of the thread safety of static @@ -92,28 +119,7 @@ CLI11_INLINE const std::vector<const char *> &args() { #ifdef _WIN32 static const std::vector<const char *> static_args = [] { - static const std::vector<std::string> static_args_as_strings = [] { - // On Windows, take arguments from GetCommandLineW and convert them to utf-8. - std::vector<std::string> args_as_strings; - int argc = 0; - - auto deleter = [](wchar_t **ptr) { LocalFree(ptr); }; - // NOLINTBEGIN(*-avoid-c-arrays) - auto wargv = - std::unique_ptr<wchar_t *[], decltype(deleter)>(CommandLineToArgvW(GetCommandLineW(), &argc), deleter); - // NOLINTEND(*-avoid-c-arrays) - - if(wargv == nullptr) { - throw std::runtime_error("CommandLineToArgvW failed with code " + std::to_string(GetLastError())); - } - - args_as_strings.reserve(static_cast<size_t>(argc)); - for(size_t i = 0; i < static_cast<size_t>(argc); ++i) { - args_as_strings.push_back(narrow(wargv[i])); - } - - return args_as_strings; - }(); + static const std::vector<std::string> static_args_as_strings = compute_win32_argv(); std::vector<const char *> static_args_result; static_args_result.reserve(static_args_as_strings.size()); diff --git a/packages/CLI11/include/CLI/impl/Config_inl.hpp b/packages/CLI11/include/CLI/impl/Config_inl.hpp index 8021d5f63aa34e1cfeac43e37b274829c49a7dd1..92537c0e8c2ad4cb9c40abe859c289af169cdc04 100644 --- a/packages/CLI11/include/CLI/impl/Config_inl.hpp +++ b/packages/CLI11/include/CLI/impl/Config_inl.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2024, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // @@ -19,9 +19,19 @@ namespace CLI { // [CLI11:config_inl_hpp:verbatim] +static constexpr auto multiline_literal_quote = R"(''')"; +static constexpr auto multiline_string_quote = R"(""")"; + namespace detail { -CLI11_INLINE std::string convert_arg_for_ini(const std::string &arg, char stringQuote, char characterQuote) { +CLI11_INLINE bool is_printable(const std::string &test_string) { + return std::all_of(test_string.begin(), test_string.end(), [](char x) { + return (isprint(static_cast<unsigned char>(x)) != 0 || x == '\n' || x == '\t'); + }); +} + +CLI11_INLINE std::string +convert_arg_for_ini(const std::string &arg, char stringQuote, char literalQuote, bool disable_multi_line) { if(arg.empty()) { return std::string(2, stringQuote); } @@ -34,12 +44,20 @@ CLI11_INLINE std::string convert_arg_for_ini(const std::string &arg, char string using CLI::detail::lexical_cast; double val = 0.0; if(lexical_cast(arg, val)) { - return arg; + if(arg.find_first_not_of("0123456789.-+eE") == std::string::npos) { + return arg; + } } } // just quote a single non numeric character if(arg.size() == 1) { - return std::string(1, characterQuote) + arg + characterQuote; + if(isprint(static_cast<unsigned char>(arg.front())) == 0) { + return binary_escape_string(arg); + } + if(arg == "'") { + return std::string(1, stringQuote) + "'" + stringQuote; + } + return std::string(1, literalQuote) + arg + literalQuote; } // handle hex, binary or octal arguments if(arg.front() == '0') { @@ -59,10 +77,16 @@ CLI11_INLINE std::string convert_arg_for_ini(const std::string &arg, char string } } } - if(arg.find_first_of(stringQuote) == std::string::npos) { - return std::string(1, stringQuote) + arg + stringQuote; + if(!is_printable(arg)) { + return binary_escape_string(arg); } - return characterQuote + arg + characterQuote; + if(detail::has_escapable_character(arg)) { + if(arg.size() > 100 && !disable_multi_line) { + return std::string(multiline_literal_quote) + arg + multiline_literal_quote; + } + return std::string(1, stringQuote) + detail::add_escaped_characters(arg) + stringQuote; + } + return std::string(1, stringQuote) + arg + stringQuote; } CLI11_INLINE std::string ini_join(const std::vector<std::string> &args, @@ -70,10 +94,12 @@ CLI11_INLINE std::string ini_join(const std::vector<std::string> &args, char arrayStart, char arrayEnd, char stringQuote, - char characterQuote) { + char literalQuote) { + bool disable_multi_line{false}; std::string joined; if(args.size() > 1 && arrayStart != '\0') { joined.push_back(arrayStart); + disable_multi_line = true; } std::size_t start = 0; for(const auto &arg : args) { @@ -83,7 +109,7 @@ CLI11_INLINE std::string ini_join(const std::vector<std::string> &args, joined.push_back(' '); } } - joined.append(convert_arg_for_ini(arg, stringQuote, characterQuote)); + joined.append(convert_arg_for_ini(arg, stringQuote, literalQuote, disable_multi_line)); } if(args.size() > 1 && arrayEnd != '\0') { joined.push_back(arrayEnd); @@ -96,22 +122,22 @@ generate_parents(const std::string §ion, std::string &name, char parentSepar std::vector<std::string> parents; if(detail::to_lower(section) != "default") { if(section.find(parentSeparator) != std::string::npos) { - parents = detail::split(section, parentSeparator); + parents = detail::split_up(section, parentSeparator); } else { parents = {section}; } } if(name.find(parentSeparator) != std::string::npos) { - std::vector<std::string> plist = detail::split(name, parentSeparator); + std::vector<std::string> plist = detail::split_up(name, parentSeparator); name = plist.back(); - detail::remove_quotes(name); plist.pop_back(); parents.insert(parents.end(), plist.begin(), plist.end()); } - // clean up quotes on the parents - for(auto &parent : parents) { - detail::remove_quotes(parent); + try { + detail::remove_quotes(parents); + } catch(const std::invalid_argument &iarg) { + throw CLI::ParseError(iarg.what(), CLI::ExitCodes::InvalidError); } return parents; } @@ -164,30 +190,59 @@ checkParentSegments(std::vector<ConfigItem> &output, const std::string ¤tS output.back().parents = std::move(parents); output.back().name = "++"; } + +/// @brief checks if a string represents a multiline comment +CLI11_INLINE bool hasMLString(std::string const &fullString, char check) { + if(fullString.length() < 3) { + return false; + } + auto it = fullString.rbegin(); + return (*it == check) && (*(it + 1) == check) && (*(it + 2) == check); +} } // namespace detail inline std::vector<ConfigItem> ConfigBase::from_config(std::istream &input) const { std::string line; + std::string buffer; 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}; + bool inMLineComment{false}; + bool inMLineValue{false}; + char aStart = (isINIArray) ? '[' : arrayStart; char aEnd = (isINIArray) ? ']' : arrayEnd; char aSep = (isINIArray && arraySeparator == ' ') ? ',' : arraySeparator; int currentSectionIndex{0}; - while(getline(input, line)) { + + std::string line_sep_chars{parentSeparatorChar, commentChar, valueDelimiter}; + while(getline(input, buffer)) { std::vector<std::string> items_buffer; std::string name; - - detail::trim(line); + line = detail::trim_copy(buffer); std::size_t len = line.length(); // lines have to be at least 3 characters to have any meaning to CLI just skip the rest if(len < 3) { continue; } + if(line.compare(0, 3, multiline_string_quote) == 0 || line.compare(0, 3, multiline_literal_quote) == 0) { + inMLineComment = true; + auto cchar = line.front(); + while(inMLineComment) { + if(getline(input, line)) { + detail::trim(line); + } else { + break; + } + if(detail::hasMLString(line, cchar)) { + inMLineComment = false; + } + } + continue; + } if(line.front() == '[' && line.back() == ']') { if(currentSection != "default") { // insert a section end which is just an empty items_buffer @@ -219,49 +274,130 @@ inline std::vector<ConfigItem> ConfigBase::from_config(std::istream &input) cons if(line.front() == ';' || line.front() == '#' || line.front() == commentChar) { continue; } - + std::size_t search_start = 0; + if(line.find_first_of("\"'`") != std::string::npos) { + while(search_start < line.size()) { + auto test_char = line[search_start]; + if(test_char == '\"' || test_char == '\'' || test_char == '`') { + search_start = detail::close_sequence(line, search_start, line[search_start]); + ++search_start; + } else if(test_char == valueDelimiter || test_char == commentChar) { + --search_start; + break; + } else if(test_char == ' ' || test_char == '\t' || test_char == parentSeparatorChar) { + ++search_start; + } else { + search_start = line.find_first_of(line_sep_chars, search_start); + } + } + } // Find = in string, split and recombine - auto pos = line.find(valueDelimiter); - 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); // NOLINT(readability-suspicious-call-argument) - detail::trim(item); + auto delimiter_pos = line.find_first_of(valueDelimiter, search_start + 1); + auto comment_pos = line.find_first_of(commentChar, search_start); + if(comment_pos < delimiter_pos) { + delimiter_pos = std::string::npos; + } + if(delimiter_pos != std::string::npos) { + + name = detail::trim_copy(line.substr(0, delimiter_pos)); + std::string item = detail::trim_copy(line.substr(delimiter_pos + 1, std::string::npos)); + bool mlquote = + (item.compare(0, 3, multiline_literal_quote) == 0 || item.compare(0, 3, multiline_string_quote) == 0); + if(!mlquote && comment_pos != std::string::npos) { + auto citems = detail::split_up(item, commentChar); + item = detail::trim_copy(citems.front()); } - if(item.size() > 1 && item.front() == aStart) { + if(mlquote) { + // mutliline string + auto keyChar = item.front(); + item = buffer.substr(delimiter_pos + 1, std::string::npos); + detail::ltrim(item); + item.erase(0, 3); + inMLineValue = true; + bool lineExtension{false}; + bool firstLine = true; + if(!item.empty() && item.back() == '\\') { + item.pop_back(); + lineExtension = true; + } + while(inMLineValue) { + std::string l2; + if(!std::getline(input, l2)) { + break; + } + line = l2; + detail::rtrim(line); + if(detail::hasMLString(line, keyChar)) { + line.pop_back(); + line.pop_back(); + line.pop_back(); + if(lineExtension) { + detail::ltrim(line); + } else if(!(firstLine && item.empty())) { + item.push_back('\n'); + } + firstLine = false; + item += line; + inMLineValue = false; + if(!item.empty() && item.back() == '\n') { + item.pop_back(); + } + if(keyChar == '\"') { + try { + item = detail::remove_escaped_characters(item); + } catch(const std::invalid_argument &iarg) { + throw CLI::ParseError(iarg.what(), CLI::ExitCodes::InvalidError); + } + } + } else { + if(lineExtension) { + detail::trim(l2); + } else if(!(firstLine && item.empty())) { + item.push_back('\n'); + } + lineExtension = false; + firstLine = false; + if(!l2.empty() && l2.back() == '\\') { + lineExtension = true; + l2.pop_back(); + } + item += l2; + } + } + items_buffer = {item}; + } else if(item.size() > 1 && item.front() == aStart) { for(std::string multiline; item.back() != aEnd && std::getline(input, multiline);) { detail::trim(multiline); item += multiline; } - items_buffer = detail::split_up(item.substr(1, item.length() - 2), aSep); + if(item.back() == aEnd) { + items_buffer = detail::split_up(item.substr(1, item.length() - 2), aSep); + } else { + items_buffer = detail::split_up(item.substr(1, std::string::npos), aSep); + } } else if((isDefaultArray || isINIArray) && item.find_first_of(aSep) != std::string::npos) { items_buffer = detail::split_up(item, aSep); } else if((isDefaultArray || isINIArray) && item.find_first_of(' ') != std::string::npos) { - items_buffer = detail::split_up(item); + items_buffer = detail::split_up(item, '\0'); } else { items_buffer = {item}; } } else { - name = detail::trim_copy(line); - auto cloc = name.find(commentChar); - if(cloc != std::string::npos) { - name.erase(cloc, std::string::npos); // NOLINT(readability-suspicious-call-argument) - detail::trim(name); - } - + name = detail::trim_copy(line.substr(0, comment_pos)); items_buffer = {"true"}; } - if(name.find(parentSeparatorChar) == std::string::npos) { - detail::remove_quotes(name); - } - // clean up quotes on the items - for(auto &it : items_buffer) { - detail::remove_quotes(it); + std::vector<std::string> parents; + try { + parents = detail::generate_parents(currentSection, name, parentSeparatorChar); + detail::process_quoted_string(name); + // clean up quotes on the items and check for escaped strings + for(auto &it : items_buffer) { + detail::process_quoted_string(it, stringQuote, literalQuote); + } + } catch(const std::invalid_argument &ia) { + throw CLI::ParseError(ia.what(), CLI::ExitCodes::InvalidError); } - std::vector<std::string> parents = detail::generate_parents(currentSection, name, parentSeparatorChar); if(parents.size() > maximumLayers) { continue; } @@ -298,6 +434,23 @@ inline std::vector<ConfigItem> ConfigBase::from_config(std::istream &input) cons return output; } +CLI11_INLINE std::string &clean_name_string(std::string &name, const std::string &keyChars) { + if(name.find_first_of(keyChars) != std::string::npos || (name.front() == '[' && name.back() == ']') || + (name.find_first_of("'`\"\\") != std::string::npos)) { + if(name.find_first_of('\'') == std::string::npos) { + name.insert(0, 1, '\''); + name.push_back('\''); + } else { + if(detail::has_escapable_character(name)) { + name = detail::add_escaped_characters(name); + } + name.insert(0, 1, '\"'); + name.push_back('\"'); + } + } + return name; +} + CLI11_INLINE std::string ConfigBase::to_config(const App *app, bool default_also, bool write_description, std::string prefix) const { std::stringstream out; @@ -305,6 +458,18 @@ ConfigBase::to_config(const App *app, bool default_also, bool write_description, commentLead.push_back(commentChar); commentLead.push_back(' '); + std::string commentTest = "#;"; + commentTest.push_back(commentChar); + commentTest.push_back(parentSeparatorChar); + + std::string keyChars = commentTest; + keyChars.push_back(literalQuote); + keyChars.push_back(stringQuote); + keyChars.push_back(arrayStart); + keyChars.push_back(arrayEnd); + keyChars.push_back(valueDelimiter); + keyChars.push_back(arraySeparator); + std::vector<std::string> groups = app->get_groups(); bool defaultUsed = false; groups.insert(groups.begin(), std::string("Options")); @@ -330,13 +495,17 @@ ConfigBase::to_config(const App *app, bool default_also, bool write_description, continue; } } - std::string name = prefix + opt->get_single_name(); + std::string single_name = opt->get_single_name(); + if(single_name.empty()) { + continue; + } + std::string value = detail::ini_join( - opt->reduced_results(), arraySeparator, arrayStart, arrayEnd, stringQuote, characterQuote); + opt->reduced_results(), arraySeparator, arrayStart, arrayEnd, stringQuote, literalQuote); if(value.empty() && default_also) { if(!opt->get_default_str().empty()) { - value = detail::convert_arg_for_ini(opt->get_default_str(), stringQuote, characterQuote); + value = detail::convert_arg_for_ini(opt->get_default_str(), stringQuote, literalQuote, false); } else if(opt->get_expected_min() == 0) { value = "false"; } else if(opt->get_run_callback_for_default()) { @@ -345,13 +514,35 @@ ConfigBase::to_config(const App *app, bool default_also, bool write_description, } if(!value.empty()) { + if(!opt->get_fnames().empty()) { - value = opt->get_flag_value(name, value); + try { + value = opt->get_flag_value(single_name, value); + } catch(const CLI::ArgumentMismatch &) { + bool valid{false}; + for(const auto &test_name : opt->get_fnames()) { + try { + value = opt->get_flag_value(test_name, value); + single_name = test_name; + valid = true; + } catch(const CLI::ArgumentMismatch &) { + continue; + } + } + if(!valid) { + value = detail::ini_join( + opt->results(), arraySeparator, arrayStart, arrayEnd, stringQuote, literalQuote); + } + } } if(write_description && opt->has_description()) { out << '\n'; out << commentLead << detail::fix_newlines(commentLead, opt->get_description()) << '\n'; } + clean_name_string(single_name, keyChars); + + std::string name = prefix + single_name; + out << name << valueDelimiter << value << '\n'; } } @@ -360,31 +551,56 @@ ConfigBase::to_config(const App *app, bool default_also, bool write_description, auto subcommands = app->get_subcommands({}); for(const App *subcom : subcommands) { if(subcom->get_name().empty()) { + if(!default_also && (subcom->count_all() == 0)) { + continue; + } if(write_description && !subcom->get_group().empty()) { out << '\n' << commentLead << subcom->get_group() << " Options\n"; } + /*if (!prefix.empty() || app->get_parent() == nullptr) { + out << '[' << prefix << "___"<< subcom->get_group() << "]\n"; + } else { + std::string subname = app->get_name() + parentSeparatorChar + "___"+subcom->get_group(); + const auto *p = app->get_parent(); + while(p->get_parent() != nullptr) { + subname = p->get_name() + parentSeparatorChar +subname; + p = p->get_parent(); + } + out << '[' << subname << "]\n"; + } + */ out << to_config(subcom, default_also, write_description, prefix); } } for(const App *subcom : subcommands) { if(!subcom->get_name().empty()) { + if(!default_also && (subcom->count_all() == 0)) { + continue; + } + std::string subname = subcom->get_name(); + clean_name_string(subname, keyChars); + if(subcom->get_configurable() && app->got_subcommand(subcom)) { if(!prefix.empty() || app->get_parent() == nullptr) { - out << '[' << prefix << subcom->get_name() << "]\n"; + + out << '[' << prefix << subname << "]\n"; } else { - std::string subname = app->get_name() + parentSeparatorChar + subcom->get_name(); + std::string appname = app->get_name(); + clean_name_string(appname, keyChars); + subname = appname + parentSeparatorChar + subname; const auto *p = app->get_parent(); while(p->get_parent() != nullptr) { - subname = p->get_name() + parentSeparatorChar + subname; + std::string pname = p->get_name(); + clean_name_string(pname, keyChars); + subname = pname + 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() + parentSeparatorChar); + out << to_config(subcom, default_also, write_description, prefix + subname + parentSeparatorChar); } } } diff --git a/packages/CLI11/include/CLI/impl/Encoding_inl.hpp b/packages/CLI11/include/CLI/impl/Encoding_inl.hpp index f5d7e9a83fbb42f7b47408cb02b67c9be48d8b3c..1c82f4fc57b11711202628cf7d9a249a386bed65 100644 --- a/packages/CLI11/include/CLI/impl/Encoding_inl.hpp +++ b/packages/CLI11/include/CLI/impl/Encoding_inl.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2024, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // diff --git a/packages/CLI11/include/CLI/impl/Formatter_inl.hpp b/packages/CLI11/include/CLI/impl/Formatter_inl.hpp index 84652fefa4aebcb2ef5257c887d0e5494d40c5f1..3b1c8c288882326cebc31adcf82c0b7a266946f4 100644 --- a/packages/CLI11/include/CLI/impl/Formatter_inl.hpp +++ b/packages/CLI11/include/CLI/impl/Formatter_inl.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2024, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // @@ -132,7 +132,7 @@ CLI11_INLINE std::string Formatter::make_usage(const App *app, std::string name) << (app->get_require_subcommand_min() == 0 ? "]" : ""); } - out << std::endl; + out << '\n'; return out.str(); } diff --git a/packages/CLI11/include/CLI/impl/Option_inl.hpp b/packages/CLI11/include/CLI/impl/Option_inl.hpp index a24df9ab293700a31bba830581247d5b01d5b412..aa6f4657d8ff8987d3ab4fe2e09d0bc7d1c2f65b 100644 --- a/packages/CLI11/include/CLI/impl/Option_inl.hpp +++ b/packages/CLI11/include/CLI/impl/Option_inl.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2024, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // @@ -309,13 +309,29 @@ CLI11_INLINE void Option::run_callback() { CLI11_NODISCARD CLI11_INLINE const std::string &Option::matching_name(const Option &other) const { static const std::string estring; - for(const std::string &sname : snames_) + for(const std::string &sname : snames_) { if(other.check_sname(sname)) return sname; - for(const std::string &lname : lnames_) + if(other.check_lname(sname)) + return sname; + } + for(const std::string &lname : lnames_) { if(other.check_lname(lname)) return lname; - + if(lname.size() == 1) { + if(other.check_sname(lname)) { + return lname; + } + } + } + if(snames_.empty() && lnames_.empty() && !pname_.empty()) { + if(other.check_sname(pname_) || other.check_lname(pname_) || pname_ == other.pname_) + return pname_; + } + if(other.snames_.empty() && other.fnames_.empty() && !other.pname_.empty()) { + if(check_sname(other.pname_) || check_lname(other.pname_) || (pname_ == other.pname_)) + return other.pname_; + } if(ignore_case_ || ignore_underscore_) { // We need to do the inverse, in case we are ignore_case or ignore underscore for(const std::string &sname : other.snames_) @@ -369,6 +385,9 @@ CLI11_NODISCARD CLI11_INLINE std::string Option::get_flag_value(const std::strin if(default_ind >= 0) { // We can static cast this to std::size_t because it is more than 0 in this block if(default_flag_values_[static_cast<std::size_t>(default_ind)].second != input_value) { + if(input_value == default_str_ && force_callback_) { + return input_value; + } throw(ArgumentMismatch::FlagOverride(name)); } } else { @@ -389,15 +408,15 @@ CLI11_NODISCARD CLI11_INLINE std::string Option::get_flag_value(const std::strin return input_value; } if(default_flag_values_[static_cast<std::size_t>(ind)].second == falseString) { - try { - auto val = detail::to_flag_value(input_value); - return (val == 1) ? falseString : (val == (-1) ? trueString : std::to_string(-val)); - } catch(const std::invalid_argument &) { + errno = 0; + auto val = detail::to_flag_value(input_value); + if(errno != 0) { + errno = 0; return input_value; } - } else { - return input_value; + return (val == 1) ? falseString : (val == (-1) ? trueString : std::to_string(-val)); } + return input_value; } CLI11_INLINE Option *Option::add_result(std::string s) { @@ -500,7 +519,8 @@ CLI11_INLINE void Option::_validate_results(results_t &res) const { if(type_size_max_ > 1) { // in this context index refers to the index in the type int index = 0; if(get_items_expected_max() < static_cast<int>(res.size()) && - multi_option_policy_ == CLI::MultiOptionPolicy::TakeLast) { + (multi_option_policy_ == CLI::MultiOptionPolicy::TakeLast || + multi_option_policy_ == CLI::MultiOptionPolicy::Reverse)) { // create a negative index for the earliest ones index = get_items_expected_max() - static_cast<int>(res.size()); } @@ -518,7 +538,8 @@ CLI11_INLINE void Option::_validate_results(results_t &res) const { } else { int index = 0; if(expected_max_ < static_cast<int>(res.size()) && - multi_option_policy_ == CLI::MultiOptionPolicy::TakeLast) { + (multi_option_policy_ == CLI::MultiOptionPolicy::TakeLast || + multi_option_policy_ == CLI::MultiOptionPolicy::Reverse)) { // create a negative index for the earliest ones index = expected_max_ - static_cast<int>(res.size()); } @@ -550,6 +571,15 @@ CLI11_INLINE void Option::_reduce_results(results_t &out, const results_t &origi out.assign(original.end() - static_cast<results_t::difference_type>(trim_size), original.end()); } } break; + case MultiOptionPolicy::Reverse: { + // Allow multi-option sizes (including 0) + std::size_t trim_size = std::min<std::size_t>( + static_cast<std::size_t>(std::max<int>(get_items_expected_max(), 1)), original.size()); + if(original.size() != trim_size || trim_size > 1) { + out.assign(original.end() - static_cast<results_t::difference_type>(trim_size), original.end()); + } + std::reverse(out.begin(), out.end()); + } break; case MultiOptionPolicy::TakeFirst: { std::size_t trim_size = std::min<std::size_t>( static_cast<std::size_t>(std::max<int>(get_items_expected_max(), 1)), original.size()); @@ -579,7 +609,12 @@ CLI11_INLINE void Option::_reduce_results(results_t &out, const results_t &origi throw ArgumentMismatch::AtLeast(get_name(), static_cast<int>(num_min), original.size()); } if(original.size() > num_max) { - throw ArgumentMismatch::AtMost(get_name(), static_cast<int>(num_max), original.size()); + if(original.size() == 2 && num_max == 1 && original[1] == "%%" && original[0] == "{}") { + // this condition is a trap for the following empty indicator check on config files + out = original; + } else { + throw ArgumentMismatch::AtMost(get_name(), static_cast<int>(num_max), original.size()); + } } break; } @@ -588,11 +623,11 @@ CLI11_INLINE void Option::_reduce_results(results_t &out, const results_t &origi // {} is the indicator for an empty container if(out.empty()) { if(original.size() == 1 && original[0] == "{}" && get_items_expected_min() > 0) { - out.push_back("{}"); - out.push_back("%%"); + out.emplace_back("{}"); + out.emplace_back("%%"); } } else if(out.size() == 1 && out[0] == "{}" && get_items_expected_min() > 0) { - out.push_back("%%"); + out.emplace_back("%%"); } } diff --git a/packages/CLI11/include/CLI/impl/Split_inl.hpp b/packages/CLI11/include/CLI/impl/Split_inl.hpp index d974f80a6f7618de7a8fb2cdf2ddade30cb48fc7..7cd8e5b3705673f4d927392c69581ff2e00cc45c 100644 --- a/packages/CLI11/include/CLI/impl/Split_inl.hpp +++ b/packages/CLI11/include/CLI/impl/Split_inl.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2024, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // @@ -106,7 +106,6 @@ get_names(const std::vector<std::string> &input) { std::vector<std::string> short_names; std::vector<std::string> long_names; std::string pos_name; - for(std::string name : input) { if(name.length() == 0) { continue; @@ -114,6 +113,8 @@ get_names(const std::vector<std::string> &input) { if(name.length() > 1 && name[0] == '-' && name[1] != '-') { if(name.length() == 2 && valid_first_char(name[1])) short_names.emplace_back(1, name[1]); + else if(name.length() > 2) + throw BadNameString::MissingDash(name); else throw BadNameString::OneCharName(name); } else if(name.length() > 2 && name.substr(0, 2) == "--") { @@ -125,12 +126,15 @@ get_names(const std::vector<std::string> &input) { } else if(name == "-" || name == "--") { throw BadNameString::DashesOnly(name); } else { - if(pos_name.length() > 0) + if(!pos_name.empty()) throw BadNameString::MultiPositionalNames(name); - pos_name = name; + if(valid_name_string(name)) { + pos_name = name; + } else { + throw BadNameString::BadPositionalName(name); + } } } - return std::make_tuple(short_names, long_names, pos_name); } diff --git a/packages/CLI11/include/CLI/impl/StringTools_inl.hpp b/packages/CLI11/include/CLI/impl/StringTools_inl.hpp index 9b81fbde3475b03f67bc1f4330b6efdfe0d87d93..5a120c39e6f55685853bffa2f6ba110642002545 100644 --- a/packages/CLI11/include/CLI/impl/StringTools_inl.hpp +++ b/packages/CLI11/include/CLI/impl/StringTools_inl.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2024, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // @@ -11,6 +11,7 @@ // [CLI11:public_includes:set] #include <string> +#include <utility> #include <vector> // [CLI11:public_includes:end] @@ -60,7 +61,17 @@ CLI11_INLINE std::string &rtrim(std::string &str, const std::string &filter) { } CLI11_INLINE std::string &remove_quotes(std::string &str) { - if(str.length() > 1 && (str.front() == '"' || str.front() == '\'')) { + if(str.length() > 1 && (str.front() == '"' || str.front() == '\'' || str.front() == '`')) { + if(str.front() == str.back()) { + str.pop_back(); + str.erase(str.begin(), str.begin() + 1); + } + } + return str; +} + +CLI11_INLINE std::string &remove_outer(std::string &str, char key) { + if(str.length() > 1 && (str.front() == key)) { if(str.front() == str.back()) { str.pop_back(); str.erase(str.begin(), str.begin() + 1); @@ -180,37 +191,220 @@ find_member(std::string name, const std::vector<std::string> names, bool ignore_ return (it != std::end(names)) ? (it - std::begin(names)) : (-1); } +static const std::string escapedChars("\b\t\n\f\r\"\\"); +static const std::string escapedCharsCode("btnfr\"\\"); +static const std::string bracketChars{"\"'`[(<{"}; +static const std::string matchBracketChars("\"'`])>}"); + +CLI11_INLINE bool has_escapable_character(const std::string &str) { + return (str.find_first_of(escapedChars) != std::string::npos); +} + +CLI11_INLINE std::string add_escaped_characters(const std::string &str) { + std::string out; + out.reserve(str.size() + 4); + for(char s : str) { + auto sloc = escapedChars.find_first_of(s); + if(sloc != std::string::npos) { + out.push_back('\\'); + out.push_back(escapedCharsCode[sloc]); + } else { + out.push_back(s); + } + } + return out; +} + +CLI11_INLINE std::uint32_t hexConvert(char hc) { + int hcode{0}; + if(hc >= '0' && hc <= '9') { + hcode = (hc - '0'); + } else if(hc >= 'A' && hc <= 'F') { + hcode = (hc - 'A' + 10); + } else if(hc >= 'a' && hc <= 'f') { + hcode = (hc - 'a' + 10); + } else { + hcode = -1; + } + return static_cast<uint32_t>(hcode); +} + +CLI11_INLINE char make_char(std::uint32_t code) { return static_cast<char>(static_cast<unsigned char>(code)); } + +CLI11_INLINE void append_codepoint(std::string &str, std::uint32_t code) { + if(code < 0x80) { // ascii code equivalent + str.push_back(static_cast<char>(code)); + } else if(code < 0x800) { // \u0080 to \u07FF + // 110yyyyx 10xxxxxx; 0x3f == 0b0011'1111 + str.push_back(make_char(0xC0 | code >> 6)); + str.push_back(make_char(0x80 | (code & 0x3F))); + } else if(code < 0x10000) { // U+0800...U+FFFF + if(0xD800 <= code && code <= 0xDFFF) { + throw std::invalid_argument("[0xD800, 0xDFFF] are not valid UTF-8."); + } + // 1110yyyy 10yxxxxx 10xxxxxx + str.push_back(make_char(0xE0 | code >> 12)); + str.push_back(make_char(0x80 | (code >> 6 & 0x3F))); + str.push_back(make_char(0x80 | (code & 0x3F))); + } else if(code < 0x110000) { // U+010000 ... U+10FFFF + // 11110yyy 10yyxxxx 10xxxxxx 10xxxxxx + str.push_back(make_char(0xF0 | code >> 18)); + str.push_back(make_char(0x80 | (code >> 12 & 0x3F))); + str.push_back(make_char(0x80 | (code >> 6 & 0x3F))); + str.push_back(make_char(0x80 | (code & 0x3F))); + } +} + +CLI11_INLINE std::string remove_escaped_characters(const std::string &str) { + + std::string out; + out.reserve(str.size()); + for(auto loc = str.begin(); loc < str.end(); ++loc) { + if(*loc == '\\') { + if(str.end() - loc < 2) { + throw std::invalid_argument("invalid escape sequence " + str); + } + auto ecloc = escapedCharsCode.find_first_of(*(loc + 1)); + if(ecloc != std::string::npos) { + out.push_back(escapedChars[ecloc]); + ++loc; + } else if(*(loc + 1) == 'u') { + // must have 4 hex characters + if(str.end() - loc < 6) { + throw std::invalid_argument("unicode sequence must have 4 hex codes " + str); + } + std::uint32_t code{0}; + std::uint32_t mplier{16 * 16 * 16}; + for(int ii = 2; ii < 6; ++ii) { + std::uint32_t res = hexConvert(*(loc + ii)); + if(res > 0x0F) { + throw std::invalid_argument("unicode sequence must have 4 hex codes " + str); + } + code += res * mplier; + mplier = mplier / 16; + } + append_codepoint(out, code); + loc += 5; + } else if(*(loc + 1) == 'U') { + // must have 8 hex characters + if(str.end() - loc < 10) { + throw std::invalid_argument("unicode sequence must have 8 hex codes " + str); + } + std::uint32_t code{0}; + std::uint32_t mplier{16 * 16 * 16 * 16 * 16 * 16 * 16}; + for(int ii = 2; ii < 10; ++ii) { + std::uint32_t res = hexConvert(*(loc + ii)); + if(res > 0x0F) { + throw std::invalid_argument("unicode sequence must have 8 hex codes " + str); + } + code += res * mplier; + mplier = mplier / 16; + } + append_codepoint(out, code); + loc += 9; + } else if(*(loc + 1) == '0') { + out.push_back('\0'); + ++loc; + } else { + throw std::invalid_argument(std::string("unrecognized escape sequence \\") + *(loc + 1) + " in " + str); + } + } else { + out.push_back(*loc); + } + } + return out; +} + +CLI11_INLINE std::size_t close_string_quote(const std::string &str, std::size_t start, char closure_char) { + std::size_t loc{0}; + for(loc = start + 1; loc < str.size(); ++loc) { + if(str[loc] == closure_char) { + break; + } + if(str[loc] == '\\') { + // skip the next character for escaped sequences + ++loc; + } + } + return loc; +} + +CLI11_INLINE std::size_t close_literal_quote(const std::string &str, std::size_t start, char closure_char) { + auto loc = str.find_first_of(closure_char, start + 1); + return (loc != std::string::npos ? loc : str.size()); +} + +CLI11_INLINE std::size_t close_sequence(const std::string &str, std::size_t start, char closure_char) { + + auto bracket_loc = matchBracketChars.find(closure_char); + switch(bracket_loc) { + case 0: + return close_string_quote(str, start, closure_char); + case 1: + case 2: + case std::string::npos: + return close_literal_quote(str, start, closure_char); + default: + break; + } + + std::string closures(1, closure_char); + auto loc = start + 1; + + while(loc < str.size()) { + if(str[loc] == closures.back()) { + closures.pop_back(); + if(closures.empty()) { + return loc; + } + } + bracket_loc = bracketChars.find(str[loc]); + if(bracket_loc != std::string::npos) { + switch(bracket_loc) { + case 0: + loc = close_string_quote(str, loc, str[loc]); + break; + case 1: + case 2: + loc = close_literal_quote(str, loc, str[loc]); + break; + default: + closures.push_back(matchBracketChars[bracket_loc]); + break; + } + } + ++loc; + } + if(loc > str.size()) { + loc = str.size(); + } + return loc; +} + CLI11_INLINE std::vector<std::string> split_up(std::string str, char delimiter) { - const std::string delims("\'\"`"); auto find_ws = [delimiter](char ch) { return (delimiter == '\0') ? std::isspace<char>(ch, std::locale()) : (ch == delimiter); }; trim(str); std::vector<std::string> output; - bool embeddedQuote = false; - char keyChar = ' '; while(!str.empty()) { - if(delims.find_first_of(str[0]) != std::string::npos) { - keyChar = str[0]; - auto end = str.find_first_of(keyChar, 1); - while((end != std::string::npos) && (str[end - 1] == '\\')) { // deal with escaped quotes - end = str.find_first_of(keyChar, end + 1); - embeddedQuote = true; - } - if(end != std::string::npos) { - output.push_back(str.substr(1, end - 1)); + if(bracketChars.find_first_of(str[0]) != std::string::npos) { + auto bracketLoc = bracketChars.find_first_of(str[0]); + auto end = close_sequence(str, 0, matchBracketChars[bracketLoc]); + if(end >= str.size()) { + output.push_back(std::move(str)); + str.clear(); + } else { + output.push_back(str.substr(0, end + 1)); if(end + 2 < str.size()) { str = str.substr(end + 2); } else { str.clear(); } - - } else { - output.push_back(str.substr(1)); - str = ""; } + } else { auto it = std::find_if(std::begin(str), std::end(str), find_ws); if(it != std::end(str)) { @@ -219,14 +413,9 @@ CLI11_INLINE std::vector<std::string> split_up(std::string str, char delimiter) str = std::string(it + 1, str.end()); } else { output.push_back(str); - str = ""; + str.clear(); } } - // transform any embedded quotes into the regular character - if(embeddedQuote) { - output.back() = find_and_replace(output.back(), std::string("\\") + keyChar, std::string(1, keyChar)); - embeddedQuote = false; - } trim(str); } return output; @@ -244,15 +433,140 @@ CLI11_INLINE std::size_t escape_detect(std::string &str, std::size_t offset) { return offset + 1; } -CLI11_INLINE std::string &add_quotes_if_needed(std::string &str) { - if((str.front() != '"' && str.front() != '\'') || str.front() != str.back()) { - char quote = str.find('"') < str.find('\'') ? '\'' : '"'; - if(str.find(' ') != std::string::npos) { - str.insert(0, 1, quote); - str.append(1, quote); +CLI11_INLINE std::string binary_escape_string(const std::string &string_to_escape) { + // s is our escaped output string + std::string escaped_string{}; + // loop through all characters + for(char c : string_to_escape) { + // check if a given character is printable + // the cast is necessary to avoid undefined behaviour + if(isprint(static_cast<unsigned char>(c)) == 0) { + std::stringstream stream; + // if the character is not printable + // we'll convert it to a hex string using a stringstream + // note that since char is signed we have to cast it to unsigned first + stream << std::hex << static_cast<unsigned int>(static_cast<unsigned char>(c)); + std::string code = stream.str(); + escaped_string += std::string("\\x") + (code.size() < 2 ? "0" : "") + code; + + } else { + escaped_string.push_back(c); } } - return str; + if(escaped_string != string_to_escape) { + auto sqLoc = escaped_string.find('\''); + while(sqLoc != std::string::npos) { + escaped_string.replace(sqLoc, sqLoc + 1, "\\x27"); + sqLoc = escaped_string.find('\''); + } + escaped_string.insert(0, "'B\"("); + escaped_string.push_back(')'); + escaped_string.push_back('"'); + escaped_string.push_back('\''); + } + return escaped_string; +} + +CLI11_INLINE bool is_binary_escaped_string(const std::string &escaped_string) { + size_t ssize = escaped_string.size(); + if(escaped_string.compare(0, 3, "B\"(") == 0 && escaped_string.compare(ssize - 2, 2, ")\"") == 0) { + return true; + } + return (escaped_string.compare(0, 4, "'B\"(") == 0 && escaped_string.compare(ssize - 3, 3, ")\"'") == 0); +} + +CLI11_INLINE std::string extract_binary_string(const std::string &escaped_string) { + std::size_t start{0}; + std::size_t tail{0}; + size_t ssize = escaped_string.size(); + if(escaped_string.compare(0, 3, "B\"(") == 0 && escaped_string.compare(ssize - 2, 2, ")\"") == 0) { + start = 3; + tail = 2; + } else if(escaped_string.compare(0, 4, "'B\"(") == 0 && escaped_string.compare(ssize - 3, 3, ")\"'") == 0) { + start = 4; + tail = 3; + } + + if(start == 0) { + return escaped_string; + } + std::string outstring; + + outstring.reserve(ssize - start - tail); + std::size_t loc = start; + while(loc < ssize - tail) { + // ssize-2 to skip )" at the end + if(escaped_string[loc] == '\\' && (escaped_string[loc + 1] == 'x' || escaped_string[loc + 1] == 'X')) { + auto c1 = escaped_string[loc + 2]; + auto c2 = escaped_string[loc + 3]; + + std::uint32_t res1 = hexConvert(c1); + std::uint32_t res2 = hexConvert(c2); + if(res1 <= 0x0F && res2 <= 0x0F) { + loc += 4; + outstring.push_back(static_cast<char>(res1 * 16 + res2)); + continue; + } + } + outstring.push_back(escaped_string[loc]); + ++loc; + } + return outstring; +} + +CLI11_INLINE void remove_quotes(std::vector<std::string> &args) { + for(auto &arg : args) { + if(arg.front() == '\"' && arg.back() == '\"') { + remove_quotes(arg); + // only remove escaped for string arguments not literal strings + arg = remove_escaped_characters(arg); + } else { + remove_quotes(arg); + } + } +} + +CLI11_INLINE bool process_quoted_string(std::string &str, char string_char, char literal_char) { + if(str.size() <= 1) { + return false; + } + if(detail::is_binary_escaped_string(str)) { + str = detail::extract_binary_string(str); + return true; + } + if(str.front() == string_char && str.back() == string_char) { + detail::remove_outer(str, string_char); + if(str.find_first_of('\\') != std::string::npos) { + str = detail::remove_escaped_characters(str); + } + return true; + } + if((str.front() == literal_char || str.front() == '`') && str.back() == str.front()) { + detail::remove_outer(str, str.front()); + return true; + } + return false; +} + +std::string get_environment_value(const std::string &env_name) { + char *buffer = nullptr; + std::string ename_string; + +#ifdef _MSC_VER + // Windows version + std::size_t sz = 0; + if(_dupenv_s(&buffer, &sz, env_name.c_str()) == 0 && buffer != nullptr) { + ename_string = std::string(buffer); + free(buffer); + } +#else + // This also works on Windows, but gives a warning + buffer = std::getenv(env_name.c_str()); + if(buffer != nullptr) { + ename_string = std::string(buffer); + } +#endif + return ename_string; } } // namespace detail diff --git a/packages/CLI11/include/CLI/impl/Validators_inl.hpp b/packages/CLI11/include/CLI/impl/Validators_inl.hpp index a2295ecdf8954d63cede377731710397ecd85de3..bf73e21a9ab53daacd7d3761970530338c85153c 100644 --- a/packages/CLI11/include/CLI/impl/Validators_inl.hpp +++ b/packages/CLI11/include/CLI/impl/Validators_inl.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2024, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // @@ -135,7 +135,7 @@ CLI11_INLINE path_type check_path(const char *file) noexcept { switch(stat.type()) { case std::filesystem::file_type::none: // LCOV_EXCL_LINE case std::filesystem::file_type::not_found: - return path_type::nonexistent; + return path_type::nonexistent; // LCOV_EXCL_LINE case std::filesystem::file_type::directory: return path_type::directory; case std::filesystem::file_type::symlink: @@ -229,10 +229,29 @@ CLI11_INLINE IPV4Validator::IPV4Validator() : Validator("IPV4") { return std::string("Each IP number must be between 0 and 255 ") + var; } } - return std::string(); + return std::string{}; }; } +CLI11_INLINE EscapedStringTransformer::EscapedStringTransformer() { + func_ = [](std::string &str) { + try { + if(str.size() > 1 && (str.front() == '\"' || str.front() == '\'' || str.front() == '`') && + str.front() == str.back()) { + process_quoted_string(str); + } else if(str.find_first_of('\\') != std::string::npos) { + if(detail::is_binary_escaped_string(str)) { + str = detail::extract_binary_string(str); + } else { + str = remove_escaped_characters(str); + } + } + return std::string{}; + } catch(const std::invalid_argument &ia) { + return std::string(ia.what()); + } + }; +} } // namespace detail CLI11_INLINE FileOnDefaultPath::FileOnDefaultPath(std::string default_path, bool enableErrorReturn) diff --git a/packages/CLI11/src/CMakeLists.txt b/packages/CLI11/src/CMakeLists.txt index 4f7af6ad190295688e2cdad3d8bd6fa7845ede5a..f62c895c2e7e19527b48b092d8183240c8f8f0a4 100644 --- a/packages/CLI11/src/CMakeLists.txt +++ b/packages/CLI11/src/CMakeLists.txt @@ -112,6 +112,10 @@ if(CLI11_SINGLE_FILE) if(CLI11_INSTALL) install(FILES "${PROJECT_BINARY_DIR}/include/CLI11.hpp" DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) + configure_file("${CLI11_SOURCE_DIR}/cmake/CLIsingle.hpp.in" + "${PROJECT_BINARY_DIR}/include/CLI/CLI.hpp" @ONLY) + install(FILES "${PROJECT_BINARY_DIR}/include/CLI/CLI.hpp" + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/CLI) endif() add_library(CLI11_SINGLE INTERFACE) target_link_libraries(CLI11_SINGLE INTERFACE CLI11) @@ -129,7 +133,7 @@ if(CLI11_INSTALL) if(NOT CLI11_SINGLE_FILE) install(FILES ${CLI11_headers} ${CLI11_library_headers} DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/CLI") - if(NOT CLI11_COMPILE) + if(NOT CLI11_PRECOMPILED) install(FILES ${CLI11_impl_headers} DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/CLI/impl") endif() endif() diff --git a/packages/CLI11/src/Precompile.cpp b/packages/CLI11/src/Precompile.cpp index 5afd54cb99b1a9511a36e7b30de3a5317d1d8bad..effb5f36e020645432f25fe00280e32e82ad8a88 100644 --- a/packages/CLI11/src/Precompile.cpp +++ b/packages/CLI11/src/Precompile.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2024, 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/AppTest.cpp b/packages/CLI11/tests/AppTest.cpp index 2cdefc4ee61d0770802c491d698e52c9a8b8e4a3..4e716a8e3075f24940fe3f88cbdd093984bbe159 100644 --- a/packages/CLI11/tests/AppTest.cpp +++ b/packages/CLI11/tests/AppTest.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2024, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // @@ -11,6 +11,8 @@ #include <complex> #include <cstdint> #include <cstdlib> +#include <limits> +#include <map> TEST_CASE_METHOD(TApp, "OneFlagShort", "[app]") { app.add_flag("-c,--count"); @@ -26,7 +28,7 @@ TEST_CASE_METHOD(TApp, "OneFlagShortValues", "[app]") { run(); CHECK(app.count("-c") == 1u); CHECK(app.count("--count") == 1u); - auto v = app["-c"]->results(); + const auto &v = app["-c"]->results(); CHECK("v1" == v[0]); CHECK_THROWS_AS(app["--invalid"], CLI::OptionNotFound); @@ -53,10 +55,21 @@ TEST_CASE_METHOD(TApp, "OneFlagShortValuesAs", "[app]") { auto vec = opt->as<std::vector<int>>(); CHECK(1 == vec[0]); CHECK(2 == vec[1]); + + flg->multi_option_policy(CLI::MultiOptionPolicy::Sum); + vec = opt->as<std::vector<int>>(); + CHECK(3 == vec[0]); + CHECK(vec.size() == 1); + flg->multi_option_policy(CLI::MultiOptionPolicy::Join); CHECK("1\n2" == opt->as<std::string>()); flg->delimiter(','); CHECK("1,2" == opt->as<std::string>()); + flg->multi_option_policy(CLI::MultiOptionPolicy::Reverse)->expected(1, 300); + vec = opt->as<std::vector<int>>(); + REQUIRE(vec.size() == 2U); + CHECK(2 == vec[0]); + CHECK(1 == vec[1]); } TEST_CASE_METHOD(TApp, "OneFlagShortWindows", "[app]") { @@ -68,28 +81,6 @@ TEST_CASE_METHOD(TApp, "OneFlagShortWindows", "[app]") { CHECK(app.count("--count") == 1u); } -TEST_CASE_METHOD(TApp, "WindowsLongShortMix1", "[app]") { - app.allow_windows_style_options(); - - auto *a = app.add_flag("-c"); - auto *b = app.add_flag("--c"); - args = {"/c"}; - run(); - CHECK(a->count() == 1u); - CHECK(b->count() == 0u); -} - -TEST_CASE_METHOD(TApp, "WindowsLongShortMix2", "[app]") { - app.allow_windows_style_options(); - - auto *a = app.add_flag("--c"); - auto *b = app.add_flag("-c"); - args = {"/c"}; - run(); - CHECK(a->count() == 1u); - CHECK(b->count() == 0u); -} - TEST_CASE_METHOD(TApp, "CountNonExist", "[app]") { app.add_flag("-c,--count"); args = {"-c"}; @@ -424,10 +415,10 @@ TEST_CASE_METHOD(TApp, "OneStringEqualVersionSingleStringQuotedEscapedCharacters app.add_option("-s,--string", str); app.add_option("-t,--tstr", str2); app.add_option("-m,--mstr", str3); - app.parse(R"raw(--string="this is my \"quoted\" string" -t 'qst\'ring 2' -m=`"quoted\` string"`")raw"); - CHECK("this is my \"quoted\" string" == str); - CHECK("qst\'ring 2" == str2); - CHECK("\"quoted` string\"" == str3); + app.parse(R"raw(--string="this is my \n\"quoted\" string" -t 'qst\ring 2' -m=`"quoted\n string"`")raw"); + CHECK("this is my \n\"quoted\" string" == str); // escaped + CHECK("qst\\ring 2" == str2); // literal + CHECK("\"quoted\\n string\"" == str3); // double quoted literal } TEST_CASE_METHOD(TApp, "OneStringEqualVersionSingleStringQuotedMultipleWithEqual", "[app]") { @@ -640,6 +631,28 @@ TEST_CASE_METHOD(TApp, "StrangeOptionNames", "[app]") { CHECK(app["--{}"]->as<int>() == 5); } +TEST_CASE_METHOD(TApp, "singledash", "[app]") { + app.add_option("-t"); + try { + app.add_option("-test"); + } catch(const CLI::BadNameString &e) { + std::string str = e.what(); + CHECK_THAT(str, Contains("2 dashes")); + CHECK_THAT(str, Contains("-test")); + } catch(...) { + CHECK(false); + } + try { + app.add_option("-!"); + } catch(const CLI::BadNameString &e) { + std::string str = e.what(); + CHECK_THAT(str, Contains("one char")); + CHECK_THAT(str, Contains("-!")); + } catch(...) { + CHECK(false); + } +} + TEST_CASE_METHOD(TApp, "FlagLikeOption", "[app]") { bool val{false}; auto *opt = app.add_option("--flag", val)->type_size(0)->default_str("true"); @@ -828,7 +841,7 @@ TEST_CASE_METHOD(TApp, "SumOptFloat", "[app]") { run(); - CHECK(0.6 == val); + CHECK(std::fabs(0.6 - val) <= std::numeric_limits<double>::epsilon()); } TEST_CASE_METHOD(TApp, "SumOptString", "[app]") { @@ -843,6 +856,29 @@ TEST_CASE_METHOD(TApp, "SumOptString", "[app]") { CHECK("i2" == val); } +TEST_CASE_METHOD(TApp, "ReverseOpt", "[app]") { + + std::vector<std::string> val; + auto *opt1 = app.add_option("--val", val)->multi_option_policy(CLI::MultiOptionPolicy::Reverse); + + args = {"--val=string1", "--val=string2", "--val", "string3", "string4"}; + + run(); + + CHECK(val.size() == 4U); + + CHECK(val.front() == "string4"); + CHECK(val.back() == "string1"); + + opt1->expected(1, 2); + run(); + CHECK(val.size() == 2U); + + CHECK(val.front() == "string4"); + CHECK(val.back() == "string3"); + CHECK(opt1->get_multi_option_policy() == CLI::MultiOptionPolicy::Reverse); +} + TEST_CASE_METHOD(TApp, "JoinOpt2", "[app]") { std::string str; @@ -1044,6 +1080,42 @@ TEST_CASE_METHOD(TApp, "emptyVectorReturn", "[app]") { CHECK_FALSE(strs3.empty()); } +TEST_CASE_METHOD(TApp, "emptyVectorReturnReduce", "[app]") { + + std::vector<std::string> strs; + std::vector<std::string> strs2; + std::vector<std::string> strs3; + auto *opt1 = app.add_option("--str", strs)->required()->expected(0, 2); + app.add_option("--str3", strs3)->expected(1, 3); + app.add_option("--str2", strs2)->expected(1, 1)->take_first(); + args = {"--str"}; + + CHECK_NOTHROW(run()); + CHECK(std::vector<std::string>({""}) == strs); + args = {"--str", "one", "two"}; + + run(); + + CHECK(std::vector<std::string>({"one", "two"}) == strs); + + args = {"--str", "{}", "--str2", "{}", "test"}; + + run(); + + CHECK(strs.empty()); + CHECK(std::vector<std::string>{"{}"} == strs2); + opt1->default_str("{}"); + args = {"--str"}; + + CHECK_NOTHROW(run()); + CHECK(strs.empty()); + opt1->required(false); + args = {"--str3", "{}"}; + + CHECK_NOTHROW(run()); + CHECK_FALSE(strs3.empty()); +} + TEST_CASE_METHOD(TApp, "RequiredOptsDoubleShort", "[app]") { std::vector<std::string> strs; @@ -1118,6 +1190,21 @@ TEST_CASE_METHOD(TApp, "PositionalAtEnd", "[app]") { CHECK_THROWS_AS(run(), CLI::ExtrasError); } +// Tests positionals at end +TEST_CASE_METHOD(TApp, "PositionalInjectSeparator", "[app]") { + std::string options; + std::vector<std::vector<std::string>> foo; + + app.add_option("-O", options); + auto *fooopt = app.add_option("foo", foo); + fooopt->inject_separator(); + args = {"test1", "-O", "Test", "test2"}; + run(); + + CHECK("Test" == options); + CHECK(foo.size() == 2U); +} + // Tests positionals at end TEST_CASE_METHOD(TApp, "RequiredPositionals", "[app]") { std::vector<std::string> sources; @@ -1715,6 +1802,30 @@ TEST_CASE_METHOD(TApp, "FileExists", "[app]") { CHECK(!CLI::ExistingFile(myfile).empty()); } +#if defined CLI11_HAS_FILESYSTEM && CLI11_HAS_FILESYSTEM > 0 && defined(_MSC_VER) +TEST_CASE_METHOD(TApp, "filesystemWideName", "[app]") { + std::filesystem::path myfile{L"voil\u20ac.txt"}; + + std::filesystem::path fpath; + app.add_option("--file", fpath)->check(CLI::ExistingFile, "existing file"); + + CHECK_THROWS_AS(app.parse(L"--file voil\u20ac.txt"), CLI::ValidationError); + + bool ok = static_cast<bool>(std::ofstream(myfile).put('a')); // create file + CHECK(ok); + + // deactivate the check, so it should run now + + CHECK_NOTHROW(app.parse(L"--file voil\u20ac.txt")); + + CHECK(fpath == myfile); + + CHECK(std::filesystem::exists(fpath)); + std::filesystem::remove(myfile); + CHECK(!std::filesystem::exists(fpath)); +} +#endif + TEST_CASE_METHOD(TApp, "NotFileExists", "[app]") { std::string myfile{"TestNonFileNotUsed.txt"}; CHECK(!CLI::ExistingFile(myfile).empty()); @@ -1991,6 +2102,28 @@ TEST_CASE_METHOD(TApp, "RangeDouble", "[app]") { run(); } +TEST_CASE_METHOD(TApp, "RangeFloat", "[app]") { + + float x{0.0f}; + /// Note that this must be a float in Range, too + app.add_option("--one", x, "testing floats")->check(CLI::Range(3.0, 6.0)); + + args = {"--one=1"}; + CHECK_THROWS_AS(run(), CLI::ValidationError); + + args = {"--one=7"}; + CHECK_THROWS_AS(run(), CLI::ValidationError); + + args = {"--one=3"}; + run(); + + args = {"--one=5"}; + run(); + + args = {"--one=6"}; + run(); +} + TEST_CASE_METHOD(TApp, "NonNegative", "[app]") { std::string res; @@ -2512,3 +2645,39 @@ TEST_CASE("System Args", "[app]") { FAIL("Executable '" << commandline << "' failed with an unknown return code"); } } + +// #845 +TEST_CASE("Ensure UTF-8", "[app]") { + const char *commandline = CLI11_ENSURE_UTF8_EXE " 1234 false \"hello world\""; + int retval = std::system(commandline); + + if(retval == -1) { + FAIL("Executable '" << commandline << "' reported that argv pointer changed where it should not have been"); + } + + if(retval > 0) { + FAIL("Executable '" << commandline << "' reported different argv at index " << (retval - 1)); + } + + if(retval != 0) { + FAIL("Executable '" << commandline << "' failed with an unknown return code"); + } +} + +// #845 +TEST_CASE("Ensure UTF-8 called twice", "[app]") { + const char *commandline = CLI11_ENSURE_UTF8_TWICE_EXE " 1234 false \"hello world\""; + int retval = std::system(commandline); + + if(retval == -1) { + FAIL("Executable '" << commandline << "' reported that argv pointer changed where it should not have been"); + } + + if(retval > 0) { + FAIL("Executable '" << commandline << "' reported different argv at index " << (retval - 1)); + } + + if(retval != 0) { + FAIL("Executable '" << commandline << "' failed with an unknown return code"); + } +} diff --git a/packages/CLI11/tests/BoostOptionTypeTest.cpp b/packages/CLI11/tests/BoostOptionTypeTest.cpp index 1dabc37da9204b754bca3dc923a4e6a431e65507..9b0ff3c6e2e2d8fc3baf4056472ce4877f50aafc 100644 --- a/packages/CLI11/tests/BoostOptionTypeTest.cpp +++ b/packages/CLI11/tests/BoostOptionTypeTest.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2024, 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 7bd47744cb7e6e625b55e6fccad83faf4eedf565..621584807298930e590e2e17fa5048530d471aa9 100644 --- a/packages/CLI11/tests/CMakeLists.txt +++ b/packages/CLI11/tests/CMakeLists.txt @@ -1,10 +1,10 @@ -if(CLI11_SANITIZERS) +if(CLI11_SANITIZERS AND ${CMAKE_VERSION} VERSION_GREATER "3.13.0") message(STATUS "Using arsenm/sanitizers-cmake") FetchContent_Declare( sanitizers GIT_REPOSITORY https://github.com/arsenm/sanitizers-cmake.git GIT_SHALLOW 1 - GIT_TAG c3dc841) + GIT_TAG 3f0542e) FetchContent_GetProperties(sanitizers) @@ -115,16 +115,15 @@ foreach(DATA_FILE IN LISTS DATA_FILES) "${CMAKE_CURRENT_BINARY_DIR}/${DATA_FILE}" MAIN_DEPENDENCY "${CMAKE_CURRENT_SOURCE_DIR}/${DATA_FILE}" VERBATIM) - target_sources(catch_main PRIVATE "${CMAKE_CURRENT_BINARY_DIR}/${DATA_FILE}") endforeach() +add_custom_target(cli11_test_data DEPENDS ${DATA_FILES}) # Build dependent applications which are launched from test code -set(CLI11_DEPENDENT_APPLICATIONS system_args) +set(CLI11_DEPENDENT_APPLICATIONS system_args ensure_utf8 ensure_utf8_twice) foreach(APP IN LISTS CLI11_DEPENDENT_APPLICATIONS) add_executable(${APP} applications/${APP}.cpp) target_include_directories(${APP} PRIVATE ${CMAKE_SOURCE_DIR}/include) - add_dependencies(catch_main ${APP}) endforeach() function(add_dependent_application_definitions TARGET) @@ -138,6 +137,7 @@ endfunction() # Target must already exist macro(add_catch_test TESTNAME) target_link_libraries(${TESTNAME} PUBLIC catch_main) + add_dependencies(${TESTNAME} cli11_test_data) add_test(${TESTNAME} ${TESTNAME}) set_target_properties(${TESTNAME} PROPERTIES FOLDER "Tests") @@ -277,3 +277,80 @@ if(CMAKE_BUILD_TYPE STREQUAL Coverage) ${CLI11_TESTS} ${CLI11_MULTIONLY_TESTS}) endif() + +set(CLI11_PACKAGE_SEARCH_LOC "") + +# tests of the cmake package and pkg-config package +if(CLI11_INSTALL_PACKAGE_TESTS) + if(NOT MSVC) + set(package_test_command --test-command "${CMAKE_CTEST_COMMAND}") + else() # don't try to run the tests on MSVC since that would require copying the dll's and doing + # some other setup that isn't that important to run on all OS + set(package_test_command) + endif() + + if(CMAKE_BUILD_TYPE) + set(CLI11_PACKAGE_TEST_BUILD_TYPE ${CMAKE_BUILD_TYPE}) + else() + set(CLI11_PACKAGE_TEST_BUILD_TYPE Release) + endif() + + file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/find_package_tests") + + if(MSVC AND ${CMAKE_VERSION} VERSION_GREATER 3.12.9) + # Tests for other CMake projects including and using CLI11 using find_package + add_test( + NAME find-package-testsA + COMMAND + ${CMAKE_COMMAND} -G "${CMAKE_GENERATOR}" -A "${CMAKE_GENERATOR_PLATFORM}" + "-DCLI11_DIR=${CMAKE_INSTALL_PREFIX}" "-DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}" + "-DCMAKE_CXX_STANDARD=${CMAKE_CXX_STANDARD}" + "-DCMAKE_RUNTIME_OUTPUT_DIRECTORY=${CMAKE_RUNTIME_OUTPUT_DIRECTORY}" + "${CMAKE_CURRENT_SOURCE_DIR}/find_package_tests" + WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/find_package_tests") + else() + add_test( + NAME find-package-testsA + COMMAND + ${CMAKE_COMMAND} -G "${CMAKE_GENERATOR}" "-DCLI11_DIR=${CMAKE_INSTALL_PREFIX}" + "-DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}" "-DCMAKE_CXX_STANDARD=${CMAKE_CXX_STANDARD}" + "-DCMAKE_RUNTIME_OUTPUT_DIRECTORY=${CMAKE_RUNTIME_OUTPUT_DIRECTORY}" + "${CMAKE_CURRENT_SOURCE_DIR}/find_package_tests" + WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/find_package_tests") + endif() + + add_test(NAME find-package-testsB + COMMAND ${CMAKE_COMMAND} --build "${CMAKE_CURRENT_BINARY_DIR}/find_package_tests" + --config ${CLI11_PACKAGE_TEST_BUILD_TYPE}) + + add_test( + NAME find-package-testsC + COMMAND ${CMAKE_CTEST_COMMAND} -C ${CLI11_PACKAGE_TEST_BUILD_TYPE} + WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/find_package_tests") + set_property(TEST find-package-testsA PROPERTY LABELS Packaging) + set_property(TEST find-package-testsB PROPERTY LABELS Packaging) + set_property(TEST find-package-testsB PROPERTY DEPENDS find-package-testsA) + set_property(TEST find-package-testsC PROPERTY LABELS Packaging) + set_property(TEST find-package-testsC PROPERTY DEPENDS find-package-testsB) + + if(NOT MSVC) + # Tests for other CMake projects using the package_config files + add_test( + package-config-tests + ${CMAKE_CTEST_COMMAND} + -C + --build-and-test + "${CMAKE_CURRENT_SOURCE_DIR}/package_config_tests" + "${CMAKE_CURRENT_BINARY_DIR}/package_config_tests" + --build-generator + "${CMAKE_GENERATOR}" + --build-generator-platform + "${CMAKE_GENERATOR_PLATFORM}" + --build-options + "-DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}" + "-DCMAKE_CXX_STANDARD=${CMAKE_CXX_STANDARD}" + "-DCLI11_DIR=${CMAKE_INSTALL_PREFIX}" + ${package_test_command}) + set_property(TEST package-config-tests PROPERTY LABELS Packaging) + endif() +endif() diff --git a/packages/CLI11/tests/ComplexTypeTest.cpp b/packages/CLI11/tests/ComplexTypeTest.cpp index adcd26c4b24da2a5d4205a8a3d262b0d8c237dcc..4747f64fca3595951f11e9f9045e28b55d802be0 100644 --- a/packages/CLI11/tests/ComplexTypeTest.cpp +++ b/packages/CLI11/tests/ComplexTypeTest.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2024, 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 206872728c1734d1632000d9f9c208cb23839060..708e71ee224d02fb6876d0b82ae644c61c434214 100644 --- a/packages/CLI11/tests/ConfigFileTest.cpp +++ b/packages/CLI11/tests/ConfigFileTest.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2024, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // @@ -27,6 +27,15 @@ TEST_CASE("StringBased: convert_arg_for_ini", "[config]") { CHECK("-22E14" == CLI::detail::convert_arg_for_ini("-22E14")); CHECK("'a'" == CLI::detail::convert_arg_for_ini("a")); + + CHECK("'\\'" == CLI::detail::convert_arg_for_ini("\\")); + + CHECK("\"'\"" == CLI::detail::convert_arg_for_ini("'")); + + std::string tstring1; + tstring1.push_back('\0'); + // binary string conversion single character + CHECK("'B\"(\\x00)\"'" == CLI::detail::convert_arg_for_ini(tstring1)); // hex CHECK("0x5461FAED" == CLI::detail::convert_arg_for_ini("0x5461FAED")); // hex fail @@ -84,6 +93,7 @@ TEST_CASE("StringBased: FirstWithComments", "[config]") { ofile << "one=three\n"; ofile << "two=four\n"; ofile << "; and another one\n"; + ofile << " ; and yet another one\n"; ofile.seekg(0, std::ios::beg); @@ -187,6 +197,135 @@ TEST_CASE("StringBased: TomlVector", "[config]") { CHECK(output.at(4).inputs.at(2) == "three"); } +TEST_CASE("StringBased: TomlMultiLineString1", "[config]") { + std::stringstream ofile; + + ofile << "one = [three]\n"; + ofile << "two = \"\"\"test\n"; + ofile << "five = [six, and, seven]\n"; + ofile << "eight\"\"\"\n"; + ofile << "three=7 \n"; + + ofile.seekg(0, std::ios::beg); + + std::vector<CLI::ConfigItem> output = CLI::ConfigINI().from_config(ofile); + + CHECK(output.size() == 3u); + CHECK(output.at(0).name == "one"); + CHECK(output.at(0).inputs.size() == 1u); + CHECK(output.at(0).inputs.at(0) == "three"); + CHECK(output.at(1).name == "two"); + CHECK(output.at(1).inputs.size() == 1u); + CHECK(output.at(1).inputs.at(0) == "test\nfive = [six, and, seven]\neight"); + CHECK(output.at(2).name == "three"); + CHECK(output.at(2).inputs.size() == 1u); + CHECK(output.at(2).inputs.at(0) == "7"); +} + +TEST_CASE("StringBased: TomlMultiLineString2", "[config]") { + std::stringstream ofile; + + ofile << "one = [three]\n"; + ofile << "two = '''test \n"; + ofile << "five = [six, and, seven] \n"; + ofile << "'''\n"; + ofile << "three=7 \n"; + + ofile.seekg(0, std::ios::beg); + + std::vector<CLI::ConfigItem> output = CLI::ConfigINI().from_config(ofile); + + CHECK(output.size() == 3u); + CHECK(output.at(0).name == "one"); + CHECK(output.at(0).inputs.size() == 1u); + CHECK(output.at(0).inputs.at(0) == "three"); + CHECK(output.at(1).name == "two"); + CHECK(output.at(1).inputs.size() == 1u); + CHECK(output.at(1).inputs.at(0) == "test \nfive = [six, and, seven] "); + CHECK(output.at(2).name == "three"); + CHECK(output.at(2).inputs.size() == 1u); + CHECK(output.at(2).inputs.at(0) == "7"); +} + +TEST_CASE("StringBased: TomlMultiLineString3", "[config]") { + std::stringstream ofile; + + ofile << "one = [three]\n"; + ofile << "two = \"\"\"\n"; + ofile << "test \\\n"; + ofile << " five = [six, and, seven] \\\n"; + ofile << "eight\"\"\"\n"; + ofile << "three=7 \n"; + + ofile.seekg(0, std::ios::beg); + + std::vector<CLI::ConfigItem> output = CLI::ConfigINI().from_config(ofile); + + CHECK(output.size() == 3u); + CHECK(output.at(0).name == "one"); + CHECK(output.at(0).inputs.size() == 1u); + CHECK(output.at(0).inputs.at(0) == "three"); + CHECK(output.at(1).name == "two"); + CHECK(output.at(1).inputs.size() == 1u); + CHECK(output.at(1).inputs.at(0) == "test five = [six, and, seven] eight"); + CHECK(output.at(2).name == "three"); + CHECK(output.at(2).inputs.size() == 1u); + CHECK(output.at(2).inputs.at(0) == "7"); +} + +TEST_CASE("StringBased: TomlMultiLineString4", "[config]") { + std::stringstream ofile; + + ofile << "one = [three]\n"; + ofile << "two = \"\"\"\n"; + ofile << "test\n"; + ofile << "five = [six, and, seven]\n"; + ofile << "\"\"\"\n"; + ofile << "three=7 \n"; + + ofile.seekg(0, std::ios::beg); + + std::vector<CLI::ConfigItem> output = CLI::ConfigINI().from_config(ofile); + + CHECK(output.size() == 3u); + CHECK(output.at(0).name == "one"); + CHECK(output.at(0).inputs.size() == 1u); + CHECK(output.at(0).inputs.at(0) == "three"); + CHECK(output.at(1).name == "two"); + CHECK(output.at(1).inputs.size() == 1u); + CHECK(output.at(1).inputs.at(0) == "test\nfive = [six, and, seven]"); + CHECK(output.at(2).name == "three"); + CHECK(output.at(2).inputs.size() == 1u); + CHECK(output.at(2).inputs.at(0) == "7"); +} + +TEST_CASE("StringBased: TomlMultiLineString5", "[config]") { + std::stringstream ofile; + + ofile << "one = [three]\n"; + ofile << "two = \"\"\" mline \\\n"; + ofile << "test\n"; + ofile << '\n'; + ofile << "five = [six, and, seven]\n"; + ofile << "\"\"\"\n"; + ofile << "three=7 \n"; + + ofile.seekg(0, std::ios::beg); + + std::vector<CLI::ConfigItem> output = CLI::ConfigINI().from_config(ofile); + + CHECK(output.size() == 3u); + CHECK(output.at(0).name == "one"); + CHECK(output.at(0).inputs.size() == 1u); + CHECK(output.at(0).inputs.at(0) == "three"); + CHECK(output.at(1).name == "two"); + CHECK(output.at(1).inputs.size() == 1u); + CHECK(output.at(1).inputs.at(0) == " mline test\n\nfive = [six, and, seven]"); + CHECK(output.at(2).name == "three"); + CHECK(output.at(2).inputs.size() == 1u); + CHECK(output.at(2).inputs.at(0) == "7"); +} + TEST_CASE("StringBased: Spaces", "[config]") { std::stringstream ofile; @@ -362,6 +501,38 @@ TEST_CASE("StringBased: Layers2LevelChange", "[config]") { CHECK(checkSections(output)); } +TEST_CASE("StringBased: Layers2LevelChangeInQuotes", "[config]") { + std::stringstream ofile; + + ofile << "simple = true\n\n"; + ofile << "[\"other\".\"sub2\".cmd]\n"; + ofile << "[other.\"sub3\".\"cmd\"]\n"; + ofile << "absolute_newest = true\n"; + ofile.seekg(0, std::ios::beg); + + std::vector<CLI::ConfigItem> output = CLI::ConfigINI().from_config(ofile); + + // 2 flags and 5 openings and 5 closings + CHECK(output.size() == 12u); + CHECK(checkSections(output)); +} + +TEST_CASE("StringBased: Layers2LevelChangeInQuotesWithDot", "[config]") { + std::stringstream ofile; + + ofile << "simple = true\n\n"; + ofile << "[\"other\".\"sub2.cmd\"]\n"; + ofile << "[other.\"sub3.cmd\"]\n"; + ofile << "absolute_newest = true\n"; + ofile.seekg(0, std::ios::beg); + + std::vector<CLI::ConfigItem> output = CLI::ConfigINI().from_config(ofile); + + // 2 flags and 3 openings and 3 closings + CHECK(output.size() == 8u); + CHECK(checkSections(output)); +} + TEST_CASE("StringBased: Layers3LevelChange", "[config]") { std::stringstream ofile; @@ -443,9 +614,9 @@ TEST_CASE_METHOD(TApp, "IniNotRequired", "[config]") { { std::ofstream out{tmpini}; - out << "[default]" << std::endl; - out << "two=99" << std::endl; - out << "three=3" << std::endl; + out << "[default]" << '\n'; + out << "two=99" << '\n'; + out << "three=3" << '\n'; } int one = 0, two = 0, three = 0; @@ -480,8 +651,8 @@ TEST_CASE_METHOD(TApp, "IniSuccessOnUnknownOption", "[config]") { { std::ofstream out{tmpini}; - out << "three=3" << std::endl; - out << "two=99" << std::endl; + out << "three=3" << '\n'; + out << "two=99" << '\n'; } int two{0}; @@ -500,8 +671,8 @@ TEST_CASE_METHOD(TApp, "IniGetRemainingOption", "[config]") { std::string ExtraOptionValue = "3"; { std::ofstream out{tmpini}; - out << ExtraOption << "=" << ExtraOptionValue << std::endl; - out << "two=99" << std::endl; + out << ExtraOption << "=" << ExtraOptionValue << '\n'; + out << "two=99" << '\n'; } int two{0}; @@ -523,7 +694,7 @@ TEST_CASE_METHOD(TApp, "IniRemainingSub", "[config]") { out << "[map]\n"; out << "a = 1\n"; out << "b=[1,2,3]\n"; - out << "c = 3" << std::endl; + out << "c = 3" << '\n'; } REQUIRE_NOTHROW(run()); @@ -556,7 +727,7 @@ TEST_CASE_METHOD(TApp, "IniGetNoRemaining", "[config]") { { std::ofstream out{tmpini}; - out << "two=99" << std::endl; + out << "two=99" << '\n'; } int two{0}; @@ -602,9 +773,9 @@ TEST_CASE_METHOD(TApp, "IniRequiredbadConfigurator", "[config]") { { std::ofstream out{tmpini}; - out << "[default]" << std::endl; - out << "two=99" << std::endl; - out << "three=3" << std::endl; + out << "[default]" << '\n'; + out << "two=99" << '\n'; + out << "three=3" << '\n'; } app.set_config("--config", tmpini)->required(); @@ -620,9 +791,9 @@ TEST_CASE_METHOD(TApp, "IniNotRequiredbadConfigurator", "[config]") { { std::ofstream out{tmpini}; - out << "[default]" << std::endl; - out << "two=99" << std::endl; - out << "three=3" << std::endl; + out << "[default]" << '\n'; + out << "two=99" << '\n'; + out << "three=3" << '\n'; } app.set_config("--config", tmpini); @@ -643,16 +814,16 @@ TEST_CASE_METHOD(TApp, "IniNotRequiredNotDefault", "[config]") { { std::ofstream out{tmpini}; - out << "[default]" << std::endl; - out << "two=99" << std::endl; - out << "three=3" << std::endl; + out << "[default]" << '\n'; + out << "two=99" << '\n'; + out << "three=3" << '\n'; } { std::ofstream out{tmpini2}; - out << "[default]" << std::endl; - out << "two=98" << std::endl; - out << "three=4" << std::endl; + out << "[default]" << '\n'; + out << "two=98" << '\n'; + out << "three=4" << '\n'; } int one{0}, two{0}, three{0}; @@ -673,6 +844,36 @@ TEST_CASE_METHOD(TApp, "IniNotRequiredNotDefault", "[config]") { CHECK(tmpini2.c_str() == app.get_config_ptr()->as<std::string>()); } +TEST_CASE_METHOD(TApp, "IniEnvironmentalFileName", "[config]") { + + TempFile tmpini{"TestIniTmp.ini"}; + + app.set_config("--config", "")->envname("CONFIG")->required(); + + { + std::ofstream out{tmpini}; + out << "[default]" << '\n'; + out << "two=99" << '\n'; + out << "three=3" << '\n'; + } + + int one{0}, two{0}, three{0}; + app.add_option("--one", one); + app.add_option("--two", two); + app.add_option("--three", three); + + put_env("CONFIG", tmpini); + + CHECK_NOTHROW(run()); + + CHECK(two == 99); + CHECK(three == 3); + + unset_env("CONFIG"); + + CHECK_THROWS_AS(run(), CLI::FileError); +} + TEST_CASE_METHOD(TApp, "MultiConfig", "[config]") { TempFile tmpini{"TestIniTmp.ini"}; @@ -682,16 +883,16 @@ TEST_CASE_METHOD(TApp, "MultiConfig", "[config]") { { std::ofstream out{tmpini}; - out << "[default]" << std::endl; - out << "two=99" << std::endl; - out << "three=3" << std::endl; + out << "[default]" << '\n'; + out << "two=99" << '\n'; + out << "three=3" << '\n'; } { std::ofstream out{tmpini2}; - out << "[default]" << std::endl; - out << "one=55" << std::endl; - out << "three=4" << std::endl; + out << "[default]" << '\n'; + out << "one=55" << '\n'; + out << "three=4" << '\n'; } int one{0}, two{0}, three{0}; @@ -714,6 +915,90 @@ TEST_CASE_METHOD(TApp, "MultiConfig", "[config]") { CHECK(one == 55); } +TEST_CASE_METHOD(TApp, "MultiConfig_takelast", "[config]") { + + TempFile tmpini{"TestIniTmp.ini"}; + TempFile tmpini2{"TestIniTmp2.ini"}; + + app.set_config("--config")->multi_option_policy(CLI::MultiOptionPolicy::TakeLast)->expected(1, 3); + + { + std::ofstream out{tmpini}; + out << "[default]" << '\n'; + out << "two=99" << '\n'; + out << "three=3" << '\n'; + } + + { + std::ofstream out{tmpini2}; + out << "[default]" << '\n'; + out << "one=55" << '\n'; + out << "three=4" << '\n'; + } + + int one{0}, two{0}, three{0}; + app.add_option("--one", one); + app.add_option("--two", two); + app.add_option("--three", three); + + args = {"--config", tmpini, "--config", tmpini2}; + run(); + + CHECK(two == 99); + CHECK(three == 3); + CHECK(one == 55); + + two = 0; + args = {"--config", tmpini2, "--config", tmpini}; + run(); + + CHECK(two == 99); + CHECK(three == 4); + CHECK(one == 55); +} + +TEST_CASE_METHOD(TApp, "MultiConfig_takeAll", "[config]") { + + TempFile tmpini{"TestIniTmp.ini"}; + TempFile tmpini2{"TestIniTmp2.ini"}; + + app.set_config("--config")->multi_option_policy(CLI::MultiOptionPolicy::TakeAll); + + { + std::ofstream out{tmpini}; + out << "[default]" << '\n'; + out << "two=99" << '\n'; + out << "three=3" << '\n'; + } + + { + std::ofstream out{tmpini2}; + out << "[default]" << '\n'; + out << "one=55" << '\n'; + out << "three=4" << '\n'; + } + + int one{0}, two{0}, three{0}; + app.add_option("--one", one); + app.add_option("--two", two); + app.add_option("--three", three); + + args = {"--config", tmpini, "--config", tmpini2}; + run(); + + CHECK(two == 99); + CHECK(three == 3); + CHECK(one == 55); + + two = 0; + args = {"--config", tmpini2, "--config", tmpini}; + run(); + + CHECK(two == 99); + CHECK(three == 4); + CHECK(one == 55); +} + TEST_CASE_METHOD(TApp, "MultiConfig_single", "[config]") { TempFile tmpini{"TestIniTmp.ini"}; @@ -723,16 +1008,16 @@ TEST_CASE_METHOD(TApp, "MultiConfig_single", "[config]") { { std::ofstream out{tmpini}; - out << "[default]" << std::endl; - out << "two=99" << std::endl; - out << "three=3" << std::endl; + out << "[default]" << '\n'; + out << "two=99" << '\n'; + out << "three=3" << '\n'; } { std::ofstream out{tmpini2}; - out << "[default]" << std::endl; - out << "one=55" << std::endl; - out << "three=4" << std::endl; + out << "[default]" << '\n'; + out << "one=55" << '\n'; + out << "three=4" << '\n'; } int one{0}, two{0}, three{0}; @@ -778,8 +1063,8 @@ TEST_CASE_METHOD(TApp, "IniOverwrite", "[config]") { TempFile tmpini{"TestIniTmp.ini"}; { std::ofstream out{tmpini}; - out << "[default]" << std::endl; - out << "two=99" << std::endl; + out << "[default]" << '\n'; + out << "two=99" << '\n'; } std::string orig = "filename_not_exist.ini"; @@ -803,9 +1088,9 @@ TEST_CASE_METHOD(TApp, "IniRequired", "[config]") { { std::ofstream out{tmpini}; - out << "[default]" << std::endl; - out << "two=99" << std::endl; - out << "three=3" << std::endl; + out << "[default]" << '\n'; + out << "two=99" << '\n'; + out << "three=3" << '\n'; } int one{0}, two{0}, three{0}; @@ -846,9 +1131,9 @@ TEST_CASE_METHOD(TApp, "IniInlineComment", "[config]") { { 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; + out << "[default]" << '\n'; + out << "two=99 ; this is a two" << '\n'; + out << "three=3; this is a three" << '\n'; } int one{0}, two{0}, three{0}; @@ -888,9 +1173,9 @@ TEST_CASE_METHOD(TApp, "TomlInlineComment", "[config]") { { 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; + out << "[default]" << '\n'; + out << "two=99 # this is a two" << '\n'; + out << "three=3# this is a three" << '\n'; } int one{0}, two{0}, three{0}; @@ -922,6 +1207,86 @@ TEST_CASE_METHOD(TApp, "TomlInlineComment", "[config]") { CHECK_THROWS_AS(run(), CLI::RequiredError); } +TEST_CASE_METHOD(TApp, "TomlDocStringComment", "[config]") { + + TempFile tmpini{"TestIniTmp.ini"}; + + app.set_config("--config", tmpini, "", true); + + { + std::ofstream out{tmpini}; + out << "[default]" << '\n'; + out << "two=99" << '\n'; + out << "three=3" << '\n'; + out << R"(""")" << '\n'; + out << "one=35" << '\n'; + out << R"(""")" << '\n'; + } + + int one{0}, two{0}, three{0}; + app.add_option("--one", one); + app.add_option("--two", two); + app.add_option("--three", three); + + CHECK_NOTHROW(run()); + CHECK(0 == one); + CHECK(99 == two); + CHECK(3 == three); +} + +TEST_CASE_METHOD(TApp, "TomlDocStringComment2", "[config]") { + + TempFile tmpini{"TestIniTmp.ini"}; + + app.set_config("--config", tmpini, "", true); + + { + std::ofstream out{tmpini}; + out << "[default]" << '\n'; + out << "two=99" << '\n'; + out << "'''" << '\n'; + out << "one=35" << '\n'; + out << "last comment line three=6 '''" << '\n'; + out << "three=3" << '\n'; + } + + int one{0}, two{0}, three{0}; + app.add_option("--one", one); + app.add_option("--two", two); + app.add_option("--three", three); + + CHECK_NOTHROW(run()); + CHECK(0 == one); + CHECK(99 == two); + CHECK(3 == three); +} + +TEST_CASE_METHOD(TApp, "TomlDocStringComment3", "[config]") { + + TempFile tmpini{"TestIniTmp.ini"}; + + app.set_config("--config", tmpini, "", true); + + { + std::ofstream out{tmpini}; + out << "[default]" << '\n'; + out << "two=99" << '\n'; + out << "three=3" << '\n'; + out << "'''" << '\n'; + out << "one=35" << '\n'; + } + + int one{0}, two{0}, three{0}; + app.add_option("--one", one); + app.add_option("--two", two); + app.add_option("--three", three); + + CHECK_NOTHROW(run()); + CHECK(0 == one); + CHECK(99 == two); + CHECK(3 == three); +} + TEST_CASE_METHOD(TApp, "ConfigModifiers", "[config]") { app.set_config("--config", "test.ini", "", true); @@ -953,9 +1318,9 @@ TEST_CASE_METHOD(TApp, "IniVector", "[config]") { { std::ofstream out{tmpini}; - out << "[default]" << std::endl; - out << "two=2 3" << std::endl; - out << "three=1 2 3" << std::endl; + out << "[default]" << '\n'; + out << "two=2 3" << '\n'; + out << "three=1 2 3" << '\n'; } std::vector<int> two, three; @@ -1141,12 +1506,155 @@ TEST_CASE_METHOD(TApp, "IniVectorMultiple", "[config]") { app.add_option("--three", three)->required(); run(); - - CHECK(two == std::vector<int>({2, 3})); - CHECK(three == std::vector<int>({1, 2, 3})); + + CHECK(two == std::vector<int>({2, 3})); + CHECK(three == std::vector<int>({1, 2, 3})); +} + +TEST_CASE_METHOD(TApp, "IniLayered", "[config]") { + + TempFile tmpini{"TestIniTmp.ini"}; + + app.set_config("--config", tmpini); + + { + std::ofstream out{tmpini}; + out << "[default]" << '\n'; + out << "val=1" << '\n'; + out << "[subcom]" << '\n'; + out << "val=2" << '\n'; + out << "subsubcom.val=3" << '\n'; + } + + 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, "IniLayeredStream", "[config]") { + + TempFile tmpini{"TestIniTmp.ini"}; + + app.set_config("--config", tmpini); + + { + std::ofstream out{tmpini}; + out << "[default]" << '\n'; + out << "val=1" << '\n'; + out << "[subcom]" << '\n'; + out << "val=2" << '\n'; + out << "subsubcom.val=3" << '\n'; + } + + 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"}; + + app.set_config("--config", tmpini); + + { + std::ofstream out{tmpini}; + out << "[default]" << '\n'; + out << "val=1" << '\n'; + out << "[subcom]" << '\n'; + out << "val=2" << '\n'; + out << "[subcom.subsubcom]" << '\n'; + out << "val=3" << '\n'; + } + + 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); + + three = 0; + // check maxlayers + app.get_config_formatter_base()->maxLayers(1); + run(); + CHECK(three == 0); +} + +TEST_CASE_METHOD(TApp, "IniLayeredDotSectionInQuotes", "[config]") { + + TempFile tmpini{"TestIniTmp.ini"}; + + app.set_config("--config", tmpini); + + { + std::ofstream out{tmpini}; + out << "[default]" << '\n'; + out << "val=1" << '\n'; + out << "['subcom']" << '\n'; + out << "val=2" << '\n'; + out << "['subcom'.\"subsubcom\"]" << '\n'; + out << "val=3" << '\n'; + } + + 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); + + three = 0; + // check maxlayers + app.get_config_formatter_base()->maxLayers(1); + run(); + CHECK(three == 0); } -TEST_CASE_METHOD(TApp, "IniLayered", "[config]") { +TEST_CASE_METHOD(TApp, "IniLayeredCustomSectionSeparator", "[config]") { TempFile tmpini{"TestIniTmp.ini"}; @@ -1154,13 +1662,14 @@ TEST_CASE_METHOD(TApp, "IniLayered", "[config]") { { 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; + out << "[default]" << '\n'; + out << "val=1" << '\n'; + out << "[subcom]" << '\n'; + out << "val=2" << '\n'; + out << "[subcom|subsubcom]" << '\n'; + out << "val=3" << '\n'; } - + app.get_config_formatter_base()->parentSeparator('|'); int one{0}, two{0}, three{0}; app.add_option("--val", one); auto *subcom = app.add_subcommand("subcom"); @@ -1178,7 +1687,31 @@ TEST_CASE_METHOD(TApp, "IniLayered", "[config]") { CHECK(!*subcom); } -TEST_CASE_METHOD(TApp, "IniLayeredStream", "[config]") { +TEST_CASE_METHOD(TApp, "IniLayeredOptionGroupAlias", "[config]") { + + TempFile tmpini{"TestIniTmp.ini"}; + + app.set_config("--config", tmpini); + + { + std::ofstream out{tmpini}; + out << "[default]" << '\n'; + out << "val=1" << '\n'; + out << "[ogroup]" << '\n'; + out << "val2=2" << '\n'; + } + int one{0}, two{0}; + app.add_option("--val", one); + auto *subcom = app.add_option_group("ogroup")->alias("ogroup"); + subcom->add_option("--val2", two); + + run(); + + CHECK(one == 1); + CHECK(two == 2); +} + +TEST_CASE_METHOD(TApp, "IniSubcommandConfigurable", "[config]") { TempFile tmpini{"TestIniTmp.ini"}; @@ -1186,32 +1719,33 @@ TEST_CASE_METHOD(TApp, "IniLayeredStream", "[config]") { { 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; + out << "[default]" << '\n'; + out << "val=1" << '\n'; + out << "[subcom]" << '\n'; + out << "val=2" << '\n'; + out << "subsubcom.val=3" << '\n'; } int one{0}, two{0}, three{0}; app.add_option("--val", one); auto *subcom = app.add_subcommand("subcom"); + subcom->configurable(); 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); + run(); CHECK(one == 1); CHECK(two == 2); CHECK(three == 3); - CHECK(0U == subcom->count()); - CHECK(!*subcom); + CHECK(1U == subcom->count()); + CHECK(*subcom); + CHECK(app.got_subcommand(subcom)); } -TEST_CASE_METHOD(TApp, "IniLayeredDotSection", "[config]") { +TEST_CASE_METHOD(TApp, "IniSubcommandConfigurableInQuotes", "[config]") { TempFile tmpini{"TestIniTmp.ini"}; @@ -1219,17 +1753,17 @@ TEST_CASE_METHOD(TApp, "IniLayeredDotSection", "[config]") { { 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; + out << "[default]" << '\n'; + out << "val=1" << '\n'; + out << "[subcom]" << '\n'; + out << "val=2" << '\n'; + out << "\"subsubcom\".'val'=3" << '\n'; } int one{0}, two{0}, three{0}; app.add_option("--val", one); auto *subcom = app.add_subcommand("subcom"); + subcom->configurable(); subcom->add_option("--val", two); auto *subsubcom = subcom->add_subcommand("subsubcom"); subsubcom->add_option("--val", three); @@ -1240,17 +1774,12 @@ TEST_CASE_METHOD(TApp, "IniLayeredDotSection", "[config]") { CHECK(two == 2); CHECK(three == 3); - CHECK(0U == subcom->count()); - CHECK(!*subcom); - - three = 0; - // check maxlayers - app.get_config_formatter_base()->maxLayers(1); - run(); - CHECK(three == 0); + CHECK(1U == subcom->count()); + CHECK(*subcom); + CHECK(app.got_subcommand(subcom)); } -TEST_CASE_METHOD(TApp, "IniLayeredCustomSectionSeparator", "[config]") { +TEST_CASE_METHOD(TApp, "IniSubcommandConfigurableInQuotesAlias", "[config]") { TempFile tmpini{"TestIniTmp.ini"}; @@ -1258,19 +1787,19 @@ TEST_CASE_METHOD(TApp, "IniLayeredCustomSectionSeparator", "[config]") { { 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; + out << "[default]" << '\n'; + out << "val=1" << '\n'; + out << "[subcom]" << '\n'; + out << "val=2" << '\n'; + out << R"("sub\tsub\t.com".'val'=3)" << '\n'; } - 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->configurable(); subcom->add_option("--val", two); - auto *subsubcom = subcom->add_subcommand("subsubcom"); + auto *subsubcom = subcom->add_subcommand("subsubcom")->alias("sub\tsub\t.com"); subsubcom->add_option("--val", three); run(); @@ -1279,11 +1808,12 @@ TEST_CASE_METHOD(TApp, "IniLayeredCustomSectionSeparator", "[config]") { CHECK(two == 2); CHECK(three == 3); - CHECK(0U == subcom->count()); - CHECK(!*subcom); + CHECK(1U == subcom->count()); + CHECK(*subcom); + CHECK(app.got_subcommand(subcom)); } -TEST_CASE_METHOD(TApp, "IniLayeredOptionGroupAlias", "[config]") { +TEST_CASE_METHOD(TApp, "IniSubcommandConfigurableInQuotesAliasWithEquals", "[config]") { TempFile tmpini{"TestIniTmp.ini"}; @@ -1291,23 +1821,33 @@ TEST_CASE_METHOD(TApp, "IniLayeredOptionGroupAlias", "[config]") { { std::ofstream out{tmpini}; - out << "[default]" << std::endl; - out << "val=1" << std::endl; - out << "[ogroup]" << std::endl; - out << "val2=2" << std::endl; + out << "[default]" << '\n'; + out << "val=1" << '\n'; + out << "[subcom]" << '\n'; + out << "val=2" << '\n'; + out << R"("sub=sub=.com".'val'=3)" << '\n'; } - int one{0}, two{0}; + + int one{0}, two{0}, three{0}; app.add_option("--val", one); - auto *subcom = app.add_option_group("ogroup")->alias("ogroup"); - subcom->add_option("--val2", two); + auto *subcom = app.add_subcommand("subcom"); + subcom->configurable(); + subcom->add_option("--val", two); + auto *subsubcom = subcom->add_subcommand("subsubcom")->alias("sub=sub=.com"); + subsubcom->add_option("--val", three); run(); CHECK(one == 1); CHECK(two == 2); + CHECK(three == 3); + + CHECK(1U == subcom->count()); + CHECK(*subcom); + CHECK(app.got_subcommand(subcom)); } -TEST_CASE_METHOD(TApp, "IniSubcommandConfigurable", "[config]") { +TEST_CASE_METHOD(TApp, "IniSubcommandConfigurableInQuotesAliasWithComment", "[config]") { TempFile tmpini{"TestIniTmp.ini"}; @@ -1315,11 +1855,11 @@ TEST_CASE_METHOD(TApp, "IniSubcommandConfigurable", "[config]") { { 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; + out << "[default]" << '\n'; + out << "val=1" << '\n'; + out << "[subcom]" << '\n'; + out << "val=2" << '\n'; + out << R"("sub#sub;.com".'val'=3)" << '\n'; } int one{0}, two{0}, three{0}; @@ -1327,7 +1867,7 @@ TEST_CASE_METHOD(TApp, "IniSubcommandConfigurable", "[config]") { auto *subcom = app.add_subcommand("subcom"); subcom->configurable(); subcom->add_option("--val", two); - auto *subsubcom = subcom->add_subcommand("subsubcom"); + auto *subsubcom = subcom->add_subcommand("subsubcom")->alias("sub#sub;.com"); subsubcom->add_option("--val", three); run(); @@ -1335,10 +1875,6 @@ TEST_CASE_METHOD(TApp, "IniSubcommandConfigurable", "[config]") { CHECK(one == 1); CHECK(two == 2); CHECK(three == 3); - - CHECK(1U == subcom->count()); - CHECK(*subcom); - CHECK(app.got_subcommand(subcom)); } TEST_CASE_METHOD(TApp, "IniSubcommandConfigurablePreParse", "[config]") { @@ -1349,11 +1885,11 @@ TEST_CASE_METHOD(TApp, "IniSubcommandConfigurablePreParse", "[config]") { { 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; + out << "[default]" << '\n'; + out << "val=1" << '\n'; + out << "[subcom]" << '\n'; + out << "val=2" << '\n'; + out << "subsubcom.val=3" << '\n'; } int one{0}, two{0}, three{0}, four{0}; @@ -1391,11 +1927,11 @@ TEST_CASE_METHOD(TApp, "IniSection", "[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; + out << "[config]" << '\n'; + out << "val=2" << '\n'; + out << "subsubcom.val=3" << '\n'; + out << "[default]" << '\n'; + out << "val=1" << '\n'; } int val{0}; @@ -1415,11 +1951,11 @@ TEST_CASE_METHOD(TApp, "IniSection2", "[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; + out << "[default]" << '\n'; + out << "val=1" << '\n'; + out << "[config]" << '\n'; + out << "val=2" << '\n'; + out << "subsubcom.val=3" << '\n'; } int val{0}; @@ -1439,11 +1975,11 @@ TEST_CASE_METHOD(TApp, "jsonLikeParsing", "[config]") { { std::ofstream out{tmpjson}; - out << "{" << std::endl; - out << "\"val\":1," << std::endl; - out << R"("val2":"test",)" << std::endl; - out << "\"flag\":true" << std::endl; - out << "}" << std::endl; + out << "{" << '\n'; + out << "\"val\":1," << '\n'; + out << R"("val2":"test",)" << '\n'; + out << "\"flag\":true" << '\n'; + out << "}" << '\n'; } int val{0}; @@ -1470,17 +2006,17 @@ TEST_CASE_METHOD(TApp, "TomlSectionNumber", "[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; - 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; + out << "[default]" << '\n'; + out << "val=1" << '\n'; + out << "[[config]]" << '\n'; + out << "val=2" << '\n'; + out << "subsubcom.val=3" << '\n'; + out << "[[config]]" << '\n'; + out << "val=4" << '\n'; + out << "subsubcom.val=3" << '\n'; + out << "[[config]]" << '\n'; + out << "val=6" << '\n'; + out << "subsubcom.val=3" << '\n'; } int val{0}; @@ -1514,12 +2050,12 @@ TEST_CASE_METHOD(TApp, "IniSubcommandConfigurableParseComplete", "[config]") { { 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; + out << "[default]" << '\n'; + out << "val=1" << '\n'; + out << "[subcom]" << '\n'; + out << "val=2" << '\n'; + out << "[subcom.subsubcom]" << '\n'; + out << "val=3" << '\n'; } int one{0}, two{0}, three{0}, four{0}; @@ -1559,14 +2095,14 @@ TEST_CASE_METHOD(TApp, "IniSubcommandMultipleSections", "[config]") { { 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; - out << "[subcom2]" << std::endl; - out << "val=4" << std::endl; + out << "[default]" << '\n'; + out << "val=1" << '\n'; + out << "[subcom]" << '\n'; + out << "val=2" << '\n'; + out << "[subcom.subsubcom]" << '\n'; + out << "val=3" << '\n'; + out << "[subcom2]" << '\n'; + out << "val=4" << '\n'; } int one{0}, two{0}, three{0}, four{0}; @@ -1607,9 +2143,9 @@ TEST_CASE_METHOD(TApp, "DuplicateSubcommandCallbacks", "[config]") { { std::ofstream out{tmptoml}; - out << "[[foo]]" << std::endl; - out << "[[foo]]" << std::endl; - out << "[[foo]]" << std::endl; + out << "[[foo]]" << '\n'; + out << "[[foo]]" << '\n'; + out << "[[foo]]" << '\n'; } auto *foo = app.add_subcommand("foo"); @@ -1631,7 +2167,7 @@ TEST_CASE_METHOD(TApp, "SubcommandCallbackSingle", "[config]") { { std::ofstream out{tmptoml}; - out << "[foo]" << std::endl; + out << "[foo]" << '\n'; } int count{0}; auto *foo = app.add_subcommand("foo"); @@ -1650,8 +2186,8 @@ TEST_CASE_METHOD(TApp, "IniFailure", "[config]") { app.allow_config_extras(false); { std::ofstream out{tmpini}; - out << "[default]" << std::endl; - out << "val=1" << std::endl; + out << "[default]" << '\n'; + out << "val=1" << '\n'; } CHECK_THROWS_AS(run(), CLI::ConfigError); @@ -1667,8 +2203,8 @@ TEST_CASE_METHOD(TApp, "IniConfigurable", "[config]") { { std::ofstream out{tmpini}; - out << "[default]" << std::endl; - out << "val=1" << std::endl; + out << "[default]" << '\n'; + out << "val=1" << '\n'; } REQUIRE_NOTHROW(run()); @@ -1685,11 +2221,48 @@ TEST_CASE_METHOD(TApp, "IniNotConfigurable", "[config]") { { std::ofstream out{tmpini}; - out << "[default]" << std::endl; - out << "val=1" << std::endl; + out << "[default]" << '\n'; + out << "val=1" << '\n'; } CHECK_THROWS_AS(run(), CLI::ConfigError); + app.allow_config_extras(CLI::config_extras_mode::ignore_all); + CHECK_NOTHROW(run()); +} + +TEST_CASE_METHOD(TApp, "IniFlagDisableOverrideFlagArray", "[config]") { + + TempFile tmpini{"TestIniTmp.ini"}; + + app.set_config("--config", tmpini); + int value{0}; + app.add_flag("--val", value)->configurable(true)->disable_flag_override(); + + { + std::ofstream out{tmpini}; + out << "[default]" << '\n'; + out << "val=[1,true,false,true]" << '\n'; + } + + REQUIRE_NOTHROW(run()); + CHECK(value == 2); +} + +TEST_CASE_METHOD(TApp, "IniFlagInvalidDisableOverrideFlagArray", "[config]") { + + TempFile tmpini{"TestIniTmp.ini"}; + + app.set_config("--config", tmpini); + int value{0}; + app.add_flag("--val", value)->configurable(true)->disable_flag_override(); + + { + std::ofstream out{tmpini}; + out << "[default]" << '\n'; + out << "val=[1,true,false,not_valid]" << '\n'; + } + + CHECK_THROWS_AS(run(), CLI::InvalidError); } TEST_CASE_METHOD(TApp, "IniSubFailure", "[config]") { @@ -1701,8 +2274,8 @@ TEST_CASE_METHOD(TApp, "IniSubFailure", "[config]") { app.allow_config_extras(false); { std::ofstream out{tmpini}; - out << "[other]" << std::endl; - out << "val=1" << std::endl; + out << "[other]" << '\n'; + out << "val=1" << '\n'; } CHECK_THROWS_AS(run(), CLI::ConfigError); @@ -1716,8 +2289,8 @@ TEST_CASE_METHOD(TApp, "IniNoSubFailure", "[config]") { app.allow_config_extras(CLI::config_extras_mode::error); { std::ofstream out{tmpini}; - out << "[other]" << std::endl; - out << "val=1" << std::endl; + out << "[other]" << '\n'; + out << "val=1" << '\n'; } CHECK_THROWS_AS(run(), CLI::ConfigError); @@ -1732,7 +2305,7 @@ TEST_CASE_METHOD(TApp, "IniFlagConvertFailure", "[config]") { { std::ofstream out{tmpini}; - out << "flag=moobook" << std::endl; + out << "flag=moobook" << '\n'; } run(); bool result{false}; @@ -1753,7 +2326,7 @@ TEST_CASE_METHOD(TApp, "IniFlagNumbers", "[config]") { { std::ofstream out{tmpini}; - out << "flag=3" << std::endl; + out << "flag=3" << '\n'; } REQUIRE_NOTHROW(run()); @@ -1771,7 +2344,7 @@ TEST_CASE_METHOD(TApp, "IniFlagDual", "[config]") { { std::ofstream out{tmpini}; - out << "flag=1 1" << std::endl; + out << "flag=1 1" << '\n'; } CHECK_THROWS_AS(run(), CLI::ConversionError); @@ -1788,7 +2361,7 @@ TEST_CASE_METHOD(TApp, "IniVectorMax", "[config]") { { std::ofstream out{tmpini}; - out << "vec=[a,b,c]" << std::endl; + out << "vec=[a,b,c]" << '\n'; } CHECK_THROWS_AS(run(), CLI::ArgumentMismatch); @@ -1804,7 +2377,58 @@ TEST_CASE_METHOD(TApp, "IniShort", "[config]") { { std::ofstream out{tmpini}; - out << "f=3" << std::endl; + out << "f=3" << '\n'; + } + + REQUIRE_NOTHROW(run()); + CHECK(3 == key); +} + +TEST_CASE_METHOD(TApp, "IniShortQuote1", "[config]") { + + TempFile tmpini{"TestIniTmp.ini"}; + + int key{0}; + app.add_option("--flag,-f", key); + app.set_config("--config", tmpini); + + { + std::ofstream out{tmpini}; + out << "\"f\"=3" << '\n'; + } + + REQUIRE_NOTHROW(run()); + CHECK(3 == key); +} + +TEST_CASE_METHOD(TApp, "IniShortQuote2", "[config]") { + + TempFile tmpini{"TestIniTmp.ini"}; + + int key{0}; + app.add_option("--flag,-f", key); + app.set_config("--config", tmpini); + + { + std::ofstream out{tmpini}; + out << "'f'=3" << '\n'; + } + + REQUIRE_NOTHROW(run()); + CHECK(3 == key); +} + +TEST_CASE_METHOD(TApp, "IniShortQuote3", "[config]") { + + TempFile tmpini{"TestIniTmp.ini"}; + + int key{0}; + app.add_option("--flag,-f", key); + app.set_config("--config", tmpini); + + { + std::ofstream out{tmpini}; + out << "`f`=3" << '\n'; } REQUIRE_NOTHROW(run()); @@ -1821,7 +2445,7 @@ TEST_CASE_METHOD(TApp, "IniDefaultPath", "[config]") { { std::ofstream out{tmpini}; - out << "f=3" << std::endl; + out << "f=3" << '\n'; } REQUIRE_NOTHROW(run()); @@ -1840,7 +2464,7 @@ TEST_CASE_METHOD(TApp, "IniMultipleDefaultPath", "[config]") { { std::ofstream out{tmpini}; - out << "f=3" << std::endl; + out << "f=3" << '\n'; } args = {"--config", "TestIniTmp.ini"}; @@ -1860,7 +2484,7 @@ TEST_CASE_METHOD(TApp, "IniMultipleDefaultPathAlternate", "[config]") { { std::ofstream out{tmpini}; - out << "f=3" << std::endl; + out << "f=3" << '\n'; } args = {"--config", "TestIniTmp.ini"}; @@ -1879,7 +2503,7 @@ TEST_CASE_METHOD(TApp, "IniPositional", "[config]") { { std::ofstream out{tmpini}; - out << "key=3" << std::endl; + out << "key=3" << '\n'; } REQUIRE_NOTHROW(run()); @@ -1896,7 +2520,7 @@ TEST_CASE_METHOD(TApp, "IniEnvironmental", "[config]") { { std::ofstream out{tmpini}; - out << "CLI11_TEST_ENV_KEY_TMP=3" << std::endl; + out << "CLI11_TEST_ENV_KEY_TMP=3" << '\n'; } REQUIRE_NOTHROW(run()); @@ -1916,10 +2540,10 @@ TEST_CASE_METHOD(TApp, "IniFlagText", "[config]") { { std::ofstream out{tmpini}; - out << "flag1=true" << std::endl; - out << "flag2=on" << std::endl; - out << "flag3=off" << std::endl; - out << "flag4=1" << std::endl; + out << "flag1=true" << '\n'; + out << "flag2=on" << '\n'; + out << "flag3=off" << '\n'; + out << "flag4=1" << '\n'; } run(); @@ -1936,13 +2560,73 @@ TEST_CASE_METHOD(TApp, "IniFlags", "[config]") { { std::ofstream out{tmpini}; - out << "[default]" << std::endl; - out << "two=2" << std::endl; - out << "three=true" << std::endl; - out << "four=on" << std::endl; - out << "five" << std::endl; + out << "[default]" << '\n'; + out << "two=2" << '\n'; + out << "three=true" << '\n'; + out << "four=on" << '\n'; + out << "five" << '\n'; + } + + int two{0}; + bool three{false}, four{false}, five{false}; + app.add_flag("--two", two); + app.add_flag("--three", three); + app.add_flag("--four", four); + app.add_flag("--five", five); + + run(); + + CHECK(two == 2); + CHECK(three); + CHECK(four); + CHECK(five); +} + +TEST_CASE_METHOD(TApp, "IniFlagsComment", "[config]") { + TempFile tmpini{"TestIniTmp.ini"}; + app.set_config("--config", tmpini); + + { + std::ofstream out{tmpini}; + out << "[default]" << '\n'; + out << "two=2 # comment 1" << '\n'; + out << "three=true" << '\n'; + out << "four=on #comment 2" << '\n'; + out << "five #comment 3" << '\n'; + out << '\n'; + } + + int two{0}; + bool three{false}, four{false}, five{false}; + app.add_flag("--two", two); + app.add_flag("--three", three); + app.add_flag("--four", four); + app.add_flag("--five", five); + + run(); + + CHECK(two == 2); + CHECK(three); + CHECK(four); + CHECK(five); +} + +TEST_CASE_METHOD(TApp, "IniFlagsAltComment", "[config]") { + TempFile tmpini{"TestIniTmp.ini"}; + app.set_config("--config", tmpini); + + { + std::ofstream out{tmpini}; + out << "[default]" << '\n'; + out << "two=2 % comment 1" << '\n'; + out << "three=true" << '\n'; + out << "four=on %% comment 2" << '\n'; + out << "five %= 3" << '\n'; + out << '\n'; } + auto config = app.get_config_formatter_base(); + config->comment('%'); int two{0}; bool three{false}, four{false}, five{false}; app.add_flag("--two", two); @@ -1964,11 +2648,11 @@ TEST_CASE_METHOD(TApp, "IniFalseFlags", "[config]") { { std::ofstream out{tmpini}; - out << "[default]" << std::endl; - out << "two=-2" << std::endl; - out << "three=false" << std::endl; - out << "four=1" << std::endl; - out << "five" << std::endl; + out << "[default]" << '\n'; + out << "two=-2" << '\n'; + out << "three=false" << '\n'; + out << "four=1" << '\n'; + out << "five" << '\n'; } int two{0}; @@ -1992,11 +2676,11 @@ TEST_CASE_METHOD(TApp, "IniFalseFlagsDef", "[config]") { { std::ofstream out{tmpini}; - out << "[default]" << std::endl; - out << "two=2" << std::endl; - out << "three=true" << std::endl; - out << "four=on" << std::endl; - out << "five" << std::endl; + out << "[default]" << '\n'; + out << "two=2" << '\n'; + out << "three=true" << '\n'; + out << "four=on" << '\n'; + out << "five" << '\n'; } int two{0}; @@ -2020,10 +2704,10 @@ TEST_CASE_METHOD(TApp, "IniFalseFlagsDefDisableOverrideError", "[config]") { { std::ofstream out{tmpini}; - out << "[default]" << std::endl; - out << "two=2" << std::endl; - out << "four=on" << std::endl; - out << "five" << std::endl; + out << "[default]" << '\n'; + out << "two=2" << '\n'; + out << "four=on" << '\n'; + out << "five" << '\n'; } int two{0}; @@ -2041,10 +2725,10 @@ TEST_CASE_METHOD(TApp, "IniFalseFlagsDefDisableOverrideSuccess", "[config]") { { std::ofstream out{tmpini}; - out << "[default]" << std::endl; - out << "two=2" << std::endl; - out << "four={}" << std::endl; - out << "val=15" << std::endl; + out << "[default]" << '\n'; + out << "two=2" << '\n'; + out << "four={}" << '\n'; + out << "val=15" << '\n'; } int two{0}, four{0}, val{0}; @@ -2071,20 +2755,20 @@ TEST_CASE_METHOD(TApp, "IniDisableFlagOverride", "[config]") { { std::ofstream out{tmpini}; - out << "[default]" << std::endl; - out << "two=2" << std::endl; + out << "[default]" << '\n'; + out << "two=2" << '\n'; } { std::ofstream out{tmpini2}; - out << "[default]" << std::endl; - out << "two=7" << std::endl; + out << "[default]" << '\n'; + out << "two=7" << '\n'; } { std::ofstream out{tmpini3}; - out << "[default]" << std::endl; - out << "three=true" << std::endl; + out << "[default]" << '\n'; + out << "three=true" << '\n'; } int val{0}; @@ -2185,6 +2869,17 @@ TEST_CASE_METHOD(TApp, "TomlOutputShortSingleDescription", "[config]") { CHECK_THAT(str, Contains("# " + description + "\n" + flag + "=false\n")); } +TEST_CASE_METHOD(TApp, "TomlOutputdefaultOptionString", "[config]") { + std::string option = "some_option"; + const std::string description = "Some short description."; + app.add_option("--" + option, description)->run_callback_for_default(); + + run(); + + std::string str = app.config_to_str(true, true); + CHECK_THAT(str, Contains("# " + description + "\n" + option + "=\"\"\n")); +} + TEST_CASE_METHOD(TApp, "TomlOutputShortDoubleDescription", "[config]") { std::string flag1 = "flagnr1"; std::string flag2 = "flagnr2"; @@ -2269,6 +2964,7 @@ TEST_CASE_METHOD(TApp, "TomlOutputOptionGroupMultiLineDescription", "[config]") og->description("Option group description.\n" "That has multiple lines."); og->add_flag("--" + flag, description); + args = {"--" + flag}; run(); std::string str = app.config_to_str(true, true); @@ -2276,6 +2972,26 @@ TEST_CASE_METHOD(TApp, "TomlOutputOptionGroupMultiLineDescription", "[config]") CHECK_THAT(str, Contains("# That has multiple lines.\n")); } +TEST_CASE_METHOD(TApp, "TomlOutputMultilineString", "[config]") { + std::string desc = "flag"; + app.add_option("--opt", desc); + + std::string argString = "this is a very long string \n that covers multiple lines \nand should be longer than 100 " + "characters \nto trigger the multiline string"; + args = {"--opt", argString}; + + run(); + + std::string str = app.config_to_str(true, true); + + std::istringstream nfile(str); + + app.clear(); + desc = ""; + app.parse_from_stream(nfile); + CHECK(desc == argString); +} + TEST_CASE_METHOD(TApp, "TomlOutputSubcommandMultiLineDescription", "[config]") { std::string flag = "flag"; const std::string description = "Short flag description.\n"; @@ -2539,7 +3255,7 @@ TEST_CASE_METHOD(TApp, "TomlOutputQuoted", "[config]") { std::string str = app.config_to_str(); CHECK_THAT(str, Contains("val1=\"I am a string\"")); - CHECK_THAT(str, Contains("val2='I am a \"confusing\" string'")); + CHECK_THAT(str, Contains("val2=\"I am a \\\"confusing\\\" string\"")); } TEST_CASE_METHOD(TApp, "DefaultsTomlOutputQuoted", "[config]") { @@ -2554,7 +3270,7 @@ TEST_CASE_METHOD(TApp, "DefaultsTomlOutputQuoted", "[config]") { std::string str = app.config_to_str(true); CHECK_THAT(str, Contains("val1=\"I am a string\"")); - CHECK_THAT(str, Contains("val2='I am a \"confusing\" string'")); + CHECK_THAT(str, Contains("val2=\"I am a \\\"confusing\\\" string\"")); } // #298 @@ -2568,7 +3284,7 @@ TEST_CASE_METHOD(TApp, "StopReadingConfigOnClear", "[config]") { { std::ofstream out{tmpini}; - out << "volume=1" << std::endl; + out << "volume=1" << '\n'; } int volume{0}; @@ -2590,7 +3306,7 @@ TEST_CASE_METHOD(TApp, "ConfigWriteReadWrite", "[config]") { std::string config1 = app.config_to_str(true, true); { std::ofstream out{tmpini}; - out << config1 << std::endl; + out << config1 << '\n'; } app.set_config("--config", tmpini, "Read an ini file", true); @@ -2613,7 +3329,7 @@ TEST_CASE_METHOD(TApp, "ConfigWriteReadNegated", "[config]") { std::string config1 = app.config_to_str(false, false); { std::ofstream out{tmpini}; - out << config1 << std::endl; + out << config1 << '\n'; } CHECK_FALSE(flag); args.clear(); @@ -2926,6 +3642,23 @@ TEST_CASE_METHOD(TApp, "IniOutputSubsubcom", "[config]") { CHECK_THAT(str, Contains("other.sub2.newest=true")); } +TEST_CASE_METHOD(TApp, "IniOutputSubsubcomWithDot", "[config]") { + + app.add_flag("--simple"); + auto *subcom = app.add_subcommand("other"); + subcom->add_flag("--newer"); + auto *subsubcom = subcom->add_subcommand("sub2.bb"); + subsubcom->add_flag("--newest"); + app.config_formatter(std::make_shared<CLI::ConfigINI>()); + args = {"--simple", "other", "--newer", "sub2.bb", "--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.bb'.newest=true")); +} + TEST_CASE_METHOD(TApp, "IniOutputSubsubcomCustomSep", "[config]") { app.add_flag("--simple"); @@ -2944,6 +3677,42 @@ TEST_CASE_METHOD(TApp, "IniOutputSubsubcomCustomSep", "[config]") { CHECK_THAT(str, Contains("other|sub2|newest=true")); } +TEST_CASE_METHOD(TApp, "IniOutputSubsubcomCustomSepWithInternalSep", "[config]") { + + app.add_flag("--simple"); + auto *subcom = app.add_subcommand("other"); + subcom->add_flag("--newer"); + auto *subsubcom = subcom->add_subcommand("sub2|BB"); + subsubcom->add_flag("--newest"); + app.config_formatter(std::make_shared<CLI::ConfigINI>()); + app.get_config_formatter_base()->parentSeparator('|'); + args = {"--simple", "other", "--newer", "sub2|BB", "--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|BB'|newest=true")); +} + +TEST_CASE_METHOD(TApp, "IniOutputSubsubcomCustomSepWithInternalQuote", "[config]") { + + app.add_flag("--simple"); + auto *subcom = app.add_subcommand("other"); + subcom->add_flag("--newer"); + auto *subsubcom = subcom->add_subcommand("sub2'BB"); + subsubcom->add_flag("--newest"); + app.config_formatter(std::make_shared<CLI::ConfigINI>()); + app.get_config_formatter_base()->parentSeparator('|'); + args = {"--simple", "other", "--newer", "sub2'BB", "--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'BB\"|newest=true")); +} + TEST_CASE_METHOD(TApp, "IniOutputSubsubcomConfigurable", "[config]") { app.add_flag("--simple"); @@ -3005,7 +3774,7 @@ TEST_CASE_METHOD(TApp, "IniOutputQuoted", "[config]") { std::string str = app.config_to_str(); CHECK_THAT(str, Contains("val1=\"I am a string\"")); - CHECK_THAT(str, Contains("val2='I am a \"confusing\" string'")); + CHECK_THAT(str, Contains("val2=\"I am a \\\"confusing\\\" string\"")); } TEST_CASE_METHOD(TApp, "DefaultsIniOutputQuoted", "[config]") { @@ -3020,5 +3789,5 @@ TEST_CASE_METHOD(TApp, "DefaultsIniOutputQuoted", "[config]") { std::string str = app.config_to_str(true); CHECK_THAT(str, Contains("val1=\"I am a string\"")); - CHECK_THAT(str, Contains("val2='I am a \"confusing\" string'")); + CHECK_THAT(str, Contains("val2=\"I am a \\\"confusing\\\" string\"")); } diff --git a/packages/CLI11/tests/CreationTest.cpp b/packages/CLI11/tests/CreationTest.cpp index a51abd4889244d4fbd453db9b98d7a6a0d9c7ba0..46f57770fb3873a8716e9a423826c519c266271b 100644 --- a/packages/CLI11/tests/CreationTest.cpp +++ b/packages/CLI11/tests/CreationTest.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2024, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // @@ -40,6 +40,16 @@ TEST_CASE_METHOD(TApp, "AddingExistingWithCase", "[creation]") { CHECK_NOTHROW(app.add_flag("--Cat,-C")); } +TEST_CASE_METHOD(TApp, "AddingExistingShortLong", "[creation]") { + app.add_flag("-c"); + CHECK_THROWS_AS(app.add_flag("--c"), CLI::OptionAlreadyAdded); +} + +TEST_CASE_METHOD(TApp, "AddingExistingLongShort", "[creation]") { + app.add_flag("--c"); + CHECK_THROWS_AS(app.add_option("-c"), CLI::OptionAlreadyAdded); +} + TEST_CASE_METHOD(TApp, "AddingExistingWithCaseAfter", "[creation]") { auto *count = app.add_flag("-c,--count"); app.add_flag("--Cat,-C"); @@ -68,6 +78,37 @@ TEST_CASE_METHOD(TApp, "AddingExistingWithUnderscoreAfter2", "[creation]") { CHECK_THROWS_AS(count->ignore_underscore(), CLI::OptionAlreadyAdded); } +TEST_CASE_METHOD(TApp, "matchPositional", "[creation]") { + app.add_option("firstoption"); + CHECK_THROWS_AS(app.add_option("--firstoption"), CLI::OptionAlreadyAdded); +} + +TEST_CASE_METHOD(TApp, "matchPositional2", "[creation]") { + app.add_option("--firstoption"); + CHECK_THROWS_AS(app.add_option("firstoption"), CLI::OptionAlreadyAdded); +} + +TEST_CASE_METHOD(TApp, "matchPositionalInOptionGroup1", "[creation]") { + + auto *g1 = app.add_option_group("group_b"); + g1->add_option("--firstoption"); + CHECK_THROWS_AS(app.add_option("firstoption"), CLI::OptionAlreadyAdded); +} + +TEST_CASE_METHOD(TApp, "matchPositionalInOptionGroup2", "[creation]") { + + app.add_option("firstoption"); + auto *g1 = app.add_option_group("group_b"); + CHECK_THROWS_AS(g1->add_option("--firstoption"), CLI::OptionAlreadyAdded); +} + +TEST_CASE_METHOD(TApp, "matchPositionalInOptionGroup3", "[creation]") { + + app.add_option("f"); + auto *g1 = app.add_option_group("group_b"); + CHECK_THROWS_AS(g1->add_option("-f"), CLI::OptionAlreadyAdded); +} + TEST_CASE_METHOD(TApp, "AddingMultipleInfPositionals", "[creation]") { std::vector<std::string> one, two; app.add_option("one", one); diff --git a/packages/CLI11/tests/DeprecatedTest.cpp b/packages/CLI11/tests/DeprecatedTest.cpp index 063c67e5e884dc6f004b23176eabb9b57bc63f3c..e4a151f2cf04142c8994ab539a72d1514ceddb93 100644 --- a/packages/CLI11/tests/DeprecatedTest.cpp +++ b/packages/CLI11/tests/DeprecatedTest.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2024, 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/EncodingTest.cpp b/packages/CLI11/tests/EncodingTest.cpp index b026ee0143dc3331f8b49f306358db7031233bf2..6b84fc20eb27e5085ad573f6a4f98124764a2358 100644 --- a/packages/CLI11/tests/EncodingTest.cpp +++ b/packages/CLI11/tests/EncodingTest.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2024, 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/FormatterTest.cpp b/packages/CLI11/tests/FormatterTest.cpp index 2563c9421c54ea03c2a5a9ea7284a8a42db11288..215dcb1022e2e019486c803aaca3b7ded835ca08 100644 --- a/packages/CLI11/tests/FormatterTest.cpp +++ b/packages/CLI11/tests/FormatterTest.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2024, 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/FuzzFailTest.cpp b/packages/CLI11/tests/FuzzFailTest.cpp index 22148368819a9a4763dde949b68c5082c445694d..124c8f4287e0818033561ae2f81500c71cefecba 100644 --- a/packages/CLI11/tests/FuzzFailTest.cpp +++ b/packages/CLI11/tests/FuzzFailTest.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2024, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // @@ -23,15 +23,78 @@ std::string loadFailureFile(const std::string &type, int index) { TEST_CASE("app_fail") { CLI::FuzzApp fuzzdata; - auto app = fuzzdata.generateApp(); - int index = GENERATE(range(1, 3)); - + int index = GENERATE(range(1, 4)); + std::string optionString; auto parseData = loadFailureFile("fuzz_app_fail", index); + if(index >= 3 && parseData.size() > 25) { + optionString = parseData.substr(0, 25); + parseData.erase(0, 25); + } + try { - app->parse(parseData); + if(!optionString.empty()) { + app->add_option(optionString, fuzzdata.buffer); + } + try { + app->parse(parseData); + } catch(const CLI::ParseError & /*e*/) { + CHECK(true); + } + } catch(const CLI::ConstructionError & /*e*/) { + CHECK(true); + } +} + +TEST_CASE("file_fail") { + CLI::FuzzApp fuzzdata; + auto app = fuzzdata.generateApp(); + + int index = GENERATE(range(1, 9)); + auto parseData = loadFailureFile("fuzz_file_fail", index); + std::stringstream out(parseData); + try { + app->parse_from_stream(out); } catch(const CLI::ParseError & /*e*/) { + CHECK(true); + } +} + +TEST_CASE("app_file_gen_fail") { + CLI::FuzzApp fuzzdata; + auto app = fuzzdata.generateApp(); + + int index = GENERATE(range(1, 40)); + std::string optionString, flagString; + auto parseData = loadFailureFile("fuzz_app_file_fail", index); + if(parseData.size() > 25) { + optionString = parseData.substr(0, 25); + parseData.erase(0, 25); + } + if(parseData.size() > 25) { + flagString = parseData.substr(0, 25); + parseData.erase(0, 25); + } + try { + + if(!optionString.empty()) { + app->add_option(optionString, fuzzdata.buffer); + } + if(!flagString.empty()) { + app->add_flag(flagString, fuzzdata.intbuffer); + } + try { + app->parse(parseData); + } catch(const CLI::ParseError & /*e*/) { + return; + } + } catch(const CLI::ConstructionError & /*e*/) { + return; } + std::string configOut = app->config_to_str(); + app->clear(); + std::stringstream out(configOut); + app->parse_from_stream(out); } diff --git a/packages/CLI11/tests/HelpTest.cpp b/packages/CLI11/tests/HelpTest.cpp index c4403f754f48ed99c8188bf3cdecae64448da4ac..e21b29b0a764bb2e974d5b60ba0ba61a9e3bcfcf 100644 --- a/packages/CLI11/tests/HelpTest.cpp +++ b/packages/CLI11/tests/HelpTest.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2024, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // @@ -10,6 +10,8 @@ #include "CLI/CLI.hpp" #endif +#include "app_helper.hpp" + #include "catch.hpp" #include <fstream> @@ -718,6 +720,22 @@ TEST_CASE("THelp: CustomHelp", "[help]") { } } +TEST_CASE("THelp: HelpSubcommandPriority", "[help]") { + CLI::App app{"My prog"}; + + app.set_help_flag("-h", "display help and exit"); + + auto *sub1 = app.add_subcommand("sub1"); + std::string someFile = ""; + + put_env("SOME_FILE", "NOT_A_FILE"); + sub1->add_option("-f,--file", someFile)->envname("SOME_FILE")->required()->expected(1)->check(CLI::ExistingFile); + + std::string input{"sub1 -h"}; + CHECK_THROWS_AS(app.parse(input), CLI::CallForHelp); + unset_env("SOME_FILE"); +} + TEST_CASE("THelp: NextLineShouldBeAlignmentInMultilineDescription", "[help]") { CLI::App app; int i{0}; @@ -1318,3 +1336,19 @@ TEST_CASE("TVersion: parse_throw", "[help]") { CHECK(1U == cptr->count()); } } + +TEST_CASE("TVersion: exit", "[help]") { + + CLI::App app; + + app.set_version_flag("--version", CLI11_VERSION); + + try { + app.parse("--version"); + } catch(const CLI::CallForVersion &v) { + std::ostringstream out; + auto ret = app.exit(v, out); + CHECK_THAT(out.str(), Contains(CLI11_VERSION)); + CHECK(0 == ret); + } +} diff --git a/packages/CLI11/tests/HelpersTest.cpp b/packages/CLI11/tests/HelpersTest.cpp index 5186b47fcfc2a5648048d42cb82570437d1962ac..44262417adfb6c0b34f499129312294a8aef0503 100644 --- a/packages/CLI11/tests/HelpersTest.cpp +++ b/packages/CLI11/tests/HelpersTest.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2024, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // @@ -165,6 +165,7 @@ TEST_CASE("String: InvalidName", "[helpers]") { CHECK(CLI::detail::valid_name_string("b@d2?")); CHECK(CLI::detail::valid_name_string("2vali?d")); CHECK_FALSE(CLI::detail::valid_name_string("!valid")); + CHECK_FALSE(CLI::detail::valid_name_string("!va\nlid")); } TEST_CASE("StringTools: Modify", "[helpers]") { @@ -201,15 +202,26 @@ TEST_CASE("StringTools: Modify3", "[helpers]") { } TEST_CASE("StringTools: flagValues", "[helpers]") { + errno = 0; CHECK(-1 == CLI::detail::to_flag_value("0")); + CHECK(errno == 0); CHECK(1 == CLI::detail::to_flag_value("t")); CHECK(1 == CLI::detail::to_flag_value("1")); CHECK(6 == CLI::detail::to_flag_value("6")); CHECK(-6 == CLI::detail::to_flag_value("-6")); CHECK(-1 == CLI::detail::to_flag_value("false")); CHECK(1 == CLI::detail::to_flag_value("YES")); - CHECK_THROWS_AS(CLI::detail::to_flag_value("frog"), std::invalid_argument); - CHECK_THROWS_AS(CLI::detail::to_flag_value("q"), std::invalid_argument); + errno = 0; + CLI::detail::to_flag_value("frog"); + CHECK(errno == EINVAL); + errno = 0; + CLI::detail::to_flag_value("q"); + CHECK(errno == EINVAL); + errno = 0; + CLI::detail::to_flag_value( + "77777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777"); + CHECK(errno == ERANGE); + errno = 0; CHECK(-1 == CLI::detail::to_flag_value("NO")); CHECK(475555233 == CLI::detail::to_flag_value("475555233")); } @@ -226,6 +238,189 @@ TEST_CASE("StringTools: Validation", "[helpers]") { CHECK_FALSE(CLI::detail::isalpha("test2")); } +TEST_CASE("StringTools: binaryEscapseConversion", "[helpers]") { + std::string testString("string1"); + std::string estring = CLI::detail::binary_escape_string(testString); + CHECK(testString == estring); + CHECK_FALSE(CLI::detail::is_binary_escaped_string(estring)); + + std::string testString2("\nstring1\n"); + estring = CLI::detail::binary_escape_string(testString2); + CHECK_FALSE(testString == estring); + CHECK(CLI::detail::is_binary_escaped_string(estring)); + std::string rstring = CLI::detail::extract_binary_string(estring); + CHECK(rstring == testString2); + + CLI::detail::remove_quotes(estring); + CHECK(CLI::detail::is_binary_escaped_string(estring)); + std::string rstringrq = CLI::detail::extract_binary_string(estring); + CHECK(rstringrq == testString2); + + testString2.push_back(0); + testString2.push_back(static_cast<char>(197)); + testString2.push_back(78); + testString2.push_back(-34); + + rstring = CLI::detail::extract_binary_string(CLI::detail::binary_escape_string(testString2)); + CHECK(rstring == testString2); + + testString2.push_back('b'); + testString2.push_back('G'); + + rstring = CLI::detail::extract_binary_string(CLI::detail::binary_escape_string(testString2)); + CHECK(rstring == testString2); + auto rstring2 = CLI::detail::extract_binary_string(rstring); + CHECK(rstring == rstring2); +} + +TEST_CASE("StringTools: binaryStrings", "[helpers]") { + std::string rstring = "B\"()\""; + CHECK(CLI::detail::extract_binary_string(rstring).empty()); + + rstring = "B\"(\\x35\\xa7)\""; + CHECK(CLI::detail::is_binary_escaped_string(rstring)); + auto result = CLI::detail::extract_binary_string(rstring); + CHECK(result[0] == static_cast<char>(0x35)); + CHECK(result[1] == static_cast<char>(0xa7)); + + rstring = "'B\"(\\x3e\\xf7)\"'"; + CHECK(CLI::detail::is_binary_escaped_string(rstring)); + result = CLI::detail::extract_binary_string(rstring); + CHECK(result[0] == static_cast<char>(0x3e)); + CHECK(result[1] == static_cast<char>(0xf7)); + + rstring = "B\"(\\x3E\\xf7)\""; + result = CLI::detail::extract_binary_string(rstring); + CHECK(result[0] == static_cast<char>(0x3e)); + CHECK(result[1] == static_cast<char>(0xf7)); + + rstring = "B\"(\\X3E\\XF7)\""; + result = CLI::detail::extract_binary_string(rstring); + CHECK(result[0] == static_cast<char>(0x3e)); + CHECK(result[1] == static_cast<char>(0xf7)); + + rstring = "B\"(\\XME\\XK7)\""; + result = CLI::detail::extract_binary_string(rstring); + CHECK(result == "\\XME\\XK7"); + + rstring = "B\"(\\XEM\\X7K)\""; + result = CLI::detail::extract_binary_string(rstring); + CHECK(result == "\\XEM\\X7K"); +} + +TEST_CASE("StringTools: escapeConversion", "[helpers]") { + CHECK(CLI::detail::remove_escaped_characters("test\\\"") == "test\""); + CHECK(CLI::detail::remove_escaped_characters("test\\\\") == "test\\"); + CHECK(CLI::detail::remove_escaped_characters("test\\b") == "test\b"); + CHECK(CLI::detail::remove_escaped_characters("test\\t") == "test\t"); + CHECK(CLI::detail::remove_escaped_characters("test\\n\\r\\t\\f") == "test\n\r\t\f"); + CHECK(CLI::detail::remove_escaped_characters("test\\r") == "test\r"); + CHECK(CLI::detail::remove_escaped_characters("test\\f") == "test\f"); + std::string zstring = "test"; + zstring.push_back('\0'); + zstring.append("test\n"); + CHECK(CLI::detail::remove_escaped_characters("test\\0test\\n") == zstring); + + CHECK_THROWS_AS(CLI::detail::remove_escaped_characters("test\\m_bad"), std::invalid_argument); + CHECK_THROWS_AS(CLI::detail::remove_escaped_characters("test\\"), std::invalid_argument); +} + +TEST_CASE("StringTools: quotedString", "[helpers]") { + + std::string rstring = "'B\"(\\x35\\xa7)\"'"; + auto s2 = rstring; + CLI::detail::process_quoted_string(s2); + CHECK(s2[0] == static_cast<char>(0x35)); + CHECK(s2[1] == static_cast<char>(0xa7)); + s2 = rstring; + CLI::detail::remove_quotes(s2); + CLI::detail::process_quoted_string(s2); + CHECK(s2[0] == static_cast<char>(0x35)); + CHECK(s2[1] == static_cast<char>(0xa7)); + + std::string qbase = R"("this\nis\na\nfour\tline test")"; + std::string qresult = "this\nis\na\nfour\tline test"; + + std::string q1 = qbase; + + // test remove quotes and escape processing + CLI::detail::process_quoted_string(q1); + CHECK(q1 == qresult); + + std::string q2 = qbase; + q2.front() = '\''; + q2.pop_back(); + q2.push_back('\''); + std::string qliteral = qbase.substr(1); + qliteral.pop_back(); + + // test remove quotes for literal string + CHECK(CLI::detail::process_quoted_string(q2)); + CHECK(q2 == qliteral); + + std::string q3 = qbase; + q3.front() = '`'; + q3.pop_back(); + q3.push_back('`'); + + // test remove quotes for literal string + CHECK(CLI::detail::process_quoted_string(q3)); + CHECK(q3 == qliteral); + + std::string q4 = qbase; + q4.front() = '|'; + q4.pop_back(); + q4.push_back('|'); + + // check that it doesn't process + CHECK_FALSE(CLI::detail::process_quoted_string(q4)); + // test custom string quote character + CHECK(CLI::detail::process_quoted_string(q4, '|')); + CHECK(q4 == qresult); + + std::string q5 = qbase; + q5.front() = '?'; + q5.pop_back(); + q5.push_back('?'); + + // test custom literal quote character + CHECK(CLI::detail::process_quoted_string(q5, '|', '?')); + CHECK(q5 == qliteral); + + q3 = qbase; + q3.front() = '`'; + q3.pop_back(); + q3.push_back('`'); + + // test that '`' still works regardless of the other specified characters + CHECK(CLI::detail::process_quoted_string(q3)); + CHECK(q3 == qliteral); +} + +TEST_CASE("StringTools: unicode_literals", "[helpers]") { + + CHECK(CLI::detail::remove_escaped_characters("test\\u03C0\\u00e9") == from_u8string(u8"test\u03C0\u00E9")); + CHECK(CLI::detail::remove_escaped_characters("test\\u73C0\\u0057") == from_u8string(u8"test\u73C0\u0057")); + + CHECK(CLI::detail::remove_escaped_characters("test\\U0001F600\\u00E9") == from_u8string(u8"test\U0001F600\u00E9")); + + CHECK_THROWS_AS(CLI::detail::remove_escaped_characters("test\\U0001M600\\u00E9"), std::invalid_argument); + CHECK_THROWS_AS(CLI::detail::remove_escaped_characters("test\\U0001E600\\u00M9"), std::invalid_argument); + CHECK_THROWS_AS(CLI::detail::remove_escaped_characters("test\\U0001E600\\uD8E9"), std::invalid_argument); + + CHECK_THROWS_AS(CLI::detail::remove_escaped_characters("test\\U0001E600\\uD8"), std::invalid_argument); + CHECK_THROWS_AS(CLI::detail::remove_escaped_characters("test\\U0001E60"), std::invalid_argument); +} + +TEST_CASE("StringTools: close_sequence", "[helpers]") { + CHECK(CLI::detail::close_sequence("[test]", 0, ']') == 5U); + CHECK(CLI::detail::close_sequence("[\"test]\"]", 0, ']') == 8U); + CHECK(CLI::detail::close_sequence("[\"test]\"],[t2]", 0, ']') == 8U); + CHECK(CLI::detail::close_sequence("[\"test]\"],[t2]", 10, ']') == 13U); + CHECK(CLI::detail::close_sequence("{\"test]\"],[t2]", 0, '}') == 14U); + CHECK(CLI::detail::close_sequence("[(),(),{},\"]]52{}\",[],[54],[[],[],()]]", 0, ']') == 37U); +} + TEST_CASE("Trim: Various", "[helpers]") { std::string s1{" sdlfkj sdflk sd s "}; std::string a1{"sdlfkj sdflk sd s"}; @@ -501,7 +696,7 @@ TEST_CASE("Validators: ProgramNameSplit", "[helpers]") { TempFile myfile{"program_name1.exe"}; { std::ofstream out{myfile}; - out << "useless string doesn't matter" << std::endl; + out << "useless string doesn't matter" << '\n'; } auto res = CLI::detail::split_program_name(std::string("./") + std::string(myfile) + " this is a bunch of extra stuff "); @@ -511,7 +706,7 @@ TEST_CASE("Validators: ProgramNameSplit", "[helpers]") { TempFile myfile2{"program name1.exe"}; { std::ofstream out{myfile2}; - out << "useless string doesn't matter" << std::endl; + out << "useless string doesn't matter" << '\n'; } res = CLI::detail::split_program_name(std::string(" ") + std::string("./") + std::string(myfile2) + " this is a bunch of extra stuff "); @@ -777,7 +972,7 @@ TEST_CASE("AppHelper: Ofstream", "[helpers]") { { std::ofstream out{myfile}; - out << "this is output" << std::endl; + out << "this is output" << '\n'; } CHECK(CLI::ExistingFile(myfile).empty()); @@ -885,47 +1080,96 @@ TEST_CASE("Join: Backward", "[helpers]") { } TEST_CASE("SplitUp: Simple", "[helpers]") { - std::vector<std::string> oput = {"one", "two three"}; + std::vector<std::string> oput = {"one", "\"two three\""}; std::string orig{R"(one "two three")"}; std::vector<std::string> result = CLI::detail::split_up(orig); CHECK(result == oput); } TEST_CASE("SplitUp: SimpleDifferentQuotes", "[helpers]") { - std::vector<std::string> oput = {"one", "two three"}; + std::vector<std::string> oput = {"one", "`two three`"}; std::string orig{R"(one `two three`)"}; std::vector<std::string> result = CLI::detail::split_up(orig); CHECK(result == oput); } +TEST_CASE("SplitUp: SimpleMissingQuotes", "[helpers]") { + std::vector<std::string> oput = {"one", "`two three"}; + std::string orig{R"(one `two three)"}; + std::vector<std::string> result = CLI::detail::split_up(orig); + CHECK(result == oput); +} + +TEST_CASE("SplitUp: SimpleMissingQuotesEscaped", "[helpers]") { + std::vector<std::string> oput = {"one", R"("two three\"")"}; + std::string orig{R"(one "two three\"")"}; + std::vector<std::string> result = CLI::detail::split_up(orig); + CHECK(result == oput); +} + TEST_CASE("SplitUp: SimpleDifferentQuotes2", "[helpers]") { - std::vector<std::string> oput = {"one", "two three"}; + std::vector<std::string> oput = {"one", "'two three'"}; std::string orig{R"(one 'two three')"}; std::vector<std::string> result = CLI::detail::split_up(orig); CHECK(result == oput); } +TEST_CASE("SplitUp: Bracket1", "[helpers]") { + std::vector<std::string> oput = {"one", "[two, three]"}; + std::string orig{"one, [two, three]"}; + std::vector<std::string> result = CLI::detail::split_up(orig, ','); + CHECK(result == oput); +} + +TEST_CASE("SplitUp: Bracket2", "[helpers]") { + std::vector<std::string> oput = {"one", "<two, three>"}; + std::string orig{"one, <two, three>"}; + std::vector<std::string> result = CLI::detail::split_up(orig, ','); + CHECK(result == oput); +} + +TEST_CASE("SplitUp: Bracket3", "[helpers]") { + std::vector<std::string> oput = {"one", "(two, three)"}; + std::string orig{"one, (two, three)"}; + std::vector<std::string> result = CLI::detail::split_up(orig, ','); + CHECK(result == oput); +} + +TEST_CASE("SplitUp: Bracket4", "[helpers]") { + std::vector<std::string> oput = {"one", "{two, three}"}; + std::string orig{"one, {two, three}"}; + std::vector<std::string> result = CLI::detail::split_up(orig, ','); + CHECK(result == oput); +} + +TEST_CASE("SplitUp: Comment", "[helpers]") { + std::vector<std::string> oput = {R"(["quote1", "#"])"}; + std::string orig{R"(["quote1", "#"])"}; + std::vector<std::string> result = CLI::detail::split_up(orig, '#'); + CHECK(result == oput); +} + TEST_CASE("SplitUp: Layered", "[helpers]") { - std::vector<std::string> output = {R"(one 'two three')"}; + std::vector<std::string> output = {R"("one 'two three'")"}; std::string orig{R"("one 'two three'")"}; std::vector<std::string> result = CLI::detail::split_up(orig); CHECK(result == output); } TEST_CASE("SplitUp: Spaces", "[helpers]") { - std::vector<std::string> oput = {"one", " two three"}; + std::vector<std::string> oput = {"one", "\" two three\""}; std::string orig{R"( one " two three" )"}; std::vector<std::string> result = CLI::detail::split_up(orig); CHECK(result == oput); } TEST_CASE("SplitUp: BadStrings", "[helpers]") { - std::vector<std::string> oput = {"one", " two three"}; + std::vector<std::string> oput = {"one", "\" two three"}; std::string orig{R"( one " two three )"}; std::vector<std::string> result = CLI::detail::split_up(orig); CHECK(result == oput); - oput = {"one", " two three"}; + oput = {"one", "' two three"}; orig = R"( one ' two three )"; result = CLI::detail::split_up(orig); CHECK(result == oput); @@ -1033,6 +1277,19 @@ TEST_CASE("Types: TypeName", "[helpers]") { CHECK((atomic_name == "INT" || atomic_name == "TEXT")); } +TEST_CASE("Types: TypeNameStrings", "[helpers]") { + auto sclass = CLI::detail::classify_object<std::string>::value; + CHECK(CLI::detail::object_category::string_assignable == sclass); + + auto wsclass = CLI::detail::classify_object<std::wstring>::value; + CHECK(CLI::detail::object_category::wstring_assignable == wsclass); + +#if defined CLI11_HAS_FILEYSTEM && CLI11_HAS_FILESYSTEM > 0 && defined(_MSC_VER) + auto fspclass = CLI::detail::classify_object<std::filesystem::path>::value; + CHECK(CLI::detail::object_category::wstring_assignable == fspclass); +#endif +} + TEST_CASE("Types: OverflowSmall", "[helpers]") { signed char x = 0; auto strmax = std::to_string((std::numeric_limits<signed char>::max)() + 1); @@ -1341,3 +1598,14 @@ TEST_CASE("FixNewLines: EdgesCheck", "[helpers]") { std::string result = CLI::detail::fix_newlines("; ", input); CHECK(output == result); } + +TEST_CASE("String: environment", "[helpers]") { + put_env("TEST1", "TESTS"); + + auto value = CLI::detail::get_environment_value("TEST1"); + CHECK(value == "TESTS"); + unset_env("TEST1"); + + value = CLI::detail::get_environment_value("TEST2"); + CHECK(value.empty()); +} diff --git a/packages/CLI11/tests/NewParseTest.cpp b/packages/CLI11/tests/NewParseTest.cpp index a72af823bb8ce9c596351f3bb77fa67dde750325..9f5aea20a44927b1110955d04c12a0fcf2500fb5 100644 --- a/packages/CLI11/tests/NewParseTest.cpp +++ b/packages/CLI11/tests/NewParseTest.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2024, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // @@ -193,7 +193,7 @@ TEST_CASE_METHOD(TApp, "custom_string_converterFail", "[newparse]") { CHECK_THROWS_AS(run(), CLI::ConversionError); } -/// Wrapper with an unconvenient interface +/// Wrapper with an inconvenient interface template <class T> class badlywrapped { public: badlywrapped() : value() {} diff --git a/packages/CLI11/tests/OptionGroupTest.cpp b/packages/CLI11/tests/OptionGroupTest.cpp index ab4d3c638d29c07ba0d0c54f3d9b216b870f6af3..9112b0be214bd6c6c1d203e8053f8031568f9a9d 100644 --- a/packages/CLI11/tests/OptionGroupTest.cpp +++ b/packages/CLI11/tests/OptionGroupTest.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2024, 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/OptionTypeTest.cpp b/packages/CLI11/tests/OptionTypeTest.cpp index 6d06a5af3ebabbe695825183d936cb81f0c2a4d2..5068a8c65d201ed6c702c332d919cb610f2da2b0 100644 --- a/packages/CLI11/tests/OptionTypeTest.cpp +++ b/packages/CLI11/tests/OptionTypeTest.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2024, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // @@ -6,6 +6,9 @@ #include "app_helper.hpp" +#include "catch.hpp" + +#include <algorithm> #include <atomic> #include <cmath> #include <complex> @@ -218,6 +221,145 @@ TEST_CASE_METHOD(TApp, "atomic_int_option", "[optiontype]") { CHECK(0 == i); } +static const std::map<std::string, double> testValuesDouble{ + {"3.14159", 3.14159}, + {"-3.14159", -3.14159}, + {"+1.0", 1.0}, + {"-0.01", -0.01}, + {"5e22", 5e22}, + {"-2E-2", -2e-2}, + {"5e+22", 5e22}, + {"1e06", 1e6}, + {"6.626e-34", 6.626e-34}, + {"6.626e+34", 6.626e34}, + {"-6.626e-34", -6.626e-34}, + {"224_617.445_991", 224617.445991}, + {"224'617.445'991", 224617.445991}, + {"inf", std::numeric_limits<double>::infinity()}, + {"+inf", std::numeric_limits<double>::infinity()}, + {"-inf", -std::numeric_limits<double>::infinity()}, + {"nan", std::numeric_limits<double>::signaling_NaN()}, + {"+nan", std::numeric_limits<double>::signaling_NaN()}, + {"-nan", -std::numeric_limits<double>::signaling_NaN()}, + +}; + +TEST_CASE_METHOD(TApp, "floatingConversions", "[optiontype]") { + auto test_data = GENERATE(from_range(testValuesDouble)); + + double val{0}; + app.add_option("--val", val); + + args = {"--val", test_data.first}; + + run(); + if(std::isnan(test_data.second)) { + CHECK(std::isnan(val)); + } else { + + CHECK_THAT(val, WithinRel(test_data.second, 1e-11)); + } +} + +static const std::map<std::string, std::int64_t> testValuesInt{ + {"+99", 99}, + {"99", 99}, + {"-99", -99}, + {"0xDEADBEEF", 0xDEADBEEF}, + {"0xdeadbeef", 0xDEADBEEF}, + {"0XDEADBEEF", 0xDEADBEEF}, + {"0Xdeadbeef", 0xDEADBEEF}, + {"0xdead_beef", 0xDEADBEEF}, + {"0xdead'beef", 0xDEADBEEF}, + {"0o01234567", 001234567}, + {"0o755", 0755}, + {"0755", 0755}, + {"995862_262", 995862262}, + {"995862262", 995862262}, + {"-995862275", -995862275}, + {"-995'862'275", -995862275}, + {"0b11010110", 0xD6}, + {"0b1101'0110", 0xD6}, + {"1_2_3_4_5", 12345}, +}; + +TEST_CASE_METHOD(TApp, "intConversions", "[optiontype]") { + + auto test_data = GENERATE(from_range(testValuesInt)); + + std::int64_t val{0}; + app.add_option("--val", val); + + args = {"--val", test_data.first}; + + run(); + + CHECK(val == test_data.second); +} + +TEST_CASE_METHOD(TApp, "intConversionsErange", "[optiontype]") { + + std::int64_t val{0}; + app.add_option("--val", val); + + args = {"--val", "0o11545241241415151512312415123125667"}; + + CHECK_THROWS_AS(run(), CLI::ParseError); + + args = {"--val", "0b1011000001101011001100110011111000101010101011111111111111111111111001010111011100"}; + + CHECK_THROWS_AS(run(), CLI::ParseError); +} + +static const std::map<std::string, std::uint64_t> testValuesUInt{ + {"+99", 99}, + {"99", 99}, + {"0xDEADBEEF", 0xDEADBEEF}, + {"0xdeadbeef", 0xDEADBEEF}, + {"0XDEADBEEF", 0xDEADBEEF}, + {"0Xdeadbeef", 0xDEADBEEF}, + {"0xdead_beef", 0xDEADBEEF}, + {"0xdead'beef", 0xDEADBEEF}, + {"0o01234567", 001234567}, + {"0o755", 0755}, + {"0755", 0755}, + {"995862_262", 995862262}, + {"995862262", 995862262}, + {"+995862275", +995862275}, + {"995'862'275", 995862275}, + {"0b11010110", 0xD6}, + {"0b1101'0110", 0xD6}, + {"1_2_3_4_5", 12345}, +}; + +TEST_CASE_METHOD(TApp, "uintConversions", "[optiontype]") { + + auto test_data = GENERATE(from_range(testValuesUInt)); + + std::uint64_t val{0}; + app.add_option("--val", val); + + args = {"--val", test_data.first}; + + run(); + + CHECK(val == test_data.second); +} + +TEST_CASE_METHOD(TApp, "uintConversionsErange", "[optiontype]") { + + std::uint64_t val{0}; + app.add_option("--val", val); + + args = {"--val", "0o11545241241415151512312415123125667"}; + + CHECK_THROWS_AS(run(), CLI::ParseError); + + args = {"--val", "0b1011000001101011001100110011111000101010101011111111111111111111111001010111011100"}; + + CHECK_THROWS_AS(run(), CLI::ParseError); +} + TEST_CASE_METHOD(TApp, "CharOption", "[optiontype]") { char c1{'t'}; app.add_option("-c", c1); diff --git a/packages/CLI11/tests/OptionalTest.cpp b/packages/CLI11/tests/OptionalTest.cpp index 3d78e3498c9a3ff99096867786552fc548d40212..986272d01bcb3912588d5ffac9321640df62d557 100644 --- a/packages/CLI11/tests/OptionalTest.cpp +++ b/packages/CLI11/tests/OptionalTest.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2024, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // @@ -53,6 +53,8 @@ #endif // [CLI11:verbatim] +TEST_CASE("OptionalNoEmpty") { CHECK(1 == 1); } + #if CLI11_STD_OPTIONAL #ifdef _MSC_VER @@ -70,13 +72,11 @@ TEST_CASE_METHOD(TApp, "StdOptionalTest", "[optional]") { args = {"-c", "1"}; run(); - CHECK(opt); - CHECK(1 == *opt); + CHECK((opt && (1 == *opt))); args = {"--count", "3"}; run(); - CHECK(opt); - CHECK(3 == *opt); + CHECK((opt && (3 == *opt))); } TEST_CASE_METHOD(TApp, "StdOptionalVectorEmptyDirect", "[optional]") { @@ -91,7 +91,7 @@ TEST_CASE_METHOD(TApp, "StdOptionalVectorEmptyDirect", "[optional]") { CHECK(!opt); args = {"-v", "1", "4", "5"}; run(); - CHECK(opt); + REQUIRE(opt); std::vector<int> expV{1, 4, 5}; CHECK(expV == *opt); } @@ -125,7 +125,7 @@ TEST_CASE_METHOD(TApp, "StdOptionalUint", "[optional]") { args = {"-i", "15"}; run(); - CHECK(15U == *opt); + CHECK((opt && (15U == *opt))); static_assert(CLI::detail::classify_object<std::optional<std::uint64_t>>::value == CLI::detail::object_category::wrapper_value); } @@ -140,13 +140,14 @@ TEST_CASE_METHOD(TApp, "StdOptionalbool", "[optional]") { args = {"--opt"}; run(); - CHECK(opt); - CHECK(*opt); + CHECK((opt && *opt)); args = {"--no-opt"}; run(); - CHECK(opt); - CHECK_FALSE(*opt); + REQUIRE(opt); + if(opt) { + CHECK_FALSE(*opt); + } static_assert(CLI::detail::classify_object<std::optional<bool>>::value == CLI::detail::object_category::wrapper_value); } @@ -186,12 +187,12 @@ TEST_CASE_METHOD(TApp, "BoostOptionalTest", "[optional]") { args = {"-c", "1"}; run(); - CHECK(opt); + REQUIRE(opt); CHECK(1 == *opt); opt = {}; args = {"--count", "3"}; run(); - CHECK(opt); + REQUIRE(opt); CHECK(3 == *opt); } @@ -203,7 +204,7 @@ TEST_CASE_METHOD(TApp, "BoostOptionalTestZarg", "[optional]") { args = {"-c", "1"}; run(); - CHECK(opt); + REQUIRE(opt); CHECK(1 == *opt); opt = {}; args = {"--count"}; @@ -219,12 +220,12 @@ TEST_CASE_METHOD(TApp, "BoostOptionalint64Test", "[optional]") { args = {"-c", "1"}; run(); - CHECK(opt); + REQUIRE(opt); CHECK(1 == *opt); opt = {}; args = {"--count", "3"}; run(); - CHECK(opt); + REQUIRE(opt); CHECK(3 == *opt); } @@ -236,12 +237,12 @@ TEST_CASE_METHOD(TApp, "BoostOptionalStringTest", "[optional]") { args = {"-s", "strval"}; run(); - CHECK(opt); + REQUIRE(opt); CHECK("strval" == *opt); opt = {}; args = {"--string", "strv"}; run(); - CHECK(opt); + REQUIRE(opt); CHECK("strv" == *opt); } namespace boost { @@ -266,13 +267,13 @@ TEST_CASE_METHOD(TApp, "BoostOptionalEnumTest", "[optional]") { args = {"-v", "3"}; run(); checkOpt = static_cast<bool>(opt); - CHECK(checkOpt); + REQUIRE(checkOpt); CHECK(*opt == eval::val3); opt = {}; args = {"--val", "1"}; run(); checkOpt = static_cast<bool>(opt); - CHECK(checkOpt); + REQUIRE(checkOpt); CHECK(*opt == eval::val1); } @@ -288,7 +289,7 @@ TEST_CASE_METHOD(TApp, "BoostOptionalVector", "[optional]") { args = {"-v", "1", "4", "5"}; run(); checkOpt = static_cast<bool>(opt); - CHECK(checkOpt); + REQUIRE(checkOpt); std::vector<int> expV{1, 4, 5}; CHECK(expV == *opt); } @@ -308,7 +309,7 @@ TEST_CASE_METHOD(TApp, "BoostOptionalVectorEmpty", "[optional]") { args = {"-v", "1", "4", "5"}; run(); checkOpt = static_cast<bool>(opt); - CHECK(checkOpt); + REQUIRE(checkOpt); std::vector<int> expV{1, 4, 5}; CHECK(expV == *opt); } @@ -328,7 +329,7 @@ TEST_CASE_METHOD(TApp, "BoostOptionalVectorEmptyDirect", "[optional]") { args = {"-v", "1", "4", "5"}; run(); checkOpt = static_cast<bool>(opt); - CHECK(checkOpt); + REQUIRE(checkOpt); std::vector<int> expV{1, 4, 5}; CHECK(expV == *opt); } @@ -344,12 +345,12 @@ TEST_CASE_METHOD(TApp, "BoostOptionalComplexDirect", "[optional]") { CHECK(!opt); args = {"-c", "1+2j"}; run(); - CHECK(opt); + REQUIRE(opt); std::complex<double> val{1, 2}; CHECK(val == *opt); args = {"-c", "3", "-4"}; run(); - CHECK(opt); + REQUIRE(opt); std::complex<double> val2{3, -4}; CHECK(val2 == *opt); } diff --git a/packages/CLI11/tests/SetTest.cpp b/packages/CLI11/tests/SetTest.cpp index b326989997824f18fdca60c47a09b38c243df9e0..3afa4759ea234d75b0ccb2396f38757234e07b6c 100644 --- a/packages/CLI11/tests/SetTest.cpp +++ b/packages/CLI11/tests/SetTest.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2024, 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/SimpleTest.cpp b/packages/CLI11/tests/SimpleTest.cpp index 14d6558b46c843a10763a0c6e0891b37806bf184..2b0127b52044bb659cc1df716c4ad8a0da7b3d67 100644 --- a/packages/CLI11/tests/SimpleTest.cpp +++ b/packages/CLI11/tests/SimpleTest.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2024, 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 cc1205fe3b7b6050e4e2e56f242dc024c3694b8b..839baf7a9923fcf577ef2b1685ea1ec5e3731090 100644 --- a/packages/CLI11/tests/StringParseTest.cpp +++ b/packages/CLI11/tests/StringParseTest.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2024, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // @@ -20,7 +20,7 @@ TEST_CASE_METHOD(TApp, "ExistingExeCheck", "[stringparse]") { { std::ofstream out{tmpexe}; - out << "useless string doesn't matter" << std::endl; + out << "useless string doesn't matter" << '\n'; } app.parse(std::string("./") + std::string(tmpexe) + @@ -42,7 +42,7 @@ TEST_CASE_METHOD(TApp, "ExistingExeCheckWithSpace", "[stringparse]") { { std::ofstream out{tmpexe}; - out << "useless string doesn't matter" << std::endl; + out << "useless string doesn't matter" << '\n'; } app.parse(std::string("./") + std::string(tmpexe) + @@ -66,7 +66,7 @@ TEST_CASE_METHOD(TApp, "ExistingExeCheckWithLotsOfSpace", "[stringparse]") { { std::ofstream out{tmpexe}; - out << "useless string doesn't matter" << std::endl; + out << "useless string doesn't matter" << '\n'; } app.parse(std::string("./") + std::string(tmpexe) + diff --git a/packages/CLI11/tests/SubcommandTest.cpp b/packages/CLI11/tests/SubcommandTest.cpp index 25415eaa7cf1241f46ac6788f6cfca285543c594..cfe532386f6bd106c2917e03ebc9c991cee81ff1 100644 --- a/packages/CLI11/tests/SubcommandTest.cpp +++ b/packages/CLI11/tests/SubcommandTest.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2024, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // @@ -187,6 +187,10 @@ TEST_CASE_METHOD(TApp, "DuplicateSubcommands", "[subcom]") { run(); CHECK(*foo); CHECK(3u == foo->count()); + + auto subs = app.get_subcommands(); + // subcommands only get triggered once + CHECK(subs.size() == 1U); } TEST_CASE_METHOD(TApp, "DuplicateSubcommandCallbacks", "[subcom]") { @@ -2114,3 +2118,26 @@ TEST_CASE_METHOD(TApp, "DotNotationSubcommandRecusive2", "[subcom]") { CHECK(extras.size() == 1); CHECK(extras.front() == "sub1.sub2.sub3.bob"); } + +// Reported bug #903 on github +TEST_CASE_METHOD(TApp, "subcommandEnvironmentName", "[subcom]") { + auto *sub1 = app.add_subcommand("sub1"); + std::string someFile; + int sub1value{0}; + sub1->add_option("-f,--file", someFile)->envname("SOME_FILE")->required()->check(CLI::ExistingFile); + sub1->add_option("-v", sub1value); + auto *sub2 = app.add_subcommand("sub2"); + int completelyUnrelatedToSub1 = 0; + sub2->add_option("-v,--value", completelyUnrelatedToSub1)->required(); + + args = {"sub2", "-v", "111"}; + CHECK_NOTHROW(run()); + + put_env("SOME_FILE", "notafile.txt"); + + CHECK_NOTHROW(run()); + + args = {"sub1", "-v", "111"}; + CHECK_THROWS_AS(run(), CLI::RequiredError); + unset_env("SOME_FILE"); +} diff --git a/packages/CLI11/tests/TimerTest.cpp b/packages/CLI11/tests/TimerTest.cpp index e15d928cf7ee2a49d5176641e77d601bf74c76eb..0dc2ca94c4a5bdac05e8b4f88993d6c69e3474ef 100644 --- a/packages/CLI11/tests/TimerTest.cpp +++ b/packages/CLI11/tests/TimerTest.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2024, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // @@ -63,6 +63,6 @@ TEST_CASE("Timer: PrintTimer", "[timer]") { TEST_CASE("Timer: TimeItTimer", "[timer]") { CLI::Timer timer; std::string output = timer.time_it([]() { std::this_thread::sleep_for(std::chrono::milliseconds(10)); }, .1); - std::cout << output << std::endl; + std::cout << output << '\n'; CHECK_THAT(output, Contains("ms")); } diff --git a/packages/CLI11/tests/TransformTest.cpp b/packages/CLI11/tests/TransformTest.cpp index 9406e0254d0a5d57b34ff005aa6c2bd89cb727a8..97935f21c7df229516e4d62f2c819c9c0eac97ef 100644 --- a/packages/CLI11/tests/TransformTest.cpp +++ b/packages/CLI11/tests/TransformTest.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2024, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // @@ -706,6 +706,53 @@ TEST_CASE_METHOD(TApp, "NumberWithUnitBadInput", "[transform]") { CHECK_THROWS_AS(run(), CLI::ValidationError); } +static const std::map<std::string, std::string> validValues = { + {"test\\u03C0\\u00e9", from_u8string(u8"test\u03C0\u00E9")}, + {"test\\u03C0\\u00e9", from_u8string(u8"test\u73C0\u0057")}, + {"test\\U0001F600\\u00E9", from_u8string(u8"test\U0001F600\u00E9")}, + {R"("this\nis\na\nfour\tline test")", "this\nis\na\nfour\tline test"}, + {"'B\"(\\x35\\xa7\\x46)\"'", std::string{0x35, static_cast<char>(0xa7), 0x46}}, + {"B\"(\\x35\\xa7\\x46)\"", std::string{0x35, static_cast<char>(0xa7), 0x46}}, + {"test\\ntest", "test\ntest"}, + {"\"test\\ntest", "\"test\ntest"}, + {R"('this\nis\na\nfour\tline test')", R"(this\nis\na\nfour\tline test)"}, + {R"("this\nis\na\nfour\tline test")", "this\nis\na\nfour\tline test"}, + {R"(`this\nis\na\nfour\tline test`)", R"(this\nis\na\nfour\tline test)"}}; + +TEST_CASE_METHOD(TApp, "StringEscapeValid", "[transform]") { + + auto test_data = GENERATE(from_range(validValues)); + + std::string value{}; + + app.add_option("-n", value)->transform(CLI::EscapedString); + + args = {"-n", test_data.first}; + + run(); + CHECK(test_data.second == value); +} + +static const std::vector<std::string> invalidValues = {"test\\U0001M600\\u00E9", + "test\\U0001E600\\u00M9", + "test\\U0001E600\\uD8E9", + "test\\U0001E600\\uD8", + "test\\U0001E60", + "test\\qbad"}; + +TEST_CASE_METHOD(TApp, "StringEscapeInvalid", "[transform]") { + + auto test_data = GENERATE(from_range(invalidValues)); + + std::string value{}; + + app.add_option("-n", value)->transform(CLI::EscapedString); + + args = {"-n", test_data}; + + CHECK_THROWS_AS(run(), CLI::ValidationError); +} + TEST_CASE_METHOD(TApp, "NumberWithUnitIntOverflow", "[transform]") { std::map<std::string, int> mapping{{"a", 1000000}, {"b", 100}, {"c", 101}}; diff --git a/packages/CLI11/tests/TrueFalseTest.cpp b/packages/CLI11/tests/TrueFalseTest.cpp index 93f2f3fb8109ccb40370bbf7ed330b3360c33e89..b14ef29835af4eb19923f3a5bd896a5da509de42 100644 --- a/packages/CLI11/tests/TrueFalseTest.cpp +++ b/packages/CLI11/tests/TrueFalseTest.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2024, 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 a17d58735a66a29fa5b2a02d614420396f6943eb..647a12a6ea8c5603c7b58edc2999b4a905a7de22 100644 --- a/packages/CLI11/tests/WindowsTest.cpp +++ b/packages/CLI11/tests/WindowsTest.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2024, 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 5479e486392ba432f6f2d25f45d6bcf40812a315..fbe2555eb717c5c43e08b248224ae724372742f9 100644 --- a/packages/CLI11/tests/app_helper.hpp +++ b/packages/CLI11/tests/app_helper.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2021, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2024, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // @@ -71,6 +71,15 @@ inline void unset_env(std::string name) { #endif } +/// these are provided for compatibility with the char8_t for C++20 that breaks stuff +CLI11_INLINE std::string from_u8string(const std::string &s) { return s; } +CLI11_INLINE std::string from_u8string(std::string &&s) { return std::move(s); } +#if defined(__cpp_lib_char8_t) +CLI11_INLINE std::string from_u8string(const std::u8string &s) { return std::string(s.begin(), s.end()); } +#elif defined(__cpp_char8_t) +CLI11_INLINE std::string from_u8string(const char8_t *s) { return std::string(reinterpret_cast<const char *>(s)); } +#endif + CLI11_INLINE void check_identical_files(const char *path1, const char *path2) { std::string err1 = CLI::ExistingFile(path1); if(!err1.empty()) { diff --git a/packages/CLI11/tests/applications/ensure_utf8.cpp b/packages/CLI11/tests/applications/ensure_utf8.cpp new file mode 100644 index 0000000000000000000000000000000000000000..84fd5f2013a32541bdcac674d81809947a72c374 --- /dev/null +++ b/packages/CLI11/tests/applications/ensure_utf8.cpp @@ -0,0 +1,35 @@ +// Copyright (c) 2017-2024, University of Cincinnati, developed by Henry Schreiner +// under NSF AWARD 1414736 and by the respective contributors. +// All rights reserved. +// +// SPDX-License-Identifier: BSD-3-Clause + +#include <CLI/CLI.hpp> +#include <cstring> +#include <iostream> + +int main(int argc, char **argv) { + CLI::App app{"App description"}; + char **original_argv = argv; + argv = app.ensure_utf8(argv); + +#ifdef _WIN32 + for(int i = 0; i < argc; i++) { + if(std::strcmp(argv[i], original_argv[i]) != 0) { + std::cerr << argv[i] << "\n"; + std::cerr << original_argv[i] << "\n"; + return i + 1; + } + argv[i][0] = 'x'; // access it to check that it is accessible + } + +#else + (void)argc; + + if(original_argv != argv) { + return -1; + } +#endif + + return 0; +} diff --git a/packages/CLI11/tests/applications/ensure_utf8_twice.cpp b/packages/CLI11/tests/applications/ensure_utf8_twice.cpp new file mode 100644 index 0000000000000000000000000000000000000000..a7d0e38029e68c53235de300a4c0a340480d9689 --- /dev/null +++ b/packages/CLI11/tests/applications/ensure_utf8_twice.cpp @@ -0,0 +1,36 @@ +// Copyright (c) 2017-2024, University of Cincinnati, developed by Henry Schreiner +// under NSF AWARD 1414736 and by the respective contributors. +// All rights reserved. +// +// SPDX-License-Identifier: BSD-3-Clause + +#include <CLI/CLI.hpp> +#include <cstring> +#include <iostream> + +int main(int argc, char **argv) { + CLI::App app{"App description"}; + char **original_argv = argv; + argv = app.ensure_utf8(argv); + argv = app.ensure_utf8(argv); // completely useless but works ok + +#ifdef _WIN32 + for(int i = 0; i < argc; i++) { + if(std::strcmp(argv[i], original_argv[i]) != 0) { + std::cerr << argv[i] << "\n"; + std::cerr << original_argv[i] << "\n"; + return i + 1; + } + argv[i][0] = 'x'; // access it to check that it is accessible + } + +#else + (void)argc; + + if(original_argv != argv) { + return -1; + } +#endif + + return 0; +} diff --git a/packages/CLI11/tests/applications/system_args.cpp b/packages/CLI11/tests/applications/system_args.cpp index e1e77ba673ce56f69117569b731475372d618a02..2cad18b19e786861ef9ca8c2a9a640a436de143f 100644 --- a/packages/CLI11/tests/applications/system_args.cpp +++ b/packages/CLI11/tests/applications/system_args.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2024, 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/catch.hpp b/packages/CLI11/tests/catch.hpp index e6de667325f48d65ea5145207bb4a608ae22c209..4dd233603720755dd04a30c1894aa9c3ce1ded32 100644 --- a/packages/CLI11/tests/catch.hpp +++ b/packages/CLI11/tests/catch.hpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2024, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // @@ -14,10 +14,14 @@ #include <catch2/catch_template_test_macros.hpp> #include <catch2/catch_test_macros.hpp> #include <catch2/generators/catch_generators.hpp> +#include <catch2/generators/catch_generators_range.hpp> +#include <catch2/matchers/catch_matchers_floating_point.hpp> #include <catch2/matchers/catch_matchers_string.hpp> -using Catch::Approx; // NOLINT(google-global-names-in-headers) -using Catch::Matchers::Equals; // NOLINT(google-global-names-in-headers) +using Catch::Approx; // NOLINT(google-global-names-in-headers) +using Catch::Generators::from_range; // NOLINT(google-global-names-in-headers) +using Catch::Matchers::Equals; // NOLINT(google-global-names-in-headers) +using Catch::Matchers::WithinRel; // NOLINT(google-global-names-in-headers) inline auto Contains(const std::string &x) { return Catch::Matchers::ContainsSubstring(x); } @@ -26,6 +30,7 @@ inline auto Contains(const std::string &x) { return Catch::Matchers::ContainsSub #include <catch2/catch.hpp> using Catch::Equals; // NOLINT(google-global-names-in-headers) +using Catch::WithinRel; // NOLINT(google-global-names-in-headers) using Catch::Matchers::Contains; // NOLINT(google-global-names-in-headers) #endif diff --git a/packages/CLI11/tests/find_package_tests/CMakeLists.txt b/packages/CLI11/tests/find_package_tests/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..6d5aa2775be761bc67d1b9fb4f0d8f0455817d58 --- /dev/null +++ b/packages/CLI11/tests/find_package_tests/CMakeLists.txt @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 3.10...3.26) + +project(CLI11-find-package-test) + +include(CTest) + +if(CLI11_DIR) + set(CMAKE_PREFIX_PATH ${CLI11_DIR}) +endif() + +# Test the CLI11 CMake package config +find_package(CLI11 2.0 REQUIRED) + +# Test the target +add_executable(package-test ../../examples/positional_validation.cpp) +target_link_libraries(package-test CLI11::CLI11) + +add_test(NAME package-test1 COMMAND package-test one) +set_property(TEST package-test1 PROPERTY PASS_REGULAR_EXPRESSION "File 1 = one") diff --git a/packages/CLI11/tests/fuzzFail/fuzz_app_fail3 b/packages/CLI11/tests/fuzzFail/fuzz_app_fail3 new file mode 100644 index 0000000000000000000000000000000000000000..0c62a1dc8cbfcfc8bfaaa5a39523a56ad0b17253 Binary files /dev/null and b/packages/CLI11/tests/fuzzFail/fuzz_app_fail3 differ diff --git a/packages/CLI11/tests/fuzzFail/fuzz_app_file_fail1 b/packages/CLI11/tests/fuzzFail/fuzz_app_file_fail1 new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/packages/CLI11/tests/fuzzFail/fuzz_app_file_fail10 b/packages/CLI11/tests/fuzzFail/fuzz_app_file_fail10 new file mode 100644 index 0000000000000000000000000000000000000000..dbe19a59cd83ff36e26117f85e283f3d23a87625 --- /dev/null +++ b/packages/CLI11/tests/fuzzFail/fuzz_app_file_fail10 @@ -0,0 +1,3 @@ +-e-vC +,c�C +,c���� diff --git a/packages/CLI11/tests/fuzzFail/fuzz_app_file_fail11 b/packages/CLI11/tests/fuzzFail/fuzz_app_file_fail11 new file mode 100644 index 0000000000000000000000000000000000000000..117520b3524704157d1f68593b4e399c6c756c27 --- /dev/null +++ b/packages/CLI11/tests/fuzzFail/fuzz_app_file_fail11 @@ -0,0 +1 @@ +=666666666~5�5�--oo?ptvtup@ diff --git a/packages/CLI11/tests/fuzzFail/fuzz_app_file_fail12 b/packages/CLI11/tests/fuzzFail/fuzz_app_file_fail12 new file mode 100644 index 0000000000000000000000000000000000000000..6b3db8e29da4c4f9bca8a7efb363c44643331ff9 Binary files /dev/null and b/packages/CLI11/tests/fuzzFail/fuzz_app_file_fail12 differ diff --git a/packages/CLI11/tests/fuzzFail/fuzz_app_file_fail13 b/packages/CLI11/tests/fuzzFail/fuzz_app_file_fail13 new file mode 100644 index 0000000000000000000000000000000000000000..00e1aac7f640d8e71f177f6872a33807d9c7eed4 --- /dev/null +++ b/packages/CLI11/tests/fuzzFail/fuzz_app_file_fail13 @@ -0,0 +1 @@ +``'``'###################### diff --git a/packages/CLI11/tests/fuzzFail/fuzz_app_file_fail14 b/packages/CLI11/tests/fuzzFail/fuzz_app_file_fail14 new file mode 100644 index 0000000000000000000000000000000000000000..431f0b47edfab2b377d8758ed1a08326ae27cc55 --- /dev/null +++ b/packages/CLI11/tests/fuzzFail/fuzz_app_file_fail14 @@ -0,0 +1,4 @@ +--vB +s + ' +sub diff --git a/packages/CLI11/tests/fuzzFail/fuzz_app_file_fail15 b/packages/CLI11/tests/fuzzFail/fuzz_app_file_fail15 new file mode 100644 index 0000000000000000000000000000000000000000..51a5dcf3ab158459da276f87e511bf94d39b28ca --- /dev/null +++ b/packages/CLI11/tests/fuzzFail/fuzz_app_file_fail15 @@ -0,0 +1 @@ + ""K���������"�� diff --git a/packages/CLI11/tests/fuzzFail/fuzz_app_file_fail16 b/packages/CLI11/tests/fuzzFail/fuzz_app_file_fail16 new file mode 100644 index 0000000000000000000000000000000000000000..97b1a80166264019db2d66d51416c88326f7276d Binary files /dev/null and b/packages/CLI11/tests/fuzzFail/fuzz_app_file_fail16 differ diff --git a/packages/CLI11/tests/fuzzFail/fuzz_app_file_fail17 b/packages/CLI11/tests/fuzzFail/fuzz_app_file_fail17 new file mode 100644 index 0000000000000000000000000000000000000000..0edbeabdbf4bbe6607b519e37ec001e57114bc27 --- /dev/null +++ b/packages/CLI11/tests/fuzzFail/fuzz_app_file_fail17 @@ -0,0 +1,9 @@ + +--vE + +����� + + + + +# diff --git a/packages/CLI11/tests/fuzzFail/fuzz_app_file_fail18 b/packages/CLI11/tests/fuzzFail/fuzz_app_file_fail18 new file mode 100644 index 0000000000000000000000000000000000000000..812e10c64ee11b87471e7b23b9089ee2cf5ee677 --- /dev/null +++ b/packages/CLI11/tests/fuzzFail/fuzz_app_file_fail18 @@ -0,0 +1 @@ +--vD \���� \ \ No newline at end of file diff --git a/packages/CLI11/tests/fuzzFail/fuzz_app_file_fail19 b/packages/CLI11/tests/fuzzFail/fuzz_app_file_fail19 new file mode 100644 index 0000000000000000000000000000000000000000..b4a8701d61c8f2e363b826e3749b0e469193147b --- /dev/null +++ b/packages/CLI11/tests/fuzzFail/fuzz_app_file_fail19 @@ -0,0 +1 @@ +1-!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!ceeecae diff --git a/packages/CLI11/tests/fuzzFail/fuzz_app_file_fail2 b/packages/CLI11/tests/fuzzFail/fuzz_app_file_fail2 new file mode 100644 index 0000000000000000000000000000000000000000..b71bde1cb6f6b46ba6ed947d176c2167eafa5760 --- /dev/null +++ b/packages/CLI11/tests/fuzzFail/fuzz_app_file_fail2 @@ -0,0 +1 @@ +-c diff --git a/packages/CLI11/tests/fuzzFail/fuzz_app_file_fail20 b/packages/CLI11/tests/fuzzFail/fuzz_app_file_fail20 new file mode 100644 index 0000000000000000000000000000000000000000..b2c3fb7b019f6933e1da5dff0f340fb33f378afb --- /dev/null +++ b/packages/CLI11/tests/fuzzFail/fuzz_app_file_fail20 @@ -0,0 +1 @@ +��������������������������������-�#e,cecb diff --git a/packages/CLI11/tests/fuzzFail/fuzz_app_file_fail21 b/packages/CLI11/tests/fuzzFail/fuzz_app_file_fail21 new file mode 100644 index 0000000000000000000000000000000000000000..df52d7d946872509d899fced875c8d0dcfe608dc Binary files /dev/null and b/packages/CLI11/tests/fuzzFail/fuzz_app_file_fail21 differ diff --git a/packages/CLI11/tests/fuzzFail/fuzz_app_file_fail22 b/packages/CLI11/tests/fuzzFail/fuzz_app_file_fail22 new file mode 100644 index 0000000000000000000000000000000000000000..555d0df16a5de2b8f7d2b8957fea8984cd7032e4 --- /dev/null +++ b/packages/CLI11/tests/fuzzFail/fuzz_app_file_fail22 @@ -0,0 +1 @@ +dwrap'a diff --git a/packages/CLI11/tests/fuzzFail/fuzz_app_file_fail23 b/packages/CLI11/tests/fuzzFail/fuzz_app_file_fail23 new file mode 100644 index 0000000000000000000000000000000000000000..d0f7d9e6986d17e21bec05d3f8c3d76599f7f095 Binary files /dev/null and b/packages/CLI11/tests/fuzzFail/fuzz_app_file_fail23 differ diff --git a/packages/CLI11/tests/fuzzFail/fuzz_app_file_fail24 b/packages/CLI11/tests/fuzzFail/fuzz_app_file_fail24 new file mode 100644 index 0000000000000000000000000000000000000000..347f5a3987b58dc8bfe733cd0e3a06f959e01ce0 Binary files /dev/null and b/packages/CLI11/tests/fuzzFail/fuzz_app_file_fail24 differ diff --git a/packages/CLI11/tests/fuzzFail/fuzz_app_file_fail25 b/packages/CLI11/tests/fuzzFail/fuzz_app_file_fail25 new file mode 100644 index 0000000000000000000000000000000000000000..2964f0403ec85d5a5f377d9954a8eebd6f26d27a Binary files /dev/null and b/packages/CLI11/tests/fuzzFail/fuzz_app_file_fail25 differ diff --git a/packages/CLI11/tests/fuzzFail/fuzz_app_file_fail26 b/packages/CLI11/tests/fuzzFail/fuzz_app_file_fail26 new file mode 100644 index 0000000000000000000000000000000000000000..0e159868fd9a6e40f4c43350fe78256f99299a3c --- /dev/null +++ b/packages/CLI11/tests/fuzzFail/fuzz_app_file_fail26 @@ -0,0 +1 @@ +--vC opCB (3tp"o3# diff --git a/packages/CLI11/tests/fuzzFail/fuzz_app_file_fail27 b/packages/CLI11/tests/fuzzFail/fuzz_app_file_fail27 new file mode 100644 index 0000000000000000000000000000000000000000..dec270759ccd49e12011c2ed56105a52451838ec --- /dev/null +++ b/packages/CLI11/tests/fuzzFail/fuzz_app_file_fail27 @@ -0,0 +1,2 @@ +--vD� ` +-5 diff --git a/packages/CLI11/tests/fuzzFail/fuzz_app_file_fail28 b/packages/CLI11/tests/fuzzFail/fuzz_app_file_fail28 new file mode 100644 index 0000000000000000000000000000000000000000..02c19c7ab65ab10246e8284e9b278bc5d7f1296b --- /dev/null +++ b/packages/CLI11/tests/fuzzFail/fuzz_app_file_fail28 @@ -0,0 +1,5 @@ + + +--vB +--vB +����,����-vC diff --git a/packages/CLI11/tests/fuzzFail/fuzz_app_file_fail29 b/packages/CLI11/tests/fuzzFail/fuzz_app_file_fail29 new file mode 100644 index 0000000000000000000000000000000000000000..67208475433e6d1018700e3dd73e9bb9de623ea4 --- /dev/null +++ b/packages/CLI11/tests/fuzzFail/fuzz_app_file_fail29 @@ -0,0 +1,4 @@ + +--vE +-3vE +0)-bb=`',,l diff --git a/packages/CLI11/tests/fuzzFail/fuzz_app_file_fail3 b/packages/CLI11/tests/fuzzFail/fuzz_app_file_fail3 new file mode 100644 index 0000000000000000000000000000000000000000..466a70736348a836c4aef0138440b7a7e4605801 --- /dev/null +++ b/packages/CLI11/tests/fuzzFail/fuzz_app_file_fail3 @@ -0,0 +1 @@ +`--vM```-````-c` diff --git a/packages/CLI11/tests/fuzzFail/fuzz_app_file_fail30 b/packages/CLI11/tests/fuzzFail/fuzz_app_file_fail30 new file mode 100644 index 0000000000000000000000000000000000000000..254c424e65c74cd53f010737cf098d1569c35838 --- /dev/null +++ b/packages/CLI11/tests/fuzzFail/fuzz_app_file_fail30 @@ -0,0 +1 @@ +[�������������appt1"wrappt1""\","""\""\"," diff --git a/packages/CLI11/tests/fuzzFail/fuzz_app_file_fail31 b/packages/CLI11/tests/fuzzFail/fuzz_app_file_fail31 new file mode 100644 index 0000000000000000000000000000000000000000..7e22d167510f5d691aad3a8c25c7c5f06265f30f --- /dev/null +++ b/packages/CLI11/tests/fuzzFail/fuzz_app_file_fail31 @@ -0,0 +1 @@ +--$,,,,,,,,,,,,,,,,,,,,A,,,,,,,,,,,-$,,,,,,,,,,,,,,,,,,,,A,,,,,,,,,,,,,,,,,,,,,;--svopt2#,,,,-sC diff --git a/packages/CLI11/tests/fuzzFail/fuzz_app_file_fail32 b/packages/CLI11/tests/fuzzFail/fuzz_app_file_fail32 new file mode 100644 index 0000000000000000000000000000000000000000..d898def9b942ad2a5edd93a156e5d6ecdc0ae53a --- /dev/null +++ b/packages/CLI11/tests/fuzzFail/fuzz_app_file_fail32 @@ -0,0 +1 @@ +-�,,,,,,,,,,,,,,,,,,,opt1�a diff --git a/packages/CLI11/tests/fuzzFail/fuzz_app_file_fail33 b/packages/CLI11/tests/fuzzFail/fuzz_app_file_fail33 new file mode 100644 index 0000000000000000000000000000000000000000..18e61dff62168999c0df845c816dc5ff9b0602a9 --- /dev/null +++ b/packages/CLI11/tests/fuzzFail/fuzz_app_file_fail33 @@ -0,0 +1,2 @@ +'''-$� +$ diff --git a/packages/CLI11/tests/fuzzFail/fuzz_app_file_fail34 b/packages/CLI11/tests/fuzzFail/fuzz_app_file_fail34 new file mode 100644 index 0000000000000000000000000000000000000000..297cbdccd2d634339dfa165834489853ffcf5c07 --- /dev/null +++ b/packages/CLI11/tests/fuzzFail/fuzz_app_file_fail34 @@ -0,0 +1 @@ +" (\\\,"��� diff --git a/packages/CLI11/tests/fuzzFail/fuzz_app_file_fail35 b/packages/CLI11/tests/fuzzFail/fuzz_app_file_fail35 new file mode 100644 index 0000000000000000000000000000000000000000..d9b5aa7c4b02cedab5a529128adc3e150b98d201 --- /dev/null +++ b/packages/CLI11/tests/fuzzFail/fuzz_app_file_fail35 @@ -0,0 +1 @@ +'^^^^^^^\^^^^^^''''''@''i� diff --git a/packages/CLI11/tests/fuzzFail/fuzz_app_file_fail36 b/packages/CLI11/tests/fuzzFail/fuzz_app_file_fail36 new file mode 100644 index 0000000000000000000000000000000000000000..ddd11facc3062bf1987bd905f21e099bff32b293 --- /dev/null +++ b/packages/CLI11/tests/fuzzFail/fuzz_app_file_fail36 @@ -0,0 +1 @@ +"\" diff --git a/packages/CLI11/tests/fuzzFail/fuzz_app_file_fail37 b/packages/CLI11/tests/fuzzFail/fuzz_app_file_fail37 new file mode 100644 index 0000000000000000000000000000000000000000..25d8567d672327f411369b18ec2ff3e5d03bd787 --- /dev/null +++ b/packages/CLI11/tests/fuzzFail/fuzz_app_file_fail37 @@ -0,0 +1 @@ +"�-t2����p'--vopt1'�''e#��'�''e diff --git a/packages/CLI11/tests/fuzzFail/fuzz_app_file_fail38 b/packages/CLI11/tests/fuzzFail/fuzz_app_file_fail38 new file mode 100644 index 0000000000000000000000000000000000000000..9812202972ccd4445e9879ed59dca5b7811adc08 --- /dev/null +++ b/packages/CLI11/tests/fuzzFail/fuzz_app_file_fail38 @@ -0,0 +1 @@ +ParseErrorEF'' --vo-d{} diff --git a/packages/CLI11/tests/fuzzFail/fuzz_app_file_fail39 b/packages/CLI11/tests/fuzzFail/fuzz_app_file_fail39 new file mode 100644 index 0000000000000000000000000000000000000000..991c5c3bdba4bf6d768d6163b650df10f603736c --- /dev/null +++ b/packages/CLI11/tests/fuzzFail/fuzz_app_file_fail39 @@ -0,0 +1 @@ +[--' diff --git a/packages/CLI11/tests/fuzzFail/fuzz_app_file_fail4 b/packages/CLI11/tests/fuzzFail/fuzz_app_file_fail4 new file mode 100644 index 0000000000000000000000000000000000000000..7f6475c6c00849b2124fa84caee54476bcd14af8 --- /dev/null +++ b/packages/CLI11/tests/fuzzFail/fuzz_app_file_fail4 @@ -0,0 +1 @@ +-ccaaaa diff --git a/packages/CLI11/tests/fuzzFail/fuzz_app_file_fail5 b/packages/CLI11/tests/fuzzFail/fuzz_app_file_fail5 new file mode 100644 index 0000000000000000000000000000000000000000..280646d579c46db99397e54d8d85ea154ac22204 --- /dev/null +++ b/packages/CLI11/tests/fuzzFail/fuzz_app_file_fail5 @@ -0,0 +1,6 @@ +� +atd +�VVV-ba��= +� +� +.-' - diff --git a/packages/CLI11/tests/fuzzFail/fuzz_app_file_fail6 b/packages/CLI11/tests/fuzzFail/fuzz_app_file_fail6 new file mode 100644 index 0000000000000000000000000000000000000000..5b8406c6748b9eaaecda2cf3170862ceaa67b9a0 Binary files /dev/null and b/packages/CLI11/tests/fuzzFail/fuzz_app_file_fail6 differ diff --git a/packages/CLI11/tests/fuzzFail/fuzz_app_file_fail7 b/packages/CLI11/tests/fuzzFail/fuzz_app_file_fail7 new file mode 100644 index 0000000000000000000000000000000000000000..2362ab3750f721d274a52a35fe5ab5ddacba960f --- /dev/null +++ b/packages/CLI11/tests/fuzzFail/fuzz_app_file_fail7 @@ -0,0 +1,3 @@ + + +.br-bN3CLI10ParseErrorEa5 diff --git a/packages/CLI11/tests/fuzzFail/fuzz_app_file_fail8 b/packages/CLI11/tests/fuzzFail/fuzz_app_file_fail8 new file mode 100644 index 0000000000000000000000000000000000000000..991c5011807fbb2fc5a5644b7c5f6fcd480342cb Binary files /dev/null and b/packages/CLI11/tests/fuzzFail/fuzz_app_file_fail8 differ diff --git a/packages/CLI11/tests/fuzzFail/fuzz_app_file_fail9 b/packages/CLI11/tests/fuzzFail/fuzz_app_file_fail9 new file mode 100644 index 0000000000000000000000000000000000000000..dc0d66e209d14137e491efb8296b875242f666c7 --- /dev/null +++ b/packages/CLI11/tests/fuzzFail/fuzz_app_file_fail9 @@ -0,0 +1 @@ +=o����������p--2v��t'�-�- diff --git a/packages/CLI11/tests/fuzzFail/fuzz_file_fail1 b/packages/CLI11/tests/fuzzFail/fuzz_file_fail1 new file mode 100644 index 0000000000000000000000000000000000000000..06b1c382f04fb5c816c1c68c371dc5eabf68ccf9 --- /dev/null +++ b/packages/CLI11/tests/fuzzFail/fuzz_file_fail1 @@ -0,0 +1 @@ +nflag2=555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555"=" diff --git a/packages/CLI11/tests/fuzzFail/fuzz_file_fail2 b/packages/CLI11/tests/fuzzFail/fuzz_file_fail2 new file mode 100644 index 0000000000000000000000000000000000000000..db657443de14bf98b9fa034becc9821b4b237679 Binary files /dev/null and b/packages/CLI11/tests/fuzzFail/fuzz_file_fail2 differ diff --git a/packages/CLI11/tests/fuzzFail/fuzz_file_fail3 b/packages/CLI11/tests/fuzzFail/fuzz_file_fail3 new file mode 100644 index 0000000000000000000000000000000000000000..607bce903b12ed8cff59f6d01d7eebe713fe9216 --- /dev/null +++ b/packages/CLI11/tests/fuzzFail/fuzz_file_fail3 @@ -0,0 +1 @@ +"\�" diff --git a/packages/CLI11/tests/fuzzFail/fuzz_file_fail4 b/packages/CLI11/tests/fuzzFail/fuzz_file_fail4 new file mode 100644 index 0000000000000000000000000000000000000000..e7aac1a293fdc63c4795d076711f6f495f3e311d --- /dev/null +++ b/packages/CLI11/tests/fuzzFail/fuzz_file_fail4 @@ -0,0 +1 @@ +""\" diff --git a/packages/CLI11/tests/fuzzFail/fuzz_file_fail5 b/packages/CLI11/tests/fuzzFail/fuzz_file_fail5 new file mode 100644 index 0000000000000000000000000000000000000000..2acfd3cbac138b8d364aa127336f613abd63dabd --- /dev/null +++ b/packages/CLI11/tests/fuzzFail/fuzz_file_fail5 @@ -0,0 +1 @@ +"\uasdwrap���-"��-"--confi������������������������������������������������������������������.��������������������������������������g diff --git a/packages/CLI11/tests/fuzzFail/fuzz_file_fail6 b/packages/CLI11/tests/fuzzFail/fuzz_file_fail6 new file mode 100644 index 0000000000000000000000000000000000000000..450895cff5fd576c18ca9ed2aae08f742747549d Binary files /dev/null and b/packages/CLI11/tests/fuzzFail/fuzz_file_fail6 differ diff --git a/packages/CLI11/tests/fuzzFail/fuzz_file_fail7 b/packages/CLI11/tests/fuzzFail/fuzz_file_fail7 new file mode 100644 index 0000000000000000000000000000000000000000..1714e4cbda7bebab78fa0cfe55358fd41620d4c9 --- /dev/null +++ b/packages/CLI11/tests/fuzzFail/fuzz_file_fail7 @@ -0,0 +1 @@ +--vdtr5=[| diff --git a/packages/CLI11/tests/fuzzFail/fuzz_file_fail8 b/packages/CLI11/tests/fuzzFail/fuzz_file_fail8 new file mode 100644 index 0000000000000000000000000000000000000000..f060d946f2bacef4ad2bd4ada46ebbf4a80607bb --- /dev/null +++ b/packages/CLI11/tests/fuzzFail/fuzz_file_fail8 @@ -0,0 +1 @@ +[�q�q[]1."�"\".saopt1[[]1."�"\".saopt1[] diff --git a/packages/CLI11/tests/informational.cpp b/packages/CLI11/tests/informational.cpp index 4f7f27b52b89a1742df5fd1d2fd7fa01f698b8c0..ae221ea7ac0d360d93ffd256bc60fa4f010cfefb 100644 --- a/packages/CLI11/tests/informational.cpp +++ b/packages/CLI11/tests/informational.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2024, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // @@ -52,5 +52,5 @@ int main() { std::cout << " boost::optional support active\n"; #endif - std::cout << std::endl; + std::cout << '\n'; } diff --git a/packages/CLI11/tests/link_test_1.cpp b/packages/CLI11/tests/link_test_1.cpp index ba1b2d83767f4e66825529312512aaeb59fb09cc..677261fb979b85585c6941d3d80f5362eb2e00a5 100644 --- a/packages/CLI11/tests/link_test_1.cpp +++ b/packages/CLI11/tests/link_test_1.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2024, 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 46d77be26308976e07dd8025451f3d24bc620b05..ae3fa0af59d7afedef581ef776a6cc0569c5b96f 100644 --- a/packages/CLI11/tests/link_test_2.cpp +++ b/packages/CLI11/tests/link_test_2.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2024, 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 451f65038588121bdb253ac2071bc3f09e0fe93a..f8d148c35a92607e0262d67963739f505d5ece37 100644 --- a/packages/CLI11/tests/main.cpp +++ b/packages/CLI11/tests/main.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2024, University of Cincinnati, developed by Henry Schreiner // under NSF AWARD 1414736 and by the respective contributors. // All rights reserved. // diff --git a/packages/CLI11/tests/meson.build b/packages/CLI11/tests/meson.build index 4847985550397356655366953240b526a5c3d768..2f1c0ad2789787c468dc84b7c475df632cb0471a 100644 --- a/packages/CLI11/tests/meson.build +++ b/packages/CLI11/tests/meson.build @@ -65,7 +65,9 @@ testnames = [ ] dependent_applications = [ - 'system_args' + 'system_args', + 'ensure_utf8', + 'ensure_utf8_twice', ] dependent_applications_definitions = [] #dependent_applications_targets = [] diff --git a/packages/CLI11/tests/mesonTest/main.cpp b/packages/CLI11/tests/mesonTest/main.cpp index 39bb7845cc3fcfab96731000dcda7611402e5441..94fb63811fac301044a59cc78f704bd50f6f9405 100644 --- a/packages/CLI11/tests/mesonTest/main.cpp +++ b/packages/CLI11/tests/mesonTest/main.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner +// Copyright (c) 2017-2024, 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/package_config_tests/CMakeLists.txt b/packages/CLI11/tests/package_config_tests/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..a775e8cfeb36902ab99098cb9d4bd11c2e98268a --- /dev/null +++ b/packages/CLI11/tests/package_config_tests/CMakeLists.txt @@ -0,0 +1,21 @@ +cmake_minimum_required(VERSION 3.10...3.26) + +project(CLI11-package-config-test) + +include(CTest) + +find_package(PkgConfig) + +if(CLI11_DIR) + set(CMAKE_PREFIX_PATH ${CLI11_DIR} ${CLI11_DIR}/lib) +endif() + +message(STATUS "${CLI11_DIR}-- ${CMAKE_PREFIX_PATH}") +pkg_check_modules(CLI11 REQUIRED IMPORTED_TARGET CLI11) + +# Test the target +add_executable(package-config-test ../../examples/positional_validation.cpp) +target_link_libraries(package-config-test PkgConfig::CLI11) + +add_test(NAME package-config-test1 COMMAND package-config-test one) +set_property(TEST package-config-test1 PROPERTY PASS_REGULAR_EXPRESSION "File 1 = one")