├── .bazelrc ├── .clang-format ├── .clang-tidy ├── .github └── workflows │ ├── ci.yml │ ├── tidy-analysis-stage-01.yml │ └── tidy-analysis-stage-02.yml ├── .gitignore ├── .stylua.toml ├── .travis.yml ├── BUILD.bazel ├── CMakeLists.txt ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── WORKSPACE.bazel ├── clang_format.bash ├── conanfile.py ├── include └── argparse │ └── argparse.hpp ├── module └── argparse.cppm ├── packaging └── pkgconfig.pc.in ├── samples ├── BUILD.bazel ├── CMakeLists.txt ├── add_sample.bzl ├── compound_arguments.cpp ├── custom_assignment_characters.cpp ├── custom_prefix_characters.cpp ├── description_epilog_metavar.cpp ├── gathering_remaining_arguments.cpp ├── is_used.cpp ├── joining_repeated_optional_arguments.cpp ├── list_of_arguments.cpp ├── negative_numbers.cpp ├── optional_flag_argument.cpp ├── parse_known_args.cpp ├── positional_argument.cpp ├── repeating_argument_to_increase_value.cpp ├── required_optional_argument.cpp └── subcommands.cpp ├── test ├── .gitignore ├── BUILD.bazel ├── CMakeLists.txt ├── README.md ├── argparse_details.cppm ├── doctest.hpp ├── main.cpp ├── test_actions.cpp ├── test_append.cpp ├── test_as_container.cpp ├── test_bool_operator.cpp ├── test_choices.cpp ├── test_compound_arguments.cpp ├── test_container_arguments.cpp ├── test_default_args.cpp ├── test_default_value.cpp ├── test_equals_form.cpp ├── test_error_reporting.cpp ├── test_get.cpp ├── test_help.cpp ├── test_hidden_alias.cpp ├── test_hidden_argument.cpp ├── test_invalid_arguments.cpp ├── test_is_used.cpp ├── test_issue_37.cpp ├── test_mutually_exclusive_group.cpp ├── test_negative_numbers.cpp ├── test_optional_arguments.cpp ├── test_parent_parsers.cpp ├── test_parse_args.cpp ├── test_parse_known_args.cpp ├── test_positional_arguments.cpp ├── test_prefix_chars.cpp ├── test_repr.cpp ├── test_required_arguments.cpp ├── test_scan.cpp ├── test_store_into.cpp ├── test_stringstream.cpp ├── test_subparsers.cpp ├── test_utility.hpp └── test_version.cpp ├── tools ├── build.bat └── build.sh └── xmake.lua /.bazelrc: -------------------------------------------------------------------------------- 1 | build --enable_platform_specific_config 2 | 3 | build:linux --cxxopt=-std=c++17 4 | 5 | build:windows --copt=/utf-8 6 | build:windows --copt=/Zc:preprocessor 7 | build:windows --cxxopt=/std:c++17 8 | build:windows --cxxopt=/Zc:__cplusplus 9 | -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | Language: Cpp 3 | # BasedOnStyle: LLVM 4 | AccessModifierOffset: -2 5 | AlignAfterOpenBracket: Align 6 | AlignConsecutiveAssignments: false 7 | AlignConsecutiveDeclarations: false 8 | AlignEscapedNewlines: Right 9 | AlignOperands: true 10 | AlignTrailingComments: true 11 | AllowAllParametersOfDeclarationOnNextLine: true 12 | AllowShortBlocksOnASingleLine: false 13 | AllowShortCaseLabelsOnASingleLine: false 14 | AllowShortFunctionsOnASingleLine: All 15 | AllowShortIfStatementsOnASingleLine: false 16 | AllowShortLoopsOnASingleLine: false 17 | AlwaysBreakAfterDefinitionReturnType: None 18 | AlwaysBreakAfterReturnType: None 19 | AlwaysBreakBeforeMultilineStrings: false 20 | AlwaysBreakTemplateDeclarations: false 21 | BinPackArguments: true 22 | BinPackParameters: true 23 | BraceWrapping: 24 | AfterClass: false 25 | AfterControlStatement: false 26 | AfterEnum: false 27 | AfterFunction: false 28 | AfterNamespace: false 29 | AfterObjCDeclaration: false 30 | AfterStruct: false 31 | AfterUnion: false 32 | AfterExternBlock: false 33 | BeforeCatch: false 34 | BeforeElse: false 35 | IndentBraces: false 36 | SplitEmptyFunction: true 37 | SplitEmptyRecord: true 38 | SplitEmptyNamespace: true 39 | BreakBeforeBinaryOperators: None 40 | BreakBeforeBraces: Attach 41 | BreakBeforeInheritanceComma: false 42 | BreakBeforeTernaryOperators: true 43 | BreakConstructorInitializersBeforeComma: false 44 | BreakConstructorInitializers: BeforeColon 45 | BreakAfterJavaFieldAnnotations: false 46 | BreakStringLiterals: true 47 | ColumnLimit: 80 48 | CommentPragmas: '^ IWYU pragma:' 49 | CompactNamespaces: false 50 | ConstructorInitializerAllOnOneLineOrOnePerLine: false 51 | ConstructorInitializerIndentWidth: 4 52 | ContinuationIndentWidth: 4 53 | Cpp11BracedListStyle: true 54 | DerivePointerAlignment: false 55 | DisableFormat: false 56 | ExperimentalAutoDetectBinPacking: false 57 | FixNamespaceComments: true 58 | ForEachMacros: 59 | - foreach 60 | - Q_FOREACH 61 | - BOOST_FOREACH 62 | IncludeBlocks: Preserve 63 | IncludeCategories: 64 | - Regex: '^"(llvm|llvm-c|clang|clang-c)/' 65 | Priority: 2 66 | - Regex: '^(<|"(gtest|gmock|isl|json)/)' 67 | Priority: 3 68 | - Regex: '.*' 69 | Priority: 1 70 | IncludeIsMainRegex: '(Test)?$' 71 | IndentCaseLabels: false 72 | IndentPPDirectives: None 73 | IndentWidth: 2 74 | IndentWrappedFunctionNames: false 75 | JavaScriptQuotes: Leave 76 | JavaScriptWrapImports: true 77 | KeepEmptyLinesAtTheStartOfBlocks: true 78 | MacroBlockBegin: '' 79 | MacroBlockEnd: '' 80 | MaxEmptyLinesToKeep: 1 81 | NamespaceIndentation: None 82 | ObjCBlockIndentWidth: 2 83 | ObjCSpaceAfterProperty: false 84 | ObjCSpaceBeforeProtocolList: true 85 | PenaltyBreakAssignment: 2 86 | PenaltyBreakBeforeFirstCallParameter: 19 87 | PenaltyBreakComment: 300 88 | PenaltyBreakFirstLessLess: 120 89 | PenaltyBreakString: 1000 90 | PenaltyExcessCharacter: 1000000 91 | PenaltyReturnTypeOnItsOwnLine: 60 92 | PointerAlignment: Right 93 | RawStringFormats: 94 | - Language: TextProto 95 | Delimiters: 96 | - 'pb' 97 | - 'proto' 98 | BasedOnStyle: google 99 | ReflowComments: true 100 | SortIncludes: true 101 | SortUsingDeclarations: true 102 | SpaceAfterCStyleCast: false 103 | SpaceAfterTemplateKeyword: true 104 | SpaceBeforeAssignmentOperators: true 105 | SpaceBeforeParens: ControlStatements 106 | SpaceInEmptyParentheses: false 107 | SpacesBeforeTrailingComments: 1 108 | SpacesInAngles: false 109 | SpacesInContainerLiterals: true 110 | SpacesInCStyleCastParentheses: false 111 | SpacesInParentheses: false 112 | SpacesInSquareBrackets: false 113 | Standard: c++17 114 | TabWidth: 8 115 | UseTab: Never 116 | ... 117 | 118 | -------------------------------------------------------------------------------- /.clang-tidy: -------------------------------------------------------------------------------- 1 | Checks: 2 | -*, 3 | clang-analyzer-*, 4 | cppcoreguidelines-avoid-c-arrays, 5 | cppcoreguidelines-special-member-functions, 6 | readability-*, 7 | 8 | CheckOptions: 9 | - { key: readability-identifier-naming.ClassCase, value: CamelCase } 10 | - { key: readability-identifier-naming.ConstexprVariableCase, value: lower_case } 11 | - { key: readability-identifier-naming.ConstexprVariableIgnoredRegexp, value: "^Is.+" } 12 | - { key: readability-identifier-naming.FunctionCase, value: lower_case } 13 | - { key: readability-identifier-naming.NamespaceCase, value: lower_case } 14 | - { key: readability-identifier-naming.ParameterCase, value: lower_case } 15 | - { key: readability-identifier-naming.PrivateMemberCase, value: lower_case } 16 | - { key: readability-identifier-naming.PrivateMemberPrefix, value: m_ } 17 | - { key: readability-identifier-naming.StructCase, value: CamelCase } 18 | - { key: readability-identifier-naming.StructIgnoredRegexp, value: "parse_number" } 19 | - { key: readability-identifier-naming.VariableCase, value: lower_case } 20 | 21 | HeaderFilterRegex: 'argparse/.+\.hpp' 22 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | 2 | name: CI 3 | 4 | on: pull_request 5 | 6 | jobs: 7 | 8 | test: 9 | 10 | name: ${{ matrix.toolchain }} 11 | runs-on: ${{ matrix.os }} 12 | 13 | strategy: 14 | 15 | matrix: 16 | 17 | toolchain: 18 | - macos-latest-clang 19 | - macos-12-clang 20 | - ubuntu-latest-clang 21 | - ubuntu-latest-gcc 22 | - windows-2019-msvc 23 | - windows-latest-msvc 24 | - windows-latest-clang 25 | 26 | include: 27 | - toolchain: macos-latest-clang 28 | os: macos-latest 29 | c_compiler: clang 30 | cxx_compiler: clang++ 31 | 32 | - toolchain: macos-12-clang 33 | os: macos-latest 34 | c_compiler: clang 35 | cxx_compiler: clang++ 36 | 37 | - toolchain: ubuntu-latest-clang 38 | os: ubuntu-latest 39 | c_compiler: clang 40 | cxx_compiler: clang++ 41 | 42 | - toolchain: ubuntu-latest-gcc 43 | os: ubuntu-latest 44 | c_compiler: cc 45 | cxx_compiler: g++ 46 | 47 | - toolchain: windows-2019-msvc 48 | os: windows-2019 49 | c_compiler: msvc 50 | cxx_compiler: msvc 51 | 52 | - toolchain: windows-latest-msvc 53 | os: windows-latest 54 | c_compiler: msvc 55 | cxx_compiler: msvc 56 | 57 | - toolchain: windows-latest-clang 58 | os: windows-latest 59 | c_compiler: clang-cl 60 | cxx_compiler: clang-cl 61 | cmake_opts: -T ClangCL 62 | 63 | steps: 64 | 65 | - name: Checkout Code 66 | uses: actions/checkout@v2 67 | 68 | - name: Configure 69 | working-directory: test 70 | run: cmake -S . -B build ${{ matrix.cmake_opts }} 71 | env: 72 | CC: ${{ matrix.c_compiler }} 73 | CXX: ${{ matrix.cxx_compiler }} 74 | 75 | - name: Build for ${{ matrix.os }} with ${{ matrix.compiler }} 76 | working-directory: test 77 | run: cmake --build build 78 | 79 | - name: Test 80 | if: ${{ ! startsWith(matrix.os, 'windows') }} 81 | working-directory: test/build 82 | run: ./tests 83 | 84 | - name: Test (Windows) 85 | if: ${{ startsWith(matrix.os, 'windows') }} 86 | working-directory: test/build 87 | run: ./Debug/tests.exe 88 | -------------------------------------------------------------------------------- /.github/workflows/tidy-analysis-stage-01.yml: -------------------------------------------------------------------------------- 1 | # Insecure workflow with limited permissions that should provide analysis 2 | # results through an artifact. 3 | name: Tidy analysis 4 | 5 | on: pull_request 6 | 7 | jobs: 8 | 9 | clang-tidy: 10 | 11 | runs-on: ubuntu-20.04 12 | 13 | steps: 14 | 15 | - uses: actions/checkout@v3 16 | with: 17 | fetch-depth: 2 18 | 19 | - name: Install clang-tidy 20 | run: | 21 | sudo apt-get update 22 | sudo apt-get install -y clang-tidy-12 23 | 24 | - name: Prepare compile_commands.json 25 | run: cmake -S . -B build -DCMAKE_EXPORT_COMPILE_COMMANDS=ON 26 | 27 | - name: Create results directory 28 | run: mkdir clang-tidy-result 29 | 30 | - name: Analyze 31 | run: git diff -U0 HEAD^ | clang-tidy-diff-12.py -p1 -regex ".+hpp" -extra-arg=-Iinclude -extra-arg=-std=c++17 -export-fixes clang-tidy-result/fixes.yml 32 | 33 | - name: Save PR metadata 34 | run: | 35 | echo ${{ github.event.number }} > clang-tidy-result/pr-id.txt 36 | echo ${{ github.event.pull_request.head.repo.full_name }} > clang-tidy-result/pr-head-repo.txt 37 | echo ${{ github.event.pull_request.head.ref }} > clang-tidy-result/pr-head-ref.txt 38 | 39 | - uses: actions/upload-artifact@v4 40 | with: 41 | name: clang-tidy-result 42 | path: clang-tidy-result/ 43 | -------------------------------------------------------------------------------- /.github/workflows/tidy-analysis-stage-02.yml: -------------------------------------------------------------------------------- 1 | # Secure workflow with access to repository secrets and GitHub token 2 | # for posting analysis results. 3 | name: Post the Tidy analysis results 4 | 5 | on: 6 | workflow_run: 7 | workflows: [ "Tidy analysis" ] 8 | types: [ completed ] 9 | 10 | jobs: 11 | 12 | clang-tidy-results: 13 | 14 | # Trigger the job only if the previous (insecure) workflow completed successfully 15 | if: ${{ github.event.workflow_run.event == 'pull_request' && github.event.workflow_run.conclusion == 'success' }} 16 | runs-on: ubuntu-20.04 17 | 18 | steps: 19 | 20 | - name: Download analysis results 21 | uses: actions/github-script@v3.1.0 22 | with: 23 | script: | 24 | let artifacts = await github.actions.listWorkflowRunArtifacts({ 25 | owner: context.repo.owner, 26 | repo: context.repo.repo, 27 | run_id: ${{github.event.workflow_run.id }}, 28 | }); 29 | let matchArtifact = artifacts.data.artifacts.filter((artifact) => { 30 | return artifact.name == "clang-tidy-result" 31 | })[0]; 32 | let download = await github.actions.downloadArtifact({ 33 | owner: context.repo.owner, 34 | repo: context.repo.repo, 35 | artifact_id: matchArtifact.id, 36 | archive_format: "zip", 37 | }); 38 | let fs = require("fs"); 39 | fs.writeFileSync("${{github.workspace}}/clang-tidy-result.zip", Buffer.from(download.data)); 40 | 41 | - name: Set environment variables 42 | run: | 43 | mkdir clang-tidy-result 44 | unzip clang-tidy-result.zip -d clang-tidy-result 45 | echo "pr_id=$(cat clang-tidy-result/pr-id.txt)" >> $GITHUB_ENV 46 | echo "pr_head_repo=$(cat clang-tidy-result/pr-head-repo.txt)" >> $GITHUB_ENV 47 | echo "pr_head_ref=$(cat clang-tidy-result/pr-head-ref.txt)" >> $GITHUB_ENV 48 | 49 | - uses: actions/checkout@v3 50 | with: 51 | repository: ${{ env.pr_head_repo }} 52 | ref: ${{ env.pr_head_ref }} 53 | persist-credentials: false 54 | 55 | - name: Redownload analysis results 56 | uses: actions/github-script@v3.1.0 57 | with: 58 | script: | 59 | let artifacts = await github.actions.listWorkflowRunArtifacts({ 60 | owner: context.repo.owner, 61 | repo: context.repo.repo, 62 | run_id: ${{github.event.workflow_run.id }}, 63 | }); 64 | let matchArtifact = artifacts.data.artifacts.filter((artifact) => { 65 | return artifact.name == "clang-tidy-result" 66 | })[0]; 67 | let download = await github.actions.downloadArtifact({ 68 | owner: context.repo.owner, 69 | repo: context.repo.repo, 70 | artifact_id: matchArtifact.id, 71 | archive_format: "zip", 72 | }); 73 | let fs = require("fs"); 74 | fs.writeFileSync("${{github.workspace}}/clang-tidy-result.zip", Buffer.from(download.data)); 75 | 76 | - name: Extract analysis results 77 | run: | 78 | mkdir clang-tidy-result 79 | unzip clang-tidy-result.zip -d clang-tidy-result 80 | 81 | - name: Run clang-tidy-pr-comments action 82 | uses: platisd/clang-tidy-pr-comments@master 83 | with: 84 | github_token: ${{ secrets.GITHUB_TOKEN }} 85 | clang_tidy_fixes: clang-tidy-result/fixes.yml 86 | pull_request_id: ${{ env.pr_id }} 87 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | bld/ 21 | [Bb]in/ 22 | [Oo]bj/ 23 | [Ll]og/ 24 | 25 | # Visual Studio 2015 cache/options directory 26 | .vs/ 27 | .vscode/ 28 | # Uncomment if you have tasks that create the project's static files in wwwroot 29 | #wwwroot/ 30 | 31 | # MSTest test Results 32 | [Tt]est[Rr]esult*/ 33 | [Bb]uild[Ll]og.* 34 | 35 | # NUNIT 36 | *.VisualState.xml 37 | TestResult.xml 38 | 39 | # Build Results of an ATL Project 40 | [Dd]ebugPS/ 41 | [Rr]eleasePS/ 42 | dlldata.c 43 | 44 | # DNX 45 | project.lock.json 46 | project.fragment.lock.json 47 | artifacts/ 48 | 49 | *_i.c 50 | *_p.c 51 | *_i.h 52 | *.ilk 53 | *.meta 54 | *.obj 55 | *.pch 56 | *.pdb 57 | *.pgc 58 | *.pgd 59 | *.rsp 60 | *.sbr 61 | *.tlb 62 | *.tli 63 | *.tlh 64 | *.tmp 65 | *.tmp_proj 66 | *.log 67 | *.vspscc 68 | *.vssscc 69 | .builds 70 | *.pidb 71 | *.svclog 72 | *.scc 73 | 74 | # Chutzpah Test files 75 | _Chutzpah* 76 | 77 | # Visual C++ cache files 78 | ipch/ 79 | *.aps 80 | *.ncb 81 | *.opendb 82 | *.opensdf 83 | *.sdf 84 | *.cachefile 85 | *.VC.db 86 | *.VC.VC.opendb 87 | 88 | # Visual Studio profiler 89 | *.psess 90 | *.vsp 91 | *.vspx 92 | *.sap 93 | 94 | # TFS 2012 Local Workspace 95 | $tf/ 96 | 97 | # Guidance Automation Toolkit 98 | *.gpState 99 | 100 | # ReSharper is a .NET coding add-in 101 | _ReSharper*/ 102 | *.[Rr]e[Ss]harper 103 | *.DotSettings.user 104 | 105 | # JustCode is a .NET coding add-in 106 | .JustCode 107 | 108 | # TeamCity is a build add-in 109 | _TeamCity* 110 | 111 | # DotCover is a Code Coverage Tool 112 | *.dotCover 113 | 114 | # NCrunch 115 | _NCrunch_* 116 | .*crunch*.local.xml 117 | nCrunchTemp_* 118 | 119 | # MightyMoose 120 | *.mm.* 121 | AutoTest.Net/ 122 | 123 | # Web workbench (sass) 124 | .sass-cache/ 125 | 126 | # Installshield output folder 127 | [Ee]xpress/ 128 | 129 | # DocProject is a documentation generator add-in 130 | DocProject/buildhelp/ 131 | DocProject/Help/*.HxT 132 | DocProject/Help/*.HxC 133 | DocProject/Help/*.hhc 134 | DocProject/Help/*.hhk 135 | DocProject/Help/*.hhp 136 | DocProject/Help/Html2 137 | DocProject/Help/html 138 | 139 | # Click-Once directory 140 | publish/ 141 | 142 | # Publish Web Output 143 | *.[Pp]ublish.xml 144 | *.azurePubxml 145 | # TODO: Comment the next line if you want to checkin your web deploy settings 146 | # but database connection strings (with potential passwords) will be unencrypted 147 | #*.pubxml 148 | *.publishproj 149 | 150 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 151 | # checkin your Azure Web App publish settings, but sensitive information contained 152 | # in these scripts will be unencrypted 153 | PublishScripts/ 154 | 155 | # NuGet Packages 156 | *.nupkg 157 | # The packages folder can be ignored because of Package Restore 158 | **/packages/* 159 | # except build/, which is used as an MSBuild target. 160 | !**/packages/build/ 161 | # Uncomment if necessary however generally it will be regenerated when needed 162 | #!**/packages/repositories.config 163 | # NuGet v3's project.json files produces more ignoreable files 164 | *.nuget.props 165 | *.nuget.targets 166 | 167 | # Microsoft Azure Build Output 168 | csx/ 169 | *.build.csdef 170 | 171 | # Microsoft Azure Emulator 172 | ecf/ 173 | rcf/ 174 | 175 | # Windows Store app package directories and files 176 | AppPackages/ 177 | BundleArtifacts/ 178 | Package.StoreAssociation.xml 179 | _pkginfo.txt 180 | 181 | # Visual Studio cache files 182 | # files ending in .cache can be ignored 183 | *.[Cc]ache 184 | # but keep track of directories ending in .cache 185 | !*.[Cc]ache/ 186 | 187 | # Others 188 | ClientBin/ 189 | ~$* 190 | *~ 191 | *.dbmdl 192 | *.dbproj.schemaview 193 | *.jfm 194 | *.pfx 195 | *.publishsettings 196 | node_modules/ 197 | orleans.codegen.cs 198 | 199 | # Since there are multiple workflows, uncomment next line to ignore bower_components 200 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 201 | #bower_components/ 202 | 203 | # RIA/Silverlight projects 204 | Generated_Code/ 205 | 206 | # Backup & report files from converting an old project file 207 | # to a newer Visual Studio version. Backup files are not needed, 208 | # because we have git ;-) 209 | _UpgradeReport_Files/ 210 | Backup*/ 211 | UpgradeLog*.XML 212 | UpgradeLog*.htm 213 | 214 | # SQL Server files 215 | *.mdf 216 | *.ldf 217 | 218 | # Business Intelligence projects 219 | *.rdl.data 220 | *.bim.layout 221 | *.bim_*.settings 222 | 223 | # Microsoft Fakes 224 | FakesAssemblies/ 225 | 226 | # GhostDoc plugin setting file 227 | *.GhostDoc.xml 228 | 229 | # Node.js Tools for Visual Studio 230 | .ntvs_analysis.dat 231 | 232 | # Visual Studio 6 build log 233 | *.plg 234 | 235 | # Visual Studio 6 workspace options file 236 | *.opt 237 | 238 | # Visual Studio LightSwitch build output 239 | **/*.HTMLClient/GeneratedArtifacts 240 | **/*.DesktopClient/GeneratedArtifacts 241 | **/*.DesktopClient/ModelManifest.xml 242 | **/*.Server/GeneratedArtifacts 243 | **/*.Server/ModelManifest.xml 244 | _Pvt_Extensions 245 | 246 | # Paket dependency manager 247 | .paket/paket.exe 248 | paket-files/ 249 | 250 | # FAKE - F# Make 251 | .fake/ 252 | 253 | # JetBrains Rider 254 | .idea/ 255 | *.sln.iml 256 | 257 | # CodeRush 258 | .cr/ 259 | 260 | # Python Tools for Visual Studio (PTVS) 261 | __pycache__/ 262 | *.pyc 263 | 264 | # CMake build directory 265 | build 266 | 267 | # Cppcheck build directory 268 | analysis-cppcheck-build-dir 269 | 270 | # Ideas directory 271 | ideas 272 | 273 | desktop.iniimages/ 274 | 275 | # Ignore all bazel-* symlinks. There is no full list since this can change 276 | # based on the name of the directory bazel is cloned into. 277 | /bazel-* 278 | -------------------------------------------------------------------------------- /.stylua.toml: -------------------------------------------------------------------------------- 1 | indent_type = "Spaces" 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | matrix: 2 | include: 3 | - os: linux 4 | dist: bionic 5 | language: cpp 6 | compiler: gcc 7 | addons: 8 | apt: 9 | sources: 10 | - ubuntu-toolchain-r-test 11 | packages: 12 | - g++-8 13 | env: CXX=g++-8 CC=gcc-8 14 | - os: osx 15 | osx_image: xcode10.2 16 | language: cpp 17 | compiler: clang 18 | - os: windows 19 | language: bash 20 | env: CXX=cl.exe 21 | install: 22 | - | 23 | if [[ $TRAVIS_OS_NAME == 'windows' ]]; then 24 | choco install ninja cmake 25 | elif [[ $TRAVIS_OS_NAME == 'osx' ]]; then 26 | export PATH=~/Library/Python/3.7/bin:$PATH 27 | pip3 install --user ninja cmake 28 | else 29 | pipenv global 3.6 30 | pip install --user ninja cmake 31 | fi 32 | script: 33 | - | 34 | if [[ $TRAVIS_OS_NAME == 'windows' ]]; then 35 | tools/build.bat 36 | else 37 | sh tools/build.sh 38 | fi 39 | - ./build/test/tests 40 | -------------------------------------------------------------------------------- /BUILD.bazel: -------------------------------------------------------------------------------- 1 | cc_library( 2 | name = "argparse", 3 | hdrs = ["include/argparse/argparse.hpp"], 4 | includes = ["include"], 5 | visibility = ["//visibility:public"], 6 | ) 7 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.12.4) 2 | 3 | if(NOT DEFINED PROJECT_NAME) 4 | set(ARGPARSE_IS_TOP_LEVEL ON) 5 | else() 6 | set(ARGPARSE_IS_TOP_LEVEL OFF) 7 | endif() 8 | 9 | project(argparse 10 | VERSION 3.2.0 11 | DESCRIPTION "A single header argument parser for C++17" 12 | HOMEPAGE_URL "https://github.com/p-ranav/argparse" 13 | LANGUAGES CXX 14 | ) 15 | 16 | option(ARGPARSE_INSTALL "Include an install target" ${ARGPARSE_IS_TOP_LEVEL}) 17 | option(ARGPARSE_BUILD_TESTS "Build tests" ${ARGPARSE_IS_TOP_LEVEL}) 18 | option(ARGPARSE_BUILD_SAMPLES "Build samples" OFF) 19 | 20 | include(GNUInstallDirs) 21 | include(CMakePackageConfigHelpers) 22 | 23 | add_library(argparse INTERFACE) 24 | add_library(argparse::argparse ALIAS argparse) 25 | 26 | target_compile_features(argparse INTERFACE cxx_std_17) 27 | target_include_directories(argparse INTERFACE 28 | $ 29 | $) 30 | 31 | if(ARGPARSE_BUILD_SAMPLES) 32 | add_subdirectory(samples) 33 | endif() 34 | 35 | if(ARGPARSE_BUILD_TESTS) 36 | add_subdirectory(test) 37 | endif() 38 | 39 | if(ARGPARSE_INSTALL) 40 | install(TARGETS argparse EXPORT argparseConfig) 41 | install(EXPORT argparseConfig 42 | NAMESPACE argparse:: 43 | DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}) 44 | install(FILES ${CMAKE_CURRENT_LIST_DIR}/include/argparse/argparse.hpp 45 | DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/argparse) 46 | 47 | 48 | set(CONFIG_FILE_NAME_WITHOUT_EXT "${PROJECT_NAME}Config") 49 | set(CMAKE_CONFIG_FILE_BASENAME "${CMAKE_CURRENT_BINARY_DIR}/${CONFIG_FILE_NAME_WITHOUT_EXT}") 50 | set(CMAKE_CONFIG_VERSION_FILE_NAME "${CMAKE_CONFIG_FILE_BASENAME}-version.cmake") 51 | set(CMAKE_CONFIG_FILE_NAME "${CMAKE_CONFIG_FILE_BASENAME}.cmake") 52 | 53 | if(${CMAKE_VERSION} VERSION_GREATER "3.14") 54 | set(OPTIONAL_ARCH_INDEPENDENT "ARCH_INDEPENDENT") 55 | endif() 56 | 57 | write_basic_package_version_file("${CMAKE_CONFIG_VERSION_FILE_NAME}" 58 | COMPATIBILITY SameMajorVersion 59 | ${OPTIONAL_ARCH_INDEPENDENT} 60 | ) 61 | 62 | export(EXPORT argparseConfig 63 | NAMESPACE argparse::) 64 | 65 | install(FILES "${CMAKE_CONFIG_VERSION_FILE_NAME}" 66 | DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}") 67 | 68 | set(PackagingTemplatesDir "${CMAKE_CURRENT_SOURCE_DIR}/packaging") 69 | 70 | set(CPACK_PACKAGE_NAME "${PROJECT_NAME}") 71 | set(CPACK_PACKAGE_VENDOR "argparse (C++) developers") 72 | set(CPACK_PACKAGE_DESCRIPTION "${PROJECT_DESCRIPTION}") 73 | set(CPACK_DEBIAN_PACKAGE_NAME "${CPACK_PACKAGE_NAME}") 74 | set(CPACK_RPM_PACKAGE_NAME "${CPACK_PACKAGE_NAME}") 75 | set(CPACK_PACKAGE_HOMEPAGE_URL "${PROJECT_HOMEPAGE_URL}") 76 | set(CPACK_PACKAGE_MAINTAINER "Pranav Srinivas Kumar") 77 | set(CPACK_DEBIAN_PACKAGE_MAINTAINER "${CPACK_PACKAGE_MAINTAINER}") 78 | set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE") 79 | set(CPACK_RESOURCE_FILE_README "${CMAKE_CURRENT_SOURCE_DIR}/README.md") 80 | 81 | set(CPACK_DEBIAN_PACKAGE_NAME "lib${PROJECT_NAME}-dev") 82 | set(CPACK_DEBIAN_PACKAGE_DEPENDS "libc6-dev") 83 | set(CPACK_DEBIAN_PACKAGE_SUGGESTS "cmake, pkg-config, pkg-conf") 84 | 85 | set(CPACK_RPM_PACKAGE_NAME "lib${PROJECT_NAME}-devel") 86 | set(CPACK_RPM_PACKAGE_SUGGESTS "${CPACK_DEBIAN_PACKAGE_SUGGESTS}") 87 | 88 | set(CPACK_DEB_COMPONENT_INSTALL ON) 89 | set(CPACK_RPM_COMPONENT_INSTALL ON) 90 | set(CPACK_NSIS_COMPONENT_INSTALL ON) 91 | set(CPACK_DEBIAN_COMPRESSION_TYPE "xz") 92 | 93 | set(PKG_CONFIG_FILE_NAME "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}.pc") 94 | configure_file("${PackagingTemplatesDir}/pkgconfig.pc.in" "${PKG_CONFIG_FILE_NAME}" @ONLY) 95 | install(FILES "${PKG_CONFIG_FILE_NAME}" 96 | DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig" 97 | ) 98 | endif() 99 | 100 | include(CPack) 101 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | Contributions are welcomed. Open a pull-request or an issue. 3 | 4 | ## Code of conduct 5 | This project adheres to the [Open Code of Conduct][code-of-conduct]. By participating, you are expected to honor this code. 6 | 7 | [code-of-conduct]: https://github.com/spotify/code-of-conduct/blob/master/code-of-conduct.md 8 | 9 | ## Code Style 10 | 11 | This project prefers, but does not strictly enforce, a specific source code style. The style is described in `.clang-format` and `.clang-tidy`. 12 | 13 | To generate a clang-tidy report: 14 | 15 | ```bash 16 | clang-tidy --extra-arg=-std=c++17 --config-file=.clang-tidy include/argparse/argparse.hpp 17 | ``` 18 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2018 Pranav Srinivas Kumar 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /WORKSPACE.bazel: -------------------------------------------------------------------------------- 1 | workspace(name="argparse") -------------------------------------------------------------------------------- /clang_format.bash: -------------------------------------------------------------------------------- 1 | clang-format -i include/argparse/*.hpp test/*.cpp samples/*.cpp 2 | -------------------------------------------------------------------------------- /conanfile.py: -------------------------------------------------------------------------------- 1 | from conans import ConanFile 2 | 3 | class ArgparseConan(ConanFile): 4 | name = "argparse" 5 | version = "3.1" 6 | exports_sources = "include/argparse.hpp" 7 | no_copy_source = True 8 | 9 | def package(self): 10 | self.copy("argparse.hpp") 11 | -------------------------------------------------------------------------------- /module/argparse.cppm: -------------------------------------------------------------------------------- 1 | /* 2 | __ _ _ __ __ _ _ __ __ _ _ __ ___ ___ 3 | / _` | '__/ _` | '_ \ / _` | '__/ __|/ _ \ Argument Parser for Modern C++ 4 | | (_| | | | (_| | |_) | (_| | | \__ \ __/ http://github.com/p-ranav/argparse 5 | \__,_|_| \__, | .__/ \__,_|_| |___/\___| 6 | |___/|_| 7 | 8 | Licensed under the MIT License . 9 | SPDX-License-Identifier: MIT 10 | Copyright (c) 2019-2022 Pranav Srinivas Kumar 11 | and other contributors. 12 | 13 | Permission is hereby granted, free of charge, to any person obtaining a copy 14 | of this software and associated documentation files (the "Software"), to deal 15 | in the Software without restriction, including without limitation the rights 16 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 17 | copies of the Software, and to permit persons to whom the Software is 18 | furnished to do so, subject to the following conditions: 19 | 20 | The above copyright notice and this permission notice shall be included in all 21 | copies or substantial portions of the Software. 22 | 23 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 24 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 25 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 26 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 27 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 28 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 29 | SOFTWARE. 30 | */ 31 | 32 | module; 33 | 34 | #ifndef ARGPARSE_MODULE_USE_STD_MODULE 35 | #include 36 | #endif 37 | 38 | export module argparse; 39 | 40 | #ifdef ARGPARSE_MODULE_USE_STD_MODULE 41 | import std; 42 | import std.compat; 43 | 44 | extern "C++" { 45 | #pragma clang diagnostic push 46 | #pragma clang diagnostic ignored "-Winclude-angled-in-module-purview" 47 | #include 48 | #pragma clang diagnostic pop 49 | } 50 | #endif 51 | 52 | 53 | export namespace argparse { 54 | using argparse::nargs_pattern; 55 | using argparse::default_arguments; 56 | using argparse::operator&; 57 | using argparse::Argument; 58 | using argparse::ArgumentParser; 59 | } 60 | 61 | -------------------------------------------------------------------------------- /packaging/pkgconfig.pc.in: -------------------------------------------------------------------------------- 1 | prefix=@CMAKE_INSTALL_PREFIX@ 2 | includedir=${prefix}/@CMAKE_INSTALL_INCLUDEDIR@ 3 | 4 | Name: @PROJECT_NAME@ 5 | Description: @PROJECT_DESCRIPTION@ 6 | Version: @PROJECT_VERSION@ 7 | Cflags: -I${includedir} 8 | -------------------------------------------------------------------------------- /samples/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load(":add_sample.bzl", "add_sample") 2 | 3 | add_sample(name = "positional_argument") 4 | 5 | add_sample(name = "optional_flag_argument") 6 | 7 | add_sample(name = "required_optional_argument") 8 | 9 | add_sample(name = "is_used") 10 | 11 | add_sample(name = "joining_repeated_optional_arguments") 12 | 13 | add_sample(name = "repeating_argument_to_increase_value") 14 | 15 | add_sample(name = "negative_numbers") 16 | 17 | add_sample(name = "description_epilog_metavar") 18 | 19 | add_sample(name = "list_of_arguments") 20 | 21 | add_sample(name = "compound_arguments") 22 | 23 | add_sample(name = "gathering_remaining_arguments") 24 | 25 | add_sample(name = "subcommands") 26 | 27 | add_sample(name = "parse_known_args") 28 | 29 | add_sample(name = "custom_prefix_characters") 30 | 31 | add_sample(name = "custom_assignment_characters") 32 | -------------------------------------------------------------------------------- /samples/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.6) 2 | project(argparse_samples) 3 | 4 | if(MSVC) 5 | # Force to always compile with W4 6 | if(CMAKE_CXX_FLAGS MATCHES "/W[0-4]") 7 | string(REGEX REPLACE "/W[0-4]" "/W4" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") 8 | else() 9 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /W4") 10 | endif() 11 | elseif(CMAKE_COMPILER_IS_GNUCC OR CMAKE_COMPILER_IS_GNUCXX) 12 | # Update if necessary 13 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wno-long-long -pedantic -Wsign-conversion -Wshadow -Wconversion") 14 | endif() 15 | 16 | if(NOT CMAKE_BUILD_TYPE) 17 | set(CMAKE_BUILD_TYPE Release) 18 | endif() 19 | 20 | # Disable deprecation for windows 21 | if (WIN32) 22 | add_compile_definitions(_CRT_SECURE_NO_WARNINGS) 23 | endif() 24 | 25 | function(add_sample NAME) 26 | ADD_EXECUTABLE(ARGPARSE_SAMPLE_${NAME} ${NAME}.cpp) 27 | INCLUDE_DIRECTORIES("../include" ".") 28 | set_target_properties(ARGPARSE_SAMPLE_${NAME} PROPERTIES OUTPUT_NAME ${NAME}) 29 | set_property(TARGET ARGPARSE_SAMPLE_${NAME} PROPERTY CXX_STANDARD 17) 30 | endfunction() 31 | 32 | add_sample(positional_argument) 33 | add_sample(optional_flag_argument) 34 | add_sample(required_optional_argument) 35 | add_sample(is_used) 36 | add_sample(joining_repeated_optional_arguments) 37 | add_sample(repeating_argument_to_increase_value) 38 | add_sample(negative_numbers) 39 | add_sample(description_epilog_metavar) 40 | add_sample(list_of_arguments) 41 | add_sample(compound_arguments) 42 | add_sample(gathering_remaining_arguments) 43 | add_sample(subcommands) 44 | add_sample(parse_known_args) 45 | add_sample(custom_prefix_characters) 46 | add_sample(custom_assignment_characters) -------------------------------------------------------------------------------- /samples/add_sample.bzl: -------------------------------------------------------------------------------- 1 | def add_sample(name): 2 | native.cc_binary( 3 | name = name, 4 | srcs = ["{}.cpp".format(name)], 5 | deps = ["//:argparse"], 6 | ) 7 | -------------------------------------------------------------------------------- /samples/compound_arguments.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | #include 4 | 5 | int main(int argc, char *argv[]) { 6 | argparse::ArgumentParser program("test"); 7 | 8 | program.add_argument("-a").flag(); 9 | 10 | program.add_argument("-b").flag(); 11 | 12 | program.add_argument("-c") 13 | .nargs(2) 14 | .default_value(std::vector{0.0f, 0.0f}) 15 | .scan<'g', float>(); 16 | 17 | try { 18 | program.parse_args(argc, argv); // Example: ./main -abc 1.95 2.47 19 | } catch (const std::exception &err) { 20 | std::cerr << err.what() << std::endl; 21 | std::cerr << program; 22 | return 1; 23 | } 24 | 25 | auto a = program.get("-a"); // true 26 | auto b = program.get("-b"); // true 27 | auto c = program.get>("-c"); // {1.95, 2.47} 28 | 29 | std::cout << "a: " << std::boolalpha << a << "\n"; 30 | std::cout << "b: " << b << "\n"; 31 | if (!c.empty()) { 32 | std::cout << "c: "; 33 | for (auto &v : c) { 34 | std::cout << v << " "; 35 | } 36 | std::cout << std::endl; 37 | } 38 | } -------------------------------------------------------------------------------- /samples/custom_assignment_characters.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | #include 4 | #include 5 | 6 | int main(int argc, char *argv[]) { 7 | argparse::ArgumentParser program("test"); 8 | program.set_prefix_chars("-+/"); 9 | program.set_assign_chars("=:"); 10 | 11 | program.add_argument("--foo"); 12 | program.add_argument("/B"); 13 | 14 | try { 15 | program.parse_args(argc, argv); 16 | } catch (const std::exception &err) { 17 | std::cerr << err.what() << std::endl; 18 | std::cerr << program; 19 | return 1; 20 | } 21 | 22 | if (program.is_used("--foo")) { 23 | std::cout << "--foo : " << program.get("--foo") << "\n"; 24 | } 25 | 26 | if (program.is_used("/B")) { 27 | std::cout << "/B : " << program.get("/B") << "\n"; 28 | } 29 | } -------------------------------------------------------------------------------- /samples/custom_prefix_characters.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | #include 4 | #include 5 | 6 | int main(int argc, char *argv[]) { 7 | argparse::ArgumentParser program("test"); 8 | program.set_prefix_chars("-+/"); 9 | 10 | program.add_argument("+f"); 11 | program.add_argument("--bar"); 12 | program.add_argument("/foo"); 13 | 14 | try { 15 | program.parse_args(argc, argv); 16 | } catch (const std::exception &err) { 17 | std::cerr << err.what() << std::endl; 18 | std::cerr << program; 19 | return 1; 20 | } 21 | 22 | if (program.is_used("+f")) { 23 | std::cout << "+f : " << program.get("+f") << "\n"; 24 | } 25 | 26 | if (program.is_used("--bar")) { 27 | std::cout << "--bar : " << program.get("--bar") << "\n"; 28 | } 29 | 30 | if (program.is_used("/foo")) { 31 | std::cout << "/foo : " << program.get("/foo") << "\n"; 32 | } 33 | } -------------------------------------------------------------------------------- /samples/description_epilog_metavar.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | #include 4 | 5 | int main(int argc, char *argv[]) { 6 | argparse::ArgumentParser program("main"); 7 | program.add_argument("thing").help("Thing to use.").metavar("THING"); 8 | program.add_argument("--member") 9 | .help("The alias for the member to pass to.") 10 | .metavar("ALIAS"); 11 | program.add_argument("--verbose").flag(); 12 | 13 | program.add_description("Forward a thing to the next member."); 14 | program.add_epilog("Possible things include betingalw, chiz, and res."); 15 | 16 | program.parse_args(argc, argv); 17 | 18 | std::cout << program << std::endl; 19 | } -------------------------------------------------------------------------------- /samples/gathering_remaining_arguments.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | #include 4 | 5 | int main(int argc, char *argv[]) { 6 | argparse::ArgumentParser program("compiler"); 7 | 8 | program.add_argument("files").remaining(); 9 | 10 | try { 11 | program.parse_args(argc, argv); 12 | } catch (const std::exception &err) { 13 | std::cerr << err.what() << std::endl; 14 | std::cerr << program; 15 | return 1; 16 | } 17 | 18 | try { 19 | auto files = program.get>("files"); 20 | std::cout << files.size() << " files provided" << std::endl; 21 | for (auto &file : files) 22 | std::cout << file << std::endl; 23 | } catch (std::logic_error &e) { 24 | std::cout << "No files provided" << std::endl; 25 | } 26 | } -------------------------------------------------------------------------------- /samples/is_used.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | #include 4 | 5 | int main(int argc, char *argv[]) { 6 | argparse::ArgumentParser program("test"); 7 | 8 | program.add_argument("--color") 9 | .default_value(std::string{ 10 | "orange"}) // might otherwise be type const char* leading to an error 11 | // when trying program.get 12 | .help("specify the cat's fur color"); 13 | 14 | try { 15 | program.parse_args(argc, argv); // Example: ./main --color orange 16 | } catch (const std::exception &err) { 17 | std::cerr << err.what() << std::endl; 18 | std::cerr << program; 19 | return 1; 20 | } 21 | 22 | auto color = program.get("--color"); // "orange" 23 | auto explicit_color = 24 | program.is_used("--color"); // true, user provided orange 25 | std::cout << "Color: " << color << "\n"; 26 | std::cout << "Argument was explicitly provided by user? " << std::boolalpha 27 | << explicit_color << "\n"; 28 | } 29 | -------------------------------------------------------------------------------- /samples/joining_repeated_optional_arguments.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | #include 4 | 5 | int main(int argc, char *argv[]) { 6 | argparse::ArgumentParser program("test"); 7 | 8 | program.add_argument("--color") 9 | .default_value>({"orange"}) 10 | .append() 11 | .help("specify the cat's fur color"); 12 | 13 | try { 14 | program.parse_args( 15 | argc, argv); // Example: ./main --color red --color green --color blue 16 | } catch (const std::exception &err) { 17 | std::cerr << err.what() << std::endl; 18 | std::cerr << program; 19 | return 1; 20 | } 21 | 22 | auto colors = program.get>( 23 | "--color"); // {"red", "green", "blue"} 24 | 25 | std::cout << "Colors: "; 26 | for (const auto &c : colors) { 27 | std::cout << c << " "; 28 | } 29 | std::cout << "\n"; 30 | } 31 | -------------------------------------------------------------------------------- /samples/list_of_arguments.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | #include 4 | 5 | int main(int argc, char *argv[]) { 6 | 7 | argparse::ArgumentParser program("main"); 8 | 9 | program.add_argument("--input_files") 10 | .help("The list of input files") 11 | .nargs(2); 12 | 13 | try { 14 | program.parse_args( 15 | argc, argv); // Example: ./main --input_files config.yml System.xml 16 | } catch (const std::exception &err) { 17 | std::cerr << err.what() << std::endl; 18 | std::cerr << program; 19 | return 1; 20 | } 21 | 22 | auto files = program.get>( 23 | "--input_files"); // {"config.yml", "System.xml"} 24 | 25 | if (!files.empty()) { 26 | std::cout << "Files: "; 27 | for (auto &file : files) { 28 | std::cout << file << " "; 29 | } 30 | std::cout << std::endl; 31 | } 32 | } -------------------------------------------------------------------------------- /samples/negative_numbers.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | #include 4 | 5 | int main(int argc, char *argv[]) { 6 | argparse::ArgumentParser program("test"); 7 | 8 | program.add_argument("integer").help("Input number").scan<'i', int>(); 9 | 10 | program.add_argument("floats") 11 | .help("Vector of floats") 12 | .nargs(4) 13 | .scan<'g', float>(); 14 | 15 | try { 16 | program.parse_args(argc, argv); 17 | } catch (const std::exception &err) { 18 | std::cerr << err.what() << std::endl; 19 | std::cerr << program; 20 | return 1; 21 | } 22 | 23 | if (program.is_used("integer")) { 24 | std::cout << "Integer : " << program.get("integer") << "\n"; 25 | } 26 | 27 | if (program.is_used("floats")) { 28 | std::cout << "Floats : "; 29 | for (const auto &f : program.get>("floats")) { 30 | std::cout << f << " "; 31 | } 32 | std::cout << std::endl; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /samples/optional_flag_argument.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | #include 4 | 5 | int main(int argc, char *argv[]) { 6 | argparse::ArgumentParser program("test"); 7 | 8 | program.add_argument("--verbose") 9 | .help("increase output verbosity") 10 | .default_value(false) 11 | .implicit_value(true); 12 | 13 | try { 14 | program.parse_args(argc, argv); 15 | } catch (const std::exception &err) { 16 | std::cerr << err.what() << std::endl; 17 | std::cerr << program; 18 | return 1; 19 | } 20 | 21 | if (program["--verbose"] == true) { 22 | std::cout << "Verbosity enabled" << std::endl; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /samples/parse_known_args.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | #include 4 | #include 5 | 6 | int main(int argc, char *argv[]) { 7 | argparse::ArgumentParser program("test"); 8 | program.add_argument("--foo").implicit_value(true).default_value(false); 9 | program.add_argument("bar"); 10 | 11 | auto unknown_args = program.parse_known_args(argc, argv); 12 | 13 | if (program.is_used("--foo")) { 14 | std::cout << "--foo : " << program.get("--foo") << "\n"; 15 | } 16 | 17 | if (program.is_used("bar")) { 18 | std::cout << "bar : " << program.get("bar") << "\n"; 19 | } 20 | 21 | if (!unknown_args.empty()) { 22 | std::cout << "Unknown args : "; 23 | for (const auto &u : unknown_args) { 24 | std::cout << u << " "; 25 | } 26 | std::cout << std::endl; 27 | } 28 | } -------------------------------------------------------------------------------- /samples/positional_argument.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | #include 4 | 5 | int main(int argc, char *argv[]) { 6 | argparse::ArgumentParser program("main"); 7 | 8 | program.add_argument("square") 9 | .help("display the square of a given number") 10 | .scan<'i', int>(); 11 | 12 | program.add_argument("--verbose").flag(); 13 | 14 | try { 15 | program.parse_args(argc, argv); 16 | } catch (const std::exception &err) { 17 | std::cerr << err.what() << std::endl; 18 | std::cerr << program; 19 | return 1; 20 | } 21 | 22 | int input = program.get("square"); 23 | 24 | if (program["--verbose"] == true) { 25 | std::cout << "The square of " << input << " is " << (input * input) 26 | << std::endl; 27 | } else { 28 | std::cout << (input * input) << std::endl; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /samples/repeating_argument_to_increase_value.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | #include 4 | 5 | int main(int argc, char *argv[]) { 6 | argparse::ArgumentParser program("test"); 7 | 8 | int verbosity = 0; 9 | program.add_argument("-V", "--verbose") 10 | .action([&](const auto &) { ++verbosity; }) 11 | .append() 12 | .default_value(false) 13 | .implicit_value(true) 14 | .nargs(0); 15 | 16 | program.parse_args(argc, argv); // Example: ./main -VVVV 17 | 18 | std::cout << "verbose level: " << verbosity << std::endl; // verbose level: 4 19 | } 20 | -------------------------------------------------------------------------------- /samples/required_optional_argument.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | #include 4 | 5 | int main(int argc, char *argv[]) { 6 | argparse::ArgumentParser program("test"); 7 | 8 | program.add_argument("-o", "--output") 9 | .required() 10 | .help("specify the output file."); 11 | 12 | try { 13 | program.parse_args(argc, argv); 14 | } catch (const std::exception &err) { 15 | std::cerr << err.what() << std::endl; 16 | std::cerr << program; 17 | return 1; 18 | } 19 | 20 | std::cout << "Output written to " << program.get("-o") << "\n"; 21 | } 22 | -------------------------------------------------------------------------------- /samples/subcommands.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | #include 4 | 5 | int main(int argc, char *argv[]) { 6 | argparse::ArgumentParser program("git"); 7 | 8 | // git add subparser 9 | argparse::ArgumentParser add_command("add"); 10 | add_command.add_description("Add file contents to the index"); 11 | add_command.add_argument("files") 12 | .help("Files to add content from. Fileglobs (e.g. *.c) can be given to " 13 | "add all matching files.") 14 | .remaining(); 15 | 16 | // git commit subparser 17 | argparse::ArgumentParser commit_command("commit"); 18 | commit_command.add_description("Record changes to the repository"); 19 | commit_command.add_argument("-a", "--all") 20 | .help("Tell the command to automatically stage files that have been " 21 | "modified and deleted.") 22 | .default_value(false) 23 | .implicit_value(true); 24 | 25 | commit_command.add_argument("-m", "--message") 26 | .help("Use the given as the commit message."); 27 | 28 | // git cat-file subparser 29 | argparse::ArgumentParser catfile_command("cat-file"); 30 | catfile_command.add_description( 31 | "Provide content or type and size information for repository objects"); 32 | catfile_command.add_argument("-t").help( 33 | "Instead of the content, show the object type identified by ."); 34 | 35 | catfile_command.add_argument("-p").help( 36 | "Pretty-print the contents of based on its type."); 37 | 38 | // git submodule subparser 39 | argparse::ArgumentParser submodule_command("submodule"); 40 | submodule_command.add_description("Initialize, update or inspect submodules"); 41 | argparse::ArgumentParser submodule_update_command("update"); 42 | submodule_update_command.add_description( 43 | "Update the registered submodules to match what the superproject " 44 | "expects"); 45 | submodule_update_command.add_argument("--init") 46 | .default_value(false) 47 | .implicit_value(true); 48 | submodule_update_command.add_argument("--recursive") 49 | .default_value(false) 50 | .implicit_value(true); 51 | submodule_command.add_subparser(submodule_update_command); 52 | 53 | program.add_subparser(add_command); 54 | program.add_subparser(commit_command); 55 | program.add_subparser(catfile_command); 56 | program.add_subparser(submodule_command); 57 | 58 | try { 59 | program.parse_args(argc, argv); 60 | } catch (const std::exception &err) { 61 | std::cerr << err.what() << std::endl; 62 | std::cerr << program; 63 | return 1; 64 | } 65 | 66 | // Use arguments 67 | } 68 | -------------------------------------------------------------------------------- /test/.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | build_linux/ -------------------------------------------------------------------------------- /test/BUILD.bazel: -------------------------------------------------------------------------------- 1 | cc_library( 2 | name = "doctest", 3 | srcs = [ 4 | "main.cpp", 5 | ], 6 | hdrs = ["doctest.hpp"], 7 | includes = ["."], 8 | local_defines = [ 9 | "DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN", 10 | ], 11 | ) 12 | 13 | cc_test( 14 | name = "test", 15 | srcs = glob(["test_*.cpp"]) + [ 16 | "test_utility.hpp", 17 | ], 18 | includes = ["."], 19 | deps = [ 20 | ":doctest", 21 | "//:argparse", 22 | ], 23 | ) 24 | -------------------------------------------------------------------------------- /test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.6) 2 | project(argparse_tests) 3 | 4 | if(MSVC) 5 | # Force to always compile with W4 6 | if(CMAKE_CXX_FLAGS MATCHES "/W[0-4]") 7 | string(REGEX REPLACE "/W[0-4]" "/W4" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") 8 | else() 9 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /W4") 10 | endif() 11 | elseif(CMAKE_COMPILER_IS_GNUCC OR CMAKE_COMPILER_IS_GNUCXX) 12 | # Update if necessary 13 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wno-long-long -Wpedantic -Wsign-conversion -Wshadow -Wconversion -Werror -Wextra") 14 | endif() 15 | 16 | if(NOT CMAKE_BUILD_TYPE) 17 | set(CMAKE_BUILD_TYPE Release) 18 | endif() 19 | 20 | # Disable deprecation for windows 21 | if (WIN32) 22 | add_compile_definitions(_CRT_SECURE_NO_WARNINGS) 23 | endif() 24 | 25 | # ARGPARSE executable 26 | file(GLOB ARGPARSE_TEST_SOURCES 27 | main.cpp 28 | test_actions.cpp 29 | test_append.cpp 30 | test_as_container.cpp 31 | test_bool_operator.cpp 32 | test_choices.cpp 33 | test_compound_arguments.cpp 34 | test_container_arguments.cpp 35 | test_default_args.cpp 36 | test_default_value.cpp 37 | test_error_reporting.cpp 38 | test_get.cpp 39 | test_help.cpp 40 | test_hidden_alias.cpp 41 | test_hidden_argument.cpp 42 | test_invalid_arguments.cpp 43 | test_is_used.cpp 44 | test_issue_37.cpp 45 | test_mutually_exclusive_group.cpp 46 | test_negative_numbers.cpp 47 | test_optional_arguments.cpp 48 | test_parent_parsers.cpp 49 | test_parse_args.cpp 50 | test_positional_arguments.cpp 51 | test_repr.cpp 52 | test_required_arguments.cpp 53 | test_scan.cpp 54 | test_store_into.cpp 55 | test_stringstream.cpp 56 | test_version.cpp 57 | test_subparsers.cpp 58 | test_parse_known_args.cpp 59 | test_equals_form.cpp 60 | test_prefix_chars.cpp 61 | ) 62 | set_source_files_properties(main.cpp 63 | PROPERTIES 64 | COMPILE_DEFINITIONS DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN) 65 | ADD_EXECUTABLE(ARGPARSE_TESTS ${ARGPARSE_TEST_SOURCES}) 66 | INCLUDE_DIRECTORIES("../include" ".") 67 | set_target_properties(ARGPARSE_TESTS PROPERTIES OUTPUT_NAME tests) 68 | set_property(TARGET ARGPARSE_TESTS PROPERTY CXX_STANDARD 17) 69 | 70 | # Set ${PROJECT_NAME} as the startup project 71 | set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT ARGPARSE_TESTS) 72 | -------------------------------------------------------------------------------- /test/README.md: -------------------------------------------------------------------------------- 1 | # Argparse Tests 2 | 3 | ## Linux 4 | 5 | ```bash 6 | $ mkdir build 7 | $ cd build 8 | $ cmake ../. 9 | $ make 10 | $ ./tests 11 | ``` 12 | 13 | ## Windows 14 | 15 | 1. Generate Visual Studio solution 16 | 17 | ```bash 18 | $ mkdir build 19 | $ cd build 20 | $ cmake ../. -G "Visual Studio 15 2017" 21 | ``` 22 | 23 | 2. Open ARGPARSE.sln 24 | 3. Build tests in RELEASE | x64 25 | 4. Run tests.exe 26 | -------------------------------------------------------------------------------- /test/argparse_details.cppm: -------------------------------------------------------------------------------- 1 | module; 2 | 3 | #include 4 | 5 | export module argparse.details; 6 | 7 | export namespace argparse::details { 8 | using argparse::details::repr; 9 | } 10 | 11 | -------------------------------------------------------------------------------- /test/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | -------------------------------------------------------------------------------- /test/test_actions.cpp: -------------------------------------------------------------------------------- 1 | #ifdef WITH_MODULE 2 | import argparse; 3 | #else 4 | #include 5 | #endif 6 | #include 7 | 8 | using doctest::test_suite; 9 | 10 | TEST_CASE("Users can use default value inside actions" * 11 | test_suite("actions")) { 12 | argparse::ArgumentParser program("test"); 13 | program.add_argument("input").default_value("bar").action( 14 | [=](const std::string &value) { 15 | static const std::vector choices = {"foo", "bar", "baz"}; 16 | if (std::find(choices.begin(), choices.end(), value) != choices.end()) { 17 | return value; 18 | } 19 | return std::string{"bar"}; 20 | }); 21 | 22 | program.parse_args({"test", "fez"}); 23 | REQUIRE(program.get("input") == "bar"); 24 | } 25 | 26 | TEST_CASE("Users can add actions that return nothing" * test_suite("actions")) { 27 | argparse::ArgumentParser program("test"); 28 | bool pressed = false; 29 | auto &arg = program.add_argument("button").action( 30 | [&](const std::string &) mutable { pressed = true; }); 31 | 32 | REQUIRE_FALSE(pressed); 33 | 34 | SUBCASE("action performed") { 35 | program.parse_args({"test", "ignored"}); 36 | REQUIRE(pressed); 37 | } 38 | 39 | SUBCASE("action performed and nothing overrides the default value") { 40 | arg.default_value(42); 41 | 42 | program.parse_args({"test", "ignored"}); 43 | REQUIRE(pressed); 44 | REQUIRE(program.get("button") == 42); 45 | } 46 | } 47 | 48 | class Image { 49 | public: 50 | int w = 0, h = 0; 51 | 52 | void resize(std::string_view geometry) { 53 | std::stringstream s; 54 | s << geometry; 55 | s >> w; 56 | s.ignore(); 57 | s >> h; 58 | } 59 | 60 | static auto create(int w, int h, std::string_view format) -> Image { 61 | auto factor = [=] { 62 | if (format == "720p") 63 | return std::min(1280. / w, 720. / h); 64 | else if (format == "1080p") 65 | return std::min(1920. / w, 1080. / h); 66 | else 67 | return 1.; 68 | }(); 69 | 70 | return {static_cast(w * factor), static_cast(h * factor)}; 71 | } 72 | }; 73 | 74 | TEST_CASE("Users can bind arguments to actions" * test_suite("actions")) { 75 | argparse::ArgumentParser program("test"); 76 | 77 | GIVEN("an default initialized object bounded by reference") { 78 | Image img; 79 | program.add_argument("--size").action(&Image::resize, std::ref(img)); 80 | 81 | WHEN("provided no command-line arguments") { 82 | program.parse_args({"test"}); 83 | 84 | THEN("the object is not updated") { 85 | REQUIRE(img.w == 0); 86 | REQUIRE(img.h == 0); 87 | } 88 | } 89 | 90 | WHEN("provided command-line arguments") { 91 | program.parse_args({"test", "--size", "320x98"}); 92 | 93 | THEN("the object is updated accordingly") { 94 | REQUIRE(img.w == 320); 95 | REQUIRE(img.h == 98); 96 | } 97 | } 98 | } 99 | 100 | GIVEN("a command-line option waiting for the last argument in its action") { 101 | program.add_argument("format").action(Image::create, 400, 300); 102 | 103 | WHEN("provided such an argument") { 104 | program.parse_args({"test", "720p"}); 105 | 106 | THEN("the option object is created as if providing all arguments") { 107 | auto img = program.get("format"); 108 | 109 | REQUIRE(img.w == 960); 110 | REQUIRE(img.h == 720); 111 | } 112 | } 113 | 114 | WHEN("provided a different argument") { 115 | program.parse_args({"test", "1080p"}); 116 | 117 | THEN("a different option object is created") { 118 | auto img = program.get("format"); 119 | 120 | REQUIRE(img.w == 1440); 121 | REQUIRE(img.h == 1080); 122 | } 123 | } 124 | } 125 | } 126 | 127 | TEST_CASE("Users can use actions on nargs=ANY arguments" * 128 | test_suite("actions")) { 129 | argparse::ArgumentParser program("sum"); 130 | 131 | int result = 0; 132 | program.add_argument("all") 133 | .nargs(argparse::nargs_pattern::any) 134 | .action( 135 | [](int &sum, std::string const &value) { sum += std::stoi(value); }, 136 | std::ref(result)); 137 | 138 | program.parse_args({"sum", "42", "100", "-3", "-20"}); 139 | REQUIRE(result == 119); 140 | } 141 | 142 | TEST_CASE("Users can use actions on remaining arguments" * 143 | test_suite("actions")) { 144 | argparse::ArgumentParser program("concat"); 145 | 146 | std::string result = ""; 147 | program.add_argument("all").remaining().action( 148 | [](std::string &sum, const std::string &value) { sum += value; }, 149 | std::ref(result)); 150 | 151 | program.parse_args({"concat", "a", "-b", "-c", "--d"}); 152 | REQUIRE(result == "a-b-c--d"); 153 | } 154 | 155 | TEST_CASE("Users can run actions on parameterless optional arguments" * 156 | test_suite("actions")) { 157 | argparse::ArgumentParser program("test"); 158 | 159 | GIVEN("a flag argument with a counting action") { 160 | int count = 0; 161 | program.add_argument("-V", "--verbose") 162 | .action([&](const auto &) { ++count; }) 163 | .append() 164 | .default_value(false) 165 | .implicit_value(true) 166 | .nargs(0); 167 | 168 | WHEN("the flag is repeated") { 169 | program.parse_args({"test", "-VVVV"}); 170 | 171 | THEN("the count increments once per use") { 172 | REQUIRE(program.get("-V")); 173 | REQUIRE(count == 4); 174 | } 175 | } 176 | } 177 | } 178 | 179 | TEST_CASE("Users can add multiple actions and they are all run" * 180 | test_suite("actions")) { 181 | argparse::ArgumentParser program("test"); 182 | 183 | GIVEN("a flag argument with two counting actions") { 184 | int count = 0; 185 | program.add_argument("-V", "--verbose") 186 | .action([&](const auto &) { ++count; }) 187 | .action([&](const auto &) { ++count; }) 188 | .append() 189 | .default_value(false) 190 | .implicit_value(true) 191 | .nargs(0); 192 | 193 | WHEN("the flag is parsed") { 194 | program.parse_args({"test", "-V"}); 195 | 196 | THEN("the count increments twice") { 197 | REQUIRE(program.get("-V")); 198 | REQUIRE(count == 2); 199 | } 200 | } 201 | } 202 | } 203 | -------------------------------------------------------------------------------- /test/test_append.cpp: -------------------------------------------------------------------------------- 1 | #ifdef WITH_MODULE 2 | import argparse; 3 | #else 4 | #include 5 | #endif 6 | #include 7 | 8 | #include 9 | #include 10 | 11 | using doctest::test_suite; 12 | 13 | TEST_CASE("Simplest .append" * test_suite("append")) { 14 | argparse::ArgumentParser program("test"); 15 | program.add_argument("--dir").append(); 16 | program.parse_args({"test", "--dir", "./Docs"}); 17 | std::string result{program.get("--dir")}; 18 | REQUIRE(result == "./Docs"); 19 | } 20 | 21 | TEST_CASE("Two parameter .append" * test_suite("append")) { 22 | argparse::ArgumentParser program("test"); 23 | program.add_argument("--dir").append(); 24 | program.parse_args({"test", "--dir", "./Docs", "--dir", "./Src"}); 25 | auto result{program.get>("--dir")}; 26 | REQUIRE(result.at(0) == "./Docs"); 27 | REQUIRE(result.at(1) == "./Src"); 28 | } 29 | 30 | TEST_CASE("Two int .append" * test_suite("append")) { 31 | argparse::ArgumentParser program("test"); 32 | program.add_argument("--factor").append().scan<'i', int>(); 33 | program.parse_args({"test", "--factor", "2", "--factor", "5"}); 34 | auto result{program.get>("--factor")}; 35 | REQUIRE(result.at(0) == 2); 36 | REQUIRE(result.at(1) == 5); 37 | } 38 | 39 | TEST_CASE("Default value with .append" * test_suite("append")) { 40 | std::vector expected{"./Src", "./Imgs"}; 41 | 42 | argparse::ArgumentParser program("test"); 43 | program.add_argument("--dir").default_value(expected).append(); 44 | program.parse_args({"test"}); 45 | auto result{program.get>("--dir")}; 46 | REQUIRE(result == expected); 47 | } 48 | -------------------------------------------------------------------------------- /test/test_as_container.cpp: -------------------------------------------------------------------------------- 1 | #ifdef WITH_MODULE 2 | import argparse; 3 | #else 4 | #include 5 | #endif 6 | #include 7 | 8 | using doctest::test_suite; 9 | 10 | TEST_CASE("Get argument with .at()" * test_suite("as_container")) { 11 | argparse::ArgumentParser program("test"); 12 | auto &dir_arg = program.add_argument("--dir"); 13 | program.at("--dir").default_value(std::string("/home/user")); 14 | 15 | SUBCASE("and default value") { 16 | program.parse_args({"test"}); 17 | REQUIRE(&(program.at("--dir")) == &dir_arg); 18 | REQUIRE(program["--dir"] == std::string("/home/user")); 19 | REQUIRE(program.at("--dir") == std::string("/home/user")); 20 | } 21 | 22 | SUBCASE("and user-supplied value") { 23 | program.parse_args({"test", "--dir", "/usr/local/database"}); 24 | REQUIRE(&(program.at("--dir")) == &dir_arg); 25 | REQUIRE(program["--dir"] == std::string("/usr/local/database")); 26 | REQUIRE(program.at("--dir") == std::string("/usr/local/database")); 27 | } 28 | 29 | SUBCASE("with unknown argument") { 30 | program.parse_args({"test"}); 31 | REQUIRE_THROWS_WITH_AS(program.at("--folder"), "No such argument: --folder", 32 | std::logic_error); 33 | } 34 | } 35 | 36 | TEST_CASE("Get subparser with .at()" * test_suite("as_container")) { 37 | argparse::ArgumentParser program("test"); 38 | 39 | argparse::ArgumentParser walk_cmd("walk"); 40 | auto &speed = walk_cmd.add_argument("speed"); 41 | 42 | program.add_subparser(walk_cmd); 43 | 44 | SUBCASE("and its argument") { 45 | program.parse_args({"test", "walk", "4km/h"}); 46 | REQUIRE(&(program.at("walk")) == &walk_cmd); 47 | REQUIRE(&(program.at("walk").at("speed")) == 48 | &speed); 49 | REQUIRE(program.at("walk").is_used("speed")); 50 | } 51 | 52 | SUBCASE("with unknown command") { 53 | program.parse_args({"test"}); 54 | REQUIRE_THROWS_WITH_AS(program.at("fly"), 55 | "No such subparser: fly", std::logic_error); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /test/test_bool_operator.cpp: -------------------------------------------------------------------------------- 1 | #ifdef WITH_MODULE 2 | import argparse; 3 | #else 4 | #include 5 | #endif 6 | 7 | #include 8 | 9 | using doctest::test_suite; 10 | 11 | TEST_CASE("ArgumentParser in bool context" * test_suite("argument_parser")) { 12 | argparse::ArgumentParser program("test"); 13 | program.add_argument("cases").remaining(); 14 | 15 | program.parse_args({"test"}); 16 | REQUIRE_FALSE(program); 17 | 18 | program.parse_args({"test", "one", "two"}); 19 | REQUIRE(program); 20 | } 21 | 22 | TEST_CASE("With subparsers in bool context" * test_suite("argument_parser")) { 23 | argparse::ArgumentParser program("test"); 24 | 25 | argparse::ArgumentParser cmd_fly("fly"); 26 | cmd_fly.add_argument("plane"); 27 | 28 | argparse::ArgumentParser cmd_soar("soar"); 29 | cmd_soar.add_argument("direction"); 30 | 31 | program.add_subparser(cmd_fly); 32 | program.add_subparser(cmd_soar); 33 | 34 | program.parse_args({"test", "fly", "glider"}); 35 | REQUIRE(program); 36 | REQUIRE(cmd_fly); 37 | REQUIRE_FALSE(cmd_soar); 38 | } 39 | 40 | TEST_CASE("Parsers remain false with unknown arguments" * 41 | test_suite("argument_parser")) { 42 | argparse::ArgumentParser program("test"); 43 | 44 | argparse::ArgumentParser cmd_build("build"); 45 | cmd_build.add_argument("--file").nargs(1); 46 | 47 | argparse::ArgumentParser cmd_run("run"); 48 | cmd_run.add_argument("--file").nargs(1); 49 | 50 | program.add_subparser(cmd_build); 51 | program.add_subparser(cmd_run); 52 | 53 | auto unknowns = 54 | program.parse_known_args({"test", "badger", "--add-meal", "grubs"}); 55 | REQUIRE_FALSE(program); 56 | REQUIRE_FALSE(cmd_build); 57 | REQUIRE_FALSE(cmd_run); 58 | } 59 | 60 | TEST_CASE("Multi-level parsers match subparser bool" * 61 | test_suite("argument_parser")) { 62 | argparse::ArgumentParser program("test"); 63 | 64 | argparse::ArgumentParser cmd_cook("cook"); 65 | cmd_cook.add_argument("--temperature"); 66 | 67 | argparse::ArgumentParser cmd_cook_boil("boil"); 68 | cmd_cook_boil.add_argument("--rate"); 69 | 70 | argparse::ArgumentParser cmd_cook_boil_stir("stir"); 71 | cmd_cook_boil_stir.add_argument("--rate"); 72 | 73 | argparse::ArgumentParser cmd_wash("wash"); 74 | 75 | program.add_subparser(cmd_cook); 76 | cmd_cook.add_subparser(cmd_cook_boil); 77 | cmd_cook_boil.add_subparser(cmd_cook_boil_stir); 78 | 79 | program.add_subparser(cmd_wash); 80 | 81 | auto unknowns = program.parse_known_args( 82 | {"test", "cook", "boil", "stir", "--rate", "fast"}); 83 | 84 | REQUIRE(program); 85 | REQUIRE(cmd_cook); 86 | REQUIRE(cmd_cook_boil); 87 | REQUIRE(cmd_cook_boil_stir); 88 | REQUIRE_FALSE(cmd_wash); 89 | } 90 | -------------------------------------------------------------------------------- /test/test_choices.cpp: -------------------------------------------------------------------------------- 1 | #ifdef WITH_MODULE 2 | import argparse; 3 | #else 4 | #include 5 | #endif 6 | 7 | #include 8 | 9 | using doctest::test_suite; 10 | 11 | TEST_CASE("Parse argument that is provided zero choices" * 12 | test_suite("choices")) { 13 | argparse::ArgumentParser program("test"); 14 | REQUIRE_THROWS_WITH_AS(program.add_argument("color").choices(), 15 | "Zero choices provided", std::runtime_error); 16 | } 17 | 18 | TEST_CASE("Parse argument that is in the fixed number of allowed choices" * 19 | test_suite("choices")) { 20 | argparse::ArgumentParser program("test"); 21 | program.add_argument("color").choices("red", "green", "blue"); 22 | 23 | program.parse_args({"test", "red"}); 24 | } 25 | 26 | TEST_CASE("Parse argument that is in the fixed number of allowed choices, with " 27 | "other positional argument" * 28 | test_suite("choices")) { 29 | argparse::ArgumentParser program("test"); 30 | program.add_argument("--input") 31 | .default_value(std::string{"baz"}) 32 | .choices("foo", "bar", "baz"); 33 | program.add_argument("--value").scan<'i', int>().default_value(0); 34 | 35 | REQUIRE_NOTHROW( 36 | program.parse_args({"test", "--input", "foo", "--value", "1"})); 37 | REQUIRE(program.get("--input") == "foo"); 38 | REQUIRE(program.get("--value") == 1); 39 | } 40 | 41 | TEST_CASE( 42 | "Parse nargs argument that is in the fixed number of allowed choices, with " 43 | "other positional argument" * 44 | test_suite("choices")) { 45 | argparse::ArgumentParser program("test"); 46 | program.add_argument("--input") 47 | .default_value(std::string{"baz"}) 48 | .choices("foo", "bar", "baz") 49 | .nargs(2); 50 | program.add_argument("--value").scan<'i', int>().default_value(0); 51 | 52 | REQUIRE_NOTHROW( 53 | program.parse_args({"test", "--input", "foo", "bar", "--value", "1"})); 54 | REQUIRE((program.get>("--input") == 55 | std::vector{"foo", "bar"})); 56 | REQUIRE(program.get("--value") == 1); 57 | } 58 | 59 | TEST_CASE("Parse argument that is in the fixed number of allowed choices, with " 60 | "other positional argument (reversed)" * 61 | test_suite("choices")) { 62 | argparse::ArgumentParser program("test"); 63 | program.add_argument("--input") 64 | .default_value(std::string{"baz"}) 65 | .choices("foo", "bar", "baz"); 66 | program.add_argument("--value").scan<'i', int>().default_value(0); 67 | 68 | REQUIRE_NOTHROW( 69 | program.parse_args({"test", "--value", "1", "--input", "foo"})); 70 | REQUIRE(program.get("--input") == "foo"); 71 | REQUIRE(program.get("--value") == 1); 72 | } 73 | 74 | TEST_CASE( 75 | "Parse nargs argument that is in the fixed number of allowed choices, with " 76 | "other positional argument (reversed)" * 77 | test_suite("choices")) { 78 | argparse::ArgumentParser program("test"); 79 | program.add_argument("--input") 80 | .default_value(std::string{"baz"}) 81 | .choices("foo", "bar", "baz") 82 | .nargs(2); 83 | program.add_argument("--value").scan<'i', int>().default_value(0); 84 | 85 | REQUIRE_NOTHROW( 86 | program.parse_args({"test", "--value", "1", "--input", "foo", "bar"})); 87 | REQUIRE((program.get>("--input") == 88 | std::vector{"foo", "bar"})); 89 | REQUIRE(program.get("--value") == 1); 90 | } 91 | 92 | TEST_CASE("Parse argument that is in the fixed number of allowed choices, with " 93 | "invalid default" * 94 | test_suite("choices")) { 95 | argparse::ArgumentParser program("test"); 96 | program.add_argument("color").default_value("yellow").choices("red", "green", 97 | "blue"); 98 | 99 | REQUIRE_THROWS_WITH_AS( 100 | program.parse_args({"test"}), 101 | "Invalid default value \"yellow\" - allowed options: {red, green, blue}", 102 | std::runtime_error); 103 | } 104 | 105 | TEST_CASE("Parse invalid argument that is not in the fixed number of allowed " 106 | "choices" * 107 | test_suite("choices")) { 108 | argparse::ArgumentParser program("test"); 109 | program.add_argument("color").choices("red", "green", "blue"); 110 | 111 | REQUIRE_THROWS_WITH_AS( 112 | program.parse_args({"test", "red2"}), 113 | "Invalid argument \"red2\" - allowed options: {red, green, blue}", 114 | std::runtime_error); 115 | } 116 | 117 | TEST_CASE( 118 | "Parse multiple arguments that are in the fixed number of allowed choices" * 119 | test_suite("choices")) { 120 | argparse::ArgumentParser program("test"); 121 | program.add_argument("color").nargs(2).choices("red", "green", "blue"); 122 | 123 | program.parse_args({"test", "red", "green"}); 124 | } 125 | 126 | TEST_CASE("Parse multiple arguments one of which is not in the fixed number of " 127 | "allowed choices" * 128 | test_suite("choices")) { 129 | argparse::ArgumentParser program("test"); 130 | program.add_argument("color").nargs(2).choices("red", "green", "blue"); 131 | 132 | REQUIRE_THROWS_WITH_AS( 133 | program.parse_args({"test", "red", "green2"}), 134 | "Invalid argument \"green2\" - allowed options: {red, green, blue}", 135 | std::runtime_error); 136 | } 137 | 138 | TEST_CASE("Parse multiple arguments that are in the fixed number of allowed " 139 | "INTEGER choices" * 140 | test_suite("choices")) { 141 | argparse::ArgumentParser program("test"); 142 | program.add_argument("indices").nargs(2).choices(1, 2, 3, 4, 5); 143 | 144 | program.parse_args({"test", "1", "2"}); 145 | } 146 | 147 | TEST_CASE("Parse multiple arguments that are not in fixed number of allowed " 148 | "INTEGER choices" * 149 | test_suite("choices")) { 150 | argparse::ArgumentParser program("test"); 151 | program.add_argument("indices").nargs(2).choices(1, 2, 3, 4, 5); 152 | 153 | REQUIRE_THROWS_WITH_AS( 154 | program.parse_args({"test", "6", "7"}), 155 | "Invalid argument \"6\" - allowed options: {1, 2, 3, 4, 5}", 156 | std::runtime_error); 157 | } 158 | 159 | TEST_CASE("Parse multiple arguments that are in range of allowed " 160 | "INTEGER choices (Min Range case)" * 161 | test_suite("choices")) { 162 | argparse::ArgumentParser program("test"); 163 | program.add_argument("indices").nargs(1, 3).choices(1, 2, 3, 4, 5); 164 | 165 | REQUIRE_NOTHROW(program.parse_args({"test", "1"})); 166 | REQUIRE(program.get>("indices") == 167 | std::vector{"1"}); 168 | } 169 | 170 | TEST_CASE("Parse multiple arguments that are in range of allowed choices (In " 171 | "Range case)" * 172 | test_suite("choices")) { 173 | argparse::ArgumentParser program("test"); 174 | program.add_argument("--foo"); 175 | program.add_argument("--bar").nargs(1, 3).choices("a", "b", "c"); 176 | 177 | REQUIRE_NOTHROW( 178 | program.parse_args({"test", "--bar", "a", "b", "--foo", "x"})); 179 | REQUIRE(program.get>("--bar") == 180 | std::vector{"a", "b"}); 181 | REQUIRE(program.get("--foo") == "x"); 182 | } 183 | 184 | TEST_CASE("Parse multiple arguments that are in range of allowed " 185 | "INTEGER choices (Max Range case)" * 186 | test_suite("choices")) { 187 | argparse::ArgumentParser program("test"); 188 | program.add_argument("indices").nargs(2, 3).choices(1, 2, 3, 4, 5); 189 | 190 | REQUIRE_NOTHROW(program.parse_args({"test", "3", "4", "5"})); 191 | REQUIRE(program.get>("indices") == 192 | std::vector{"3", "4", "5"}); 193 | } 194 | 195 | TEST_CASE("Parse multiple arguments that are not in range of allowed choices" * 196 | test_suite("choices")) { 197 | argparse::ArgumentParser program("test"); 198 | program.add_argument("--foo"); 199 | program.add_argument("--bar").nargs(1, 3).choices("a", "b", "c"); 200 | 201 | REQUIRE_THROWS_WITH_AS( 202 | program.parse_args({"test", "--bar", "d", "--foo", "x"}), 203 | "Invalid argument \"d\" - allowed options: {a, b, c}", 204 | std::runtime_error); 205 | } 206 | -------------------------------------------------------------------------------- /test/test_compound_arguments.cpp: -------------------------------------------------------------------------------- 1 | #ifdef WITH_MODULE 2 | import argparse; 3 | #else 4 | #include 5 | #endif 6 | #include 7 | #include 8 | 9 | using doctest::test_suite; 10 | 11 | TEST_CASE("Parse compound toggle arguments with implicit values" * 12 | test_suite("compound_arguments")) { 13 | argparse::ArgumentParser program("test"); 14 | program.add_argument("-a").flag(); 15 | 16 | program.add_argument("-u").flag(); 17 | 18 | program.add_argument("-x").flag(); 19 | 20 | program.parse_args({"./test.exe", "-aux"}); 21 | REQUIRE(program.get("-a") == true); 22 | REQUIRE(program.get("-u") == true); 23 | REQUIRE(program.get("-x") == true); 24 | } 25 | 26 | TEST_CASE("Parse compound toggle arguments with implicit values and nargs" * 27 | test_suite("compound_arguments")) { 28 | argparse::ArgumentParser program("test"); 29 | program.add_argument("-a").flag(); 30 | 31 | program.add_argument("-b").flag(); 32 | 33 | program.add_argument("-c").nargs(2).scan<'g', float>(); 34 | 35 | program.add_argument("--input_files").nargs(3); 36 | 37 | program.parse_args({"./test.exe", "-abc", "3.14", "2.718", "--input_files", 38 | "a.txt", "b.txt", "c.txt"}); 39 | REQUIRE(program.get("-a") == true); 40 | REQUIRE(program.get("-b") == true); 41 | auto c = program.get>("-c"); 42 | REQUIRE(c.size() == 2); 43 | REQUIRE(c[0] == 3.14f); 44 | REQUIRE(c[1] == 2.718f); 45 | auto input_files = program.get>("--input_files"); 46 | REQUIRE(input_files.size() == 3); 47 | REQUIRE(input_files[0] == "a.txt"); 48 | REQUIRE(input_files[1] == "b.txt"); 49 | REQUIRE(input_files[2] == "c.txt"); 50 | } 51 | 52 | TEST_CASE("Parse compound toggle arguments with implicit values and nargs and " 53 | "other positional arguments" * 54 | test_suite("compound_arguments")) { 55 | argparse::ArgumentParser program("test"); 56 | 57 | program.add_argument("numbers").nargs(3).scan<'i', int>(); 58 | 59 | program.add_argument("-a").flag(); 60 | 61 | program.add_argument("-b").flag(); 62 | 63 | program.add_argument("-c").nargs(2).scan<'g', float>(); 64 | 65 | program.add_argument("--input_files").nargs(3); 66 | 67 | REQUIRE_THROWS( 68 | program.parse_args({"./test.exe", "1", "-abc", "3.14", "2.718", "2", 69 | "--input_files", "a.txt", "b.txt", "c.txt", "3"})); 70 | } 71 | 72 | TEST_CASE("Parse out-of-order compound arguments" * 73 | test_suite("compound_arguments")) { 74 | argparse::ArgumentParser program("test"); 75 | 76 | program.add_argument("-a").flag(); 77 | 78 | program.add_argument("-b").flag(); 79 | 80 | program.add_argument("-c").nargs(2).scan<'g', float>(); 81 | 82 | program.parse_args({"./main", "-cab", "3.14", "2.718"}); 83 | 84 | auto a = program.get("-a"); // true 85 | auto b = program.get("-b"); // true 86 | auto c = program.get>("-c"); // {3.14f, 2.718f} 87 | REQUIRE(a == true); 88 | REQUIRE(b == true); 89 | REQUIRE(program["-c"] == std::vector{3.14f, 2.718f}); 90 | } 91 | 92 | TEST_CASE("Parse out-of-order compound arguments. Second variation" * 93 | test_suite("compound_arguments")) { 94 | argparse::ArgumentParser program("test"); 95 | 96 | program.add_argument("-a").flag(); 97 | 98 | program.add_argument("-b").flag(); 99 | 100 | program.add_argument("-c") 101 | .nargs(2) 102 | .default_value(std::vector{0.0f, 0.0f}) 103 | .scan<'g', float>(); 104 | 105 | program.parse_args({"./main", "-cb"}); 106 | 107 | auto a = program.get("-a"); 108 | auto b = program.get("-b"); 109 | auto c = program.get>("-c"); 110 | 111 | REQUIRE(a == false); 112 | REQUIRE(b == true); 113 | REQUIRE(program["-c"] == std::vector{0.0f, 0.0f}); 114 | } 115 | -------------------------------------------------------------------------------- /test/test_container_arguments.cpp: -------------------------------------------------------------------------------- 1 | #ifdef WITH_MODULE 2 | import argparse; 3 | #else 4 | #include 5 | #endif 6 | #include 7 | #include 8 | 9 | using doctest::test_suite; 10 | 11 | TEST_CASE("Parse vector of arguments" * test_suite("vector")) { 12 | argparse::ArgumentParser program("test"); 13 | program.add_argument("input").nargs(2); 14 | 15 | program.parse_args({"test", "rocket.mesh", "thrust_profile.csv"}); 16 | 17 | auto inputs = program.get>("input"); 18 | REQUIRE(inputs.size() == 2); 19 | REQUIRE(inputs[0] == "rocket.mesh"); 20 | REQUIRE(inputs[1] == "thrust_profile.csv"); 21 | } 22 | 23 | TEST_CASE("Parse list of arguments" * test_suite("vector")) { 24 | argparse::ArgumentParser program("test"); 25 | program.add_argument("input").nargs(2); 26 | 27 | program.parse_args({"test", "rocket.mesh", "thrust_profile.csv"}); 28 | 29 | auto inputs = program.get>("input"); 30 | REQUIRE(inputs.size() == 2); 31 | REQUIRE(testutility::get_from_list(inputs, 0) == "rocket.mesh"); 32 | REQUIRE(testutility::get_from_list(inputs, 1) == "thrust_profile.csv"); 33 | } 34 | 35 | TEST_CASE("Parse list of arguments with default values" * 36 | test_suite("vector")) { 37 | argparse::ArgumentParser program("test"); 38 | program.add_argument("--input") 39 | .default_value(std::list{1, 2, 3, 4, 5}) 40 | .nargs(5); 41 | 42 | program.parse_args({"test"}); 43 | 44 | auto inputs = program.get>("--input"); 45 | REQUIRE(inputs.size() == 5); 46 | REQUIRE(testutility::get_from_list(inputs, 0) == 1); 47 | REQUIRE(testutility::get_from_list(inputs, 1) == 2); 48 | REQUIRE(testutility::get_from_list(inputs, 2) == 3); 49 | REQUIRE(testutility::get_from_list(inputs, 3) == 4); 50 | REQUIRE(testutility::get_from_list(inputs, 4) == 5); 51 | REQUIRE(program["--input"] == std::list{1, 2, 3, 4, 5}); 52 | } 53 | 54 | TEST_CASE("Parse list of arguments and save in an object" * 55 | test_suite("vector")) { 56 | 57 | struct ConfigManager { 58 | std::vector files; 59 | void add_file(const std::string &file) { files.push_back(file); } 60 | }; 61 | 62 | ConfigManager config_manager; 63 | 64 | argparse::ArgumentParser program("test"); 65 | program.add_argument("--input_files") 66 | .nargs(2) 67 | .action([&](const std::string &value) { 68 | config_manager.add_file(value); 69 | return value; 70 | }); 71 | 72 | program.parse_args({"test", "--input_files", "config.xml", "system.json"}); 73 | 74 | auto file_args = program.get>("--input_files"); 75 | REQUIRE(file_args.size() == 2); 76 | REQUIRE(file_args[0] == "config.xml"); 77 | REQUIRE(file_args[1] == "system.json"); 78 | 79 | REQUIRE(config_manager.files.size() == 2); 80 | REQUIRE(config_manager.files[0] == "config.xml"); 81 | REQUIRE(config_manager.files[1] == "system.json"); 82 | REQUIRE(program["--input_files"] == 83 | std::vector{"config.xml", "system.json"}); 84 | } 85 | -------------------------------------------------------------------------------- /test/test_default_args.cpp: -------------------------------------------------------------------------------- 1 | #ifdef WITH_MODULE 2 | import argparse; 3 | #else 4 | #include 5 | #endif 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | using doctest::test_suite; 13 | 14 | TEST_CASE("Include all default arguments" * test_suite("default_args")) { 15 | argparse::ArgumentParser parser("test"); 16 | auto help_msg{parser.help().str()}; 17 | REQUIRE(help_msg.find("shows help message") != std::string::npos); 18 | REQUIRE(help_msg.find("prints version information") != std::string::npos); 19 | } 20 | 21 | TEST_CASE("Do not include default arguments" * test_suite("default_args")) { 22 | argparse::ArgumentParser parser("test", "1.0", 23 | argparse::default_arguments::none); 24 | parser.parse_args({"test"}); 25 | REQUIRE_THROWS_AS(parser.get("--help"), std::logic_error); 26 | REQUIRE_THROWS_AS(parser.get("--version"), std::logic_error); 27 | } 28 | 29 | TEST_CASE("Do not exit on default arguments" * test_suite("default_args")) { 30 | argparse::ArgumentParser parser("test", "1.0", 31 | argparse::default_arguments::all, false); 32 | std::stringstream buf; 33 | std::streambuf *saved_cout_buf = std::cout.rdbuf(buf.rdbuf()); 34 | parser.parse_args({"test", "--help"}); 35 | std::cout.rdbuf(saved_cout_buf); 36 | REQUIRE(parser.is_used("--help")); 37 | } 38 | -------------------------------------------------------------------------------- /test/test_default_value.cpp: -------------------------------------------------------------------------------- 1 | #ifdef WITH_MODULE 2 | import argparse; 3 | #else 4 | #include 5 | #endif 6 | #include 7 | #include 8 | 9 | using doctest::test_suite; 10 | 11 | TEST_CASE("Use a 'string' default value" * test_suite("default_value")) { 12 | argparse::ArgumentParser program("test"); 13 | 14 | SUBCASE("Use a const char[] default value") { 15 | program.add_argument("--arg").default_value("array of char"); 16 | REQUIRE_NOTHROW(program.parse_args({"test"})); 17 | REQUIRE(program.get("--arg") == std::string("array of char")); 18 | } 19 | 20 | SUBCASE("Use a std::string default value") { 21 | program.add_argument("--arg").default_value(std::string("string object")); 22 | REQUIRE_NOTHROW(program.parse_args({"test"})); 23 | REQUIRE(program.get("--arg") == std::string("string object")); 24 | } 25 | } 26 | 27 | TEST_CASE("Use a default value with flag arguments" * 28 | test_suite("default_value")) { 29 | 30 | argparse::ArgumentParser program("test"); 31 | 32 | program.add_argument("-inc_chr", "--include_chromes") 33 | .help(std::string{"only process the anchor whose one of the end is " 34 | "contained on the specified " 35 | "chromatin, used ',' to split."}) 36 | .default_value("all"); 37 | 38 | program.add_argument("-l").flag(); 39 | program.add_argument("-o").flag(); 40 | 41 | program.add_argument("filename"); 42 | 43 | SUBCASE("Leading optional argument with default_value") { 44 | REQUIRE_NOTHROW(program.parse_args({"test", "-inc_chr", "-lo", "my.log"})); 45 | REQUIRE(program.get("-inc_chr") == std::string{"all"}); 46 | } 47 | 48 | SUBCASE("Trailing optional argument with default_value") { 49 | REQUIRE_NOTHROW(program.parse_args({"test", "-lo", "my.log", "-inc_chr"})); 50 | REQUIRE(program.get("-inc_chr") == std::string{"all"}); 51 | } 52 | } 53 | 54 | TEST_CASE("Position of the argument with default value") { 55 | argparse::ArgumentParser program("test"); 56 | program.add_argument("-g").default_value("the_default_value"); 57 | program.add_argument("-s"); 58 | 59 | SUBCASE("Arg with default value not passed") { 60 | REQUIRE_NOTHROW(program.parse_args({"test", "-s", "./src"})); 61 | REQUIRE(program.get("-g") == std::string("the_default_value")); 62 | REQUIRE(program.get("-s") == std::string("./src")); 63 | } 64 | 65 | SUBCASE("Arg with default value passed last") { 66 | REQUIRE_NOTHROW(program.parse_args({"test", "-s", "./src", "-g"})); 67 | REQUIRE(program.get("-g") == std::string("the_default_value")); 68 | REQUIRE(program.get("-s") == std::string("./src")); 69 | } 70 | 71 | SUBCASE("Arg with default value passed before last") { 72 | REQUIRE_NOTHROW(program.parse_args({"test", "-g", "-s", "./src"})); 73 | REQUIRE(program.get("-g") == std::string("the_default_value")); 74 | REQUIRE(program.get("-s") == std::string("./src")); 75 | } 76 | 77 | SUBCASE("Arg with default value replaces the value if given") { 78 | REQUIRE_NOTHROW( 79 | program.parse_args({"test", "-g", "a_different_value", "-s", "./src"})); 80 | REQUIRE(program.get("-g") == std::string("a_different_value")); 81 | REQUIRE(program.get("-s") == std::string("./src")); 82 | } 83 | } -------------------------------------------------------------------------------- /test/test_equals_form.cpp: -------------------------------------------------------------------------------- 1 | #ifdef WITH_MODULE 2 | import argparse; 3 | #else 4 | #include 5 | #endif 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | using doctest::test_suite; 13 | 14 | TEST_CASE("Basic --value=value" * test_suite("equals_form")) { 15 | argparse::ArgumentParser parser("test"); 16 | parser.add_argument("--long"); 17 | parser.parse_args({"test", "--long=value"}); 18 | std::string result{parser.get("--long")}; 19 | REQUIRE(result == "value"); 20 | } 21 | 22 | TEST_CASE("Fallback = in regular option name" * test_suite("equals_form")) { 23 | argparse::ArgumentParser parser("test"); 24 | parser.add_argument("--long=mislead"); 25 | parser.parse_args({"test", "--long=mislead", "value"}); 26 | std::string result{parser.get("--long=mislead")}; 27 | REQUIRE(result == "value"); 28 | } 29 | 30 | TEST_CASE("Duplicate =-named and standard" * test_suite("equals_form")) { 31 | argparse::ArgumentParser parser("test"); 32 | parser.add_argument("--long=mislead"); 33 | parser.add_argument("--long").default_value(std::string{"NO_VALUE"}); 34 | parser.parse_args({"test", "--long=mislead", "value"}); 35 | std::string result{parser.get("--long=mislead")}; 36 | REQUIRE(result == "value"); 37 | std::string result2{parser.get("--long")}; 38 | REQUIRE(result2 == "NO_VALUE"); 39 | } 40 | 41 | TEST_CASE("Basic --value=value with nargs(2)" * test_suite("equals_form")) { 42 | argparse::ArgumentParser parser("test"); 43 | parser.add_argument("--long").nargs(2); 44 | parser.parse_args({"test", "--long=value1", "value2"}); 45 | REQUIRE((parser.get>("--long") == 46 | std::vector{"value1", "value2"})); 47 | } 48 | -------------------------------------------------------------------------------- /test/test_error_reporting.cpp: -------------------------------------------------------------------------------- 1 | #ifdef WITH_MODULE 2 | import argparse; 3 | #else 4 | #include 5 | #endif 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | using doctest::test_suite; 13 | 14 | TEST_CASE("Missing optional argument name" * test_suite("error_reporting")) { 15 | argparse::ArgumentParser parser("test"); 16 | parser.add_argument("-a"); 17 | parser.add_argument("-b"); 18 | 19 | SUBCASE("Good case") { 20 | REQUIRE_NOTHROW(parser.parse_args({"test", "-a", "1", "-b", "2"})); 21 | } 22 | 23 | SUBCASE("Bad case") { 24 | REQUIRE_THROWS_WITH_AS( 25 | parser.parse_args({"test", "-a", "1", "2"}), 26 | "Zero positional arguments expected, did you mean -b VAR", 27 | std::runtime_error); 28 | } 29 | 30 | SUBCASE("Bad case 2") { 31 | REQUIRE_THROWS_WITH_AS( 32 | parser.parse_args({"test", "1", "2"}), 33 | "Zero positional arguments expected, did you mean -a VAR", 34 | std::runtime_error); 35 | } 36 | } 37 | 38 | TEST_CASE("Missing optional argument name (some flag arguments)" * 39 | test_suite("error_reporting")) { 40 | argparse::ArgumentParser parser("test"); 41 | parser.add_argument("-a").flag(); 42 | parser.add_argument("-b").flag(); 43 | parser.add_argument("-c"); 44 | parser.add_argument("-d"); 45 | 46 | SUBCASE("Good case") { 47 | REQUIRE_NOTHROW(parser.parse_args({"test", "-a", "-b", "-c", "2"})); 48 | } 49 | 50 | SUBCASE("Bad case") { 51 | REQUIRE_THROWS_WITH_AS( 52 | parser.parse_args({"test", "-a", "-b", "2"}), 53 | "Zero positional arguments expected, did you mean -c VAR", 54 | std::runtime_error); 55 | } 56 | 57 | SUBCASE("Bad case 2") { 58 | REQUIRE_THROWS_WITH_AS( 59 | parser.parse_args({"test", "-abc", "1", "2"}), 60 | "Zero positional arguments expected, did you mean -d VAR", 61 | std::runtime_error); 62 | } 63 | } 64 | 65 | TEST_CASE("Missing optional argument name (multiple names)" * 66 | test_suite("error_reporting")) { 67 | argparse::ArgumentParser parser("test"); 68 | parser.add_argument("-a", "--number-of-apples"); 69 | parser.add_argument("-b"); 70 | 71 | SUBCASE("Bad case 2") { 72 | REQUIRE_THROWS_WITH_AS(parser.parse_args({"test", "1", "2"}), 73 | "Zero positional arguments expected, did you mean " 74 | "-a/--number-of-apples VAR", 75 | std::runtime_error); 76 | } 77 | } 78 | 79 | TEST_CASE("Missing optional argument name with other positional arguments" * 80 | test_suite("error_reporting")) { 81 | argparse::ArgumentParser parser("test"); 82 | parser.add_argument("-a"); 83 | parser.add_argument("-b"); 84 | parser.add_argument("c"); 85 | 86 | SUBCASE("Good case") { 87 | REQUIRE_NOTHROW(parser.parse_args({"test", "-a", "1", "-b", "2", "3"})); 88 | } 89 | 90 | SUBCASE("Bad case") { 91 | REQUIRE_THROWS_WITH_AS( 92 | parser.parse_args({"test", "-a", "1", "2", "3", "4"}), 93 | "Maximum number of positional arguments exceeded, failed to parse '3'", 94 | std::runtime_error); 95 | } 96 | } 97 | 98 | TEST_CASE("Detect unknown subcommand" * test_suite("error_reporting")) { 99 | argparse::ArgumentParser program("git"); 100 | argparse::ArgumentParser log_command("log"); 101 | argparse::ArgumentParser notes_command("notes"); 102 | argparse::ArgumentParser add_command("add"); 103 | program.add_subparser(log_command); 104 | program.add_subparser(notes_command); 105 | program.add_subparser(add_command); 106 | 107 | SUBCASE("Typo for 'notes'") { 108 | REQUIRE_THROWS_WITH_AS(program.parse_args({"git", "tote"}), 109 | "Failed to parse 'tote', did you mean 'notes'", 110 | std::runtime_error); 111 | } 112 | 113 | SUBCASE("Typo for 'add'") { 114 | REQUIRE_THROWS_WITH_AS(program.parse_args({"git", "bad"}), 115 | "Failed to parse 'bad', did you mean 'add'", 116 | std::runtime_error); 117 | } 118 | 119 | SUBCASE("Typo for 'log'") { 120 | REQUIRE_THROWS_WITH_AS(program.parse_args({"git", "logic"}), 121 | "Failed to parse 'logic', did you mean 'log'", 122 | std::runtime_error); 123 | } 124 | } -------------------------------------------------------------------------------- /test/test_get.cpp: -------------------------------------------------------------------------------- 1 | #ifdef WITH_MODULE 2 | import argparse; 3 | #else 4 | #include 5 | #endif 6 | #include 7 | 8 | #include 9 | 10 | using doctest::test_suite; 11 | 12 | TEST_CASE("Getting a simple argument" * test_suite("ArgumentParser::get")) { 13 | argparse::ArgumentParser program("test"); 14 | program.add_argument("-s", "--stuff"); 15 | REQUIRE_NOTHROW(program.parse_args({"test", "-s", "./src"})); 16 | REQUIRE(program.get("--stuff") == "./src"); 17 | } 18 | 19 | TEST_CASE("Skipped call to parse_args" * test_suite("ArgumentParser::get")) { 20 | argparse::ArgumentParser program("test"); 21 | program.add_argument("stuff"); 22 | REQUIRE_THROWS_WITH_AS(program.get("stuff"), 23 | "Nothing parsed, no arguments are available.", 24 | std::logic_error); 25 | } 26 | 27 | TEST_CASE("Missing argument" * test_suite("ArgumentParser::get")) { 28 | argparse::ArgumentParser program("test"); 29 | program.add_argument("-s", "--stuff"); 30 | REQUIRE_NOTHROW(program.parse_args({"test"})); 31 | REQUIRE_THROWS_WITH_AS(program.get("--stuff"), 32 | "No value provided for '--stuff'.", std::logic_error); 33 | } 34 | 35 | TEST_CASE("Implicit argument" * test_suite("ArgumentParser::get")) { 36 | argparse::ArgumentParser program("test"); 37 | program.add_argument("-s", "--stuff").nargs(1); 38 | REQUIRE_NOTHROW(program.parse_args({"test"})); 39 | REQUIRE_THROWS_WITH_AS(program.get("--stuff"), 40 | "No value provided for '--stuff'.", std::logic_error); 41 | } 42 | 43 | TEST_CASE("Mismatched type for argument" * test_suite("ArgumentParser::get")) { 44 | argparse::ArgumentParser program("test"); 45 | program.add_argument("-s", "--stuff"); // as default type, a std::string 46 | REQUIRE_NOTHROW(program.parse_args({"test", "-s", "321"})); 47 | REQUIRE_THROWS_AS(program.get("--stuff"), std::bad_any_cast); 48 | } 49 | -------------------------------------------------------------------------------- /test/test_help.cpp: -------------------------------------------------------------------------------- 1 | #ifdef WITH_MODULE 2 | import argparse; 3 | #else 4 | #include 5 | #endif 6 | #include 7 | 8 | #include 9 | #include 10 | 11 | using doctest::test_suite; 12 | 13 | TEST_CASE("Users can format help message" * test_suite("help")) { 14 | argparse::ArgumentParser program("test"); 15 | 16 | SUBCASE("Simple arguments") { 17 | program.add_argument("input").help("positional input"); 18 | program.add_argument("-c").help("optional input"); 19 | } 20 | SUBCASE("Default values") { 21 | program.add_argument("-a").default_value(42); 22 | program.add_argument("-b").default_value(4.4e-7); 23 | program.add_argument("-c") 24 | .default_value(std::vector{1, 2, 3, 4, 5}) 25 | .nargs(5); 26 | program.add_argument("-d").default_value("I am a string"); 27 | program.add_argument("-e").default_value(std::optional{}); 28 | program.add_argument("-f").default_value(false); 29 | } 30 | std::ostringstream s; 31 | s << program; 32 | REQUIRE_FALSE(s.str().empty()); 33 | 34 | auto msg = program.help().str(); 35 | REQUIRE(msg == s.str()); 36 | } 37 | 38 | TEST_CASE("Users can override the help options" * test_suite("help")) { 39 | GIVEN("a program that meant to take -h as a normal option") { 40 | argparse::ArgumentParser program("test"); 41 | program.add_argument("input"); 42 | program.add_argument("-h").implicit_value('h').default_value('x'); 43 | 44 | WHEN("provided -h without fulfilling other requirements") { 45 | THEN("validation fails") { 46 | REQUIRE_THROWS_AS(program.parse_args({"test", "-h"}), 47 | std::runtime_error); 48 | } 49 | } 50 | 51 | WHEN("provided arguments to all parameters") { 52 | program.parse_args({"test", "-h", "some input"}); 53 | 54 | THEN("these parameters get their values") { 55 | REQUIRE(program["-h"] == 'h'); 56 | REQUIRE(program.get("input") == "some input"); 57 | } 58 | } 59 | } 60 | } 61 | 62 | TEST_CASE("Users can disable default -h/--help" * test_suite("help")) { 63 | argparse::ArgumentParser program("test", "1.0", 64 | argparse::default_arguments::version); 65 | REQUIRE_THROWS_AS(program.parse_args({"test", "-h"}), std::runtime_error); 66 | } 67 | 68 | TEST_CASE("Users can replace default -h/--help" * test_suite("help")) { 69 | argparse::ArgumentParser program("test", "1.0", 70 | argparse::default_arguments::version); 71 | std::stringstream buffer; 72 | program.add_argument("-h", "--help") 73 | .action([&](const auto &) { buffer << program; }) 74 | .default_value(false) 75 | .implicit_value(true) 76 | .nargs(0); 77 | 78 | REQUIRE(buffer.str().empty()); 79 | program.parse_args({"test", "--help"}); 80 | REQUIRE_FALSE(buffer.str().empty()); 81 | } 82 | 83 | TEST_CASE("Multiline help message alignment") { 84 | // '#' is used at the beginning of each help message line to simplify testing. 85 | // It is important to ensure that this character doesn't appear elsewhere in 86 | // the test case. Default arguments (e.g., -h/--help, -v/--version) are not 87 | // included in this test. 88 | argparse::ArgumentParser program("program"); 89 | program.add_argument("INPUT1").help( 90 | "#This is the first line of help message.\n" 91 | "#And this is the second line of help message."); 92 | program.add_argument("program_input2").help("#There is only one line."); 93 | program.add_argument("-p", "--prog_input3") 94 | .help( 95 | R"(#Lorem ipsum dolor sit amet, consectetur adipiscing elit. 96 | #Sed ut perspiciatis unde omnis iste natus error sit voluptatem 97 | #accusantium doloremque laudantium, totam rem aperiam...)"); 98 | program.add_argument("--verbose").flag(); 99 | 100 | std::ostringstream stream; 101 | stream << program; 102 | std::istringstream iss(stream.str()); 103 | 104 | auto help_message_start = std::string::npos; 105 | std::string line; 106 | while (std::getline(iss, line)) { 107 | // Find the position of '#', which indicates the start of the help message 108 | // line 109 | auto pos = line.find('#'); 110 | 111 | if (pos == std::string::npos) { 112 | continue; 113 | } 114 | 115 | if (help_message_start == std::string::npos) { 116 | help_message_start = pos; 117 | } else { 118 | REQUIRE(pos == help_message_start); 119 | } 120 | } 121 | 122 | // Make sure we have at least one help message 123 | REQUIRE(help_message_start != -1); 124 | } 125 | 126 | TEST_CASE("Exclusive arguments, only") { 127 | argparse::ArgumentParser program("program"); 128 | auto &group = program.add_mutually_exclusive_group(); 129 | group.add_argument("-a").flag(); 130 | group.add_argument("-b").flag(); 131 | REQUIRE(program.usage() == "Usage: program [--help] [--version] [[-a]|[-b]]"); 132 | } 133 | 134 | TEST_CASE("Exclusive arguments, several groups") { 135 | argparse::ArgumentParser program("program"); 136 | auto &group = program.add_mutually_exclusive_group(); 137 | group.add_argument("-a").flag(); 138 | group.add_argument("-b").flag(); 139 | auto &group2 = program.add_mutually_exclusive_group(); 140 | group2.add_argument("-c").flag(); 141 | group2.add_argument("-d").flag(); 142 | REQUIRE(program.usage() == "Usage: program [--help] [--version] [[-a]|[-b]] [[-c]|[-d]]"); 143 | } 144 | 145 | TEST_CASE("Exclusive arguments, several groups, in between arg") { 146 | argparse::ArgumentParser program("program"); 147 | auto &group = program.add_mutually_exclusive_group(); 148 | group.add_argument("-a").flag(); 149 | group.add_argument("-b").flag(); 150 | program.add_argument("-X").flag(); 151 | auto &group2 = program.add_mutually_exclusive_group(); 152 | group2.add_argument("-c").flag(); 153 | group2.add_argument("-d").flag(); 154 | REQUIRE(program.usage() == "Usage: program [--help] [--version] [[-a]|[-b]] [-X] [[-c]|[-d]]"); 155 | } 156 | 157 | TEST_CASE("Argument repeatable") { 158 | argparse::ArgumentParser program("program"); 159 | program.add_argument("-a").flag().append(); 160 | REQUIRE(program.usage() == "Usage: program [--help] [--version] [-a]..."); 161 | 162 | std::ostringstream s; 163 | s << program; 164 | // std::cout << "DEBUG:" << s.str() << std::endl; 165 | REQUIRE(s.str().find(" -a [may be repeated]") != std::string::npos); 166 | } 167 | 168 | TEST_CASE("Argument with nargs(2) and metavar ") { 169 | argparse::ArgumentParser program("program"); 170 | program.add_argument("-foo").metavar(" ").nargs(2); 171 | REQUIRE(program.usage() == "Usage: program [--help] [--version] [-foo ]"); 172 | } 173 | 174 | TEST_CASE("add_group help") { 175 | argparse::ArgumentParser program("program"); 176 | program.add_argument("-a").flag().help("help_a"); 177 | program.add_group("Advanced options"); 178 | program.add_argument("-b").flag().help("help_b"); 179 | REQUIRE(program.usage() == "Usage: program [--help] [--version] [-a] [-b]"); 180 | 181 | std::ostringstream s; 182 | s << program; 183 | // std::cout << "DEBUG:" << s.str() << std::endl; 184 | REQUIRE(s.str().find( 185 | " -a help_a \n" 186 | "\n" 187 | "Advanced options (detailed usage):\n" 188 | " -b help_b") != std::string::npos); 189 | } 190 | 191 | TEST_CASE("multiline usage, several groups") { 192 | argparse::ArgumentParser program("program"); 193 | program.set_usage_max_line_width(80); 194 | program.add_argument("-a").flag().help("help_a"); 195 | program.add_group("Advanced options"); 196 | program.add_argument("-b").flag().help("help_b"); 197 | // std::cout << "DEBUG:" << program.usage() << std::endl; 198 | REQUIRE(program.usage() == 199 | "Usage: program [--help] [--version] [-a]\n" 200 | "\n" 201 | "Advanced options:\n" 202 | " [-b]"); 203 | } 204 | 205 | TEST_CASE("multiline usage, no break on mutex") { 206 | argparse::ArgumentParser program("program"); 207 | program.set_usage_max_line_width(80); 208 | program.set_usage_break_on_mutex(); 209 | program.add_argument("--quite-long-option-name").flag(); 210 | auto &group = program.add_mutually_exclusive_group(); 211 | group.add_argument("-a").flag(); 212 | group.add_argument("-b").flag(); 213 | program.add_argument("-c").flag(); 214 | program.add_argument("--another-one").flag(); 215 | program.add_argument("-d").flag(); 216 | program.add_argument("--yet-another-long-one").flag(); 217 | program.add_argument("--will-go-on-new-line").flag(); 218 | // std::cout << "DEBUG:" << program.usage() << std::endl; 219 | REQUIRE(program.usage() == 220 | "Usage: program [--help] [--version] [--quite-long-option-name]\n" 221 | " [[-a]|[-b]]\n" 222 | " [-c] [--another-one] [-d] [--yet-another-long-one]\n" 223 | " [--will-go-on-new-line]"); 224 | } 225 | 226 | TEST_CASE("multiline usage, break on mutex") { 227 | argparse::ArgumentParser program("program"); 228 | program.set_usage_max_line_width(80); 229 | program.add_argument("--quite-long-option-name").flag(); 230 | auto &group = program.add_mutually_exclusive_group(); 231 | group.add_argument("-a").flag(); 232 | group.add_argument("-b").flag(); 233 | program.add_argument("-c").flag(); 234 | program.add_argument("--another-one").flag(); 235 | program.add_argument("-d").flag(); 236 | program.add_argument("--yet-another-long-one").flag(); 237 | program.add_argument("--will-go-on-new-line").flag(); 238 | program.add_usage_newline(); 239 | program.add_argument("--on-a-dedicated-line").flag(); 240 | // std::cout << "DEBUG:" << program.usage() << std::endl; 241 | REQUIRE(program.usage() == 242 | "Usage: program [--help] [--version] [--quite-long-option-name] [[-a]|[-b]] [-c]\n" 243 | " [--another-one] [-d] [--yet-another-long-one]\n" 244 | " [--will-go-on-new-line]\n" 245 | " [--on-a-dedicated-line]"); 246 | } 247 | 248 | TEST_CASE("multiline usage, single arg that is larger than the max width") { 249 | argparse::ArgumentParser program("program"); 250 | program.set_usage_max_line_width(80); 251 | program.add_argument("--lots-of-choices").metavar(""); 252 | // std::cout << "DEBUG:" << program.usage() << std::endl; 253 | REQUIRE(program.usage() == 254 | "Usage: program [--help] [--version]\n" 255 | " [--lots-of-choices ]"); 256 | } 257 | -------------------------------------------------------------------------------- /test/test_hidden_alias.cpp: -------------------------------------------------------------------------------- 1 | #ifdef WITH_MODULE 2 | import argparse; 3 | #else 4 | #include 5 | #endif 6 | #include 7 | 8 | using doctest::test_suite; 9 | 10 | TEST_CASE("Test setting a hidden alias for an argument" * 11 | test_suite("hidden_alias")) { 12 | argparse::ArgumentParser program("test"); 13 | auto &arg = program.add_argument("--suppress").flag(); 14 | program.add_hidden_alias_for(arg, "--supress"); // old misspelled alias 15 | 16 | program.parse_args({"./test.exe", "--supress"}); 17 | REQUIRE(program.get("--suppress") == true); 18 | } 19 | -------------------------------------------------------------------------------- /test/test_hidden_argument.cpp: -------------------------------------------------------------------------------- 1 | #ifdef WITH_MODULE 2 | import argparse; 3 | #else 4 | #include 5 | #endif 6 | #include 7 | 8 | using doctest::test_suite; 9 | 10 | TEST_CASE("Test setting a hidden argument" * test_suite("hidden_argument")) { 11 | argparse::ArgumentParser program("program"); 12 | program.add_argument("--hidden").flag().hidden(); 13 | program.add_argument("--regular").flag(); 14 | program.add_argument("regular_positional"); 15 | // only makes sense if last and optional... 16 | program.add_argument("hidden_positional").nargs(0, 1).hidden(); 17 | 18 | program.parse_args({"./test.exe", "--hidden", "--regular", 19 | "regular_positional_val", "hidden_positional_val"}); 20 | REQUIRE(program.get("--hidden") == true); 21 | REQUIRE(program.get("--regular") == true); 22 | REQUIRE(program.get("regular_positional") == 23 | "regular_positional_val"); 24 | REQUIRE(program.get("hidden_positional") == 25 | "hidden_positional_val"); 26 | 27 | REQUIRE(program.usage() == 28 | "Usage: program [--help] [--version] [--regular] regular_positional"); 29 | 30 | std::ostringstream s; 31 | s << program; 32 | // std::cout << "DEBUG:" << s.str() << std::endl; 33 | REQUIRE(s.str().find("hidden") == std::string::npos); 34 | REQUIRE(s.str().find("--regular") != std::string::npos); 35 | REQUIRE(s.str().find("regular_positional") != std::string::npos); 36 | } 37 | -------------------------------------------------------------------------------- /test/test_invalid_arguments.cpp: -------------------------------------------------------------------------------- 1 | #ifdef WITH_MODULE 2 | import argparse; 3 | #else 4 | #include 5 | #endif 6 | #include 7 | 8 | using doctest::test_suite; 9 | 10 | TEST_CASE("Parse unknown optional argument" * 11 | test_suite("compound_arguments")) { 12 | 13 | argparse::ArgumentParser bfm("bfm"); 14 | 15 | bfm.add_argument("-l", "--load").help("load a VMM into the kernel"); 16 | 17 | bfm.add_argument("-x", "--start") 18 | .default_value(false) 19 | .implicit_value(true) 20 | .help("start a previously loaded VMM"); 21 | 22 | bfm.add_argument("-d", "--dump") 23 | .default_value(false) 24 | .implicit_value(true) 25 | .help("output the contents of the VMM's debug buffer"); 26 | 27 | bfm.add_argument("-s", "--stop") 28 | .default_value(false) 29 | .implicit_value(true) 30 | .help("stop a previously started VMM"); 31 | 32 | bfm.add_argument("-u", "--unload") 33 | .default_value(false) 34 | .implicit_value(true) 35 | .help("unload a previously loaded VMM"); 36 | 37 | bfm.add_argument("-m", "--mem") 38 | .default_value(64ULL) 39 | .scan<'u', unsigned long long>() 40 | .help("memory in MB to give the VMM when loading"); 41 | 42 | REQUIRE_THROWS_WITH_AS(bfm.parse_args({"./test.exe", "-om"}), 43 | "Unknown argument: -om", std::runtime_error); 44 | } 45 | -------------------------------------------------------------------------------- /test/test_is_used.cpp: -------------------------------------------------------------------------------- 1 | #ifdef WITH_MODULE 2 | import argparse; 3 | #else 4 | #include 5 | #endif 6 | #include 7 | 8 | using doctest::test_suite; 9 | 10 | TEST_CASE("User-supplied argument" * test_suite("is_used")) { 11 | argparse::ArgumentParser program("test"); 12 | program.add_argument("--dir").default_value(std::string("/")); 13 | program.parse_args({"test", "--dir", "/home/user"}); 14 | REQUIRE(program.get("--dir") == "/home/user"); 15 | REQUIRE(program.is_used("--dir") == true); 16 | } 17 | 18 | TEST_CASE("Not user-supplied argument" * test_suite("is_used")) { 19 | argparse::ArgumentParser program("test"); 20 | program.add_argument("--dir").default_value(std::string("/")); 21 | program.parse_args({"test"}); 22 | REQUIRE(program.get("--dir") == "/"); 23 | REQUIRE(program.is_used("--dir") == false); 24 | } 25 | -------------------------------------------------------------------------------- /test/test_issue_37.cpp: -------------------------------------------------------------------------------- 1 | #ifdef WITH_MODULE 2 | import argparse; 3 | #else 4 | #include 5 | #endif 6 | #include 7 | 8 | using doctest::test_suite; 9 | 10 | TEST_CASE("Issues with implicit values #37" * test_suite("implicit_values")) { 11 | argparse::ArgumentParser m_bfm("test"); 12 | m_bfm.add_argument("-l", "--load").help("load a VMM into the kernel"); 13 | 14 | m_bfm.add_argument("-u", "--unload") 15 | .default_value(false) 16 | .implicit_value(true) 17 | .help("unload a previously loaded VMM"); 18 | 19 | m_bfm.add_argument("-x", "--start") 20 | .default_value(false) 21 | .implicit_value(true) 22 | .help("start a previously loaded VMM"); 23 | 24 | m_bfm.add_argument("-s", "--stop") 25 | .default_value(false) 26 | .implicit_value(true) 27 | .help("stop a previously started VMM"); 28 | 29 | m_bfm.add_argument("-d", "--dump") 30 | .default_value(false) 31 | .implicit_value(true) 32 | .help("output the contents of the VMM's debug buffer"); 33 | 34 | m_bfm.add_argument("-m", "--mem") 35 | .default_value(100) 36 | .required() 37 | .scan<'u', unsigned long long>() 38 | .help("memory in MB to give the VMM when loading"); 39 | m_bfm.parse_args({"test", "-l", "blah", "-d", "-u"}); 40 | 41 | REQUIRE(m_bfm.get("--load") == "blah"); 42 | REQUIRE(m_bfm.get("-l") == "blah"); 43 | REQUIRE(m_bfm.get("-u") == true); 44 | REQUIRE(m_bfm.get("-d") == true); 45 | REQUIRE(m_bfm.get("-s") == false); 46 | REQUIRE(m_bfm.get("--unload") == true); 47 | } 48 | -------------------------------------------------------------------------------- /test/test_mutually_exclusive_group.cpp: -------------------------------------------------------------------------------- 1 | #ifdef WITH_MODULE 2 | import argparse; 3 | #else 4 | #include 5 | #endif 6 | #include 7 | 8 | using doctest::test_suite; 9 | 10 | TEST_CASE("Create mutually exclusive group with 2 arguments" * 11 | test_suite("mutex_args")) { 12 | argparse::ArgumentParser program("test"); 13 | 14 | auto &group = program.add_mutually_exclusive_group(); 15 | group.add_argument("--first"); 16 | group.add_argument("--second"); 17 | 18 | REQUIRE_THROWS_WITH_AS( 19 | program.parse_args({"test", "--first", "1", "--second", "2"}), 20 | "Argument '--second VAR' not allowed with '--first VAR'", 21 | std::runtime_error); 22 | } 23 | 24 | TEST_CASE( 25 | "Create mutually exclusive group with 2 arguments with required flag" * 26 | test_suite("mutex_args")) { 27 | argparse::ArgumentParser program("test"); 28 | 29 | auto &group = program.add_mutually_exclusive_group(true); 30 | group.add_argument("--first"); 31 | group.add_argument("--second"); 32 | 33 | REQUIRE_THROWS_WITH_AS( 34 | program.parse_args({"test"}), 35 | "One of the arguments '--first VAR' or '--second VAR' is required", 36 | std::runtime_error); 37 | } 38 | 39 | TEST_CASE( 40 | "Create mutually exclusive group with 3 arguments with required flag" * 41 | test_suite("mutex_args")) { 42 | argparse::ArgumentParser program("test"); 43 | 44 | auto &group = program.add_mutually_exclusive_group(true); 45 | group.add_argument("--first"); 46 | group.add_argument("--second"); 47 | group.add_argument("--third"); 48 | 49 | REQUIRE_THROWS_WITH_AS(program.parse_args({"test"}), 50 | "One of the arguments '--first VAR' or '--second VAR' " 51 | "or '--third VAR' is required", 52 | std::runtime_error); 53 | } 54 | 55 | TEST_CASE("Create mutually exclusive group with 3 arguments" * 56 | test_suite("mutex_args")) { 57 | argparse::ArgumentParser program("test"); 58 | 59 | auto &group = program.add_mutually_exclusive_group(); 60 | group.add_argument("--first"); 61 | group.add_argument("--second"); 62 | group.add_argument("--third"); 63 | 64 | REQUIRE_THROWS_WITH_AS( 65 | program.parse_args({"test", "--first", "1", "--third", "2"}), 66 | "Argument '--third VAR' not allowed with '--first VAR'", 67 | std::runtime_error); 68 | } 69 | 70 | TEST_CASE("Create two mutually exclusive groups" * test_suite("mutex_args")) { 71 | argparse::ArgumentParser program("test"); 72 | 73 | auto &group_1 = program.add_mutually_exclusive_group(); 74 | group_1.add_argument("--first"); 75 | group_1.add_argument("--second"); 76 | group_1.add_argument("--third"); 77 | 78 | auto &group_2 = program.add_mutually_exclusive_group(); 79 | group_2.add_argument("-a"); 80 | group_2.add_argument("-b"); 81 | 82 | REQUIRE_THROWS_WITH_AS( 83 | program.parse_args({"test", "--first", "1", "-a", "2", "-b", "3"}), 84 | "Argument '-b VAR' not allowed with '-a VAR'", std::runtime_error); 85 | } -------------------------------------------------------------------------------- /test/test_negative_numbers.cpp: -------------------------------------------------------------------------------- 1 | #ifdef WITH_MODULE 2 | import argparse; 3 | #else 4 | #include 5 | #endif 6 | #include 7 | 8 | using doctest::test_suite; 9 | 10 | TEST_CASE("Parse negative integer" * test_suite("positional_arguments")) { 11 | argparse::ArgumentParser program; 12 | program.add_argument("--verbose", "-v") 13 | .help("enable verbose logging") 14 | .default_value(false) 15 | .implicit_value(true); 16 | 17 | program.add_argument("number").help("Input number").scan<'i', int>(); 18 | 19 | program.parse_args({"./main", "-1"}); 20 | REQUIRE(program.get("number") == -1); 21 | } 22 | 23 | TEST_CASE("Parse negative integers into a vector" * 24 | test_suite("positional_arguments")) { 25 | argparse::ArgumentParser program; 26 | program.add_argument("--verbose", "-v") 27 | .help("enable verbose logging") 28 | .default_value(false) 29 | .implicit_value(true); 30 | 31 | program.add_argument("number").help("Input number").nargs(3).scan<'i', int>(); 32 | 33 | program.parse_args({"./main", "-1", "-2", "3"}); 34 | REQUIRE(program["number"] == std::vector{-1, -2, 3}); 35 | } 36 | 37 | TEST_CASE("Parse negative float" * test_suite("positional_arguments")) { 38 | argparse::ArgumentParser program; 39 | program.add_argument("--verbose", "-v") 40 | .help("enable verbose logging") 41 | .default_value(false) 42 | .implicit_value(true); 43 | 44 | program.add_argument("number").help("Input number").scan<'g', float>(); 45 | 46 | program.parse_args({"./main", "-1.0"}); 47 | REQUIRE(program.get("number") == -1.0); 48 | } 49 | 50 | TEST_CASE("Parse negative floats into a vector" * 51 | test_suite("positional_arguments")) { 52 | argparse::ArgumentParser program; 53 | program.add_argument("--verbose", "-v") 54 | .help("enable verbose logging") 55 | .default_value(false) 56 | .implicit_value(true); 57 | 58 | program.add_argument("number") 59 | .help("Input number") 60 | .nargs(3) 61 | .scan<'g', double>(); 62 | 63 | program.parse_args({"./main", "-1.001", "-2.002", "3.003"}); 64 | REQUIRE(program["number"] == std::vector{-1.001, -2.002, 3.003}); 65 | } 66 | 67 | TEST_CASE("Parse numbers in E notation" * test_suite("positional_arguments")) { 68 | argparse::ArgumentParser program; 69 | program.add_argument("--verbose", "-v") 70 | .help("enable verbose logging") 71 | .default_value(false) 72 | .implicit_value(true); 73 | 74 | program.add_argument("number").help("Input number").scan<'g', double>(); 75 | 76 | program.parse_args({"./main", "-1.2e3"}); 77 | REQUIRE(program.get("number") == -1200.0); 78 | } 79 | 80 | TEST_CASE("Parse numbers in E notation (capital E)" * 81 | test_suite("positional_arguments")) { 82 | argparse::ArgumentParser program; 83 | program.add_argument("--verbose", "-v") 84 | .help("enable verbose logging") 85 | .default_value(false) 86 | .implicit_value(true); 87 | 88 | program.add_argument("number").help("Input number").scan<'g', double>(); 89 | 90 | program.parse_args({"./main", "-1.32E4"}); 91 | REQUIRE(program.get("number") == -13200.0); 92 | } 93 | 94 | TEST_CASE("Recognize negative decimal numbers" * 95 | test_suite("positional_arguments")) { 96 | argparse::ArgumentParser program("test"); 97 | program.add_argument("positional"); 98 | 99 | SUBCASE("zero") { REQUIRE_NOTHROW(program.parse_args({"test", "-0"})); } 100 | SUBCASE("not a decimal") { 101 | REQUIRE_THROWS_AS(program.parse_args({"test", "-00"}), std::runtime_error); 102 | } 103 | SUBCASE("looks like an octal") { 104 | REQUIRE_THROWS_AS(program.parse_args({"test", "-003"}), std::runtime_error); 105 | } 106 | SUBCASE("nonzero-digit") { 107 | REQUIRE_NOTHROW(program.parse_args({"test", "-9"})); 108 | } 109 | SUBCASE("nonzero-digit digit-sequence") { 110 | REQUIRE_NOTHROW(program.parse_args({"test", "-92180"})); 111 | } 112 | SUBCASE("zero dot") { REQUIRE_NOTHROW(program.parse_args({"test", "-0."})); } 113 | SUBCASE("nonzero-digit dot") { 114 | REQUIRE_NOTHROW(program.parse_args({"test", "-8."})); 115 | } 116 | SUBCASE("nonzero-digit digit-sequence dot") { 117 | REQUIRE_NOTHROW(program.parse_args({"test", "-200."})); 118 | } 119 | SUBCASE("integer-part dot") { 120 | REQUIRE_NOTHROW(program.parse_args({"test", "-003."})); 121 | } 122 | SUBCASE("dot digit-sequence") { 123 | REQUIRE_NOTHROW(program.parse_args({"test", "-.0927"})); 124 | } 125 | SUBCASE("not a single dot") { 126 | REQUIRE_THROWS_AS(program.parse_args({"test", "-."}), std::runtime_error); 127 | } 128 | SUBCASE("not a single e") { 129 | REQUIRE_THROWS_AS(program.parse_args({"test", "-e"}), std::runtime_error); 130 | } 131 | SUBCASE("not dot e") { 132 | REQUIRE_THROWS_AS(program.parse_args({"test", "-.e"}), std::runtime_error); 133 | } 134 | SUBCASE("integer-part exponent-part without sign") { 135 | REQUIRE_NOTHROW(program.parse_args({"test", "-1e32"})); 136 | } 137 | SUBCASE("integer-part exponent-part with positive sign") { 138 | REQUIRE_NOTHROW(program.parse_args({"test", "-1e+32"})); 139 | } 140 | SUBCASE("integer-part exponent-part with negative sign") { 141 | REQUIRE_NOTHROW(program.parse_args({"test", "-00e-0"})); 142 | } 143 | SUBCASE("missing mantissa") { 144 | REQUIRE_THROWS_AS(program.parse_args({"test", "-e32"}), std::runtime_error); 145 | } 146 | SUBCASE("missing mantissa but with positive sign") { 147 | REQUIRE_THROWS_AS(program.parse_args({"test", "-e+7"}), std::runtime_error); 148 | } 149 | SUBCASE("missing mantissa but with negative sign") { 150 | REQUIRE_THROWS_AS(program.parse_args({"test", "-e-1"}), std::runtime_error); 151 | } 152 | SUBCASE("nothing after e followed by zero") { 153 | REQUIRE_THROWS_AS(program.parse_args({"test", "-0e"}), std::runtime_error); 154 | } 155 | SUBCASE("nothing after e followed by integer-part") { 156 | REQUIRE_THROWS_AS(program.parse_args({"test", "-13e"}), std::runtime_error); 157 | } 158 | SUBCASE("integer-part dot exponent-part without sign") { 159 | REQUIRE_NOTHROW(program.parse_args({"test", "-18.e0"})); 160 | } 161 | SUBCASE("integer-part dot exponent-part with positive sign") { 162 | REQUIRE_NOTHROW(program.parse_args({"test", "-18.e+92"})); 163 | } 164 | SUBCASE("integer-part dot exponent-part with negative sign") { 165 | REQUIRE_NOTHROW(program.parse_args({"test", "-0.e-92"})); 166 | } 167 | SUBCASE("nothing after e followed by integer-part dot") { 168 | REQUIRE_THROWS_AS(program.parse_args({"test", "-13.e"}), 169 | std::runtime_error); 170 | } 171 | SUBCASE("dot digit-sequence exponent-part without sign") { 172 | REQUIRE_NOTHROW(program.parse_args({"test", "-.023e0"})); 173 | } 174 | SUBCASE("dot digit-sequence exponent-part with positive sign") { 175 | REQUIRE_NOTHROW(program.parse_args({"test", "-.2e+92"})); 176 | } 177 | SUBCASE("dot digit-sequence exponent-part with negative sign") { 178 | REQUIRE_NOTHROW(program.parse_args({"test", "-.71564e-92"})); 179 | } 180 | SUBCASE("nothing after e in fractional-part") { 181 | REQUIRE_THROWS_AS(program.parse_args({"test", "-.283e"}), 182 | std::runtime_error); 183 | } 184 | SUBCASE("exponent-part followed by only a dot") { 185 | REQUIRE_THROWS_AS(program.parse_args({"test", "-.e3"}), std::runtime_error); 186 | } 187 | SUBCASE("exponent-part followed by only a dot but with positive sign") { 188 | REQUIRE_THROWS_AS(program.parse_args({"test", "-.e+3"}), 189 | std::runtime_error); 190 | } 191 | SUBCASE("exponent-part followed by only a dot but with negative sign") { 192 | REQUIRE_THROWS_AS(program.parse_args({"test", "-.e-3"}), 193 | std::runtime_error); 194 | } 195 | SUBCASE("integer-part dot digit-sequence exponent-part without sign") { 196 | REQUIRE_NOTHROW(program.parse_args({"test", "-02.023e4000"})); 197 | } 198 | SUBCASE("integer-part dot digit-sequence exponent-part with positive sign") { 199 | REQUIRE_NOTHROW(program.parse_args({"test", "-3.239e+76"})); 200 | } 201 | SUBCASE("integer-part dot digit-sequence exponent-part with negative sign") { 202 | REQUIRE_NOTHROW(program.parse_args({"test", "-238237.0e-2"})); 203 | } 204 | SUBCASE("nothing after e") { 205 | REQUIRE_THROWS_AS(program.parse_args({"test", "-3.14e"}), 206 | std::runtime_error); 207 | } 208 | SUBCASE("nothing after e and positive sign") { 209 | REQUIRE_THROWS_AS(program.parse_args({"test", "-2.17e+"}), 210 | std::runtime_error); 211 | } 212 | SUBCASE("nothing after e and negative sign") { 213 | REQUIRE_THROWS_AS(program.parse_args({"test", "-13.6e-"}), 214 | std::runtime_error); 215 | } 216 | SUBCASE("more than one sign present in exponent-part") { 217 | REQUIRE_THROWS_AS(program.parse_args({"test", "-13.6e+-23"}), 218 | std::runtime_error); 219 | } 220 | SUBCASE("sign at wrong position") { 221 | REQUIRE_THROWS_AS(program.parse_args({"test", "-3.6e23+"}), 222 | std::runtime_error); 223 | } 224 | SUBCASE("more than one exponent-part") { 225 | REQUIRE_THROWS_AS(program.parse_args({"test", "-3.6e2e9"}), 226 | std::runtime_error); 227 | } 228 | SUBCASE("more than one fractional-part") { 229 | REQUIRE_THROWS_AS(program.parse_args({"test", "-3.6.3"}), 230 | std::runtime_error); 231 | } 232 | SUBCASE("number has its own sign") { 233 | REQUIRE_THROWS_AS(program.parse_args({"test", "-+42"}), std::runtime_error); 234 | } 235 | SUBCASE("looks like hexadecimal integer") { 236 | REQUIRE_THROWS_AS(program.parse_args({"test", "-0x0"}), std::runtime_error); 237 | } 238 | SUBCASE("looks like hexadecimal floating-point") { 239 | REQUIRE_THROWS_AS(program.parse_args({"test", "-0x27.8p1"}), 240 | std::runtime_error); 241 | } 242 | SUBCASE("looks like hexadecimal floating-point without prefix") { 243 | REQUIRE_THROWS_AS(program.parse_args({"test", "-3.8p1"}), 244 | std::runtime_error); 245 | } 246 | SUBCASE("Richard's pp-number") { 247 | REQUIRE_THROWS_AS(program.parse_args({"test", "-0x1e+2"}), 248 | std::runtime_error); 249 | } 250 | SUBCASE("Infinity") { 251 | REQUIRE_THROWS_AS(program.parse_args({"test", "-inf"}), std::runtime_error); 252 | REQUIRE_THROWS_AS(program.parse_args({"test", "-INFINITY"}), 253 | std::runtime_error); 254 | } 255 | SUBCASE("NaN") { 256 | REQUIRE_THROWS_AS(program.parse_args({"test", "-nan"}), std::runtime_error); 257 | REQUIRE_THROWS_AS(program.parse_args({"test", "-NAN"}), std::runtime_error); 258 | } 259 | } 260 | -------------------------------------------------------------------------------- /test/test_optional_arguments.cpp: -------------------------------------------------------------------------------- 1 | #ifdef WITH_MODULE 2 | import argparse; 3 | #else 4 | #include 5 | #endif 6 | #include 7 | 8 | using doctest::test_suite; 9 | 10 | TEST_CASE("Parse toggle arguments with default value" * 11 | test_suite("optional_arguments")) { 12 | argparse::ArgumentParser program("test"); 13 | program.add_argument("--verbose", "-v") 14 | .default_value(false) 15 | .implicit_value(true); 16 | 17 | program.parse_args({"./test.exe"}); 18 | REQUIRE(program.get("--verbose") == false); 19 | REQUIRE(program["--verbose"] == false); 20 | } 21 | 22 | TEST_CASE("Argument '-' is not an optional argument" * 23 | test_suite("optional_arguments")) { 24 | argparse::ArgumentParser program("test"); 25 | program.add_argument("input"); 26 | program.parse_args({"./test.exe", "-"}); 27 | REQUIRE(program.get("input") == "-"); 28 | } 29 | 30 | TEST_CASE("Argument '-' is not an optional argument but '-l' is" * 31 | test_suite("optional_arguments")) { 32 | argparse::ArgumentParser program("test"); 33 | program.add_argument("-l").flag(); 34 | program.add_argument("input"); 35 | program.parse_args({"./test.exe", "-l", "-"}); 36 | REQUIRE(program.get("-l") == true); 37 | REQUIRE(program.get("input") == "-"); 38 | } 39 | 40 | TEST_CASE("Argument '-l' is an optional argument but '-' is not" * 41 | test_suite("optional_arguments")) { 42 | argparse::ArgumentParser program("test"); 43 | program.add_argument("-l").flag(); 44 | program.add_argument("input"); 45 | program.parse_args({"./test.exe", "-", "-l"}); 46 | REQUIRE(program.get("-l") == true); 47 | REQUIRE(program.get("input") == "-"); 48 | } 49 | 50 | TEST_CASE("Parse toggle arguments with implicit value" * 51 | test_suite("optional_arguments")) { 52 | argparse::ArgumentParser program("test"); 53 | program.add_argument("--verbose").flag(); 54 | 55 | program.parse_args({"./test.exe", "--verbose"}); 56 | REQUIRE(program.get("--verbose") == true); 57 | REQUIRE(program["--verbose"] == true); 58 | REQUIRE(program["--verbose"] != false); 59 | } 60 | 61 | TEST_CASE("Parse multiple toggle arguments with implicit values" * 62 | test_suite("optional_arguments")) { 63 | argparse::ArgumentParser program("test"); 64 | program.add_argument("-a").flag(); 65 | 66 | program.add_argument("-u").flag(); 67 | 68 | program.add_argument("-x").flag(); 69 | 70 | program.parse_args({"./test.exe", "-a", "-x"}); 71 | REQUIRE(program.get("-a") == true); 72 | REQUIRE(program.get("-u") == false); 73 | REQUIRE(program.get("-x") == true); 74 | } 75 | 76 | TEST_CASE("Parse optional arguments of many values" * 77 | test_suite("optional_arguments")) { 78 | GIVEN("a program that accepts an optional argument of many values") { 79 | argparse::ArgumentParser program("test"); 80 | program.add_argument("-i").remaining().scan<'i', int>(); 81 | 82 | WHEN("provided no argument") { 83 | THEN("the program accepts it but gets nothing") { 84 | REQUIRE_NOTHROW(program.parse_args({"test"})); 85 | REQUIRE_THROWS_AS(program.get>("-i"), 86 | std::logic_error); 87 | } 88 | } 89 | 90 | WHEN("provided remaining arguments follow the option") { 91 | program.parse_args({"test", "-i", "-42", "8", "100", "300"}); 92 | 93 | THEN("the optional parameter consumes all of them") { 94 | auto inputs = program.get>("-i"); 95 | REQUIRE(inputs.size() == 4); 96 | REQUIRE(inputs[0] == -42); 97 | REQUIRE(inputs[1] == 8); 98 | REQUIRE(inputs[2] == 100); 99 | REQUIRE(inputs[3] == 300); 100 | } 101 | } 102 | } 103 | } 104 | 105 | TEST_CASE("Parse 2 optional arguments of many values" * 106 | test_suite("optional_arguments")) { 107 | GIVEN("a program that accepts 2 optional arguments of many values") { 108 | argparse::ArgumentParser program("test"); 109 | program.add_argument("-i") 110 | .nargs(argparse::nargs_pattern::any) 111 | .scan<'i', int>(); 112 | program.add_argument("-s").nargs(argparse::nargs_pattern::any); 113 | 114 | WHEN("provided no argument") { 115 | THEN("the program accepts it and gets empty container") { 116 | REQUIRE_NOTHROW(program.parse_args({"test"})); 117 | auto i = program.get>("-i"); 118 | REQUIRE(i.size() == 0); 119 | 120 | auto s = program.get>("-s"); 121 | REQUIRE(s.size() == 0); 122 | } 123 | } 124 | 125 | WHEN("provided 2 options with many arguments") { 126 | program.parse_args({"test", "-i", "-42", "8", "100", "300", "-s", "ok", 127 | "this", "works"}); 128 | 129 | THEN("the optional parameter consumes each arguments") { 130 | auto i = program.get>("-i"); 131 | REQUIRE(i.size() == 4); 132 | REQUIRE(i[0] == -42); 133 | REQUIRE(i[1] == 8); 134 | REQUIRE(i[2] == 100); 135 | REQUIRE(i[3] == 300); 136 | 137 | auto s = program.get>("-s"); 138 | REQUIRE(s.size() == 3); 139 | REQUIRE(s[0] == "ok"); 140 | REQUIRE(s[1] == "this"); 141 | REQUIRE(s[2] == "works"); 142 | } 143 | } 144 | } 145 | } 146 | 147 | TEST_CASE("Parse an optional argument of many values" 148 | " and a positional argument of many values" * 149 | test_suite("optional_arguments")) { 150 | GIVEN("a program that accepts an optional argument of many values" 151 | " and a positional argument of many values") { 152 | argparse::ArgumentParser program("test"); 153 | program.add_argument("-s").nargs(argparse::nargs_pattern::any); 154 | program.add_argument("input").nargs(argparse::nargs_pattern::any); 155 | 156 | WHEN("provided no argument") { 157 | program.parse_args({"test"}); 158 | THEN("the program accepts it and gets empty containers") { 159 | auto s = program.get>("-s"); 160 | REQUIRE(s.size() == 0); 161 | 162 | auto input = program.get>("input"); 163 | REQUIRE(input.size() == 0); 164 | } 165 | } 166 | 167 | WHEN("provided many arguments followed by an option with many arguments") { 168 | program.parse_args({"test", "foo", "bar", "-s", "ok", "this", "works"}); 169 | 170 | THEN("the parameters consume each arguments") { 171 | auto s = program.get>("-s"); 172 | REQUIRE(s.size() == 3); 173 | REQUIRE(s[0] == "ok"); 174 | REQUIRE(s[1] == "this"); 175 | REQUIRE(s[2] == "works"); 176 | 177 | auto input = program.get>("input"); 178 | REQUIRE(input.size() == 2); 179 | REQUIRE(input[0] == "foo"); 180 | REQUIRE(input[1] == "bar"); 181 | } 182 | } 183 | } 184 | } 185 | 186 | TEST_CASE("Parse arguments of different types" * 187 | test_suite("optional_arguments")) { 188 | using namespace std::literals; 189 | 190 | argparse::ArgumentParser program("test"); 191 | program.add_argument("--this-argument-is-longer-than-any-sso-buffer-that-" 192 | "makes-sense-unless-your-cache-line-is-this-long"s); 193 | 194 | REQUIRE_NOTHROW(program.parse_args({"test"})); 195 | 196 | program.add_argument("-string"s, "-string-view"sv, "-builtin") 197 | .default_value(false) 198 | .implicit_value(true); 199 | 200 | program.parse_args({"test", "-string-view"}); 201 | REQUIRE(program["-string"sv] == true); 202 | REQUIRE(program["-string-view"] == true); 203 | REQUIRE(program["-builtin"s] == true); 204 | } 205 | -------------------------------------------------------------------------------- /test/test_parent_parsers.cpp: -------------------------------------------------------------------------------- 1 | #ifdef WITH_MODULE 2 | import argparse; 3 | #else 4 | #include 5 | #endif 6 | #include 7 | 8 | using doctest::test_suite; 9 | 10 | TEST_CASE("Add parent parsers" * test_suite("parent_parsers")) { 11 | argparse::ArgumentParser parent_parser("main"); 12 | parent_parser.add_argument("--verbose") 13 | .default_value(false) 14 | .implicit_value(true); 15 | 16 | argparse::ArgumentParser child_parser("foo"); 17 | child_parser.add_parents(parent_parser); 18 | child_parser.parse_args({"./main", "--verbose"}); 19 | REQUIRE(child_parser["--verbose"] == true); 20 | REQUIRE(parent_parser["--verbose"] == false); 21 | } 22 | 23 | TEST_CASE("Add parent to multiple parent parsers" * 24 | test_suite("parent_parsers")) { 25 | argparse::ArgumentParser parent_parser("main"); 26 | parent_parser.add_argument("--parent").default_value(0).scan<'i', int>(); 27 | 28 | argparse::ArgumentParser foo_parser("foo"); 29 | foo_parser.add_argument("foo"); 30 | foo_parser.add_parents(parent_parser); 31 | foo_parser.parse_args({"./main", "--parent", "2", "XXX"}); 32 | REQUIRE(foo_parser["--parent"] == 2); 33 | REQUIRE(foo_parser["foo"] == std::string("XXX")); 34 | REQUIRE(parent_parser["--parent"] == 0); 35 | 36 | argparse::ArgumentParser bar_parser("bar"); 37 | bar_parser.add_argument("--bar"); 38 | bar_parser.parse_args({"./main", "--bar", "YYY"}); 39 | REQUIRE(bar_parser["--bar"] == std::string("YYY")); 40 | } 41 | -------------------------------------------------------------------------------- /test/test_parse_args.cpp: -------------------------------------------------------------------------------- 1 | #ifdef WITH_MODULE 2 | import argparse; 3 | #else 4 | #include 5 | #endif 6 | #include 7 | 8 | #include 9 | 10 | using doctest::test_suite; 11 | 12 | TEST_CASE("Missing argument" * test_suite("parse_args")) { 13 | argparse::ArgumentParser program("test"); 14 | program.add_argument("--config").nargs(1); 15 | REQUIRE_THROWS_WITH_AS(program.parse_args({"test", "--config"}), 16 | "Too few arguments for '--config'.", 17 | std::runtime_error); 18 | } 19 | 20 | TEST_CASE("Missing argument, not last" * test_suite("parse_args")) { 21 | argparse::ArgumentParser program("test"); 22 | program.add_argument("--config").nargs(1); 23 | program.add_argument("--foo"); 24 | REQUIRE_THROWS_WITH_AS(program.parse_args({"test", "--config", "--foo"}), 25 | "Too few arguments for '--config'.", 26 | std::runtime_error); 27 | } 28 | 29 | TEST_CASE("Parse a string argument with value" * test_suite("parse_args")) { 30 | argparse::ArgumentParser program("test"); 31 | program.add_argument("--config"); 32 | program.parse_args({"test", "--config", "config.yml"}); 33 | REQUIRE(program.get("--config") == "config.yml"); 34 | } 35 | 36 | TEST_CASE("Parse a string argument with default value" * 37 | test_suite("parse_args")) { 38 | argparse::ArgumentParser program("test"); 39 | program.add_argument("--config").default_value(std::string("foo.yml")); 40 | program.parse_args({"test", "--config"}); 41 | REQUIRE(program.get("--config") == "foo.yml"); 42 | } 43 | 44 | TEST_CASE("Parse a string argument without default value" * 45 | test_suite("parse_args")) { 46 | argparse::ArgumentParser program("test"); 47 | program.add_argument("--config"); 48 | 49 | WHEN("no value provided") { 50 | program.parse_args({"test"}); 51 | 52 | THEN("the option is nullopt") { 53 | auto opt = program.present("--config"); 54 | REQUIRE_FALSE(opt); 55 | REQUIRE(opt == std::nullopt); 56 | } 57 | } 58 | 59 | WHEN("a value is provided") { 60 | program.parse_args({"test", "--config", ""}); 61 | 62 | THEN("the option has a value") { 63 | auto opt = program.present("--config"); 64 | REQUIRE(opt); 65 | REQUIRE(opt->empty()); 66 | } 67 | } 68 | } 69 | 70 | TEST_CASE("Parse an int argument with value" * test_suite("parse_args")) { 71 | argparse::ArgumentParser program("test"); 72 | program.add_argument("--count").scan<'i', int>(); 73 | program.parse_args({"test", "--count", "5"}); 74 | REQUIRE(program.get("--count") == 5); 75 | } 76 | 77 | TEST_CASE("Parse an int argument with default value" * 78 | test_suite("parse_args")) { 79 | argparse::ArgumentParser program("test"); 80 | program.add_argument("--count").default_value(2).scan<'i', int>(); 81 | program.parse_args({"test", "--count"}); 82 | REQUIRE(program.get("--count") == 2); 83 | } 84 | 85 | TEST_CASE("Parse a float argument with value" * test_suite("parse_args")) { 86 | argparse::ArgumentParser program("test"); 87 | program.add_argument("--ratio").scan<'g', float>(); 88 | program.parse_args({"test", "--ratio", "5.6645"}); 89 | REQUIRE(program.get("--ratio") == 5.6645f); 90 | } 91 | 92 | TEST_CASE("Parse a float argument with default value" * 93 | test_suite("parse_args")) { 94 | argparse::ArgumentParser program("test"); 95 | program.add_argument("--ratio").default_value(3.14f).scan<'g', float>(); 96 | program.parse_args({"test", "--ratio"}); 97 | REQUIRE(program.get("--ratio") == 3.14f); 98 | } 99 | 100 | TEST_CASE("Parse a double argument with value" * test_suite("parse_args")) { 101 | argparse::ArgumentParser program("test"); 102 | program.add_argument("--ratio").scan<'g', double>(); 103 | program.parse_args({"test", "--ratio", "5.6645"}); 104 | REQUIRE(program.get("--ratio") == 5.6645); 105 | } 106 | 107 | TEST_CASE("Parse a double argument with default value" * 108 | test_suite("parse_args")) { 109 | argparse::ArgumentParser program("test"); 110 | program.add_argument("--ratio").default_value(3.14).scan<'g', double>(); 111 | program.parse_args({"test", "--ratio"}); 112 | REQUIRE(program.get("--ratio") == 3.14); 113 | } 114 | 115 | TEST_CASE("Parse a vector of integer arguments" * test_suite("parse_args")) { 116 | argparse::ArgumentParser program("test"); 117 | program.add_argument("--vector").nargs(5).scan<'i', int>(); 118 | program.parse_args({"test", "--vector", "1", "2", "3", "4", "5"}); 119 | auto vector = program.get>("--vector"); 120 | REQUIRE(vector.size() == 5); 121 | REQUIRE(vector[0] == 1); 122 | REQUIRE(vector[1] == 2); 123 | REQUIRE(vector[2] == 3); 124 | REQUIRE(vector[3] == 4); 125 | REQUIRE(vector[4] == 5); 126 | } 127 | 128 | TEST_CASE("Parse a vector of float arguments" * test_suite("parse_args")) { 129 | argparse::ArgumentParser program("test"); 130 | program.add_argument("--vector").nargs(5).scan<'g', float>(); 131 | program.parse_args({"test", "--vector", "1.1", "2.2", "3.3", "4.4", "5.5"}); 132 | auto vector = program.get>("--vector"); 133 | REQUIRE(vector.size() == 5); 134 | REQUIRE(vector[0] == 1.1f); 135 | REQUIRE(vector[1] == 2.2f); 136 | REQUIRE(vector[2] == 3.3f); 137 | REQUIRE(vector[3] == 4.4f); 138 | REQUIRE(vector[4] == 5.5f); 139 | } 140 | 141 | TEST_CASE("Parse a vector of float without default value" * 142 | test_suite("parse_args")) { 143 | argparse::ArgumentParser program("test"); 144 | program.add_argument("--vector").scan<'g', float>().nargs(3); 145 | 146 | WHEN("no value is provided") { 147 | program.parse_args({"test"}); 148 | 149 | THEN("the option is nullopt") { 150 | auto opt = program.present>("--vector"); 151 | REQUIRE_FALSE(opt.has_value()); 152 | REQUIRE(opt == std::nullopt); 153 | } 154 | } 155 | 156 | WHEN("a value is provided") { 157 | program.parse_args({"test", "--vector", ".3", "1.3", "6"}); 158 | 159 | THEN("the option has a value") { 160 | auto opt = program.present>("--vector"); 161 | REQUIRE(opt.has_value()); 162 | 163 | auto &&vec = opt.value(); 164 | REQUIRE(vec.size() == 3); 165 | REQUIRE(vec[0] == .3f); 166 | REQUIRE(vec[1] == 1.3f); 167 | REQUIRE(vec[2] == 6.f); 168 | } 169 | } 170 | } 171 | 172 | TEST_CASE("Parse a vector of double arguments" * test_suite("parse_args")) { 173 | argparse::ArgumentParser program("test"); 174 | program.add_argument("--vector").nargs(5).scan<'g', double>(); 175 | program.parse_args({"test", "--vector", "1.1", "2.2", "3.3", "4.4", "5.5"}); 176 | auto vector = program.get>("--vector"); 177 | REQUIRE(vector.size() == 5); 178 | REQUIRE(vector[0] == 1.1); 179 | REQUIRE(vector[1] == 2.2); 180 | REQUIRE(vector[2] == 3.3); 181 | REQUIRE(vector[3] == 4.4); 182 | REQUIRE(vector[4] == 5.5); 183 | } 184 | 185 | TEST_CASE("Parse a vector of string arguments" * test_suite("parse_args")) { 186 | argparse::ArgumentParser program("test"); 187 | program.add_argument("--vector").nargs(5); 188 | program.parse_args({"test", "--vector", "abc", "def", "ghi", "jkl", "mno"}); 189 | auto vector = program.get>("--vector"); 190 | REQUIRE(vector.size() == 5); 191 | REQUIRE(vector[0] == "abc"); 192 | REQUIRE(vector[1] == "def"); 193 | REQUIRE(vector[2] == "ghi"); 194 | REQUIRE(vector[3] == "jkl"); 195 | REQUIRE(vector[4] == "mno"); 196 | } 197 | 198 | TEST_CASE("Parse a vector of character arguments" * test_suite("parse_args")) { 199 | argparse::ArgumentParser program("test"); 200 | program.add_argument("--vector") 201 | .nargs(5) 202 | .action([](const std::string &value) { return value[0]; }); 203 | program.parse_args({"test", "--vector", "a", "b", "c", "d", "e"}); 204 | auto vector = program.get>("--vector"); 205 | REQUIRE(vector.size() == 5); 206 | REQUIRE(vector[0] == 'a'); 207 | REQUIRE(vector[1] == 'b'); 208 | REQUIRE(vector[2] == 'c'); 209 | REQUIRE(vector[3] == 'd'); 210 | REQUIRE(vector[4] == 'e'); 211 | } 212 | 213 | TEST_CASE("Parse a vector of string arguments and construct objects" * 214 | test_suite("parse_args")) { 215 | 216 | class Foo { 217 | public: 218 | Foo(const std::string &value) : m_value(value) {} 219 | std::string m_value; 220 | }; 221 | 222 | argparse::ArgumentParser program("test"); 223 | program.add_argument("--vector") 224 | .nargs(5) 225 | .action([](const std::string &value) { return Foo(value); }); 226 | program.parse_args({"test", "--vector", "abc", "def", "ghi", "jkl", "mno"}); 227 | auto vector = program.get>("--vector"); 228 | REQUIRE(vector.size() == 5); 229 | REQUIRE(vector[0].m_value == Foo("abc").m_value); 230 | REQUIRE(vector[1].m_value == Foo("def").m_value); 231 | REQUIRE(vector[2].m_value == Foo("ghi").m_value); 232 | REQUIRE(vector[3].m_value == Foo("jkl").m_value); 233 | REQUIRE(vector[4].m_value == Foo("mno").m_value); 234 | } 235 | -------------------------------------------------------------------------------- /test/test_parse_known_args.cpp: -------------------------------------------------------------------------------- 1 | #ifdef WITH_MODULE 2 | import argparse; 3 | #else 4 | #include 5 | #endif 6 | #include 7 | 8 | #include 9 | #include 10 | 11 | using doctest::test_suite; 12 | 13 | TEST_CASE("Parse unknown optional and positional arguments without exceptions" * 14 | test_suite("parse_known_args")) { 15 | argparse::ArgumentParser program("test"); 16 | program.add_argument("--foo").implicit_value(true).default_value(false); 17 | program.add_argument("bar"); 18 | 19 | SUBCASE("Parse unknown optional and positional arguments") { 20 | auto unknown_args = 21 | program.parse_known_args({"test", "--foo", "--badger", "BAR", "spam"}); 22 | REQUIRE((unknown_args == std::vector{"--badger", "spam"})); 23 | REQUIRE(program.get("--foo") == true); 24 | REQUIRE(program.get("bar") == std::string{"BAR"}); 25 | } 26 | 27 | SUBCASE("Parse unknown compound arguments") { 28 | auto unknown_args = program.parse_known_args({"test", "-jc", "BAR"}); 29 | REQUIRE((unknown_args == std::vector{"-jc"})); 30 | REQUIRE(program.get("--foo") == false); 31 | REQUIRE(program.get("bar") == std::string{"BAR"}); 32 | } 33 | } 34 | 35 | TEST_CASE("Parse unknown optional and positional arguments in subparsers " 36 | "without exceptions" * 37 | test_suite("parse_known_args")) { 38 | argparse::ArgumentParser program("test"); 39 | program.add_argument("--output"); 40 | 41 | argparse::ArgumentParser command_1("add"); 42 | command_1.add_argument("file").nargs(2); 43 | 44 | argparse::ArgumentParser command_2("clean"); 45 | command_2.add_argument("--fullclean") 46 | .default_value(false) 47 | .implicit_value(true); 48 | 49 | program.add_subparser(command_1); 50 | program.add_subparser(command_2); 51 | 52 | SUBCASE("Parse unknown optional argument") { 53 | auto unknown_args = 54 | program.parse_known_args({"test", "add", "--badger", "BAR", "spam"}); 55 | REQUIRE(program.is_subcommand_used("add") == true); 56 | REQUIRE((command_1.get>("file") == 57 | std::vector{"BAR", "spam"})); 58 | REQUIRE((unknown_args == std::vector{"--badger"})); 59 | } 60 | 61 | SUBCASE("Parse unknown positional argument") { 62 | auto unknown_args = 63 | program.parse_known_args({"test", "add", "FOO", "BAR", "spam"}); 64 | REQUIRE(program.is_subcommand_used("add") == true); 65 | REQUIRE((command_1.get>("file") == 66 | std::vector{"FOO", "BAR"})); 67 | REQUIRE((unknown_args == std::vector{"spam"})); 68 | } 69 | 70 | SUBCASE("Parse unknown positional and optional arguments") { 71 | auto unknown_args = program.parse_known_args( 72 | {"test", "add", "--verbose", "FOO", "5", "BAR", "-jn", "spam"}); 73 | REQUIRE(program.is_subcommand_used("add") == true); 74 | REQUIRE((command_1.get>("file") == 75 | std::vector{"FOO", "5"})); 76 | REQUIRE((unknown_args == 77 | std::vector{"--verbose", "BAR", "-jn", "spam"})); 78 | } 79 | 80 | SUBCASE("Parse unknown positional and optional arguments 2") { 81 | auto unknown_args = 82 | program.parse_known_args({"test", "clean", "--verbose", "FOO", "5", 83 | "BAR", "--fullclean", "-jn", "spam"}); 84 | REQUIRE(program.is_subcommand_used("clean") == true); 85 | REQUIRE(command_2.get("--fullclean") == true); 86 | REQUIRE((unknown_args == std::vector{"--verbose", "FOO", "5", 87 | "BAR", "-jn", "spam"})); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /test/test_positional_arguments.cpp: -------------------------------------------------------------------------------- 1 | #ifdef WITH_MODULE 2 | import argparse; 3 | #else 4 | #include 5 | #endif 6 | #include 7 | #include 8 | 9 | using doctest::test_suite; 10 | 11 | TEST_CASE("Parse positional arguments" * test_suite("positional_arguments")) { 12 | argparse::ArgumentParser program("test"); 13 | program.add_argument("input"); 14 | program.add_argument("output"); 15 | program.parse_args({"test", "rocket.mesh", "thrust_profile.csv"}); 16 | REQUIRE(program.get("input") == "rocket.mesh"); 17 | REQUIRE(program.get("output") == "thrust_profile.csv"); 18 | } 19 | 20 | TEST_CASE("Missing expected positional argument" * 21 | test_suite("positional_arguments")) { 22 | argparse::ArgumentParser program("test"); 23 | program.add_argument("input"); 24 | REQUIRE_THROWS_WITH_AS(program.parse_args({"test"}), 25 | "input: 1 argument(s) expected. 0 provided.", 26 | std::runtime_error); 27 | } 28 | 29 | TEST_CASE("Parse positional arguments with fixed nargs" * 30 | test_suite("positional_arguments")) { 31 | argparse::ArgumentParser program("test"); 32 | program.add_argument("input"); 33 | program.add_argument("output").nargs(2); 34 | program.parse_args( 35 | {"test", "rocket.mesh", "thrust_profile.csv", "output.mesh"}); 36 | REQUIRE(program.get("input") == "rocket.mesh"); 37 | auto outputs = program.get>("output"); 38 | REQUIRE(outputs.size() == 2); 39 | REQUIRE(outputs[0] == "thrust_profile.csv"); 40 | REQUIRE(outputs[1] == "output.mesh"); 41 | } 42 | 43 | TEST_CASE("Parse positional arguments with optional arguments" * 44 | test_suite("positional_arguments")) { 45 | argparse::ArgumentParser program("test"); 46 | program.add_argument("input"); 47 | program.add_argument("output").nargs(2); 48 | program.add_argument("--num_iterations").scan<'i', int>(); 49 | program.parse_args({"test", "rocket.mesh", "--num_iterations", "15", 50 | "thrust_profile.csv", "output.mesh"}); 51 | REQUIRE(program.get("--num_iterations") == 15); 52 | REQUIRE(program.get("input") == "rocket.mesh"); 53 | auto outputs = program.get>("output"); 54 | REQUIRE(outputs.size() == 2); 55 | REQUIRE(outputs[0] == "thrust_profile.csv"); 56 | REQUIRE(outputs[1] == "output.mesh"); 57 | } 58 | 59 | TEST_CASE("Parse positional arguments with optional arguments in the middle" * 60 | test_suite("positional_arguments")) { 61 | argparse::ArgumentParser program("test"); 62 | program.add_argument("input"); 63 | program.add_argument("output").nargs(2); 64 | program.add_argument("--num_iterations").scan<'i', int>(); 65 | REQUIRE_THROWS( 66 | program.parse_args({"test", "rocket.mesh", "thrust_profile.csv", 67 | "--num_iterations", "15", "output.mesh"})); 68 | } 69 | 70 | TEST_CASE("Parse positional nargs=1..2 arguments" * 71 | test_suite("positional_arguments")) { 72 | GIVEN("a program that accepts an optional argument and nargs=1..2 positional " 73 | "arguments") { 74 | argparse::ArgumentParser program("test"); 75 | program.add_argument("-o"); 76 | program.add_argument("input").nargs(1, 2); 77 | 78 | WHEN("provided no argument") { 79 | THEN("the program does not accept it") { 80 | REQUIRE_THROWS(program.parse_args({"test"})); 81 | } 82 | } 83 | 84 | WHEN("provided 1 argument") { 85 | THEN("the program accepts it") { 86 | REQUIRE_NOTHROW(program.parse_args({"test", "a.c"})); 87 | 88 | auto inputs = program.get>("input"); 89 | REQUIRE(inputs.size() == 1); 90 | REQUIRE(inputs[0] == "a.c"); 91 | } 92 | } 93 | 94 | WHEN("provided 2 arguments") { 95 | THEN("the program accepts it") { 96 | REQUIRE_NOTHROW(program.parse_args({"test", "a.c", "b.c"})); 97 | 98 | auto inputs = program.get>("input"); 99 | REQUIRE(inputs.size() == 2); 100 | REQUIRE(inputs[0] == "a.c"); 101 | REQUIRE(inputs[1] == "b.c"); 102 | } 103 | } 104 | 105 | WHEN("provided 3 arguments") { 106 | THEN("the program does not accept it") { 107 | REQUIRE_THROWS(program.parse_args({"test", "a.c", "b.c", "main.c"})); 108 | } 109 | } 110 | 111 | WHEN("provided an optional followed by positional arguments") { 112 | program.parse_args({"test", "-o", "a.out", "a.c", "b.c"}); 113 | 114 | THEN("the optional parameter consumes an argument") { 115 | using namespace std::literals; 116 | REQUIRE(program["-o"] == "a.out"s); 117 | 118 | auto inputs = program.get>("input"); 119 | REQUIRE(inputs.size() == 2); 120 | REQUIRE(inputs[0] == "a.c"); 121 | REQUIRE(inputs[1] == "b.c"); 122 | } 123 | } 124 | 125 | WHEN("provided an optional preceded by positional arguments") { 126 | program.parse_args({"test", "a.c", "b.c", "-o", "a.out"}); 127 | 128 | THEN("the optional parameter consumes an argument") { 129 | using namespace std::literals; 130 | REQUIRE(program["-o"] == "a.out"s); 131 | 132 | auto inputs = program.get>("input"); 133 | REQUIRE(inputs.size() == 2); 134 | REQUIRE(inputs[0] == "a.c"); 135 | REQUIRE(inputs[1] == "b.c"); 136 | } 137 | } 138 | 139 | WHEN("provided an optional in between positional arguments") { 140 | THEN("the program does not accept it") { 141 | REQUIRE_THROWS( 142 | program.parse_args({"test", "a.c", "-o", "a.out", "b.c"})); 143 | } 144 | } 145 | } 146 | } 147 | 148 | TEST_CASE("Parse positional nargs=ANY arguments" * 149 | test_suite("positional_arguments")) { 150 | GIVEN("a program that accepts an optional argument and nargs=ANY positional " 151 | "arguments") { 152 | argparse::ArgumentParser program("test"); 153 | program.add_argument("-o"); 154 | program.add_argument("input").nargs(argparse::nargs_pattern::any); 155 | 156 | WHEN("provided no argument") { 157 | THEN("the program accepts it and gets empty container") { 158 | REQUIRE_NOTHROW(program.parse_args({"test"})); 159 | 160 | auto inputs = program.get>("input"); 161 | REQUIRE(inputs.size() == 0); 162 | } 163 | } 164 | 165 | WHEN("provided an optional followed by positional arguments") { 166 | program.parse_args({"test", "-o", "a.out", "a.c", "b.c", "main.c"}); 167 | 168 | THEN("the optional parameter consumes an argument") { 169 | using namespace std::literals; 170 | REQUIRE(program["-o"] == "a.out"s); 171 | 172 | auto inputs = program.get>("input"); 173 | REQUIRE(inputs.size() == 3); 174 | REQUIRE(inputs[0] == "a.c"); 175 | REQUIRE(inputs[1] == "b.c"); 176 | REQUIRE(inputs[2] == "main.c"); 177 | } 178 | } 179 | 180 | WHEN("provided an optional preceded by positional arguments") { 181 | program.parse_args({"test", "a.c", "b.c", "main.c", "-o", "a.out"}); 182 | 183 | THEN("the optional parameter consumes an argument") { 184 | using namespace std::literals; 185 | REQUIRE(program["-o"] == "a.out"s); 186 | 187 | auto inputs = program.get>("input"); 188 | REQUIRE(inputs.size() == 3); 189 | REQUIRE(inputs[0] == "a.c"); 190 | REQUIRE(inputs[1] == "b.c"); 191 | REQUIRE(inputs[2] == "main.c"); 192 | } 193 | } 194 | } 195 | } 196 | 197 | TEST_CASE("Parse remaining arguments deemed positional" * 198 | test_suite("positional_arguments")) { 199 | GIVEN("a program that accepts an optional argument and remaining arguments") { 200 | argparse::ArgumentParser program("test"); 201 | program.add_argument("-o"); 202 | program.add_argument("input").remaining(); 203 | 204 | WHEN("provided no argument") { 205 | THEN("the program accepts it but gets nothing") { 206 | REQUIRE_NOTHROW(program.parse_args({"test"})); 207 | REQUIRE_THROWS_AS(program.get>("input"), 208 | std::logic_error); 209 | } 210 | } 211 | 212 | WHEN("provided an optional followed by remaining arguments") { 213 | program.parse_args({"test", "-o", "a.out", "a.c", "b.c", "main.c"}); 214 | 215 | THEN("the optional parameter consumes an argument") { 216 | using namespace std::literals; 217 | REQUIRE(program["-o"] == "a.out"s); 218 | 219 | auto inputs = program.get>("input"); 220 | REQUIRE(inputs.size() == 3); 221 | REQUIRE(inputs[0] == "a.c"); 222 | REQUIRE(inputs[1] == "b.c"); 223 | REQUIRE(inputs[2] == "main.c"); 224 | } 225 | } 226 | 227 | WHEN("provided remaining arguments including optional arguments") { 228 | program.parse_args({"test", "a.c", "b.c", "main.c", "-o", "a.out"}); 229 | 230 | THEN("the optional argument is deemed remaining") { 231 | REQUIRE_THROWS_AS(program.get("-o"), std::logic_error); 232 | 233 | auto inputs = program.get>("input"); 234 | REQUIRE(inputs.size() == 5); 235 | REQUIRE(inputs[0] == "a.c"); 236 | REQUIRE(inputs[1] == "b.c"); 237 | REQUIRE(inputs[2] == "main.c"); 238 | REQUIRE(inputs[3] == "-o"); 239 | REQUIRE(inputs[4] == "a.out"); 240 | } 241 | } 242 | } 243 | } 244 | 245 | TEST_CASE("Reversed order nargs is not allowed" * 246 | test_suite("positional_arguments")) { 247 | argparse::ArgumentParser program("test"); 248 | REQUIRE_THROWS_AS(program.add_argument("output").nargs(2, 1), 249 | std::logic_error); 250 | } 251 | 252 | TEST_CASE("Square a number" * test_suite("positional_arguments")) { 253 | argparse::ArgumentParser program; 254 | program.add_argument("--verbose", "-v") 255 | .help("enable verbose logging") 256 | .default_value(false) 257 | .implicit_value(true); 258 | 259 | program.add_argument("square") 260 | .help("display a square of a given number") 261 | .action( 262 | [](const std::string &value) { return pow(std::stoi(value), 2); }); 263 | 264 | program.parse_args({"./main", "15"}); 265 | REQUIRE(program.get("square") == 225); 266 | } 267 | 268 | TEST_CASE("At_least_one_followed_by_exactly_one" * test_suite("positional_arguments")) { 269 | GIVEN("a program that accepts a positional argument with at_least_one cardinality followed by another positional argument with 1:1") { 270 | argparse::ArgumentParser program; 271 | 272 | std::vector at_least_one; 273 | program.add_argument("at_least_one") 274 | .nargs(argparse::nargs_pattern::at_least_one) 275 | .store_into(at_least_one); 276 | 277 | std::string exactly_one; 278 | program.add_argument("exactly_one") 279 | .store_into(exactly_one); 280 | 281 | WHEN("provided one, two") { 282 | THEN("parse_args works") { 283 | program.parse_args({"./main", "one", "two"}); 284 | REQUIRE(at_least_one == std::vector{"one"}); 285 | REQUIRE(exactly_one == "two"); 286 | } 287 | } 288 | 289 | WHEN("provided one, two, three") { 290 | THEN("parse_args works") { 291 | program.parse_args({"./main", "one", "two", "three"}); 292 | REQUIRE(at_least_one == std::vector{"one", "two"}); 293 | REQUIRE(exactly_one == "three"); 294 | } 295 | } 296 | 297 | WHEN("provided one, two") { 298 | THEN("parse_args throws") { 299 | REQUIRE_THROWS(program.parse_args({"./main", "one"})); 300 | } 301 | } 302 | } 303 | } 304 | -------------------------------------------------------------------------------- /test/test_prefix_chars.cpp: -------------------------------------------------------------------------------- 1 | #ifdef WITH_MODULE 2 | import argparse; 3 | #else 4 | #include 5 | #endif 6 | #include 7 | #include 8 | 9 | using doctest::test_suite; 10 | 11 | TEST_CASE("Parse with custom prefix chars" * test_suite("prefix_chars")) { 12 | argparse::ArgumentParser program("test"); 13 | program.set_prefix_chars("-+"); 14 | program.add_argument("+f"); 15 | program.add_argument("++bar"); 16 | program.parse_args({"test", "+f", "X", "++bar", "Y"}); 17 | REQUIRE(program.get("+f") == "X"); 18 | REQUIRE(program.get("++bar") == "Y"); 19 | } 20 | 21 | TEST_CASE("Parse with custom Windows-style prefix chars" * 22 | test_suite("prefix_chars")) { 23 | argparse::ArgumentParser program("dir"); 24 | program.set_prefix_chars("/"); 25 | program.add_argument("/A").nargs(1); 26 | program.add_argument("/B").flag(); 27 | program.add_argument("/C").flag(); 28 | program.parse_args({"dir", "/A", "D", "/B", "/C"}); 29 | REQUIRE(program.get("/A") == "D"); 30 | REQUIRE(program.get("/B") == true); 31 | } 32 | 33 | TEST_CASE("Parse with custom Windows-style prefix chars and assign chars" * 34 | test_suite("prefix_chars")) { 35 | argparse::ArgumentParser program("dir"); 36 | program.set_prefix_chars("/"); 37 | program.set_assign_chars(":="); 38 | program.add_argument("/A").nargs(1); 39 | program.add_argument("/B").nargs(1); 40 | program.add_argument("/C").flag(); 41 | program.parse_args({"dir", "/A:D", "/B=Boo", "/C"}); 42 | REQUIRE(program.get("/A") == "D"); 43 | REQUIRE(program.get("/B") == "Boo"); 44 | REQUIRE(program.get("/C") == true); 45 | } -------------------------------------------------------------------------------- /test/test_repr.cpp: -------------------------------------------------------------------------------- 1 | #ifdef WITH_MODULE 2 | import argparse; 3 | import argparse.details; 4 | #else 5 | #include 6 | #endif 7 | #include 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | using doctest::test_suite; 14 | 15 | TEST_CASE("Test bool representation" * test_suite("repr")) { 16 | REQUIRE(argparse::details::repr(true) == "true"); 17 | REQUIRE(argparse::details::repr(false) == "false"); 18 | } 19 | 20 | TEST_CASE_TEMPLATE("Test built-in int types representation" * 21 | test_suite("repr"), 22 | T, char, short, int, long long, unsigned char, unsigned, 23 | unsigned long long) { 24 | std::stringstream ss; 25 | T v = 42; 26 | ss << v; 27 | REQUIRE(argparse::details::repr(v) == ss.str()); 28 | } 29 | 30 | TEST_CASE_TEMPLATE("Test built-in float types representation" * 31 | test_suite("repr"), 32 | T, float, double, long double) { 33 | std::stringstream ss; 34 | T v = static_cast(0.3333333333); 35 | ss << v; 36 | REQUIRE(argparse::details::repr(v) == ss.str()); 37 | } 38 | 39 | TEST_CASE_TEMPLATE("Test container representation" * test_suite("repr"), T, 40 | std::vector, std::list, std::set) { 41 | T empty; 42 | T one = {42}; 43 | T small = {1, 2, 3}; 44 | T big = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}; 45 | 46 | REQUIRE(argparse::details::repr(empty) == "{}"); 47 | REQUIRE(argparse::details::repr(one) == "{42}"); 48 | REQUIRE(argparse::details::repr(small) == "{1 2 3}"); 49 | REQUIRE(argparse::details::repr(big) == "{1 2 3 4...15}"); 50 | } 51 | 52 | TEST_CASE_TEMPLATE("Test string representation" * test_suite("repr"), T, 53 | char const *, std::string, std::string_view) { 54 | T empty = ""; 55 | T str = "A A A#"; 56 | 57 | REQUIRE(argparse::details::repr(empty) == "\"\""); 58 | REQUIRE(argparse::details::repr(str) == "\"A A A#\""); 59 | } 60 | 61 | TEST_CASE("Test unknown representation" * test_suite("repr")) { 62 | struct TestClass {}; 63 | REQUIRE(argparse::details::repr(TestClass{}) == ""); 64 | } 65 | -------------------------------------------------------------------------------- /test/test_required_arguments.cpp: -------------------------------------------------------------------------------- 1 | #ifdef WITH_MODULE 2 | import argparse; 3 | #else 4 | #include 5 | #endif 6 | #include 7 | 8 | using doctest::test_suite; 9 | 10 | TEST_CASE( 11 | "Parse required arguments which are not set and don't have default value" * 12 | test_suite("required_arguments")) { 13 | argparse::ArgumentParser program("test"); 14 | program.add_argument("--output", "-o").required(); 15 | REQUIRE_THROWS(program.parse_args({"./main"})); 16 | } 17 | 18 | TEST_CASE("Parse required arguments which are set as empty value and don't " 19 | "have default value" * 20 | test_suite("required_arguments")) { 21 | argparse::ArgumentParser program("test"); 22 | program.add_argument("--output", "-o").required(); 23 | REQUIRE_THROWS(program.parse_args({"./main", "-o"})); 24 | } 25 | 26 | TEST_CASE("Parse required arguments which are set as some value and don't have " 27 | "default value" * 28 | test_suite("required_arguments")) { 29 | argparse::ArgumentParser program("test"); 30 | program.add_argument("--output", "-o").required(); 31 | program.parse_args({"./main", "-o", "filename"}); 32 | REQUIRE(program.get("--output") == "filename"); 33 | REQUIRE(program.get("-o") == "filename"); 34 | } 35 | 36 | TEST_CASE("Parse required arguments which are not set and have default value" * 37 | test_suite("required_arguments")) { 38 | argparse::ArgumentParser program("test"); 39 | program.add_argument("--output", "-o") 40 | .required() 41 | .default_value(std::string("filename")); 42 | program.parse_args({"./main"}); 43 | REQUIRE(program.get("--output") == "filename"); 44 | REQUIRE(program.get("-o") == "filename"); 45 | } 46 | 47 | TEST_CASE( 48 | "Parse required arguments which are set as empty and have default value" * 49 | test_suite("required_arguments")) { 50 | argparse::ArgumentParser program("test"); 51 | program.add_argument("--output", "-o") 52 | .required() 53 | .default_value(std::string("filename")); 54 | REQUIRE_THROWS(program.parse_args({"./main", "-o"})); 55 | } 56 | 57 | TEST_CASE("Parse required arguments which are set as some value and have " 58 | "default value" * 59 | test_suite("required_arguments")) { 60 | argparse::ArgumentParser program("test"); 61 | program.add_argument("--output", "-o") 62 | .required() 63 | .default_value(std::string("filename")); 64 | program.parse_args({"./main", "-o", "anotherfile"}); 65 | REQUIRE(program.get("--output") == "anotherfile"); 66 | REQUIRE(program.get("-o") == "anotherfile"); 67 | } 68 | -------------------------------------------------------------------------------- /test/test_scan.cpp: -------------------------------------------------------------------------------- 1 | #ifdef WITH_MODULE 2 | import argparse; 3 | #else 4 | #include 5 | #endif 6 | #include 7 | #include 8 | 9 | using doctest::test_suite; 10 | 11 | TEST_CASE_TEMPLATE("Parse a decimal integer argument" * test_suite("scan"), T, 12 | int8_t, int16_t, int32_t, int64_t, uint8_t, uint16_t, 13 | uint32_t, uint64_t) { 14 | argparse::ArgumentParser program("test"); 15 | program.add_argument("-n").scan<'d', T>(); 16 | 17 | SUBCASE("zero") { 18 | program.parse_args({"test", "-n", "0"}); 19 | REQUIRE(program.get("-n") == 0); 20 | } 21 | 22 | SUBCASE("non-negative") { 23 | program.parse_args({"test", "-n", "5"}); 24 | REQUIRE(program.get("-n") == 5); 25 | } 26 | 27 | SUBCASE("negative") { 28 | if constexpr (std::is_signed_v) { 29 | program.parse_args({"test", "-n", "-128"}); 30 | REQUIRE(program.get("-n") == -128); 31 | } else { 32 | REQUIRE_THROWS_AS(program.parse_args({"test", "-n", "-135"}), 33 | std::invalid_argument); 34 | } 35 | } 36 | 37 | SUBCASE("left-padding is not allowed") { 38 | REQUIRE_THROWS_AS(program.parse_args({"test", "-n", " 32"}), 39 | std::invalid_argument); 40 | } 41 | 42 | SUBCASE("right-padding is not allowed") { 43 | REQUIRE_THROWS_AS(program.parse_args({"test", "-n", "12 "}), 44 | std::invalid_argument); 45 | } 46 | 47 | SUBCASE("plus sign is not allowed") { 48 | REQUIRE_THROWS_AS(program.parse_args({"test", "-n", "+12"}), 49 | std::invalid_argument); 50 | } 51 | 52 | SUBCASE("does not fit") { 53 | REQUIRE_THROWS_AS( 54 | program.parse_args({"test", "-n", "987654321987654321987654321"}), 55 | std::range_error); 56 | } 57 | } 58 | 59 | TEST_CASE_TEMPLATE("Parse an octal integer argument" * test_suite("scan"), T, 60 | uint8_t, uint16_t, uint32_t, uint64_t) { 61 | argparse::ArgumentParser program("test"); 62 | program.add_argument("-n").scan<'o', T>(); 63 | 64 | SUBCASE("zero") { 65 | program.parse_args({"test", "-n", "0"}); 66 | REQUIRE(program.get("-n") == 0); 67 | } 68 | 69 | SUBCASE("with octal base") { 70 | program.parse_args({"test", "-n", "066"}); 71 | REQUIRE(program.get("-n") == 066); 72 | } 73 | 74 | SUBCASE("minus sign produces an optional argument") { 75 | REQUIRE_THROWS_AS(program.parse_args({"test", "-n", "-003"}), 76 | std::runtime_error); 77 | } 78 | 79 | SUBCASE("plus sign is not allowed") { 80 | REQUIRE_THROWS_AS(program.parse_args({"test", "-n", "+012"}), 81 | std::invalid_argument); 82 | } 83 | 84 | SUBCASE("does not fit") { 85 | REQUIRE_THROWS_AS( 86 | program.parse_args({"test", "-n", "02000000000000000000001"}), 87 | std::range_error); 88 | } 89 | } 90 | 91 | TEST_CASE_TEMPLATE("Parse a hexadecimal integer argument" * test_suite("scan"), 92 | T, uint8_t, uint16_t, uint32_t, uint64_t) { 93 | argparse::ArgumentParser program("test"); 94 | program.add_argument("-n").scan<'X', T>(); 95 | 96 | SUBCASE("with hex digit") { 97 | program.parse_args({"test", "-n", "0x1a"}); 98 | REQUIRE(program.get("-n") == 0x1a); 99 | } 100 | 101 | SUBCASE("minus sign produces an optional argument") { 102 | REQUIRE_THROWS_AS(program.parse_args({"test", "-n", "-0x1"}), 103 | std::runtime_error); 104 | } 105 | 106 | SUBCASE("plus sign is not allowed") { 107 | REQUIRE_THROWS_AS(program.parse_args({"test", "-n", "+0x1a"}), 108 | std::invalid_argument); 109 | } 110 | 111 | SUBCASE("does not fit") { 112 | REQUIRE_THROWS_AS(program.parse_args({"test", "-n", "0XFFFFFFFFFFFFFFFF1"}), 113 | std::range_error); 114 | } 115 | 116 | SUBCASE("with hex digit without prefix") { 117 | program.parse_args({"test", "-n", "1a"}); 118 | REQUIRE(program.get("-n") == 0x1a); 119 | } 120 | 121 | SUBCASE("minus sign without prefix produces an optional argument") { 122 | REQUIRE_THROWS_AS(program.parse_args({"test", "-n", "-1"}), 123 | std::invalid_argument); 124 | } 125 | 126 | SUBCASE("plus sign without prefix is not allowed") { 127 | REQUIRE_THROWS_AS(program.parse_args({"test", "-n", "+1a"}), 128 | std::invalid_argument); 129 | } 130 | 131 | SUBCASE("without prefix does not fit") { 132 | REQUIRE_THROWS_AS(program.parse_args({"test", "-n", "FFFFFFFFFFFFFFFF1"}), 133 | std::range_error); 134 | } 135 | } 136 | 137 | TEST_CASE("Parse multiple hex numbers without prefix" * test_suite("scan")) { 138 | argparse::ArgumentParser program("test"); 139 | program.add_argument("-x", "--hex") 140 | .help("bytes in hex separated by spaces") 141 | .nargs(1, std::numeric_limits::max()) 142 | .scan<'x', uint8_t>(); 143 | 144 | REQUIRE_NOTHROW( 145 | program.parse_args({"test", "-x", "f2", "b2", "10", "80", "64"})); 146 | const auto &input_bytes = program.get>("-x"); 147 | REQUIRE((input_bytes == std::vector{0xf2, 0xb2, 0x10, 0x80, 0x64})); 148 | } 149 | 150 | TEST_CASE_TEMPLATE("Parse integer argument of any format" * test_suite("scan"), 151 | T, int8_t, int16_t, int32_t, int64_t, uint8_t, uint16_t, 152 | uint32_t, uint64_t) { 153 | argparse::ArgumentParser program("test"); 154 | program.add_argument("-n").scan<'i', T>(); 155 | 156 | SUBCASE("zero") { 157 | program.parse_args({"test", "-n", "0"}); 158 | REQUIRE(program.get("-n") == 0); 159 | } 160 | 161 | SUBCASE("octal") { 162 | program.parse_args({"test", "-n", "077"}); 163 | REQUIRE(program.get("-n") == 077); 164 | } 165 | 166 | SUBCASE("no negative octal") { 167 | REQUIRE_THROWS_AS(program.parse_args({"test", "-n", "-0777"}), 168 | std::runtime_error); 169 | } 170 | 171 | SUBCASE("hex") { 172 | program.parse_args({"test", "-n", "0X2c"}); 173 | REQUIRE(program.get("-n") == 0X2c); 174 | } 175 | 176 | SUBCASE("no negative hex") { 177 | REQUIRE_THROWS_AS(program.parse_args({"test", "-n", "-0X2A"}), 178 | std::runtime_error); 179 | } 180 | 181 | SUBCASE("decimal") { 182 | program.parse_args({"test", "-n", "98"}); 183 | REQUIRE(program.get("-n") == 98); 184 | } 185 | 186 | SUBCASE("negative decimal") { 187 | if constexpr (std::is_signed_v) { 188 | program.parse_args({"test", "-n", "-39"}); 189 | REQUIRE(program.get("-n") == -39); 190 | } else { 191 | REQUIRE_THROWS_AS(program.parse_args({"test", "-n", "-39"}), 192 | std::invalid_argument); 193 | } 194 | } 195 | 196 | SUBCASE("left-padding is not allowed") { 197 | REQUIRE_THROWS_AS(program.parse_args({"test", "-n", "\t32"}), 198 | std::invalid_argument); 199 | } 200 | 201 | SUBCASE("right-padding is not allowed") { 202 | REQUIRE_THROWS_AS(program.parse_args({"test", "-n", "32\n"}), 203 | std::invalid_argument); 204 | } 205 | 206 | SUBCASE("plus sign is not allowed") { 207 | REQUIRE_THROWS_AS(program.parse_args({"test", "-n", "+670"}), 208 | std::invalid_argument); 209 | } 210 | } 211 | 212 | TEST_CASE_TEMPLATE("Parse a binary argument" * test_suite("scan"), T, uint8_t, 213 | uint16_t, uint32_t, uint64_t) { 214 | argparse::ArgumentParser program("test"); 215 | program.add_argument("-n").scan<'b', T>(); 216 | 217 | SUBCASE("with binary digit") { 218 | program.parse_args({"test", "-n", "0b101"}); 219 | REQUIRE(program.get("-n") == 0b101); 220 | } 221 | 222 | SUBCASE("minus sign produces an optional argument") { 223 | REQUIRE_THROWS_AS(program.parse_args({"test", "-n", "-0b101"}), 224 | std::runtime_error); 225 | } 226 | 227 | SUBCASE("plus sign is not allowed") { 228 | REQUIRE_THROWS_AS(program.parse_args({"test", "-n", "+0b101"}), 229 | std::invalid_argument); 230 | } 231 | 232 | SUBCASE("does not fit") { 233 | REQUIRE_THROWS_AS( 234 | program.parse_args( 235 | {"test", "-n", 236 | "0b111111111111111111111111111111111111111111111111111111111111111" 237 | "11111111111111111111111111111111111111111111111111111111111111111" 238 | "11111111111111111111111111111111111111111111111111111111111111111" 239 | "11111111111111111111111111111111111111111111111111111111111111111" 240 | "11111111111111111111111111111111111111111111111111111111111111111" 241 | "11111111111111111111111111111111111111111111111111111111111111111" 242 | "11111111111111111111111111111111111111111111111111111111111111111" 243 | "11111111111111111111111111111111111111111111111111111111111111111" 244 | "11111111111111111111111111111111111111111111111111111111111111111" 245 | "11111111111111111111111111111111111111111111111111111111111111111" 246 | "11111111111111111111111111111111111111111111111111111111111111111" 247 | "11111111111111111111111111111111111111111111111111111111111111111" 248 | "11111111111111111111111111111111111111111111111111111111111111111" 249 | "1111111111111111111111111111111111111111111111111111111111111111" 250 | "1"}), 251 | std::range_error); 252 | } 253 | } 254 | 255 | #define FLOAT_G(t, literal) \ 256 | ([] { \ 257 | if constexpr (std::is_same_v) \ 258 | return literal##f; \ 259 | else if constexpr (std::is_same_v) \ 260 | return literal; \ 261 | else if constexpr (std::is_same_v) \ 262 | return literal##l; \ 263 | }()) 264 | 265 | TEST_CASE_TEMPLATE("Parse floating-point argument of general format" * 266 | test_suite("scan"), 267 | T, float, double, long double) { 268 | argparse::ArgumentParser program("test"); 269 | program.add_argument("-n").scan<'g', T>(); 270 | 271 | SUBCASE("zero") { 272 | program.parse_args({"test", "-n", "0"}); 273 | REQUIRE(program.get("-n") == 0.); 274 | } 275 | 276 | SUBCASE("non-negative") { 277 | program.parse_args({"test", "-n", "3.14"}); 278 | REQUIRE(program.get("-n") == FLOAT_G(T, 3.14)); 279 | } 280 | 281 | SUBCASE("negative") { 282 | program.parse_args({"test", "-n", "-0.12"}); 283 | REQUIRE(program.get("-n") == FLOAT_G(T, -0.12)); 284 | } 285 | 286 | SUBCASE("left-padding is not allowed") { 287 | REQUIRE_THROWS_AS(program.parse_args({"test", "-n", "\t.32"}), 288 | std::invalid_argument); 289 | } 290 | 291 | SUBCASE("right-padding is not allowed") { 292 | REQUIRE_THROWS_AS(program.parse_args({"test", "-n", ".32\n"}), 293 | std::invalid_argument); 294 | } 295 | 296 | SUBCASE("plus sign is not allowed") { 297 | REQUIRE_THROWS_AS(program.parse_args({"test", "-n", "+.12"}), 298 | std::invalid_argument); 299 | } 300 | 301 | SUBCASE("plus sign after padding is not allowed") { 302 | REQUIRE_THROWS_AS(program.parse_args({"test", "-n", " +.12"}), 303 | std::invalid_argument); 304 | } 305 | 306 | SUBCASE("hexfloat is not allowed") { 307 | REQUIRE_THROWS_AS(program.parse_args({"test", "-n", "0x1a.3p+1"}), 308 | std::invalid_argument); 309 | } 310 | 311 | SUBCASE("does not fit") { 312 | REQUIRE_THROWS_AS(program.parse_args({"test", "-n", "1.3e+5000"}), 313 | std::range_error); 314 | } 315 | } 316 | 317 | TEST_CASE_TEMPLATE("Parse hexadecimal floating-point argument" * 318 | test_suite("scan"), 319 | T, float, double, long double) { 320 | argparse::ArgumentParser program("test"); 321 | program.add_argument("-n").scan<'a', T>(); 322 | 323 | SUBCASE("zero") { 324 | // binary-exponent-part is not optional in C++ grammar 325 | program.parse_args({"test", "-n", "0x0"}); 326 | REQUIRE(program.get("-n") == 0x0.p0); 327 | } 328 | 329 | SUBCASE("non-negative") { 330 | program.parse_args({"test", "-n", "0x1a.3p+1"}); 331 | REQUIRE(program.get("-n") == 0x1a.3p+1); 332 | } 333 | 334 | SUBCASE("minus sign produces an optional argument") { 335 | // XXX may worth a fix 336 | REQUIRE_THROWS_AS(program.parse_args({"test", "-n", "-0x0.12p1"}), 337 | std::runtime_error); 338 | } 339 | 340 | SUBCASE("plus sign is not allowed") { 341 | REQUIRE_THROWS_AS(program.parse_args({"test", "-n", "+0x1p0"}), 342 | std::invalid_argument); 343 | } 344 | 345 | SUBCASE("general format is not allowed") { 346 | REQUIRE_THROWS_AS(program.parse_args({"test", "-n", "3.14"}), 347 | std::invalid_argument); 348 | } 349 | } 350 | 351 | TEST_CASE_TEMPLATE("Parse floating-point argument of scientific format" * 352 | test_suite("scan"), 353 | T, float, double, long double) { 354 | argparse::ArgumentParser program("test"); 355 | program.add_argument("-n").scan<'e', T>(); 356 | 357 | SUBCASE("zero") { 358 | program.parse_args({"test", "-n", "0e0"}); 359 | REQUIRE(program.get("-n") == 0e0); 360 | } 361 | 362 | SUBCASE("non-negative") { 363 | program.parse_args({"test", "-n", "3.14e-1"}); 364 | REQUIRE(program.get("-n") == FLOAT_G(T, 3.14e-1)); 365 | } 366 | 367 | SUBCASE("negative") { 368 | program.parse_args({"test", "-n", "-0.12e+1"}); 369 | REQUIRE(program.get("-n") == FLOAT_G(T, -0.12e+1)); 370 | } 371 | 372 | SUBCASE("plus sign is not allowed") { 373 | REQUIRE_THROWS_AS(program.parse_args({"test", "-n", "+.12e+1"}), 374 | std::invalid_argument); 375 | } 376 | 377 | SUBCASE("fixed format is not allowed") { 378 | REQUIRE_THROWS_AS(program.parse_args({"test", "-n", "3.14"}), 379 | std::invalid_argument); 380 | } 381 | 382 | SUBCASE("hexfloat is not allowed") { 383 | REQUIRE_THROWS_AS(program.parse_args({"test", "-n", "0x1.33p+0"}), 384 | std::invalid_argument); 385 | } 386 | 387 | SUBCASE("does not fit") { 388 | REQUIRE_THROWS_AS(program.parse_args({"test", "-n", "1.3e+5000"}), 389 | std::range_error); 390 | } 391 | } 392 | 393 | TEST_CASE_TEMPLATE("Parse floating-point argument of fixed format" * 394 | test_suite("scan"), 395 | T, float, double, long double) { 396 | argparse::ArgumentParser program("test"); 397 | program.add_argument("-n").scan<'f', T>(); 398 | 399 | SUBCASE("zero") { 400 | program.parse_args({"test", "-n", ".0"}); 401 | REQUIRE(program.get("-n") == .0); 402 | } 403 | 404 | SUBCASE("non-negative") { 405 | program.parse_args({"test", "-n", "3.14"}); 406 | REQUIRE(program.get("-n") == FLOAT_G(T, 3.14)); 407 | } 408 | 409 | SUBCASE("negative") { 410 | program.parse_args({"test", "-n", "-0.12"}); 411 | REQUIRE(program.get("-n") == FLOAT_G(T, -0.12)); 412 | } 413 | 414 | SUBCASE("plus sign is not allowed") { 415 | REQUIRE_THROWS_AS(program.parse_args({"test", "-n", "+.12"}), 416 | std::invalid_argument); 417 | } 418 | 419 | SUBCASE("scientific format is not allowed") { 420 | REQUIRE_THROWS_AS(program.parse_args({"test", "-n", "3.14e+0"}), 421 | std::invalid_argument); 422 | } 423 | 424 | SUBCASE("hexfloat is not allowed") { 425 | REQUIRE_THROWS_AS(program.parse_args({"test", "-n", "0x1.33p+0"}), 426 | std::invalid_argument); 427 | } 428 | } 429 | 430 | TEST_CASE("Test that scan also works with a custom action" * 431 | test_suite("scan")) { 432 | 433 | GIVEN("an argument with scan followed by a custom action") { 434 | argparse::ArgumentParser program("test"); 435 | int res; 436 | program.add_argument("--int").scan<'i', int>().action([&](const auto &s) {res = std::stoi(s);}); 437 | 438 | WHEN("the argument is parsed") { 439 | 440 | SUBCASE("with a valid value") { 441 | program.parse_args({"./test.exe", "--int", "3"}); 442 | THEN("the value is stored") { 443 | REQUIRE(res == 3); 444 | } 445 | } 446 | 447 | SUBCASE("with an invalid value") { 448 | REQUIRE_THROWS_AS(program.parse_args({"./test.exe", "--int", "XXX"}), 449 | std::invalid_argument); 450 | } 451 | } 452 | } 453 | 454 | GIVEN("an argument with a custom action followed by scan") { 455 | argparse::ArgumentParser program("test"); 456 | int res; 457 | program.add_argument("--int").action([&](const auto &s) {res = std::stoi(s);}).scan<'i', int>(); 458 | 459 | WHEN("the argument is parsed") { 460 | 461 | SUBCASE("with a valid value") { 462 | program.parse_args({"./test.exe", "--int", "3"}); 463 | THEN("the value is stored") { 464 | REQUIRE(res == 3); 465 | } 466 | } 467 | 468 | SUBCASE("with an invalid value") { 469 | REQUIRE_THROWS_AS(program.parse_args({"./test.exe", "--int", "XXX"}), 470 | std::invalid_argument); 471 | } 472 | } 473 | } 474 | 475 | } 476 | -------------------------------------------------------------------------------- /test/test_store_into.cpp: -------------------------------------------------------------------------------- 1 | #ifdef WITH_MODULE 2 | import argparse; 3 | #else 4 | #include 5 | #endif 6 | #include 7 | #include 8 | 9 | using doctest::test_suite; 10 | 11 | TEST_CASE("Test store_into(bool), flag not specified" * 12 | test_suite("store_into")) { 13 | argparse::ArgumentParser program("test"); 14 | bool flag; 15 | program.add_argument("--flag").store_into(flag); 16 | 17 | program.parse_args({"./test.exe"}); 18 | REQUIRE(flag == false); 19 | } 20 | 21 | TEST_CASE("Test store_into(bool), flag specified" * 22 | test_suite("store_into")) { 23 | argparse::ArgumentParser program("test"); 24 | bool flag; 25 | program.add_argument("--flag").store_into(flag); 26 | 27 | program.parse_args({"./test.exe", "--flag"}); 28 | REQUIRE(flag == true); 29 | } 30 | 31 | // int cases 32 | 33 | TEST_CASE("Test store_into(int), no default value, non specified" * 34 | test_suite("store_into")) { 35 | argparse::ArgumentParser program("test"); 36 | int res = -1; 37 | program.add_argument("--int-opt").store_into(res); 38 | 39 | program.parse_args({"./test.exe"}); 40 | REQUIRE(res == -1); 41 | } 42 | 43 | TEST_CASE("Test store_into(int), default value, non specified" * 44 | test_suite("store_into")) { 45 | argparse::ArgumentParser program("test"); 46 | int res = -1; 47 | program.add_argument("--int-opt").default_value(3).store_into(res); 48 | 49 | program.parse_args({"./test.exe"}); 50 | REQUIRE(res == 3); 51 | } 52 | 53 | TEST_CASE("Test store_into(int), default value, specified" * 54 | test_suite("store_into")) { 55 | argparse::ArgumentParser program("test"); 56 | int res = -1; 57 | program.add_argument("--int-opt").default_value(3).store_into(res); 58 | 59 | program.parse_args({"./test.exe", "--int-opt", "5"}); 60 | REQUIRE(res == 5); 61 | } 62 | 63 | // integral cases 64 | 65 | TEST_CASE("Test store_into(uint8_t), no default value, non specified" * 66 | test_suite("store_into")) { 67 | argparse::ArgumentParser program("test"); 68 | uint8_t res = 55; 69 | program.add_argument("--int-opt").store_into(res); 70 | 71 | program.parse_args({"./test.exe"}); 72 | REQUIRE(res == 55); 73 | } 74 | 75 | TEST_CASE("Test store_into(uint8_t), default value, non specified" * 76 | test_suite("store_into")) { 77 | argparse::ArgumentParser program("test"); 78 | uint8_t res = 55; 79 | program.add_argument("--int-opt").default_value((uint8_t)3).store_into(res); 80 | 81 | program.parse_args({"./test.exe"}); 82 | REQUIRE(res == 3); 83 | } 84 | 85 | TEST_CASE("Test store_into(uint8_t), default value, specified" * 86 | test_suite("store_into")) { 87 | argparse::ArgumentParser program("test"); 88 | uint8_t res = 55; 89 | program.add_argument("--int-opt").default_value((uint8_t)3).store_into(res); 90 | 91 | program.parse_args({"./test.exe", "--int-opt", "5"}); 92 | REQUIRE(res == 5); 93 | } 94 | 95 | // Double cases 96 | 97 | TEST_CASE("Test store_into(double), no default value, non specified" * 98 | test_suite("store_into")) { 99 | argparse::ArgumentParser program("test"); 100 | double res = -1; 101 | program.add_argument("--double-opt").store_into(res); 102 | 103 | program.parse_args({"./test.exe"}); 104 | REQUIRE(res == -1); 105 | } 106 | 107 | TEST_CASE("Test store_into(double), default value, non specified" * 108 | test_suite("store_into")) { 109 | argparse::ArgumentParser program("test"); 110 | double res = -1; 111 | program.add_argument("--double-opt").default_value(3.5).store_into(res); 112 | 113 | program.parse_args({"./test.exe"}); 114 | REQUIRE(res == 3.5); 115 | } 116 | 117 | TEST_CASE("Test store_into(double), default value, specified" * 118 | test_suite("store_into")) { 119 | argparse::ArgumentParser program("test"); 120 | double res = -1; 121 | program.add_argument("--double-opt").default_value(3.5).store_into(res); 122 | 123 | program.parse_args({"./test.exe", "--double-opt", "5.5"}); 124 | REQUIRE(res == 5.5); 125 | } 126 | 127 | // Float cases 128 | 129 | TEST_CASE("Test store_into(float), no default value, non specified" * 130 | test_suite("store_into")) { 131 | argparse::ArgumentParser program("test"); 132 | float res = -1.0f; 133 | program.add_argument("--float-opt").store_into(res); 134 | 135 | program.parse_args({"./test.exe"}); 136 | REQUIRE(res == -1.0f); 137 | } 138 | 139 | TEST_CASE("Test store_into(float), default value, non specified" * 140 | test_suite("store_into")) { 141 | argparse::ArgumentParser program("test"); 142 | float res = -1.0f; 143 | program.add_argument("--float-opt").default_value(3.5f).store_into(res); 144 | 145 | program.parse_args({"./test.exe"}); 146 | REQUIRE(res == 3.5f); 147 | } 148 | 149 | TEST_CASE("Test store_into(float), default value, specified" * 150 | test_suite("store_into")) { 151 | argparse::ArgumentParser program("test"); 152 | float res = -1.0f; 153 | program.add_argument("--float-opt").default_value(3.5f).store_into(res); 154 | 155 | program.parse_args({"./test.exe", "--float-opt", "5.5"}); 156 | REQUIRE(res == 5.5f); 157 | } 158 | 159 | TEST_CASE("Test store_into(string), no default value, non specified" * 160 | test_suite("store_into")) { 161 | argparse::ArgumentParser program("test"); 162 | std::string res = "init"; 163 | program.add_argument("--str-opt").store_into(res); 164 | 165 | program.parse_args({"./test.exe"}); 166 | REQUIRE(res == "init"); 167 | } 168 | 169 | TEST_CASE("Test store_into(string), default value, non specified" * 170 | test_suite("store_into")) { 171 | argparse::ArgumentParser program("test"); 172 | std::string res; 173 | program.add_argument("--str-opt").default_value("default").store_into(res); 174 | 175 | program.parse_args({"./test.exe"}); 176 | REQUIRE(res == "default"); 177 | } 178 | 179 | TEST_CASE("Test store_into(string), default value, specified" * 180 | test_suite("store_into")) { 181 | argparse::ArgumentParser program("test"); 182 | std::string res; 183 | program.add_argument("--str-opt").default_value("default").store_into(res); 184 | 185 | program.parse_args({"./test.exe", "--str-opt", "foo"}); 186 | REQUIRE(res == "foo"); 187 | } 188 | 189 | TEST_CASE("Test store_into(vector of string), no default value, non specified" * 190 | test_suite("store_into")) { 191 | argparse::ArgumentParser program("test"); 192 | std::vector res; 193 | program.add_argument("--strvector-opt").append().store_into(res); 194 | 195 | program.parse_args({"./test.exe"}); 196 | REQUIRE(res == std::vector{}); 197 | } 198 | 199 | TEST_CASE("Test store_into(vector of string), default value, non specified" * 200 | test_suite("store_into")) { 201 | argparse::ArgumentParser program("test"); 202 | std::vector res; 203 | program.add_argument("--strvector-opt").append().default_value( 204 | std::vector{"a", "b"}).store_into(res); 205 | 206 | program.parse_args({"./test.exe"}); 207 | REQUIRE(res == std::vector{"a", "b"}); 208 | } 209 | 210 | TEST_CASE("Test store_into(vector of string), default value, specified" * 211 | test_suite("store_into")) { 212 | argparse::ArgumentParser program("test"); 213 | std::vector res; 214 | program.add_argument("--strvector-opt").append().default_value( 215 | std::vector{"a", "b"}).store_into(res); 216 | 217 | program.parse_args({"./test.exe", "--strvector-opt", "foo", "--strvector-opt", "bar"}); 218 | REQUIRE(res == std::vector{"foo", "bar"}); 219 | } 220 | 221 | TEST_CASE("Test store_into(vector of string), default value, multi valued, specified" * 222 | test_suite("store_into")) { 223 | argparse::ArgumentParser program("test"); 224 | std::vector res; 225 | program.add_argument("--strvector-opt").nargs(2).default_value( 226 | std::vector{"a", "b"}).store_into(res); 227 | 228 | program.parse_args({"./test.exe", "--strvector-opt", "foo", "bar"}); 229 | REQUIRE(res == std::vector{"foo", "bar"}); 230 | } 231 | 232 | TEST_CASE("Test store_into(vector of int), no default value, non specified" * 233 | test_suite("store_into")) { 234 | argparse::ArgumentParser program("test"); 235 | std::vector res; 236 | program.add_argument("--intvector-opt").append().store_into(res); 237 | 238 | program.parse_args({"./test.exe"}); 239 | REQUIRE(res == std::vector{}); 240 | } 241 | 242 | TEST_CASE("Test store_into(vector of int), default value, non specified" * 243 | test_suite("store_into")) { 244 | argparse::ArgumentParser program("test"); 245 | std::vector res; 246 | program.add_argument("--intvector-opt").append().default_value( 247 | std::vector{1, 2}).store_into(res); 248 | 249 | program.parse_args({"./test.exe"}); 250 | REQUIRE(res == std::vector{1, 2}); 251 | } 252 | 253 | TEST_CASE("Test store_into(vector of int), default value, specified" * 254 | test_suite("store_into")) { 255 | argparse::ArgumentParser program("test"); 256 | std::vector res; 257 | program.add_argument("--intvector-opt").append().default_value( 258 | std::vector{1, 2}).store_into(res); 259 | 260 | program.parse_args({"./test.exe", "--intvector-opt", "3", "--intvector-opt", "4"}); 261 | REQUIRE(res == std::vector{3, 4}); 262 | } 263 | 264 | TEST_CASE("Test store_into(vector of int), default value, multi valued, specified" * 265 | test_suite("store_into")) { 266 | argparse::ArgumentParser program("test"); 267 | std::vector res; 268 | program.add_argument("--intvector-opt").nargs(2).default_value( 269 | std::vector{1, 2}).store_into(res); 270 | 271 | program.parse_args({"./test.exe", "--intvector-opt", "3", "4"}); 272 | REQUIRE(res == std::vector{3, 4}); 273 | } 274 | 275 | TEST_CASE("Test store_into(set of int), default value, multi valued, specified" * 276 | test_suite("store_into")) { 277 | 278 | { 279 | argparse::ArgumentParser program("test"); 280 | std::set res; 281 | program.add_argument("--intset-opt").nargs(2).default_value( 282 | std::set{1, 2}).store_into(res); 283 | 284 | program.parse_args({"./test.exe", "--intset-opt", "3", "4"}); 285 | REQUIRE(res == std::set{3, 4}); 286 | } 287 | 288 | { 289 | argparse::ArgumentParser program("test"); 290 | std::set res; 291 | program.add_argument("--intset-opt").nargs(2).default_value( 292 | std::set{1, 2}).store_into(res); 293 | program.parse_args({"./test.exe"}); 294 | REQUIRE(res == std::set{1, 2}); 295 | } 296 | } 297 | 298 | TEST_CASE("Test store_into(set of string), default value, multi valued, specified" * 299 | test_suite("store_into")) { 300 | 301 | { 302 | argparse::ArgumentParser program("test"); 303 | std::set res; 304 | program.add_argument("--stringset-opt").nargs(2).default_value( 305 | std::set{"1", "2"}).store_into(res); 306 | 307 | program.parse_args({"./test.exe", "--stringset-opt", "3", "4"}); 308 | REQUIRE(res == std::set{"3", "4"}); 309 | } 310 | 311 | { 312 | argparse::ArgumentParser program("test"); 313 | std::set res; 314 | program.add_argument("--stringset-opt").nargs(2).default_value( 315 | std::set{"1", "2"}).store_into(res); 316 | program.parse_args({"./test.exe"}); 317 | REQUIRE(res == std::set{"1", "2"}); 318 | } 319 | } 320 | 321 | TEST_CASE("Test store_into(int) still works with a custom action" * 322 | test_suite("store_into")) { 323 | 324 | GIVEN("an argument with store_into followed by a custom action ") { 325 | argparse::ArgumentParser program("test"); 326 | int res; 327 | std::string string_res; 328 | program.add_argument("--int").store_into(res).action([&](const auto &s) {string_res.append(s);}); 329 | 330 | WHEN("the argument is parsed") { 331 | program.parse_args({"./test.exe", "--int", "3"}); 332 | THEN("the value is stored and the action was executed") { 333 | REQUIRE(res == 3); 334 | REQUIRE(string_res == "3"); 335 | } 336 | } 337 | } 338 | 339 | GIVEN("an argument with a custom action followed by store_into") 340 | { 341 | argparse::ArgumentParser program("test"); 342 | int res; 343 | std::string string_res; 344 | program.add_argument("--int").action([&](const auto &s) {string_res.append(s);}).store_into(res); 345 | 346 | WHEN("the argument is parsed") { 347 | program.parse_args({"./test.exe", "--int", "3"}); 348 | THEN("the value is stored and the action was executed") { 349 | REQUIRE(res == 3); 350 | REQUIRE(string_res == "3"); 351 | } 352 | } 353 | } 354 | } 355 | 356 | -------------------------------------------------------------------------------- /test/test_stringstream.cpp: -------------------------------------------------------------------------------- 1 | #ifdef WITH_MODULE 2 | import argparse; 3 | #else 4 | #include 5 | #endif 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | using doctest::test_suite; 13 | 14 | TEST_CASE("Get Version String" * test_suite("stringstream")) { 15 | std::stringstream os; 16 | argparse::ArgumentParser program("test", "1.0", 17 | argparse::default_arguments::all, false, os); 18 | program.parse_args({"test", "--version"}); 19 | REQUIRE(os.str() == "1.0\n"); 20 | } -------------------------------------------------------------------------------- /test/test_subparsers.cpp: -------------------------------------------------------------------------------- 1 | #ifdef WITH_MODULE 2 | import argparse; 3 | #else 4 | #include 5 | #endif 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | using doctest::test_suite; 13 | 14 | TEST_CASE("Add subparsers" * test_suite("subparsers")) { 15 | argparse::ArgumentParser program("test"); 16 | program.add_argument("--output"); 17 | 18 | argparse::ArgumentParser command_1("add"); 19 | command_1.add_argument("file").nargs(2); 20 | 21 | argparse::ArgumentParser command_2("clean"); 22 | 23 | program.add_subparser(command_1); 24 | program.add_subparser(command_2); 25 | 26 | program.parse_args({"test", "--output", "thrust_profile.csv"}); 27 | REQUIRE(program.is_subcommand_used("add") == false); 28 | REQUIRE(program.get("--output") == "thrust_profile.csv"); 29 | } 30 | 31 | TEST_CASE("Parse subparser command" * test_suite("subparsers")) { 32 | argparse::ArgumentParser program("test"); 33 | program.add_argument("--output"); 34 | 35 | argparse::ArgumentParser command_1("add"); 36 | command_1.add_argument("file").nargs(2); 37 | 38 | argparse::ArgumentParser command_2("clean"); 39 | command_2.add_argument("--fullclean") 40 | .default_value(false) 41 | .implicit_value(true); 42 | 43 | program.add_subparser(command_1); 44 | program.add_subparser(command_2); 45 | 46 | SUBCASE("command 1") { 47 | program.parse_args({"test", "add", "file1.txt", "file2.txt"}); 48 | REQUIRE(program.is_subcommand_used("add") == true); 49 | REQUIRE(command_1.is_used("file")); 50 | REQUIRE((command_1.get>("file") == 51 | std::vector{"file1.txt", "file2.txt"})); 52 | } 53 | 54 | SUBCASE("command 2") { 55 | program.parse_args({"test", "clean", "--fullclean"}); 56 | REQUIRE(program.is_subcommand_used("clean") == true); 57 | REQUIRE(command_2.get("--fullclean") == true); 58 | } 59 | } 60 | 61 | TEST_CASE("Parse subparser command with optional argument" * 62 | test_suite("subparsers")) { 63 | argparse::ArgumentParser program("test"); 64 | program.add_argument("--verbose").flag(); 65 | 66 | argparse::ArgumentParser command_1("add"); 67 | command_1.add_argument("file"); 68 | 69 | argparse::ArgumentParser command_2("clean"); 70 | command_2.add_argument("--fullclean") 71 | .default_value(false) 72 | .implicit_value(true); 73 | 74 | program.add_subparser(command_1); 75 | program.add_subparser(command_2); 76 | 77 | SUBCASE("Optional argument BEFORE subcommand") { 78 | program.parse_args({"test", "--verbose", "clean", "--fullclean"}); 79 | REQUIRE(program.is_subcommand_used("clean") == true); 80 | REQUIRE(program.get("--verbose") == true); 81 | REQUIRE(command_2.get("--fullclean") == true); 82 | } 83 | 84 | SUBCASE("Optional argument AFTER subcommand") { 85 | REQUIRE_THROWS_WITH_AS( 86 | program.parse_args({"test", "clean", "--fullclean", "--verbose"}), 87 | "Unknown argument: --verbose", std::runtime_error); 88 | } 89 | } 90 | 91 | TEST_CASE("Parse subparser command with parent parser" * 92 | test_suite("subparsers")) { 93 | argparse::ArgumentParser program("test"); 94 | 95 | argparse::ArgumentParser parent("parent"); 96 | parent.add_argument("--verbose").flag(); 97 | program.add_parents(parent); 98 | 99 | argparse::ArgumentParser command_1("add"); 100 | command_1.add_argument("file"); 101 | 102 | argparse::ArgumentParser command_2("clean"); 103 | command_2.add_argument("--fullclean") 104 | .default_value(false) 105 | .implicit_value(true); 106 | 107 | program.add_subparser(command_1); 108 | program.add_subparser(command_2); 109 | 110 | SUBCASE("Optional argument BEFORE subcommand") { 111 | program.parse_args({"test", "--verbose", "clean", "--fullclean"}); 112 | REQUIRE(program.is_subcommand_used("clean") == true); 113 | REQUIRE(program.get("--verbose") == true); 114 | REQUIRE(command_2.get("--fullclean") == true); 115 | } 116 | 117 | SUBCASE("Optional argument AFTER subcommand") { 118 | REQUIRE_THROWS_WITH_AS( 119 | program.parse_args({"test", "clean", "--fullclean", "--verbose"}), 120 | "Unknown argument: --verbose", std::runtime_error); 121 | } 122 | } 123 | 124 | TEST_CASE("Parse git commands" * test_suite("subparsers")) { 125 | argparse::ArgumentParser program("git"); 126 | 127 | argparse::ArgumentParser add_command("add"); 128 | add_command.add_argument("files").remaining(); 129 | 130 | argparse::ArgumentParser commit_command("commit"); 131 | commit_command.add_argument("-a").flag(); 132 | 133 | commit_command.add_argument("-m"); 134 | 135 | argparse::ArgumentParser catfile_command("cat-file"); 136 | catfile_command.add_argument("-t"); 137 | catfile_command.add_argument("-p"); 138 | 139 | argparse::ArgumentParser submodule_command("submodule"); 140 | argparse::ArgumentParser submodule_update_command("update"); 141 | submodule_update_command.add_argument("--init") 142 | .default_value(false) 143 | .implicit_value(true); 144 | submodule_update_command.add_argument("--recursive") 145 | .default_value(false) 146 | .implicit_value(true); 147 | submodule_command.add_subparser(submodule_update_command); 148 | 149 | program.add_subparser(add_command); 150 | program.add_subparser(commit_command); 151 | program.add_subparser(catfile_command); 152 | program.add_subparser(submodule_command); 153 | 154 | SUBCASE("git add") { 155 | program.parse_args({"git", "add", "main.cpp", "foo.hpp", "foo.cpp"}); 156 | REQUIRE(program.is_subcommand_used("add") == true); 157 | REQUIRE((add_command.get>("files") == 158 | std::vector{"main.cpp", "foo.hpp", "foo.cpp"})); 159 | } 160 | 161 | SUBCASE("git commit") { 162 | program.parse_args({"git", "commit", "-am", "Initial commit"}); 163 | REQUIRE(program.is_subcommand_used("commit") == true); 164 | REQUIRE(commit_command.get("-a") == true); 165 | REQUIRE(commit_command.get("-m") == 166 | std::string{"Initial commit"}); 167 | } 168 | 169 | SUBCASE("git cat-file -t") { 170 | program.parse_args({"git", "cat-file", "-t", "3739f5"}); 171 | REQUIRE(program.is_subcommand_used("cat-file") == true); 172 | REQUIRE(catfile_command.get("-t") == std::string{"3739f5"}); 173 | } 174 | 175 | SUBCASE("git cat-file -p") { 176 | program.parse_args({"git", "cat-file", "-p", "3739f5"}); 177 | REQUIRE(program.is_subcommand_used("cat-file") == true); 178 | REQUIRE(catfile_command.get("-p") == std::string{"3739f5"}); 179 | } 180 | 181 | SUBCASE("git submodule update") { 182 | program.parse_args({"git", "submodule", "update"}); 183 | REQUIRE(program.is_subcommand_used("submodule") == true); 184 | REQUIRE(submodule_command.is_subcommand_used("update") == true); 185 | } 186 | 187 | SUBCASE("git submodule update --init") { 188 | program.parse_args({"git", "submodule", "update", "--init"}); 189 | REQUIRE(program.is_subcommand_used("submodule") == true); 190 | REQUIRE(submodule_command.is_subcommand_used("update") == true); 191 | REQUIRE(submodule_update_command.get("--init") == true); 192 | REQUIRE(submodule_update_command.get("--recursive") == false); 193 | } 194 | 195 | SUBCASE("git submodule update --recursive") { 196 | program.parse_args({"git", "submodule", "update", "--recursive"}); 197 | REQUIRE(program.is_subcommand_used("submodule") == true); 198 | REQUIRE(submodule_command.is_subcommand_used("update") == true); 199 | REQUIRE(submodule_update_command.get("--recursive") == true); 200 | } 201 | 202 | SUBCASE("git submodule update --init --recursive") { 203 | program.parse_args({"git", "submodule", "update", "--init", "--recursive"}); 204 | REQUIRE(program.is_subcommand_used("submodule") == true); 205 | REQUIRE(submodule_command.is_subcommand_used("update") == true); 206 | REQUIRE(submodule_update_command.get("--init") == true); 207 | REQUIRE(submodule_update_command.get("--recursive") == true); 208 | } 209 | } 210 | 211 | TEST_CASE("Check is_subcommand_used after parse" * test_suite("subparsers")) { 212 | argparse::ArgumentParser command_1("add"); 213 | 214 | argparse::ArgumentParser command_2("clean"); 215 | command_2.add_argument("--fullclean") 216 | .default_value(false) 217 | .implicit_value(true); 218 | 219 | argparse::ArgumentParser program("test"); 220 | program.add_subparser(command_1); 221 | program.add_subparser(command_2); 222 | 223 | SUBCASE("command 1") { 224 | program.parse_args({"test", "add"}); 225 | REQUIRE(program.is_subcommand_used("add") == true); 226 | REQUIRE(program.is_subcommand_used(command_1) == true); 227 | REQUIRE(program.is_subcommand_used("clean") == false); 228 | REQUIRE(program.is_subcommand_used(command_2) == false); 229 | } 230 | 231 | SUBCASE("command 2") { 232 | program.parse_args({"test", "clean", "--fullclean"}); 233 | REQUIRE(program.is_subcommand_used("add") == false); 234 | REQUIRE(program.is_subcommand_used(command_1) == false); 235 | REQUIRE(program.is_subcommand_used("clean") == true); 236 | REQUIRE(program.is_subcommand_used(command_2) == true); 237 | } 238 | 239 | SUBCASE("none") { 240 | program.parse_args({"test"}); 241 | REQUIRE(program.is_subcommand_used("add") == false); 242 | REQUIRE(program.is_subcommand_used(command_1) == false); 243 | REQUIRE(program.is_subcommand_used("clean") == false); 244 | REQUIRE(program.is_subcommand_used(command_2) == false); 245 | } 246 | } 247 | 248 | static bool contains(const std::string &haystack, const std::string &needle) { 249 | return haystack.find(needle) != std::string::npos; 250 | } 251 | 252 | TEST_CASE("Check set_suppress" * test_suite("subparsers")) { 253 | argparse::ArgumentParser command("cmd"); 254 | command.add_argument("arg").remaining(); 255 | 256 | argparse::ArgumentParser program("test"); 257 | program.add_subparser(command); 258 | 259 | SUBCASE("help message contain info if subcommand not suppressed") { 260 | command.set_suppress(false); 261 | REQUIRE(contains(program.help().str(), "Subcommands") == true); 262 | REQUIRE(contains(program.help().str(), "cmd") == true); 263 | } 264 | 265 | SUBCASE("help message does not contain info if subcommand suppressed") { 266 | command.set_suppress(true); 267 | REQUIRE(contains(program.help().str(), "Subcommands") == false); 268 | REQUIRE(contains(program.help().str(), "cmd") == false); 269 | } 270 | 271 | SUBCASE("help message contain info if not all subcommands suppressed") { 272 | argparse::ArgumentParser command_2("command_2"); 273 | program.add_subparser(command_2); 274 | 275 | command.set_suppress(true); 276 | command_2.set_suppress(false); 277 | 278 | REQUIRE(contains(program.help().str(), "Subcommands") == true); 279 | REQUIRE(contains(program.help().str(), "cmd") == false); 280 | REQUIRE(contains(program.help().str(), "command_2") == true); 281 | } 282 | } 283 | 284 | 285 | TEST_CASE("Help of subparsers" * test_suite("subparsers")) { 286 | argparse::ArgumentParser program("test"); 287 | 288 | argparse::ArgumentParser command_1("add", "1.0", argparse::default_arguments::version); 289 | 290 | std::stringstream buffer; 291 | command_1.add_argument("--help") 292 | .action([&](const auto &) { buffer << command_1; }) 293 | .default_value(false) 294 | .implicit_value(true) 295 | .nargs(0); 296 | 297 | program.add_subparser(command_1); 298 | 299 | REQUIRE(command_1.usage() == "Usage: test add [--version] [--help]"); 300 | 301 | REQUIRE(buffer.str().empty()); 302 | program.parse_args({"test", "add", "--help"}); 303 | REQUIRE(buffer.str() == "Usage: test add [--version] [--help]\n" 304 | "\n" 305 | "Optional arguments:\n" 306 | " -v, --version prints version information and exits \n" 307 | " --help \n"); 308 | } 309 | -------------------------------------------------------------------------------- /test/test_utility.hpp: -------------------------------------------------------------------------------- 1 | #ifndef ARGPARSE_TEST_UTILITY_HPP 2 | #define ARGPARSE_TEST_UTILITY_HPP 3 | 4 | #include 5 | 6 | namespace testutility { 7 | // Get value at index from std::list 8 | template 9 | T get_from_list(const std::list& aList, size_t aIndex) { 10 | if (aList.size() > aIndex) { 11 | auto tIterator = aList.begin(); 12 | std::advance(tIterator, aIndex); 13 | return *tIterator; 14 | } 15 | return T(); 16 | } 17 | } 18 | 19 | #endif //ARGPARSE_TEST_UTILITY_HPP 20 | -------------------------------------------------------------------------------- /test/test_version.cpp: -------------------------------------------------------------------------------- 1 | #ifdef WITH_MODULE 2 | import argparse; 3 | #else 4 | #include 5 | #endif 6 | #include 7 | #include 8 | 9 | using doctest::test_suite; 10 | 11 | TEST_CASE("Users can print version and exit" * test_suite("version") * 12 | doctest::skip()) { 13 | argparse::ArgumentParser program("cli-test", "1.9.0"); 14 | program.add_argument("-d", "--dir").required(); 15 | program.parse_args({"test", "--version"}); 16 | REQUIRE(program.get("--version") == "1.9.0"); 17 | } 18 | 19 | TEST_CASE("Users can disable default -v/--version" * test_suite("version")) { 20 | argparse::ArgumentParser program("test", "1.0", 21 | argparse::default_arguments::help); 22 | REQUIRE_THROWS_WITH_AS(program.parse_args({"test", "--version"}), 23 | "Unknown argument: --version", std::runtime_error); 24 | } 25 | 26 | TEST_CASE("Users can replace default -v/--version" * test_suite("version")) { 27 | std::string version{"3.1415"}; 28 | argparse::ArgumentParser program("test", version, 29 | argparse::default_arguments::help); 30 | std::stringstream buffer; 31 | program.add_argument("-v", "--version") 32 | .action([&](const auto &) { buffer << version; }) 33 | .default_value(true) 34 | .implicit_value(false) 35 | .nargs(0); 36 | 37 | REQUIRE(buffer.str().empty()); 38 | program.parse_args({"test", "--version"}); 39 | REQUIRE_FALSE(buffer.str().empty()); 40 | } 41 | -------------------------------------------------------------------------------- /tools/build.bat: -------------------------------------------------------------------------------- 1 | call "C:\Program Files (x86)\Microsoft Visual Studio\2017\BuildTools\VC\Auxiliary\Build\vcvars64.bat" 2 | 3 | cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=Release -DARGPARSE_BUILD_TESTS=ON 4 | ninja -C build 5 | -------------------------------------------------------------------------------- /tools/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=Release -DARGPARSE_BUILD_TESTS=ON 4 | ninja -C build 5 | -------------------------------------------------------------------------------- /xmake.lua: -------------------------------------------------------------------------------- 1 | set_xmakever("2.8.2") 2 | set_project("argparse") 3 | 4 | set_version("3.2.0", { build = "%Y%m%d%H%M" }) 5 | 6 | option("enable_module") 7 | option("enable_std_import", { defines = "ARGPARSE_MODULE_USE_STD_MODULE" }) 8 | option("enable_tests") 9 | option("enable_samples") 10 | 11 | add_cxxflags( 12 | "-Wall", 13 | "-Wno-long-long", 14 | "-pedantic", 15 | "-Wsign-conversion", 16 | "-Wshadow", 17 | "-Wconversion", 18 | { toolsets = { "clang", "gcc" } } 19 | ) 20 | add_cxxflags("cl::/W4") 21 | 22 | if is_plat("windows") then 23 | add_defines("_CRT_SECURE_NO_WARNINGS") 24 | end 25 | 26 | target("argparse", function() 27 | set_languages("c++17") 28 | set_kind("headeronly") 29 | if get_config("enable_module") then 30 | set_languages("c++20") 31 | set_kind("static") -- static atm because of a XMake bug, headeronly doesn't generate package module metadata 32 | end 33 | 34 | add_options("enable_std_import") 35 | 36 | add_includedirs("include", { public = true }) 37 | add_headerfiles("include/argparse/argparse.hpp") 38 | if get_config("enable_module") then 39 | add_files("module/argparse.cppm", { install = true }) 40 | end 41 | end) 42 | 43 | if get_config("enable_tests") then 44 | target("argparse_tests", function() 45 | set_kind("binary") 46 | set_languages("c++17") 47 | set_basename("tests") 48 | 49 | add_includedirs("test") 50 | 51 | add_files("test/main.cpp", { defines = { "DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN" } }) 52 | add_files("test/**.cpp") 53 | 54 | add_deps("argparse") 55 | end) 56 | 57 | if get_config("enable_module") then 58 | target("argparse_module_tests", function() 59 | set_kind("binary") 60 | set_languages("c++20") 61 | set_basename("module_tests") 62 | 63 | add_defines("WITH_MODULE") 64 | 65 | add_includedirs("test") 66 | 67 | add_files("test/main.cpp", { defines = { "DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN" } }) 68 | add_files("test/**.cpp") 69 | add_files("test/argparse_details.cppm") 70 | 71 | add_deps("argparse") 72 | end) 73 | end 74 | end 75 | 76 | if get_config("enable_samples") then 77 | for _, sample_file in ipairs(os.files("samples/*.cpp")) do 78 | target(path.basename(sample_file), function() 79 | set_kind("binary") 80 | set_languages("c++17") 81 | 82 | add_files(sample_file) 83 | 84 | set_policy("build.c++.modules", false) 85 | 86 | add_deps("argparse") 87 | end) 88 | end 89 | end 90 | --------------------------------------------------------------------------------