├── .github └── workflows │ ├── build.yaml │ └── docs.yaml ├── .gitignore ├── LICENSE.txt ├── README.md ├── codecov.yml ├── docs ├── ad.tree ├── build_code_snippets.d ├── c.list ├── cfg │ └── buildprofiles.xml ├── code_snippets │ ├── allowed_values.d │ ├── ansiStylingArgument.d │ ├── arity.d │ ├── arity_no_values.d │ ├── call_parser1.d │ ├── call_parser2.d │ ├── call_parser4.d │ ├── call_parser5.d │ ├── config_addHelpArgument.d │ ├── config_assignChar.d │ ├── config_assignKeyValueChar.d │ ├── config_bundling.d │ ├── config_caseSensitive.d │ ├── config_endOfNamedArgs.d │ ├── config_errorHandler.d │ ├── config_namedArgPrefix.d │ ├── config_styling.d │ ├── config_stylingMode.d │ ├── config_valueSep.d │ ├── config_variadicNamedArgument.d │ ├── double_dash.d │ ├── envfallback.d │ ├── help_argument_group.d │ ├── help_example.d │ ├── mutually_exclusive.d │ ├── mutually_exclusive_required.d │ ├── mutually_required.d │ ├── mutually_required_required.d │ ├── named_arguments.d │ ├── optional_required_arguments.d │ ├── parsing_customization.d │ ├── positional_arguments.d │ ├── styling_helloworld.d │ ├── styling_help.d │ ├── subcommands.d │ ├── subcommands_common_args.d │ ├── subcommands_default.d │ ├── subcommands_enumerate.d │ ├── subcommands_names.d │ ├── types_array.d │ ├── types_assoc_array.d │ ├── types_counter.d │ ├── types_counter_bundling.d │ ├── types_custom.d │ ├── types_enum.d │ ├── types_enum_custom_values.d │ ├── types_function.d │ └── types_number_string.d ├── dub.json ├── images │ ├── allowed_values_error.png │ ├── allowed_values_error_dark.png │ ├── ansiStylingArgument.png │ ├── ansiStylingArgument_dark.png │ ├── config_help.png │ ├── config_help_dark.png │ ├── config_styling.png │ ├── config_stylingMode.png │ ├── config_stylingMode_dark.png │ ├── config_styling_dark.png │ ├── default_styling.png │ ├── default_styling_dark.png │ ├── hello_world_with_uda.png │ ├── hello_world_with_uda_dark.png │ ├── hello_world_without_uda.png │ ├── hello_world_without_uda_dark.png │ ├── help_argument_group.png │ ├── help_argument_group_dark.png │ ├── help_example.png │ ├── help_example1.png │ ├── help_example1_dark.png │ ├── help_example_dark.png │ ├── styling_help.png │ └── styling_help_dark.png ├── redirection-rules.xml ├── topics │ ├── ANSI-coloring-and-styling.md │ ├── Argument-dependencies.md │ ├── Arguments-bundling.md │ ├── Arguments.md │ ├── Arity.md │ ├── Calling-the-parser.md │ ├── End-of-named-arguments.md │ ├── Environment-Fallback.md │ ├── Getting-started.md │ ├── Help-generation.md │ ├── Introduction.md │ ├── Named-arguments.md │ ├── Optional-and-required-arguments.md │ ├── Parsing-customization.md │ ├── Positional-arguments.md │ ├── Restrict-allowed-values.md │ ├── Shell-completion.md │ ├── Subcommands.md │ ├── Supported-types.md │ └── reference │ │ ├── AllowedValues.md │ │ ├── ArgumentGroup.md │ │ ├── CLI-API.md │ │ ├── Command.md │ │ ├── Config.md │ │ ├── MutuallyExclusive.md │ │ ├── Param-RawParam.md │ │ ├── PositionalNamedArgument.md │ │ ├── RequiredTogether.md │ │ ├── Result.md │ │ ├── SubCommand.md │ │ └── ansiStylingArgument.md ├── v.list └── writerside.cfg ├── dub.json ├── examples ├── .gitignore ├── completion │ ├── separate_main │ │ ├── app │ │ │ ├── app.d │ │ │ └── dub.json │ │ ├── completer │ │ │ ├── app.d │ │ │ └── dub.json │ │ └── source │ │ │ └── cli.d │ └── single_main │ │ ├── app │ │ └── dub.json │ │ ├── completer │ │ └── dub.json │ │ └── source │ │ └── app.d ├── dub.json ├── getting_started │ ├── with_uda │ │ ├── app.d │ │ └── dub.json │ └── without_uda │ │ ├── app.d │ │ └── dub.json └── sub_commands │ ├── advanced │ ├── app.d │ └── dub.json │ ├── basic │ ├── app.d │ └── dub.json │ ├── common_args │ ├── app.d │ └── dub.json │ └── default │ ├── app.d │ └── dub.json ├── source └── argparse │ ├── ansi.d │ ├── api │ ├── ansi.d │ ├── argument.d │ ├── argumentgroup.d │ ├── cli.d │ ├── command.d │ ├── enums.d │ ├── restriction.d │ └── subcommand.d │ ├── config.d │ ├── internal │ ├── actionfunc.d │ ├── arguments.d │ ├── argumentuda.d │ ├── argumentudahelpers.d │ ├── calldispatcher.d │ ├── command.d │ ├── commandinfo.d │ ├── commandstack.d │ ├── completer.d │ ├── enumhelpers.d │ ├── errorhelpers.d │ ├── help.d │ ├── lazystring.d │ ├── novalueactionfunc.d │ ├── parsefunc.d │ ├── parser.d │ ├── restriction.d │ ├── style.d │ ├── tokenizer.d │ ├── typetraits.d │ ├── utils.d │ ├── validationfunc.d │ └── valueparser.d │ ├── package.d │ ├── param.d │ └── result.d └── tools └── deps.sh /.github/workflows/build.yaml: -------------------------------------------------------------------------------- 1 | name: Build sources 2 | on: 3 | push: 4 | paths-ignore: 5 | - '**.md' 6 | pull_request: 7 | paths-ignore: 8 | - '**.md' 9 | schedule: 10 | - cron: '0 0 * * 0' 11 | 12 | jobs: 13 | matrix-build: 14 | name: Matrix Build 15 | strategy: 16 | matrix: 17 | os: [ubuntu-latest, windows-latest] 18 | dc: [dmd-latest, ldc-latest] 19 | include: 20 | - { os: macOS-latest, dc: ldc-latest } 21 | 22 | runs-on: ${{ matrix.os }} 23 | steps: 24 | - uses: actions/checkout@v4 25 | 26 | - name: Install D compiler 27 | uses: dlang-community/setup-dlang@v1 28 | with: 29 | compiler: ${{ matrix.dc }} 30 | 31 | - name: Run tests 32 | run: dub test --build=unittest-cov --verbose 33 | 34 | - name: Run tests (dip1000) 35 | env: 36 | DFLAGS: -dip1000 37 | run: dub test --build=unittest-cov --verbose 38 | 39 | - name: Run tests (in) 40 | env: 41 | DFLAGS: -preview=in 42 | run: dub test --build=unittest-cov --verbose 43 | 44 | - name: Run tests (dip1000 & in) 45 | env: 46 | DFLAGS: -dip1000 -preview=in 47 | run: dub test --build=unittest-cov --verbose 48 | 49 | - name: Build examples 50 | run: dub --root examples build --parallel --verbose 51 | 52 | - name: Build code snippets in documentation 53 | run: dub --root docs --verbose run 54 | 55 | - name: Upload coverage to Codecov 56 | uses: codecov/codecov-action@v3 57 | with: 58 | token: ${{ secrets.CODECOV_TOKEN }} 59 | -------------------------------------------------------------------------------- /.github/workflows/docs.yaml: -------------------------------------------------------------------------------- 1 | name: Build documentation 2 | 3 | on: 4 | push: 5 | branches: ["master"] 6 | pull_request: 7 | 8 | # Gives the workflow permissions to clone the repo and create a page deployment 9 | permissions: 10 | id-token: write 11 | pages: write 12 | 13 | env: 14 | # Name of help module and instance id separated by a slash 15 | INSTANCE: docs/ad 16 | # AD = the ID of the instance in capital letters 17 | ARTIFACT: webHelpAD2-all.zip 18 | # Writerside docker image version 19 | DOCKER_VERSION: 241.15989 20 | # Add the variable below to upload Algolia indexes 21 | # AD = the ID of the instance in capital letters 22 | ALGOLIA_ARTIFACT: algolia-indexes-AD.zip 23 | 24 | jobs: 25 | build: 26 | runs-on: ubuntu-latest 27 | steps: 28 | - name: Checkout repository 29 | uses: actions/checkout@v4 30 | 31 | - name: Build Writerside docs using Docker 32 | uses: JetBrains/writerside-github-action@v4 33 | with: 34 | instance: ${{ env.INSTANCE }} 35 | artifact: ${{ env.ARTIFACT }} 36 | docker-version: ${{ env.DOCKER_VERSION }} 37 | 38 | - name: Upload documentation 39 | uses: actions/upload-artifact@v4 40 | with: 41 | name: docs 42 | retention-days: 7 43 | path: | 44 | artifacts/${{ env.ARTIFACT }} 45 | artifacts/report.json 46 | 47 | # Add the step below to upload Algolia indexes 48 | - name: Upload algolia-indexes 49 | uses: actions/upload-artifact@v4 50 | with: 51 | name: algolia-indexes 52 | retention-days: 7 53 | path: artifacts/${{ env.ALGOLIA_ARTIFACT }} 54 | 55 | # fail the build when documentation contains errors 56 | test: 57 | # Requires build job results 58 | needs: build 59 | runs-on: ubuntu-latest 60 | steps: 61 | - name: Download artifacts 62 | uses: actions/download-artifact@v4 63 | with: 64 | name: docs 65 | path: artifacts 66 | 67 | - name: Test documentation 68 | uses: JetBrains/writerside-checker-action@v1 69 | with: 70 | instance: ${{ env.INSTANCE }} 71 | 72 | deploy: 73 | if: ${{ github.ref_name == 'master' }} 74 | environment: 75 | name: github-pages 76 | url: ${{ steps.deployment.outputs.page_url }} 77 | # Requires the test job results 78 | needs: test 79 | runs-on: ubuntu-latest 80 | steps: 81 | - name: Download artifact 82 | uses: actions/download-artifact@v4 83 | with: 84 | name: docs 85 | 86 | - name: Unzip artifact 87 | run: unzip -O UTF-8 -qq ${{ env.ARTIFACT }} -d dir 88 | 89 | - name: Setup Pages 90 | uses: actions/configure-pages@v4 91 | 92 | - name: Upload artifact 93 | uses: actions/upload-pages-artifact@v3 94 | with: 95 | path: dir 96 | 97 | - name: Deploy to GitHub Pages 98 | id: deployment 99 | uses: actions/deploy-pages@v4 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .dub 2 | .idea 3 | out/ 4 | docs.json 5 | __dummy.html 6 | /argparse 7 | argparse.iml 8 | *argparse.so 9 | *argparse.dylib 10 | *argparse.dll 11 | *argparse.a 12 | *argparse.lib 13 | argparse-test-* 14 | *.exe 15 | *.o 16 | *.obj 17 | *.lst 18 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Boost Software License - Version 1.0 - August 17th, 2003 2 | 3 | Permission is hereby granted, free of charge, to any person or organization 4 | obtaining a copy of the software and accompanying documentation covered by 5 | this license (the "Software") to use, reproduce, display, distribute, 6 | execute, and transmit the Software, and to prepare derivative works of the 7 | Software, and to permit third-parties to whom the Software is furnished to 8 | do so, all subject to the following: 9 | 10 | The copyright notices in the Software and this entire statement, including 11 | the above license grant, this restriction and the following disclaimer, 12 | must be included in all copies of the Software, in whole or in part, and 13 | all derivative works of the Software, unless such copies or derivative 14 | works are solely in the form of machine-executable object code generated by 15 | a source language processor. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT 20 | SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE 21 | FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, 22 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Build](https://github.com/andrey-zherikov/argparse/actions/workflows/build.yaml/badge.svg) 2 | [![codecov](https://codecov.io/gh/andrey-zherikov/argparse/branch/master/graph/badge.svg?token=H810TEZEHP)](https://codecov.io/gh/andrey-zherikov/argparse) 3 | 4 | # Parser for command line arguments 5 | 6 | `argparse` is a flexible utility for [D programming language](https://dlang.org/) to parse command line arguments. 7 | 8 | > [!WARNING] 9 | > :warning: Version 2.* contains breaking changes comparing to 1.* - see [change log](https://github.com/andrey-zherikov/argparse/releases) for details 10 | > 11 | > :warning: This includes changes in documentation - documentation for 1.* version is [here](https://github.com/andrey-zherikov/argparse/blob/release/1.x/README.md) 12 | 13 | 14 | ## Features 15 | 16 | - [Positional arguments](https://andrey-zherikov.github.io/argparse/positional-arguments.html): 17 | - Automatic type conversion of the value. 18 | - Required by default, can be marked as optional. 19 | - [Named arguments](https://andrey-zherikov.github.io/argparse/named-arguments.html): 20 | - Multiple names are supported, including short (`-v`) and long (`--verbose`) ones. 21 | - [Case-sensitive/-insensitive parsing.](https://andrey-zherikov.github.io/argparse/config.html#caseSensitive) 22 | - [Bundling of short names](https://andrey-zherikov.github.io/argparse/arguments-bundling.html) (`-vvv` is same as `-v -v -v`). 23 | - [Equals sign is accepted](https://andrey-zherikov.github.io/argparse/config.html#assignChar) (`-v=debug`, `--verbose=debug`). 24 | - Automatic type conversion of the value. 25 | - Optional by default, can be marked as required. 26 | - [Support different types of destination data member](https://andrey-zherikov.github.io/argparse/supported-types.html): 27 | - Scalar (e.g., `int`, `float`, `bool`). 28 | - String arguments. 29 | - Enum arguments. 30 | - Array arguments. 31 | - Hash (associative array) arguments. 32 | - Callbacks. 33 | - [Different workflows are supported](https://andrey-zherikov.github.io/argparse/calling-the-parser.html): 34 | - Mixin to inject standard `main` function. 35 | - Parsing of known arguments only (returning not recognized ones). 36 | - Enforcing that there are no unknown arguments provided. 37 | - [Shell completion](https://andrey-zherikov.github.io/argparse/shell-completion.html). 38 | - [Options terminator](https://andrey-zherikov.github.io/argparse/end-of-named-arguments.html) (e.g., parsing up to `--` leaving any argument specified after it). 39 | - [Arguments groups](https://andrey-zherikov.github.io/argparse/argument-dependencies.html). 40 | - [Subcommands](https://andrey-zherikov.github.io/argparse/subcommands.html). 41 | - [Fully customizable parsing](https://andrey-zherikov.github.io/argparse/parsing-customization.html): 42 | - Raw (`string`) data validation (i.e., before parsing). 43 | - Custom conversion of argument value (`string` -> any `destination type`). 44 | - Validation of parsed data (i.e., after conversion to `destination type`). 45 | - Custom action on parsed data (doing something different from storing the parsed value in a member of destination 46 | object). 47 | - [ANSI colors and styles](https://andrey-zherikov.github.io/argparse/ansi-coloring-and-styling.html). 48 | - [Built-in reporting of error happened during argument parsing](https://andrey-zherikov.github.io/argparse/config.html#errorHandler). 49 | - [Built-in help generation](https://andrey-zherikov.github.io/argparse/help-generation.html). 50 | 51 | ## Documentation 52 | 53 | Please find up-to-date documentation [here](https://andrey-zherikov.github.io/argparse/). 54 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | coverage: 2 | status: 3 | project: 4 | default: 5 | threshold: 5% 6 | patch: 7 | default: 8 | threshold: 5% 9 | -------------------------------------------------------------------------------- /docs/ad.tree: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | -------------------------------------------------------------------------------- /docs/build_code_snippets.d: -------------------------------------------------------------------------------- 1 | import std.stdio; 2 | import std.path; 3 | import std.file; 4 | import std.array; 5 | import std.process; 6 | import std.parallelism; 7 | 8 | immutable string root_path = __FILE_FULL_PATH__.dirName.chainPath("..").asNormalizedPath.array; 9 | immutable string source_path = root_path.chainPath("source").array; 10 | immutable string code_snippets_path = root_path.chainPath("docs", "code_snippets").array; 11 | 12 | int main() 13 | { 14 | writeln("Root directory: ", root_path); 15 | writeln("Source directory: ", source_path); 16 | writeln("Code snippets directory: ", code_snippets_path); 17 | 18 | shared bool failed = false; 19 | 20 | foreach(file; parallel(dirEntries(code_snippets_path, "*.d",SpanMode.shallow), 1)) 21 | { 22 | if(!failed) 23 | { 24 | writeln("\nBuilding ", file); 25 | 26 | immutable command = [ 27 | "rdmd", 28 | `--eval=mixin(import("` ~ file.baseName ~ `"))`, 29 | `-I` ~ source_path, 30 | `-J` ~ code_snippets_path 31 | ]; 32 | 33 | auto proc = execute(command); 34 | if(proc.status != 0) 35 | { 36 | writeln("Command `", command, "` failed, output:"); 37 | writeln(proc.output); 38 | failed = true; 39 | } 40 | } 41 | } 42 | 43 | return failed ? 1 : 0; 44 | } -------------------------------------------------------------------------------- /docs/c.list: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /docs/cfg/buildprofiles.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | false 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /docs/code_snippets/allowed_values.d: -------------------------------------------------------------------------------- 1 | import argparse; 2 | 3 | struct T 4 | { 5 | @(NamedArgument.AllowedValues("apple","pear","banana")) 6 | string fruit; 7 | } 8 | 9 | T t; 10 | assert(CLI!T.parseArgs(t, ["--fruit", "apple"])); 11 | assert(t == T("apple")); 12 | 13 | // "kiwi" is not allowed 14 | assert(!CLI!T.parseArgs(t, ["--fruit", "kiwi"])); 15 | -------------------------------------------------------------------------------- /docs/code_snippets/ansiStylingArgument.d: -------------------------------------------------------------------------------- 1 | import argparse; 2 | 3 | struct T 4 | { 5 | static auto color = ansiStylingArgument; 6 | } 7 | 8 | T t; 9 | CLI!T.parseArgs(t, ["-h"]); 10 | 11 | // This is a way to detect whether `--color` argument was specified in the command line 12 | // Note that 'autodetect' is converted to either 'on' or 'off' 13 | assert(CLI!T.parseArgs(t, ["--color"])); 14 | assert(t.color); 15 | 16 | assert(CLI!T.parseArgs(t, ["--color=always"])); 17 | assert(t.color); 18 | 19 | assert(CLI!T.parseArgs(t, ["--color=never"])); 20 | assert(!t.color); 21 | -------------------------------------------------------------------------------- /docs/code_snippets/arity.d: -------------------------------------------------------------------------------- 1 | import argparse; 2 | 3 | struct T 4 | { 5 | @(NamedArgument.NumberOfValues(1,3)) 6 | int[] a; 7 | @(NamedArgument.NumberOfValues(2)) 8 | int[] b; 9 | } 10 | 11 | { 12 | T t; 13 | assert(CLI!T.parseArgs(t, ["-a", "1", "-a", "2", "-a", "3", "-b", "4", "-b", "5"])); 14 | assert(t == T([1, 2, 3], [4, 5])); 15 | } 16 | { 17 | enum Config config = { variadicNamedArgument: true }; 18 | T t; 19 | assert(CLI!(config, T).parseArgs(t, ["-a", "1","2", "3", "-b", "4", "5"])); 20 | assert(t == T([1, 2, 3], [4, 5])); 21 | } 22 | -------------------------------------------------------------------------------- /docs/code_snippets/arity_no_values.d: -------------------------------------------------------------------------------- 1 | import argparse; 2 | 3 | struct T 4 | { 5 | @(NamedArgument.AllowNoValue(10)) int a; 6 | @(NamedArgument.ForceNoValue(20)) int b; 7 | } 8 | 9 | T t; 10 | assert(CLI!T.parseArgs(t, ["-a"])); 11 | assert(t.a == 10); 12 | 13 | assert(CLI!T.parseArgs(t, ["-b"])); 14 | assert(t.b == 20); 15 | 16 | assert(CLI!T.parseArgs(t, ["-a","30"])); 17 | assert(t.a == 30); 18 | 19 | assert(!CLI!T.parseArgs(t, ["-b","30"])); // Unrecognized arguments: ["30"] -------------------------------------------------------------------------------- /docs/code_snippets/call_parser1.d: -------------------------------------------------------------------------------- 1 | import argparse; 2 | 3 | struct T 4 | { 5 | string a; 6 | string b; 7 | } 8 | 9 | mixin CLI!T.main!((args) 10 | { 11 | // 'args' has 'T' type 12 | static assert(is(typeof(args) == T)); 13 | 14 | // do whatever you need 15 | import std.stdio: writeln; 16 | args.writeln; 17 | return 0; 18 | }); 19 | -------------------------------------------------------------------------------- /docs/code_snippets/call_parser2.d: -------------------------------------------------------------------------------- 1 | import argparse; 2 | 3 | struct cmd1 4 | { 5 | string a; 6 | } 7 | 8 | struct cmd2 9 | { 10 | string b; 11 | } 12 | 13 | mixin CLI!(cmd1, cmd2).main!((args, unparsed) 14 | { 15 | import std.stdio: writeln; 16 | 17 | // 'args' has either 'cmd1' or 'cmd2' type 18 | static if(is(typeof(args) == cmd1)) 19 | writeln("cmd1: ", args); 20 | else static if(is(typeof(args) == cmd2)) 21 | writeln("cmd2: ", args); 22 | else 23 | static assert(false); // this would never happen 24 | 25 | // unparsed arguments has 'string[]' type 26 | static assert(is(typeof(unparsed) == string[])); 27 | 28 | return 0; 29 | }); 30 | -------------------------------------------------------------------------------- /docs/code_snippets/call_parser4.d: -------------------------------------------------------------------------------- 1 | import argparse; 2 | 3 | struct COMMAND 4 | { 5 | string a; 6 | string b; 7 | } 8 | 9 | int main(string[] argv) 10 | { 11 | COMMAND cmd; 12 | 13 | if(!CLI!COMMAND.parseArgs(cmd, argv[1..$])) 14 | return 1; // parsing failure 15 | 16 | // Do whatever is needed 17 | 18 | return 0; 19 | } 20 | -------------------------------------------------------------------------------- /docs/code_snippets/call_parser5.d: -------------------------------------------------------------------------------- 1 | import argparse; 2 | 3 | struct T 4 | { 5 | string a; 6 | } 7 | 8 | auto arguments = [ "-a", "A", "-c", "C" ]; 9 | 10 | T result; 11 | assert(CLI!T.parseKnownArgs(result, arguments)); 12 | assert(result == T("A")); 13 | assert(arguments == ["-c", "C"]); 14 | -------------------------------------------------------------------------------- /docs/code_snippets/config_addHelpArgument.d: -------------------------------------------------------------------------------- 1 | import argparse; 2 | 3 | struct T 4 | { 5 | string a, b; 6 | } 7 | 8 | T t1; 9 | CLI!T.parseArgs(t1, ["-a", "A", "-h", "-b", "B"]); 10 | assert(t1 == T("A")); 11 | 12 | enum Config cfg = { addHelpArgument: false }; 13 | 14 | T t2; 15 | string[] unrecognizedArgs; 16 | CLI!(cfg, T).parseKnownArgs(t2, ["-a", "A", "-h", "-b", "B"], unrecognizedArgs); 17 | assert(t2 == T("A","B")); 18 | assert(unrecognizedArgs == ["-h"]); 19 | -------------------------------------------------------------------------------- /docs/code_snippets/config_assignChar.d: -------------------------------------------------------------------------------- 1 | import argparse; 2 | 3 | struct T 4 | { 5 | string[] a; 6 | } 7 | 8 | enum Config cfg = { assignChar: ':' }; 9 | 10 | T t; 11 | assert(CLI!(cfg, T).parseArgs(t, ["-a:1","-a:2","-a:3"])); 12 | assert(t == T(["1","2","3"])); 13 | -------------------------------------------------------------------------------- /docs/code_snippets/config_assignKeyValueChar.d: -------------------------------------------------------------------------------- 1 | import argparse; 2 | 3 | struct T 4 | { 5 | int[string] a; 6 | } 7 | 8 | enum Config cfg = { assignKeyValueChar: ':' }; 9 | 10 | T t; 11 | assert(CLI!(cfg, T).parseArgs(t, ["-a=A:1","-a=B:2,C:3"])); 12 | assert(t == T(["A":1,"B":2,"C":3])); 13 | -------------------------------------------------------------------------------- /docs/code_snippets/config_bundling.d: -------------------------------------------------------------------------------- 1 | import argparse; 2 | 3 | struct T 4 | { 5 | bool a; 6 | bool b; 7 | string c; 8 | } 9 | 10 | enum Config cfg = { bundling: true }; 11 | 12 | T t; 13 | 14 | assert(CLI!(cfg, T).parseArgs(t, ["-ab"])); 15 | assert(t == T(true, true)); 16 | 17 | assert(CLI!(cfg, T).parseArgs(t, ["-abc=foo"])); 18 | assert(t == T(true, true, "foo")); 19 | 20 | assert(CLI!(cfg, T).parseArgs(t, ["-a","-bc=foo"])); 21 | assert(t == T(true, true, "foo")); 22 | 23 | assert(CLI!(cfg, T).parseArgs(t, ["-a","-bcfoo"])); 24 | assert(t == T(true, true, "foo")); 25 | -------------------------------------------------------------------------------- /docs/code_snippets/config_caseSensitive.d: -------------------------------------------------------------------------------- 1 | import argparse; 2 | 3 | struct T 4 | { 5 | string[] param; 6 | string[] s; 7 | } 8 | 9 | enum Config cfg = { caseSensitiveShortName: false, caseSensitiveLongName: false }; 10 | 11 | T t; 12 | assert(CLI!(cfg, T).parseArgs(t, ["--param","1","--PARAM","2","--PaRaM","3","-s","a","-S","b"])); 13 | assert(t == T(["1","2","3"],["a","b"])); 14 | -------------------------------------------------------------------------------- /docs/code_snippets/config_endOfNamedArgs.d: -------------------------------------------------------------------------------- 1 | import argparse; 2 | 3 | struct T 4 | { 5 | @NamedArgument string a; 6 | @PositionalArgument string b; 7 | @PositionalArgument string[] c; 8 | } 9 | 10 | enum Config cfg = { endOfNamedArgs: "---" }; 11 | 12 | T t; 13 | assert(CLI!(cfg, T).parseArgs(t, ["B","-a","foo","---","--","-a","boo"])); 14 | assert(t == T("foo","B",["--","-a","boo"])); 15 | -------------------------------------------------------------------------------- /docs/code_snippets/config_errorHandler.d: -------------------------------------------------------------------------------- 1 | import argparse; 2 | 3 | struct T 4 | { 5 | string a; 6 | } 7 | 8 | enum Config cfg = { 9 | errorHandler: 10 | (text) 11 | { 12 | try { 13 | import std.stdio : stderr; 14 | stderr.writeln("Detected an error: ", text); 15 | } 16 | catch(Exception e) 17 | { 18 | throw new Error(e.msg); 19 | } 20 | } 21 | }; 22 | 23 | T t; 24 | assert(!CLI!(cfg, T).parseArgs(t, ["-b"])); 25 | -------------------------------------------------------------------------------- /docs/code_snippets/config_namedArgPrefix.d: -------------------------------------------------------------------------------- 1 | import argparse; 2 | 3 | struct T 4 | { 5 | string a; 6 | string baz; 7 | } 8 | 9 | enum Config cfg = { shortNamePrefix: "+", longNamePrefix: "==" }; 10 | 11 | T t; 12 | assert(CLI!(cfg, T).parseArgs(t, ["+a","foo","==baz","BAZZ"])); 13 | assert(t == T("foo","BAZZ")); 14 | -------------------------------------------------------------------------------- /docs/code_snippets/config_styling.d: -------------------------------------------------------------------------------- 1 | import argparse; 2 | import argparse.ansi; 3 | 4 | struct T 5 | { 6 | string a, b; 7 | } 8 | 9 | enum Config cfg = { styling: { programName: blue, argumentName: green.italic } }; 10 | 11 | T t; 12 | CLI!(cfg, T).parseArgs(t, ["-a", "A", "-h", "-b", "B"]); 13 | assert(t == T("A")); 14 | -------------------------------------------------------------------------------- /docs/code_snippets/config_stylingMode.d: -------------------------------------------------------------------------------- 1 | import argparse; 2 | 3 | struct T 4 | { 5 | string a, b; 6 | } 7 | 8 | enum Config cfg = { stylingMode: Config.StylingMode.off }; 9 | 10 | T t; 11 | CLI!(cfg, T).parseArgs(t, ["-a", "A", "-h", "-b", "B"]); 12 | assert(t == T("A")); 13 | -------------------------------------------------------------------------------- /docs/code_snippets/config_valueSep.d: -------------------------------------------------------------------------------- 1 | import argparse; 2 | 3 | struct T 4 | { 5 | string[] a; 6 | } 7 | 8 | T t1; 9 | assert(CLI!T.parseArgs(t1, ["-a=1:2:3","-a","4"])); 10 | assert(t1 == T(["1:2:3","4"])); 11 | 12 | enum Config cfg = { valueSep: ':' }; 13 | 14 | T t2; 15 | assert(CLI!(cfg, T).parseArgs(t2, ["-a=1:2:3","-a","4"])); 16 | assert(t2 == T(["1","2","3","4"])); 17 | -------------------------------------------------------------------------------- /docs/code_snippets/config_variadicNamedArgument.d: -------------------------------------------------------------------------------- 1 | import argparse; 2 | 3 | struct T 4 | { 5 | @NamedArgument 6 | int[] a; 7 | } 8 | 9 | { 10 | T t; 11 | assert(CLI!T.parseArgs(t, ["-a", "1", "-a", "2", "-a", "3"])); 12 | assert(t == T([1, 2, 3])); 13 | } 14 | { 15 | enum Config config = { variadicNamedArgument: true }; 16 | T t; 17 | assert(CLI!(config, T).parseArgs(t, ["-a", "1", "2", "3"])); 18 | assert(t == T([1, 2, 3])); 19 | } 20 | -------------------------------------------------------------------------------- /docs/code_snippets/double_dash.d: -------------------------------------------------------------------------------- 1 | import argparse; 2 | 3 | struct T 4 | { 5 | @NamedArgument 6 | string a; 7 | @NamedArgument 8 | string b; 9 | 10 | @PositionalArgument 11 | string[] args; 12 | } 13 | 14 | T t; 15 | assert(CLI!T.parseArgs(t, ["-a","A","--","-b","B"])); 16 | assert(t == T("A","",["-b","B"])); 17 | -------------------------------------------------------------------------------- /docs/code_snippets/envfallback.d: -------------------------------------------------------------------------------- 1 | import argparse; 2 | 3 | version (Windows) 4 | immutable UserVariable = "USERNAME"; 5 | else 6 | immutable UserVariable = "USER"; 7 | 8 | struct T 9 | { 10 | @(PositionalArgument.EnvFallback("XDG_RUNTIME_DIR")) 11 | string dir; 12 | 13 | @(NamedArgument.EnvFallback(UserVariable)) 14 | string user; 15 | } 16 | 17 | // This can be called argument-less on most Linux machines as both variables 18 | // will be set. Calling with `./myprog some/path` will use `some/path` for `dir`, 19 | // and `--user username` will take precedence over the `$USER` or `%USERNAME% 20 | // environment variable. 21 | -------------------------------------------------------------------------------- /docs/code_snippets/help_argument_group.d: -------------------------------------------------------------------------------- 1 | import argparse; 2 | 3 | @(Command("MYPROG") 4 | .Description("custom description") 5 | .Epilog("custom epilog") 6 | ) 7 | struct T 8 | { 9 | @(ArgumentGroup("group1").Description("group1 description")) 10 | { 11 | @NamedArgument 12 | { 13 | string a; 14 | string b; 15 | } 16 | @PositionalArgument string p; 17 | } 18 | 19 | @(ArgumentGroup("group2").Description("group2 description")) 20 | @NamedArgument 21 | { 22 | string c; 23 | string d; 24 | } 25 | @PositionalArgument string q; 26 | } 27 | 28 | T t; 29 | CLI!T.parseArgs(t, ["-h"]); 30 | -------------------------------------------------------------------------------- /docs/code_snippets/help_example.d: -------------------------------------------------------------------------------- 1 | import argparse; 2 | 3 | @(Command("MYPROG") 4 | .Description("custom description") 5 | .Epilog("custom epilog") 6 | ) 7 | struct T 8 | { 9 | @NamedArgument string s; 10 | @(NamedArgument.Placeholder("VALUE")) string p; 11 | 12 | @(NamedArgument.Hidden) string hidden; 13 | 14 | enum Fruit { apple, pear }; 15 | @(NamedArgument("f","fruit").Required.Description("This is a help text for fruit. Very very very very very very very very very very very very very very very very very very very long text")) Fruit f; 16 | 17 | @(NamedArgument.AllowedValues(1,4,16,8)) int i; 18 | 19 | @(PositionalArgument(0).Description("This is a help text for param0. Very very very very very very very very very very very very very very very very very very very long text")) string param0; 20 | @(PositionalArgument(1).AllowedValues("q","a")) string param1; 21 | } 22 | 23 | T t; 24 | CLI!T.parseArgs(t, ["-h"]); 25 | -------------------------------------------------------------------------------- /docs/code_snippets/mutually_exclusive.d: -------------------------------------------------------------------------------- 1 | import argparse; 2 | 3 | struct T 4 | { 5 | @MutuallyExclusive() 6 | { 7 | string a; 8 | string b; 9 | } 10 | } 11 | 12 | T t; 13 | 14 | // One of them or no argument is allowed 15 | assert(CLI!T.parseArgs(t, ["-a","a"])); 16 | assert(CLI!T.parseArgs(t, ["-b","b"])); 17 | assert(CLI!T.parseArgs(t, [])); 18 | 19 | // Both arguments or no argument is not allowed 20 | assert(!CLI!T.parseArgs(t, ["-a","a","-b","b"])); 21 | -------------------------------------------------------------------------------- /docs/code_snippets/mutually_exclusive_required.d: -------------------------------------------------------------------------------- 1 | import argparse; 2 | 3 | struct T 4 | { 5 | @(MutuallyExclusive.Required) 6 | { 7 | string a; 8 | string b; 9 | } 10 | } 11 | 12 | T t; 13 | 14 | // Either argument is allowed 15 | assert(CLI!T.parseArgs(t, ["-a","a"])); 16 | assert(CLI!T.parseArgs(t, ["-b","b"])); 17 | 18 | // Both or no arguments are not allowed 19 | assert(!CLI!T.parseArgs(t, ["-a","a","-b","b"])); 20 | assert(!CLI!T.parseArgs(t, [])); 21 | -------------------------------------------------------------------------------- /docs/code_snippets/mutually_required.d: -------------------------------------------------------------------------------- 1 | import argparse; 2 | 3 | struct T 4 | { 5 | @RequiredTogether() 6 | { 7 | string a; 8 | string b; 9 | } 10 | } 11 | 12 | T t; 13 | 14 | // Both or no argument is allowed 15 | assert(CLI!T.parseArgs(t, ["-a","a","-b","b"])); 16 | assert(CLI!T.parseArgs(t, [])); 17 | 18 | // Only one argument is not allowed 19 | assert(!CLI!T.parseArgs(t, ["-a","a"])); 20 | assert(!CLI!T.parseArgs(t, ["-b","b"])); 21 | -------------------------------------------------------------------------------- /docs/code_snippets/mutually_required_required.d: -------------------------------------------------------------------------------- 1 | import argparse; 2 | 3 | struct T 4 | { 5 | @(RequiredTogether.Required) 6 | { 7 | string a; 8 | string b; 9 | } 10 | } 11 | 12 | T t; 13 | 14 | // Both arguments are allowed 15 | assert(CLI!T.parseArgs(t, ["-a","a","-b","b"])); 16 | 17 | // Single argument or no argument is not allowed 18 | assert(!CLI!T.parseArgs(t, ["-a","a"])); 19 | assert(!CLI!T.parseArgs(t, ["-b","b"])); 20 | assert(!CLI!T.parseArgs(t, [])); 21 | -------------------------------------------------------------------------------- /docs/code_snippets/named_arguments.d: -------------------------------------------------------------------------------- 1 | import argparse; 2 | 3 | struct Params 4 | { 5 | // If name is not provided then member name is used: "--greeting" 6 | @NamedArgument 7 | string greeting; 8 | 9 | // If member name is single character then it becomes a short name: "-a" 10 | @NamedArgument 11 | string a; 12 | 13 | // Argument with multiple names: "--name", "--first-name", "-n" 14 | // Note that single character becomes a short name 15 | @NamedArgument(["name", "first-name", "n"]) 16 | string name; 17 | 18 | // Another way to specify multiple names: "--family", "--last-name" 19 | @NamedArgument("family", "last-name") 20 | string family; 21 | 22 | // Explicitly providing short and long names: "-m","-middle", "--middle", "--middle-name" 23 | @NamedArgument(["m","middle"], ["middle", "middle-name"]) 24 | string middle; 25 | } 26 | -------------------------------------------------------------------------------- /docs/code_snippets/optional_required_arguments.d: -------------------------------------------------------------------------------- 1 | import argparse; 2 | 3 | struct T 4 | { 5 | @(PositionalArgument(0, "a").Optional) 6 | string a = "not set"; 7 | 8 | @(NamedArgument.Required) 9 | int b; 10 | } 11 | 12 | T t; 13 | assert(CLI!T.parseArgs(t, ["-b", "4"])); 14 | assert(t == T("not set", 4)); 15 | -------------------------------------------------------------------------------- /docs/code_snippets/parsing_customization.d: -------------------------------------------------------------------------------- 1 | import argparse; 2 | 3 | struct T 4 | { 5 | @(NamedArgument 6 | .PreValidation((string s) { return s.length > 1 && s[0] == '!'; }) 7 | .Parse ((string s) { return cast(char) s[1]; }) 8 | .Validation ((char v) { return v >= '0' && v <= '9'; }) 9 | .Action ((ref int a, char v) { a = v - '0'; }) 10 | ) 11 | int a; 12 | } 13 | 14 | T t; 15 | assert(CLI!T.parseArgs(t, ["-a","!4"])); 16 | assert(t == T(4)); 17 | -------------------------------------------------------------------------------- /docs/code_snippets/positional_arguments.d: -------------------------------------------------------------------------------- 1 | import argparse; 2 | 3 | struct Params1 4 | { 5 | @PositionalArgument 6 | string firstName; 7 | 8 | @PositionalArgument("lastName") 9 | string arg; 10 | } 11 | 12 | struct Params2 13 | { 14 | @PositionalArgument 15 | string firstName; 16 | 17 | @PositionalArgument 18 | string arg; 19 | } 20 | -------------------------------------------------------------------------------- /docs/code_snippets/styling_helloworld.d: -------------------------------------------------------------------------------- 1 | import argparse.ansi; 2 | 3 | void printText(bool enableStyle) 4 | { 5 | // style is enabled at runtime when `enableStyle` is true 6 | auto myStyle = enableStyle ? bold.italic.cyan.onRed : noStyle; 7 | 8 | // "Hello" is always printed in green; 9 | // "world!" is printed in bold, italic, cyan and on red when `enableStyle` is true, or "as is" otherwise 10 | writeln(green("Hello "), myStyle("world!")); 11 | } 12 | 13 | printText(true); 14 | printText(false); -------------------------------------------------------------------------------- /docs/code_snippets/styling_help.d: -------------------------------------------------------------------------------- 1 | import argparse; 2 | import argparse.ansi; 3 | 4 | struct T 5 | { 6 | @(NamedArgument("red").Description(bold.underline("Colorize the output:")~" make everything "~red("red"))) 7 | bool red_; 8 | } 9 | 10 | T t; 11 | CLI!T.parseArgs(t, ["-h"]); 12 | -------------------------------------------------------------------------------- /docs/code_snippets/subcommands.d: -------------------------------------------------------------------------------- 1 | import argparse; 2 | 3 | struct cmd1 {} 4 | struct cmd2 {} 5 | struct cmd3 {} 6 | 7 | struct T 8 | { 9 | // name of the subcommand is the same as a name of the type by default 10 | SubCommand!(cmd1, cmd2, cmd3) cmd; 11 | } 12 | 13 | T t; 14 | 15 | assert(CLI!T.parseArgs(t, [])); 16 | assert(t == T.init); 17 | 18 | assert(CLI!T.parseArgs(t, ["cmd1"])); 19 | assert(t == T(typeof(T.cmd)(cmd1.init))); 20 | 21 | assert(CLI!T.parseArgs(t, ["cmd2"])); 22 | assert(t == T(typeof(T.cmd)(cmd2.init))); 23 | 24 | assert(CLI!T.parseArgs(t, ["cmd3"])); 25 | assert(t == T(typeof(T.cmd)(cmd3.init))); 26 | -------------------------------------------------------------------------------- /docs/code_snippets/subcommands_common_args.d: -------------------------------------------------------------------------------- 1 | import argparse; 2 | 3 | struct min {} 4 | struct max {} 5 | struct sum {} 6 | 7 | struct T 8 | { 9 | int[] n; // common argument for all subcommands 10 | 11 | // name of the subcommand is the same as a name of the type by default 12 | SubCommand!(min, max, sum) cmd; 13 | } 14 | 15 | enum Config config = { variadicNamedArgument:true }; 16 | 17 | T t; 18 | 19 | assert(CLI!(config, T).parseArgs(t, ["min","-n","1","2","3"])); 20 | assert(t == T([1,2,3],typeof(T.cmd)(min.init))); 21 | 22 | t = T.init; 23 | assert(CLI!(config, T).parseArgs(t, ["max","-n","4","5","6"])); 24 | assert(t == T([4,5,6],typeof(T.cmd)(max.init))); 25 | 26 | t = T.init; 27 | assert(CLI!(config, T).parseArgs(t, ["sum","-n","7","8","9"])); 28 | assert(t == T([7,8,9],typeof(T.cmd)(sum.init))); 29 | -------------------------------------------------------------------------------- /docs/code_snippets/subcommands_default.d: -------------------------------------------------------------------------------- 1 | import argparse; 2 | 3 | struct min {} 4 | struct max {} 5 | struct sum {} 6 | 7 | struct T 8 | { 9 | int[] n; // common argument for all subcommands 10 | 11 | // name of the subcommand is the same as a name of the type by default 12 | SubCommand!(min, max, Default!sum) cmd; 13 | } 14 | 15 | enum Config config = { variadicNamedArgument:true }; 16 | 17 | T t; 18 | 19 | assert(CLI!(config, T).parseArgs(t, ["-n","7","8","9"])); 20 | assert(t == T([7,8,9],typeof(T.cmd)(sum.init))); 21 | 22 | t = T.init; 23 | assert(CLI!(config, T).parseArgs(t, ["max","-n","4","5","6"])); 24 | assert(t == T([4,5,6],typeof(T.cmd)(max.init))); 25 | -------------------------------------------------------------------------------- /docs/code_snippets/subcommands_enumerate.d: -------------------------------------------------------------------------------- 1 | import argparse; 2 | 3 | struct cmd1 {} 4 | struct cmd2 {} 5 | struct cmd3 {} 6 | 7 | // This mixin defines standard main function that parses command line and calls the provided function: 8 | mixin CLI!(cmd1, cmd2, cmd3).main!((cmd) 9 | { 10 | import std.stdio; 11 | cmd.writeln; 12 | }); 13 | -------------------------------------------------------------------------------- /docs/code_snippets/subcommands_names.d: -------------------------------------------------------------------------------- 1 | import argparse; 2 | 3 | @Command("minimum", "min") 4 | struct min {} 5 | @Command("maximum", "max") 6 | struct max {} 7 | 8 | struct T 9 | { 10 | int[] n; // common argument for all subcommands 11 | 12 | SubCommand!(min, max) cmd; 13 | } 14 | 15 | enum Config config = { variadicNamedArgument:true }; 16 | 17 | T t; 18 | 19 | assert(CLI!(config, T).parseArgs(t, ["minimum","-n","1","2","3"])); 20 | assert(t == T([1,2,3],typeof(T.cmd)(min.init))); 21 | 22 | t = T.init; 23 | assert(CLI!(config, T).parseArgs(t, ["max","-n","4","5","6"])); 24 | assert(t == T([4,5,6],typeof(T.cmd)(max.init))); 25 | -------------------------------------------------------------------------------- /docs/code_snippets/types_array.d: -------------------------------------------------------------------------------- 1 | import argparse; 2 | 3 | struct T 4 | { 5 | int[] a; 6 | int[][] b; 7 | } 8 | 9 | T t; 10 | assert(CLI!T.parseArgs(t, ["-a=1,2,3","-a","4,5"])); 11 | assert(t == T([1,2,3,4,5])); 12 | 13 | assert(CLI!T.parseArgs(t, ["-b=1,2,3","-b","4,5"])); 14 | assert(t.b == [[1,2,3],[4,5]]); 15 | -------------------------------------------------------------------------------- /docs/code_snippets/types_assoc_array.d: -------------------------------------------------------------------------------- 1 | import argparse; 2 | 3 | struct T 4 | { 5 | int[string] a; 6 | } 7 | 8 | T t; 9 | assert(CLI!T.parseArgs(t, ["-a=foo=3,boo=7","-a","bar=4,baz=9"])); 10 | assert(t == T(["foo":3,"boo":7,"bar":4,"baz":9])); 11 | -------------------------------------------------------------------------------- /docs/code_snippets/types_counter.d: -------------------------------------------------------------------------------- 1 | import argparse; 2 | 3 | struct T 4 | { 5 | @(NamedArgument.Counter) 6 | int v; 7 | } 8 | 9 | T t; 10 | assert(CLI!T.parseArgs(t, ["-v","-v","-v"])); 11 | assert(t == T(3)); 12 | -------------------------------------------------------------------------------- /docs/code_snippets/types_counter_bundling.d: -------------------------------------------------------------------------------- 1 | import argparse; 2 | 3 | struct T 4 | { 5 | @(NamedArgument.Counter) 6 | int v; 7 | } 8 | 9 | enum Config cfg = { bundling: true }; 10 | 11 | T t; 12 | assert(CLI!(cfg, T).parseArgs(t, ["-vv","-v"])); 13 | assert(t == T(3)); 14 | -------------------------------------------------------------------------------- /docs/code_snippets/types_custom.d: -------------------------------------------------------------------------------- 1 | import argparse; 2 | 3 | struct Value 4 | { 5 | string a; 6 | } 7 | struct T 8 | { 9 | @(NamedArgument.Parse((string s) { return Value(s); })) 10 | Value s; 11 | } 12 | 13 | T t; 14 | assert(CLI!T.parseArgs(t, ["-s","foo"])); 15 | assert(t == T(Value("foo"))); -------------------------------------------------------------------------------- /docs/code_snippets/types_enum.d: -------------------------------------------------------------------------------- 1 | import argparse; 2 | 3 | struct T 4 | { 5 | enum Fruit { apple, pear }; 6 | 7 | Fruit a; 8 | } 9 | 10 | T t; 11 | assert(CLI!T.parseArgs(t, ["-a","apple"])); 12 | assert(t == T(T.Fruit.apple)); 13 | 14 | assert(CLI!T.parseArgs(t, ["-a=pear"])); 15 | assert(t == T(T.Fruit.pear)); 16 | -------------------------------------------------------------------------------- /docs/code_snippets/types_enum_custom_values.d: -------------------------------------------------------------------------------- 1 | import argparse; 2 | 3 | struct T 4 | { 5 | enum Fruit { 6 | apple, 7 | @AllowedValues("no-apple","noapple") 8 | noapple 9 | }; 10 | 11 | Fruit a; 12 | } 13 | 14 | T t; 15 | assert(CLI!T.parseArgs(t, ["-a=no-apple"])); 16 | assert(t == T(T.Fruit.noapple)); 17 | 18 | assert(CLI!T.parseArgs(t, ["-a","noapple"])); 19 | assert(t == T(T.Fruit.noapple)); 20 | -------------------------------------------------------------------------------- /docs/code_snippets/types_function.d: -------------------------------------------------------------------------------- 1 | import argparse; 2 | 3 | struct T 4 | { 5 | int a_; 6 | string[] b_; 7 | string[][] c_; 8 | 9 | @NamedArgument 10 | { 11 | void a() { a_++; } 12 | void b(string s) { b_ ~= s; } 13 | void c(string[] s) { c_ ~= s; } 14 | } 15 | } 16 | 17 | T t; 18 | assert(CLI!T.parseArgs(t, ["-a","-b","1","-c","q,w", 19 | "-a","-b","2","-c","e,r", 20 | "-a","-b","3","-c","t,y",])); 21 | assert(t == T(3, ["1","2","3"], [["q","w"],["e","r"],["t","y"]])); 22 | -------------------------------------------------------------------------------- /docs/code_snippets/types_number_string.d: -------------------------------------------------------------------------------- 1 | import argparse; 2 | 3 | struct T 4 | { 5 | int i; 6 | uint u; 7 | double f; 8 | string s; 9 | wstring w; 10 | dstring d; 11 | } 12 | 13 | T t; 14 | assert(CLI!T.parseArgs(t, ["-i","-5","-u","8","-f","12.345","-s","sss","-w","www","-d","ddd"])); 15 | assert(t == T(-5,8,12.345,"sss","www","ddd")); 16 | -------------------------------------------------------------------------------- /docs/dub.json: -------------------------------------------------------------------------------- 1 | { 2 | "license": "BSL-1.0", 3 | "name": "build_code_snippets", 4 | "targetType": "executable", 5 | "sourceFiles": ["build_code_snippets.d"] 6 | } -------------------------------------------------------------------------------- /docs/images/allowed_values_error.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andrey-zherikov/argparse/10b7bce1cc813e9930ed85bcc6d54a89c0cb65f9/docs/images/allowed_values_error.png -------------------------------------------------------------------------------- /docs/images/allowed_values_error_dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andrey-zherikov/argparse/10b7bce1cc813e9930ed85bcc6d54a89c0cb65f9/docs/images/allowed_values_error_dark.png -------------------------------------------------------------------------------- /docs/images/ansiStylingArgument.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andrey-zherikov/argparse/10b7bce1cc813e9930ed85bcc6d54a89c0cb65f9/docs/images/ansiStylingArgument.png -------------------------------------------------------------------------------- /docs/images/ansiStylingArgument_dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andrey-zherikov/argparse/10b7bce1cc813e9930ed85bcc6d54a89c0cb65f9/docs/images/ansiStylingArgument_dark.png -------------------------------------------------------------------------------- /docs/images/config_help.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andrey-zherikov/argparse/10b7bce1cc813e9930ed85bcc6d54a89c0cb65f9/docs/images/config_help.png -------------------------------------------------------------------------------- /docs/images/config_help_dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andrey-zherikov/argparse/10b7bce1cc813e9930ed85bcc6d54a89c0cb65f9/docs/images/config_help_dark.png -------------------------------------------------------------------------------- /docs/images/config_styling.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andrey-zherikov/argparse/10b7bce1cc813e9930ed85bcc6d54a89c0cb65f9/docs/images/config_styling.png -------------------------------------------------------------------------------- /docs/images/config_stylingMode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andrey-zherikov/argparse/10b7bce1cc813e9930ed85bcc6d54a89c0cb65f9/docs/images/config_stylingMode.png -------------------------------------------------------------------------------- /docs/images/config_stylingMode_dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andrey-zherikov/argparse/10b7bce1cc813e9930ed85bcc6d54a89c0cb65f9/docs/images/config_stylingMode_dark.png -------------------------------------------------------------------------------- /docs/images/config_styling_dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andrey-zherikov/argparse/10b7bce1cc813e9930ed85bcc6d54a89c0cb65f9/docs/images/config_styling_dark.png -------------------------------------------------------------------------------- /docs/images/default_styling.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andrey-zherikov/argparse/10b7bce1cc813e9930ed85bcc6d54a89c0cb65f9/docs/images/default_styling.png -------------------------------------------------------------------------------- /docs/images/default_styling_dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andrey-zherikov/argparse/10b7bce1cc813e9930ed85bcc6d54a89c0cb65f9/docs/images/default_styling_dark.png -------------------------------------------------------------------------------- /docs/images/hello_world_with_uda.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andrey-zherikov/argparse/10b7bce1cc813e9930ed85bcc6d54a89c0cb65f9/docs/images/hello_world_with_uda.png -------------------------------------------------------------------------------- /docs/images/hello_world_with_uda_dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andrey-zherikov/argparse/10b7bce1cc813e9930ed85bcc6d54a89c0cb65f9/docs/images/hello_world_with_uda_dark.png -------------------------------------------------------------------------------- /docs/images/hello_world_without_uda.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andrey-zherikov/argparse/10b7bce1cc813e9930ed85bcc6d54a89c0cb65f9/docs/images/hello_world_without_uda.png -------------------------------------------------------------------------------- /docs/images/hello_world_without_uda_dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andrey-zherikov/argparse/10b7bce1cc813e9930ed85bcc6d54a89c0cb65f9/docs/images/hello_world_without_uda_dark.png -------------------------------------------------------------------------------- /docs/images/help_argument_group.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andrey-zherikov/argparse/10b7bce1cc813e9930ed85bcc6d54a89c0cb65f9/docs/images/help_argument_group.png -------------------------------------------------------------------------------- /docs/images/help_argument_group_dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andrey-zherikov/argparse/10b7bce1cc813e9930ed85bcc6d54a89c0cb65f9/docs/images/help_argument_group_dark.png -------------------------------------------------------------------------------- /docs/images/help_example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andrey-zherikov/argparse/10b7bce1cc813e9930ed85bcc6d54a89c0cb65f9/docs/images/help_example.png -------------------------------------------------------------------------------- /docs/images/help_example1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andrey-zherikov/argparse/10b7bce1cc813e9930ed85bcc6d54a89c0cb65f9/docs/images/help_example1.png -------------------------------------------------------------------------------- /docs/images/help_example1_dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andrey-zherikov/argparse/10b7bce1cc813e9930ed85bcc6d54a89c0cb65f9/docs/images/help_example1_dark.png -------------------------------------------------------------------------------- /docs/images/help_example_dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andrey-zherikov/argparse/10b7bce1cc813e9930ed85bcc6d54a89c0cb65f9/docs/images/help_example_dark.png -------------------------------------------------------------------------------- /docs/images/styling_help.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andrey-zherikov/argparse/10b7bce1cc813e9930ed85bcc6d54a89c0cb65f9/docs/images/styling_help.png -------------------------------------------------------------------------------- /docs/images/styling_help_dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andrey-zherikov/argparse/10b7bce1cc813e9930ed85bcc6d54a89c0cb65f9/docs/images/styling_help_dark.png -------------------------------------------------------------------------------- /docs/redirection-rules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | Created after removal of "Param and RawParam" from argparse documentation 11 | Param-and-RawParam.html 12 | 13 | 14 | Created after removal of "ANSI styling" from argparse documentation 15 | ANSI-styling.html 16 | 17 | 18 | Created after removal of "ANSI colors and styles" from argparse documentation 19 | ANSI-colors-and-styles.html 20 | 21 | 22 | Created after removal of "Without User-Defined Attributes" from argparse documentation 23 | 24 | Without-User-Defined-Attributes.html 25 | 26 | 27 | Created after removal of "With User-Defined Attributes" from argparse documentation 28 | With-User-Defined-Attributes.html 29 | 30 | 31 | Created after removal of "About argparse documentation" from argparse documentation 32 | starter-topic.html 33 | 34 | 35 | Created after removal of "Default" from argparse documentation 36 | Default.html 37 | 38 | -------------------------------------------------------------------------------- /docs/topics/ANSI-coloring-and-styling.md: -------------------------------------------------------------------------------- 1 | # ANSI coloring and styling 2 | 3 | Using colors in the command line tool’s output does not just look good: **contrasting** important elements like argument 4 | names from the rest of the text **reduces the cognitive load** on the user. `argparse` uses [ANSI escape sequences](https://en.wikipedia.org/wiki/ANSI_escape_code) 5 | to add coloring and styling to the error messages and help text. In addition, `argparse` offers public API to apply 6 | colors and styles to any text printed to the console (see below). 7 | 8 | > Coloring and styling API is provided in a separate `argparse.ansi` submodule. It has no dependencies on other parts of 9 | > `argparse` so can be easily used in any other parts of a program unrelated to command line parsing. 10 | > 11 | {style="tip"} 12 | 13 | Default styling 14 | 15 | ## Styles and colors 16 | 17 | The following styles and colors are available in `argparse.ansi` submodule: 18 | 19 | **Font styles:** 20 | - `bold` 21 | - `italic` 22 | - `underline` 23 | 24 | **Colors:** 25 | 26 | | **Foreground** | **Background** | 27 | |----------------|------------------| 28 | | `black` | `onBlack` | 29 | | `red` | `onRed` | 30 | | `green` | `onGreen` | 31 | | `yellow` | `onYellow` | 32 | | `blue` | `onBlue` | 33 | | `magenta` | `onMagenta` | 34 | | `cyan` | `onCyan` | 35 | | `lightGray` | `onLightGray` | 36 | | `darkGray` | `onDarkGray` | 37 | | `lightRed` | `onLightRed` | 38 | | `lightGreen` | `onLightGreen` | 39 | | `lightYellow` | `onLightYellow` | 40 | | `lightBlue` | `onLightBlue` | 41 | | `lightMagenta` | `onLightMagenta` | 42 | | `lightCyan` | `onLightCyan` | 43 | | `white` | `onWhite` | 44 | 45 | There is also a “virtual” style `noStyle` that means no styling is applied. It’s useful in ternary operations as a fallback 46 | for the case when styling is disabled. See below example for details. 47 | 48 | All styles above can be combined using `.` and even be used in regular output: 49 | 50 | 51 | 52 | The following example shows how styling can be used in custom help text (`Usage`, `Description`, `ShortDescription`, `Epilog` API): 53 | 54 | 55 | 56 | Here is how help screen will look like: 57 | 58 | Config help example 59 | 60 | 61 | ## Enable/disable the styling 62 | 63 | By default `argparse` will try to detect whether ANSI styling is supported, and if so, it will apply styling to the help text and error messages. 64 | Note that detection works for stdout and stderr separately so, for example, if stdout is redirected to a file (so stdout styling is disabled) 65 | then stderr output (eg. error messages) will still have styling applied. 66 | 67 | There is `Config.stylingMode` parameter that can be used to override default behavior: 68 | - If it’s set to `Config.StylingMode.on`, then styling is **always enabled**. 69 | - If it’s set to `Config.StylingMode.off`, then styling is **always disabled**. 70 | - If it’s set to `Config.StylingMode.autodetect`, then [heuristics](#heuristic) are used to determine 71 | whether styling will be applied. 72 | 73 | In some cases styling control should be exposed to a user as a command line argument (similar to `--color` argument in `ls` or `grep` command). 74 | `argparse` supports this use case – just add an argument to a command (it can be customized with `@NamedArgument` UDA): 75 | 76 | 77 | 78 | This will add the following argument: 79 | 80 | ansiStylingArgument 81 | 82 | 83 | ## Heuristics for enabling styling {id="heuristic"} 84 | 85 | Below is the exact sequence of steps `argparse` uses to determine whether or not to emit ANSI escape codes 86 | (see detectSupport() function [here](https://github.com/andrey-zherikov/argparse/blob/master/source/argparse/ansi.d) for details): 87 | 88 | 1. If environment variable `NO_COLOR != ""`, then styling is **disabled**. See [here](https://no-color.org/) for details. 89 | 2. If environment variable `CLICOLOR_FORCE != "0"`, then styling is **enabled**. See [here](https://bixense.com/clicolors/) for details. 90 | 3. If environment variable `CLICOLOR == "0"`, then styling is **disabled**. See [here](https://bixense.com/clicolors/) for details. 91 | 4. If environment variable `ConEmuANSI == "OFF"`, then styling is **disabled**. See [here](https://conemu.github.io/en/AnsiEscapeCodes.html#Environment_variable) for details. 92 | 5. If environment variable `ConEmuANSI == "ON"`, then styling is **enabled**. See [here](https://conemu.github.io/en/AnsiEscapeCodes.html#Environment_variable) for details. 93 | 6. If environment variable `ANSICON` is defined (regardless of its value), then styling is **enabled**. See [here](https://github.com/adoxa/ansicon/blob/master/readme.txt) for details. 94 | 7. **Windows only** (`version(Windows)`): 95 | 1. If environment variable `TERM` contains `"cygwin"` or starts with `"xterm"`, then styling is **enabled**. 96 | 2. If `GetConsoleMode` call for `STD_OUTPUT_HANDLE`/`STD_ERROR_HANDLE` returns a mode that has `ENABLE_VIRTUAL_TERMINAL_PROCESSING` set, then styling is **enabled**. 97 | 3. If `SetConsoleMode` call for `STD_OUTPUT_HANDLE`/`STD_ERROR_HANDLE` with `ENABLE_VIRTUAL_TERMINAL_PROCESSING` mode was successful, then styling is **enabled**. 98 | 8. **Posix only** (`version(Posix)`): 99 | 1. If `STDOUT`/`STDERR` is **not** redirected (`isatty` returns 1), then styling is **enabled**. 100 | 9. If none of the above applies, then styling is **disabled**. 101 | -------------------------------------------------------------------------------- /docs/topics/Argument-dependencies.md: -------------------------------------------------------------------------------- 1 | # Argument dependencies 2 | 3 | ## Mutually exclusive arguments {id="MutuallyExclusive"} 4 | 5 | Mutually exclusive arguments (i.e., those that can’t be used together) can be declared using `MutuallyExclusive()` UDA: 6 | 7 | 8 | 9 | > Parentheses `()` are required for this UDA to work correctly. 10 | > 11 | {style="warning"} 12 | 13 | Set of mutually exclusive arguments can be marked as required in order to require exactly one of the arguments: 14 | 15 | 16 | 17 | 18 | ## Mutually required arguments {id="RequiredTogether"} 19 | 20 | Mutually required arguments (i.e., those that require other arguments) can be declared using `RequiredTogether()` UDA: 21 | 22 | 23 | 24 | > Parentheses `()` are required for this UDA to work correctly. 25 | > 26 | {style="warning"} 27 | 28 | Set of mutually required arguments can be marked as required in order to require all arguments: 29 | 30 | 31 | -------------------------------------------------------------------------------- /docs/topics/Arguments-bundling.md: -------------------------------------------------------------------------------- 1 | # Arguments bundling 2 | 3 | Some command line tools allow bundling of single-character argument names in a form of `-abc` where `a`, `b` and `c` are 4 | separate arguments. `argparse` supports this through [`Config.bundling`](Config.md#bundling) setting and allows the following usages: 5 | 6 | 7 | 8 | To explain what happens under the hood, let's consider that a command line has `-abc` entry and there is no `abc` argument. 9 | In this case, `argparse` tries to parse it as `-a bc` if there is an `a` argument and it accepts a value, or as `-a -bc` 10 | if there is an `a` argument and it does not accept any value. In case if there is no `a` argument, `argparse` will error out. 11 | -------------------------------------------------------------------------------- /docs/topics/Arguments.md: -------------------------------------------------------------------------------- 1 | # Arguments 2 | 3 | Command line usually consists of arguments that can be separated into _named arguments_ and _positional arguments_. 4 | There is no restriction that one should be before another one so they may be mixed in command line. 5 | -------------------------------------------------------------------------------- /docs/topics/Arity.md: -------------------------------------------------------------------------------- 1 | # Arity 2 | 3 | Sometimes an argument might accept more than one value. This is especially a case when a data member is an array or associative array. 4 | In this case `argparse` supports two ways of specifying multiple values for an argument: 5 | - `--arg value1 value2 ...` 6 | - `--arg=value1,value2,...` 7 | > Note that `=` is a value of [`Config.assignChar`](Config.md#assignChar) and `,` is a value of [`Config.valueSep`](Config.md#valueSep) 8 | > 9 | {style="note"} 10 | 11 | 12 | `argparse` supports these use cases for arity: 13 | - Exact number of values. 14 | - Limited range of minimum-maximum number of values. 15 | - Unlimited range where only minimum number of values is provided (e.g. argument accepts _any number_ of values). 16 | 17 | To adjust the arity, use one the following API: 18 | - `NumberOfValues(size_t min, size_t max)` – sets both minimum and maximum number of values. 19 | - `NumberOfValues(size_t num)` – sets the exact number of values. 20 | - `MinNumberOfValues(size_t min)` – sets minimum number of values. 21 | - `MaxNumberOfValues(size_t max)` – sets maximum number of values. 22 | 23 | > Positional argument must have at least one value. 24 | > 25 | {style="warning"} 26 | 27 | Example: 28 | 29 | 30 | 31 | ## Default arity 32 | 33 | | Type | Default arity | Notes | 34 | |-----------------------|:---------------:|-----------------------------------------------------------------------------------------------------------------------------| 35 | | `bool` | 0 | Boolean flags do not accept values with the only exception when they are specified in `--flag=true` format in command line. | 36 | | String or scalar | 1 | Exactly one value is accepted. | 37 | | Static array | Length of array | If a range is desired then use provided API to adjust arity. | 38 | | Dynamic array | 1 ... ∞ | | 39 | | Associative array | 1 ... ∞ | | 40 | | `function ()` | 0 | Same as boolean flag. | 41 | | `function (string)` | 1 | Same as `string`. | 42 | | `function (string[])` | 1 ... ∞ | Same as `string[]` array. | 43 | | `function (RawParam)` | 1 ... ∞ | Same as `string[]` array. | 44 | 45 | ## Named arguments with no values 46 | 47 | Sometimes named arguments can have no values in command line. Here are two cases that arise in this situation: 48 | 49 | - If value is optional and argument should get specific value in this case then use `AllowNoValue`. 50 | 51 | - If argument must not have any value in command line then use `ForceNoValue` in this case. 52 | 53 | Both `AllowNoValue` and `ForceNoValue` accept a value that should be used when no value is provided in the command line. 54 | The difference between them can be seen in this example: 55 | 56 | 57 | -------------------------------------------------------------------------------- /docs/topics/Calling-the-parser.md: -------------------------------------------------------------------------------- 1 | # Calling the parser 2 | 3 | `argparse` provides `CLI` template to call the parser covering different use cases. It has the following signatures: 4 | - `template CLI(Config config, COMMAND)` – this is main template that provides multiple API (see below) for all 5 | supported use cases. 6 | - `template CLI(Config config, COMMANDS...)` – convenience wrapper of the previous template that provides `main` 7 | template mixin only for the simplest use case with subcommands. See [Subcommands](Subcommands.md) section for details. 8 | - `alias CLI(COMMANDS...) = CLI!(Config.init, COMMANDS)` – alias provided for convenience that allows using default 9 | `Config`, i.e., `config = Config.init`. 10 | 11 | ## Wrapper for `main` function 12 | 13 | The recommended and most convenient way to use `argparse` is through `CLI!(...).main(alias newMain)` mixin template. 14 | It declares the standard `main` function that parses command line arguments and calls provided `newMain` function with 15 | an object that contains parsed arguments. 16 | 17 | `newMain` function must satisfy these requirements: 18 | - It must accept `COMMAND` type as a first parameter if `CLI` template is used with one `COMMAND`. 19 | - It must accept all `COMMANDS` types as a first parameter if `CLI` template is used with multiple `COMMANDS...`. 20 | `argparse` uses `std.sumtype.match` for matching. Possible implementation of such `newMain` function would be a 21 | function that is overridden for every command type from `COMMANDS`. Another example would be a lambda that does 22 | compile-time checking of the type of the first parameter (see examples below for details). 23 | - Optionally `newMain` function can take a `string[]` parameter as a second argument. Providing such a function will 24 | mean that `argparse` will parse known arguments only and all unknown ones will be passed into the second parameter of 25 | `newMain` function. If `newMain` function doesn’t have such parameter, then `argparse` will error out if there is an 26 | unknown argument provided in command line. 27 | - Optionally `newMain` can return an `int`. In this case, this result will be returned from 28 | standard `main` function. 29 | 30 | **Usage examples:** 31 | 32 | 33 | 34 | 35 | 36 | 37 | ## Low-level calling of parser 38 | 39 | For the cases when providing `newMain` function is not possible or feasible, `parseArgs` function can accept a reference 40 | to an object that receives the values of command line arguments: 41 | 42 | `Result parseArgs(ref COMMAND receiver, string[] args)` 43 | 44 | **Parameters:** 45 | 46 | - `receiver` – object that is populated with parsed values. 47 | - `args` – raw command line arguments (excluding `argv[0]` – first command line argument in `main` function). 48 | 49 | **Return value:** 50 | 51 | An object that can be cast to `bool` to check whether the parsing was successful or not. 52 | 53 | > Note that this function will error out if command line contains unknown arguments. 54 | > 55 | {style="warning"} 56 | 57 | **Usage example:** 58 | 59 | 60 | 61 | 62 | ## Partial argument parsing 63 | 64 | Sometimes a program may only parse a few of the command line arguments and process the remaining arguments in some different 65 | way. In these cases, `CLI!(...).parseKnownArgs` function can be used. It works much like `CLI!(...).parseArgs` except 66 | that it does not produce an error when unknown arguments are present. It has the following signatures: 67 | 68 | - `Result parseKnownArgs(ref COMMAND receiver, string[] args, out string[] unrecognizedArgs)` 69 | 70 | **Parameters:** 71 | 72 | - `receiver` – the object that’s populated with parsed values. 73 | - `args` – raw command line arguments (excluding `argv[0]` – first command line argument in `main` function). 74 | - `unrecognizedArgs` – raw command line arguments that were not parsed. 75 | 76 | **Return value:** 77 | 78 | An object that can be cast to `bool` to check whether the parsing was successful or not. 79 | 80 | - `Result parseKnownArgs(ref COMMAND receiver, ref string[] args)` 81 | 82 | **Parameters:** 83 | 84 | - `receiver` – the object that’s populated with parsed values. 85 | - `args` – raw command line arguments that are modified to have parsed arguments removed (excluding `argv[0]` – first 86 | command line argument in `main` function). 87 | 88 | **Return value:** 89 | 90 | An object that can be cast to `bool` to check whether the parsing was successful or not. 91 | 92 | **Usage example:** 93 | 94 | 95 | -------------------------------------------------------------------------------- /docs/topics/End-of-named-arguments.md: -------------------------------------------------------------------------------- 1 | # End of named arguments 2 | 3 | When the command line contains an entry that is equal to [`Config.endOfNamedArgs`](Config.md#endOfNamedArgs) 4 | (double dash `--` by default), `argparse` interprets all following command line entries as positional arguments, even 5 | if they can match a named argument or a subcommand. 6 | 7 | 8 | -------------------------------------------------------------------------------- /docs/topics/Environment-Fallback.md: -------------------------------------------------------------------------------- 1 | # Fallback to environment variable 2 | 3 | Arguments can be marked to fall back to environment variable if no value is provided on the command line. This allows to implement convenient fallback mechanisms (such as automatically picking up the username) or [12 Factor Apps](https://12factor.net/). 4 | 5 | Example: 6 | 7 | 8 | -------------------------------------------------------------------------------- /docs/topics/Getting-started.md: -------------------------------------------------------------------------------- 1 | # Getting started 2 | 3 | `argparse` provides User-Defined Attributes (UDA) that can be used to annotate `struct` members that are part of 4 | command line interface. 5 | 6 | ## Without User-Defined Attributes 7 | 8 | Using UDAs is not required and if a `struct` has no UDAs then all data members are treated as named command line 9 | arguments: 10 | 11 | 12 | 13 | Running the program above with `-h` argument will have the following output: 14 | 15 | Hello world example 16 | 17 | ## With User-Defined Attributes 18 | 19 | Although UDA-less approach is useful as a starting point, it's not enough for real command line tool: 20 | 21 | 22 | 23 | Running the program above with `-h` argument will have the following output: 24 | 25 | Hello world example 26 | -------------------------------------------------------------------------------- /docs/topics/Help-generation.md: -------------------------------------------------------------------------------- 1 | # Help generation 2 | 3 | 4 | ## Command 5 | 6 | `Command` UDA provides few customizations that affect help text. It can be used for **top-level command** and **subcommands**. 7 | 8 | - Program name (i.e., the name of top-level command) and subcommand name can be provided to `Command` UDA as a parameter. 9 | If program name is not provided, then `Runtime.args[0]` (a.k.a. `argv[0]` from `main` function) is used. 10 | If subcommand name is not provided (e.g., `@(Command.Description(...))`), then the name of the type that represents the command is used. 11 | - `Usage` – allows custom usage text. By default, the parser calculates the usage message from the arguments it contains 12 | but this can be overridden with `Usage` call. If the custom text contains `%(PROG)` then it will be replaced by the 13 | command/program name. 14 | - `Description` – used to provide a description of what the command/program does and how it works. In help messages, the 15 | description is displayed between the usage string and the list of the command arguments. 16 | - `ShortDescription` – used to provide a brief description of what the subcommand does. It is applicable to subcommands 17 | only and is displayed in *Available commands* section on help screen of the parent command. 18 | - `Epilog` – custom text that is printed after the list of the arguments. 19 | 20 | `Usage`, `Description`, `ShortDescription` and `Epilog` modifiers take either `string` or `string function()` 21 | value – the latter can be used to return a value that is not known at compile time. 22 | 23 | ## Argument 24 | 25 | There are some customizations supported on argument level for both `PositionalArgument` and `NamedArgument` UDAs: 26 | 27 | - `Description` – provides brief description of the argument. This text is printed next to the argument 28 | in the argument-list section of a help message. `Description` takes either `string` or `string function()` 29 | value – the latter can be used to return a value that is not known at compile time. 30 | - `Hidden` – can be used to indicate that the argument shouldn’t be printed in help message. 31 | - `Placeholder` – provides custom text that is used to indicate the value of the argument in help message. 32 | 33 | ## Help text styling 34 | 35 | `argparse` uses `Config.styling` to determine what style should be applied to different parts of the help text. 36 | Please refer to [ANSI coloring and styling](ANSI-coloring-and-styling.md) section for details. 37 | 38 | ## Example 39 | 40 | Here is an example of how this customization can be used: 41 | 42 | 43 | 44 | This example will print the following help message: 45 | 46 | Help example 47 | 48 | ## Argument groups 49 | 50 | By default, parser groups command line arguments into “required arguments” and “optional arguments” when displaying help 51 | message. When there is a better conceptual grouping of arguments than this default one, appropriate groups can be 52 | created using `ArgumentGroup` UDA. 53 | 54 | This UDA has some customization for displaying help text: 55 | 56 | - `Description` – provides brief description of the group. This text is printed right after group name. 57 | It takes either `string` or `string function()` value – the latter can be used to return a value that is not known 58 | at compile time. 59 | 60 | Example: 61 | 62 | 63 | 64 | When an argument is attributed with a group, the parser treats it just like a normal argument, but displays the argument 65 | in a separate group for help messages: 66 | 67 | Help argument group 68 | -------------------------------------------------------------------------------- /docs/topics/Introduction.md: -------------------------------------------------------------------------------- 1 | # Introduction 2 | 3 | `argparse` is a flexible parser of command line arguments that allows easy creation of terminal applications with rich 4 | command line interface. 5 | 6 | ## Features 7 | 8 | `argparse` provides a lot of features to support different use cases: 9 | - Positional arguments 10 | - Named arguments 11 | - Argument grouping 12 | - Subcommands 13 | - Built-in help screen 14 | - ANSI colors and styles 15 | - Shell completion 16 | - Support for different types, for example: scalars, enums, arrays, associative arrays, callbacks 17 | 18 | Example of help screen with ANSI colors and styles: 19 | 20 | Help example 21 | -------------------------------------------------------------------------------- /docs/topics/Named-arguments.md: -------------------------------------------------------------------------------- 1 | # Named arguments 2 | 3 | _Named arguments_ (they are also called as flags or options) have one or more names. Each name can be short, long or both. 4 | They can be declared using `NamedArgument` UDA which has the following parameters (see [reference](PositionalNamedArgument.md) 5 | for details): 6 | 7 | ```c++ 8 | NamedArgument(string[] names...) 9 | NamedArgument(string[] shortNames, string[] longNames) 10 | ``` 11 | 12 | | Name | Type | Optional/
Required | Description | 13 | |--------------|---------------|------------------------|---------------------------------| 14 | | `name` | `string[]...` | optional | Name(s) of this argument. | 15 | | `shortNames` | `string[]` | required | Short name(s) of this argument. | 16 | | `longNames` | `string[]` | required | Long name(s) of this argument. | 17 | 18 | Example: 19 | 20 | 21 | 22 | ## Short names 23 | 24 | - _Short names_ in command line are those that start with short name prefix which is a single dash `-` by default (see 25 | [`Config.shortNamePrefix`](Config.md#shortNamePrefix) for customization). 26 | 27 | If short names are not explicitly passed to `NamedArgument` UDA then all single-character names are considered short names 28 | (see [reference](PositionalNamedArgument.md) for details). 29 | 30 | > Note that short names can be longer than one character (they must be explicitly specified to `NamesArgument` UDA). 31 | > 32 | {style="note"} 33 | 34 | The following usages of the argument short name in the command line are equivalent: 35 | - `-name John` 36 | - `-name=John` 37 | - `-n John` 38 | - `-n=John` 39 | 40 | > Any other character can be used instead of `=` – see [`Config.assignChar`](Config.md#assignChar) for details. 41 | > 42 | {style="note"} 43 | 44 | Additionally, for single-character short names the following is supported: 45 | - Omitting of [assign character](Config.md#assignChar): `-nJohn` is an equivalent to `-n=John`. 46 | - Arguments [bundling](Config.md#bundling): `-ab` is and equivalent to `-a -b`. 47 | 48 | ## Long names 49 | 50 | _Long names_ in command line are those that start with long name prefix which is double dash `--` by default (see 51 | [`Config.longNamePrefix`](Config.md#longNamePrefix) for customization). 52 | 53 | If long names are not explicitly passed to `NamedArgument` UDA then all multi-character names are considered long names 54 | (see [reference](PositionalNamedArgument.md) for details). 55 | 56 | > Note that long names can be single-character (they must be explicitly specified to `NamesArgument` UDA). 57 | > 58 | {style="note"} 59 | 60 | The following usages of the argument long names in the command line are equivalent: 61 | - `--name John` 62 | - `--name=John` 63 | - `--n John` 64 | - `--n=John` 65 | 66 | > Any other character can be used instead of `=` – see [`Config.assignChar`](Config.md#assignChar) for details. 67 | > 68 | {style="note"} 69 | -------------------------------------------------------------------------------- /docs/topics/Optional-and-required-arguments.md: -------------------------------------------------------------------------------- 1 | # Optional and required arguments 2 | 3 | Arguments can be marked as required or optional by adding `.Required` or `.Optional` to UDA. If required argument is 4 | not present in command line, `argparse` will error out. 5 | 6 | By default, _positional arguments_ are **required** and _named arguments_ are **optional**. 7 | 8 | Example: 9 | 10 | 11 | -------------------------------------------------------------------------------- /docs/topics/Parsing-customization.md: -------------------------------------------------------------------------------- 1 | # Parsing customization 2 | 3 | Sometime the functionality that is provided out of the box is not enough and needs to be tuned. 4 | `argparse` allows customizing of every step of command line parsing: 5 | 6 | - **Pre-validation** – argument values are validated as raw strings. 7 | - **Parsing** – raw argument values are converted to a different type (usually the type of the receiving data member). 8 | - **Validation** – converted value is validated. 9 | - **Action** – depending on a type of the receiving data member, for example, it can be an assignment of converted value to a 10 | data member, or appending value if type is an array. 11 | 12 | In case if argument does not have any value to parse, then the only one step is involved in parsing: 13 | 14 | - **Action if no value** – similar to **Action** step above but without converted value. 15 | 16 | > If any of the steps above fails, then the command line parsing fails as well. 17 | > 18 | {style="note"} 19 | 20 | Each of the steps above can be customized with UDA modifiers below. 21 | 22 | ## Pre-validation {id="PreValidation"} 23 | 24 | `PreValidation` modifier can be used to customize the validation of raw string values. It accepts a function with one of 25 | the following signatures: 26 | 27 | - `bool validate(string value)` 28 | 29 | `Result validate(string value)` 30 | 31 | > In this case, function will be called once for every value specified in command line for an argument in case of multiple values. 32 | > 33 | {style="note"} 34 | 35 | - `bool validate(string[] value)` 36 | 37 | `Result validate(string[] value)` 38 | 39 | - `bool validate(RawParam param)` 40 | 41 | `Result validate(RawParam param)` 42 | 43 | Parameters: 44 | 45 | - `value`/`param` values to be parsed. 46 | 47 | Return value: 48 | 49 | - `true`/`Result.Success` if validation passed or 50 | - `false`/`Result.Error` otherwise. 51 | 52 | ## Parsing {id="Parse"} 53 | 54 | `Parse` modifier allows providing custom conversion from raw string to a typed value. It accepts a function with one of 55 | the following signatures: 56 | 57 | - `ParseType parse(string value)` 58 | 59 | `ParseType parse(string[] value)` 60 | 61 | `ParseType parse(RawParam param)` 62 | 63 | > `ParseType` is a type that a string value is supposed to be parsed to and it is not required be the same as 64 | a type of destination - `argparse` tries to detect this type from provided function. 65 | > 66 | > `ParseType` must not be immutable or const. 67 | > 68 | {title="ParseType"} 69 | 70 | - `void parse(ref ParseType receiver, RawParam param)` 71 | 72 | `bool parse(ref ParseType receiver, RawParam param)` 73 | 74 | `Result parse(ref ParseType receiver, RawParam param)` 75 | 76 | 77 | Parameters: 78 | 79 | - `value`/`param` raw (string) values to be parsed. 80 | - `receiver` is an output variable for parsed value. 81 | 82 | Return value for functions that return `bool` or `Result` (in other cases parsing is always considered successful): 83 | - `true`/`Result.Success` if parsing was successful or 84 | - `false`/`Result.Error` otherwise. 85 | 86 | ## Validation {id="Validation"} 87 | 88 | `Validation` modifier can be used to validate parsed value. It accepts a function with one of the following 89 | signatures: 90 | 91 | - `bool validate(ParseType value)` 92 | 93 | `Result validate(ParseType value)` 94 | 95 | - `bool validate(Param!ParseType param)` 96 | 97 | `Result validate(Param!ParseType param)` 98 | 99 | > `ParseType` is a type that is used in `Parse` modifier or `string` if the latter is omitted. 100 | > 101 | {title="ParseType"} 102 | 103 | Parameters: 104 | 105 | - `value`/`param` contains a value returned from `Parse` step. 106 | 107 | Return value: 108 | 109 | - `true`/`Result.Success` if validation passed or 110 | - `false`/`Result.Error` otherwise. 111 | 112 | ## Action {id="Action"} 113 | 114 | `Action` modifier allows customizing a logic of how "destination" should be changed when argument has a value in 115 | command line. It accepts a function with one of the following signatures: 116 | 117 | - `void action(ref T receiver, ParseType value)` 118 | 119 | `bool action(ref T receiver, ParseType value)` 120 | 121 | `Result action(ref T receiver, ParseType value)` 122 | 123 | - `void action(ref T receiver, Param!ParseType param)` 124 | 125 | `bool action(ref T receiver, Param!ParseType param)` 126 | 127 | `Result action(ref T receiver, Param!ParseType param)` 128 | 129 | > `ParseType` is a type that is used in `Parse` modifier or `string` if the latter is omitted. 130 | > 131 | {title="ParseType"} 132 | 133 | Parameters: 134 | 135 | - `receiver` is a receiver (destination) which is supposed to be changed based on a `value`/`param`. 136 | - `value`/`param` has a value returned from `Parse` step. 137 | 138 | Return value: 139 | 140 | - `true`/`Result.Success` if operation was successful or 141 | - `false`/`Result.Error` otherwise. 142 | 143 | ## Example 144 | 145 | All the above modifiers can be combined in any way: 146 | 147 | 148 | -------------------------------------------------------------------------------- /docs/topics/Positional-arguments.md: -------------------------------------------------------------------------------- 1 | # Positional arguments 2 | 3 | _Positional arguments_ are arguments that have specific position within the command line. This argument can be declared 4 | using `PositionalArgument` UDA. It has the following parameters: 5 | 6 | ```c++ 7 | PositionalArgument() 8 | PositionalArgument(string placeholder) 9 | PositionalArgument(uint position) 10 | PositionalArgument(uint position, string placeholder) 11 | ``` 12 | 13 | | Name | Type | Optional/
Required | Description | 14 | |---------------|----------|------------------------|---------------------------------------------------------------------------------------------------------------------------------------------| 15 | | `position` | `uint` | required | Zero-based position of the argument. If it's omitted then the order of positional arguments is the same as the order of member declaration. | 16 | | `placeholder` | `string` | optional | Name of this argument that is shown in help text.
If not provided, then the name of data member is used. | 17 | 18 | Since that both _named_ and _positional arguments_ can be mixed in the command line, `argparse` enforces the following 19 | restrictions to be able to parse a command line unambiguously: 20 | - Mixing of `PositionalArgument` with and without position is not allowed. 21 | - Positions of _positional arguments_ must be consecutive starting with zero without missing or repeating. 22 | - _Positional argument_ must not have variable number of values except for the last argument. 23 | - Optional _positional argument_ must come after required _positional arguments_. 24 | - If a command has default subcommand (see Subcommand section for details) the optional _positional argument_ is not 25 | allowed in this command. 26 | 27 | Example: 28 | 29 | 30 | -------------------------------------------------------------------------------- /docs/topics/Restrict-allowed-values.md: -------------------------------------------------------------------------------- 1 | # Restrict allowed values 2 | 3 | In some cases an argument can receive one of the limited set of values. This can be achieved by adding `.AllowedValues()` 4 | to UDA: 5 | 6 | 7 | 8 | For the value that is not in the allowed list, this error will be printed: 9 | 10 | Allowed values error 11 | 12 | > If the type of destination data member is `enum`, then the allowed values are automatically limited to those 13 | > listed in the `enum`. 14 | > 15 | > See [Enum](Supported-types.md#enum) section for details. 16 | > 17 | {style="note"} -------------------------------------------------------------------------------- /docs/topics/Shell-completion.md: -------------------------------------------------------------------------------- 1 | # Shell completion 2 | 3 | `argparse` supports tab completion of last argument for certain shells (see below). However, this support is limited 4 | to the names of arguments and subcommands. 5 | 6 | > Note that hidden arguments are not shown in shell completion. See [Hidden()](PositionalNamedArgument.md#hidden) for details. 7 | > 8 | {style="note"} 9 | or returned in shell completion 10 | 11 | ## Wrappers for main function 12 | 13 | If you are using `CLI!(...).main(alias newMain)` mixin template in your code then you can easily build a completer 14 | (program that provides completion) by defining `argparse_completion` version (`-version=argparse_completion` option of 15 | `dmd`). Don’t forget to use different output file for completer than your main program (`-of` option in `dmd`). No other 16 | changes are necessary to generate completer, but you should consider minimizing the set of imported modules when 17 | `argparse_completion` version is defined. For example, you can put all imports into your main function that is passed to 18 | `CLI!(...).main(alias newMain)` – `newMain` parameter is not used in completer. 19 | 20 | If you prefer having separate main module for completer, then you can use `CLI!(...).mainComplete` mixin template: 21 | ```c++ 22 | mixin CLI!(...).mainComplete; 23 | ``` 24 | 25 | In case if you prefer to have your own `main` function and would like to call completer by yourself, you can use 26 | `int CLI!(...).complete(string[] args)` function. This function executes the completer by parsing provided `args` (note 27 | that you should remove the first argument from `argv` passed to `main` function). The returned value is meant to be 28 | returned from `main` function, having zero value in case of success. 29 | 30 | ## Low level completion 31 | 32 | In case if none of the above methods is suitable, `argparse` provides `string[] CLI!(...).completeArgs(string[] args)` 33 | function. It takes arguments that should be completed and returns all possible completions. 34 | 35 | `completeArgs` function expects to receive all command line arguments (excluding `argv[0]` – first command line argument 36 | in `main` function) in order to provide completions correctly (set of available arguments depends on subcommand). This 37 | function supports two workflows: 38 | - If the last argument in `args` is empty and it’s not supposed to be a value for a command line argument, then all 39 | available arguments and subcommands (if any) are returned. 40 | - If the last argument in `args` is not empty and it’s not supposed to be a value for a command line argument, then only 41 | those arguments and subcommands (if any) are returned that start with the same text as the last argument in `args`. 42 | 43 | For example, if there are `--foo`, `--bar` and `--baz` arguments available, then: 44 | - Completion for `args=[""]` will be `["--foo", "--bar", "--baz"]`. 45 | - Completion for `args=["--b"]` will be `["--bar", "--baz"]`. 46 | 47 | ## Using the completer 48 | 49 | Completer that is provided by `argparse` supports the following shells: 50 | - bash 51 | - zsh 52 | - tcsh 53 | - fish 54 | 55 | Its usage consists of two steps: completion setup and completing of the command line. Both are implemented as 56 | subcommands (`init` and `complete` accordingly). 57 | 58 | ### Completion setup 59 | 60 | Before using completion, completer should be added to the shell. This can be achieved by using `init` subcommand. It 61 | accepts the following arguments (you can get them by running ` init --help`): 62 | - `--bash`: provide completion for bash. 63 | - `--zsh`: provide completion for zsh. Note: zsh completion is done through bash completion so you should execute `bashcompinit` first. 64 | - `--tcsh`: provide completion for tcsh. 65 | - `--fish`: provide completion for fish. 66 | - `--completerPath `: path to completer. By default, the path to itself is used. 67 | - `--commandName `: command name that should be completed. By default, the first name of your main command is used. 68 | 69 | Either `--bash`, `--zsh`, `--tcsh` or `--fish` is expected. 70 | 71 | As a result, completer prints the script to setup completion for requested shell into standard output (`stdout`) 72 | which should be executed. To make this more streamlined, you can execute the output inside the current shell or to do 73 | this during shell initialization (e.g., in `.bashrc` for bash). To help doing so, completer also prints sourcing 74 | recommendation to standard output as a comment. 75 | 76 | Example of completer output for ` init --bash --commandName mytool --completerPath /path/to/completer` arguments: 77 | ```bash 78 | # Add this source command into .bashrc: 79 | # source <(/path/to/completer init --bash --commandName mytool) 80 | complete -C 'eval /path/to/completer --bash -- $COMP_LINE ---' mytool 81 | ``` 82 | 83 | Recommended workflow is to install completer into a system according to your installation policy and update shell 84 | initialization/config file to source the output of `init` command. 85 | 86 | ### Completing of the command line 87 | 88 | Argument completion is done by `complete` subcommand (it’s default one). It accepts the following arguments (you can get them by running ` complete --help`): 89 | - `--bash`: provide completion for bash. 90 | - `--tcsh`: provide completion for tcsh. 91 | - `--fish`: provide completion for fish. 92 | 93 | As a result, completer prints all available completions, one per line, assuming that it’s called according to the output 94 | of `init` command. 95 | 96 | -------------------------------------------------------------------------------- /docs/topics/Subcommands.md: -------------------------------------------------------------------------------- 1 | # Subcommands 2 | 3 | Sophisticated command line tools, like `git`, have many subcommands (e.g., `git clone`, `git commit`, `git push`, etc.), 4 | each with its own set of arguments. There are few ways how to use subcommands with `argparse`. 5 | 6 | ## `Subcommand` type 7 | 8 | General approach to declare subcommands is to use `SubCommand` type. This type is behaving like a `SumType` from standard library 9 | with few additions: 10 | - It allows no command to be chosen. 11 | - It supports at most one default subcommand (see [below](#default-subcommand) for details). 12 | 13 | > See [SubCommand](SubCommand.md) section in the Reference for more detailed description of `SubCommand` type. 14 | > 15 | {style="note"} 16 | 17 | 18 | 19 | ## Subcommands with shared common arguments 20 | 21 | In some cases command line tool has arguments that are common across all subcommands. They can be specified as regular 22 | arguments in a struct that represents the whole program: 23 | 24 | 25 | 26 | ## Subcommand name and aliases 27 | 28 | Using type name as a subcommand name in command line might not be convenient, moreover, the same subcommand might have 29 | multiple names in command line (e.g. short and long versions). `Command` UDA can be used to list all acceptable names for 30 | a subcommand: 31 | 32 | 33 | 34 | ## Default subcommand 35 | 36 | Default subcommand is one that is selected when user does not specify any subcommand in the command line. 37 | To mark a subcommand as default, use `Default` template: 38 | 39 | 40 | 41 | ## Enumerating subcommands in CLI mixin 42 | 43 | One of the possible ways to use subcommands with `argparse` is to list all subcommands in `CLI` mixin. Although this might 44 | be a useful feature, it is very limited: `CLI` mixin only allows overriding of the `main` function for this case: 45 | 46 | 47 | -------------------------------------------------------------------------------- /docs/topics/Supported-types.md: -------------------------------------------------------------------------------- 1 | # Supported types 2 | 3 | When command line entries are mapped to the annotated data members, the text value is converted to the type of the 4 | data member. 5 | 6 | ## Boolean flags 7 | 8 | Boolean types usually represent command line flags. `argparse` supports multiple ways of providing flag value including 9 | negation (i.e., `--no-flag`): 10 | 11 | | Command line entries | Result | 12 | |--------------------------------|-----------| 13 | | `-b` / `--boo` | `true` | 14 | | `-b=` / `--boo=` | `` | 15 | | `--no-b` / `--no-boo` | `false` | 16 | | `-b ` / `--boo ` | error | 17 | 18 | > `` is accepted only if it's provided with assignment `=` character ([`Config.assignChar`](Config.md#assignChar)), 19 | > not as a separate command line entry. 20 | > 21 | {style="warning"} 22 | 23 | `argparse` supports the following strings as a `` (comparison is case-insensitive): 24 | 25 | | `` | Result | 26 | |------------------|--------| 27 | | `true`,`yes`,`y` | true | 28 | | `false`,`no`,`n` | false | 29 | 30 | ## Numbers and strings 31 | 32 | Numeric (according to `std.traits.isNumeric`) and string (according to `std.traits.isSomeString`) data types are 33 | seamlessly converted to destination type using `std.conv.to`: 34 | 35 | 36 | 37 | ## Arrays 38 | 39 | `argparse` supports 1D and 2D arrays: 40 | - If an argument is bound to 1D array, a new element is appended to this array each time the argument is provided in 41 | command line. 42 | - In case of 2D array, new elements are grouped in a way as they appear in 43 | command line and then each group is appended to this array. 44 | 45 | The difference can be easily shown in the following example: 46 | 47 | 48 | 49 | ## Associative arrays 50 | 51 | `argparse` also supports associative array where simple value type (e.g. numbers, strings etc.). In this case, expected 52 | format of the value is `key=value` (equal sign can be customized with [`Config.assignChar`](Config.md#assignChar)): 53 | 54 | 55 | 56 | ## Enums {id="enum"} 57 | 58 | It is encouraged to use `enum` types for arguments that have a limited set of valid values. In this case, `argparse` 59 | validates that the value specified in command line matches one of enum identifiers: 60 | 61 | 62 | 63 | In some cases the value for command line argument might have characters that are not allowed in enum identifiers. 64 | Actual values that are allowed in command line can be adjusted with `AllowedValues` UDA: 65 | 66 | 67 | 68 | > When `AllowedValues` UDA is used, enum identifier is ignored so if argument is supposed to accept it, identifier 69 | > must be listed in the UDA as well - see `"noapple"` in the example above. 70 | > 71 | {style="note"} 72 | 73 | ## Counter 74 | 75 | Counter is an argument that tracks the number of times it's specified in the command line: 76 | 77 | 78 | 79 | The same example with enabled [bundling](Arguments-bundling.md): 80 | 81 | 82 | 83 | ## Callback 84 | 85 | If member type is a function, `argparse` will try to call it when the corresponding argument is specified in the 86 | command line. 87 | 88 | `argparse` supports the following function signatures (return value is ignored, if any): 89 | 90 | - `... func()` - argument is treated as a boolean flag. 91 | 92 | - `... func(string)` - argument has exactly one value. The value specified in command line is provided into `string` parameter. 93 | 94 | - `... func(string[])` - argument has zero or more values. Values specified in command line are provided into `string[]` parameter. 95 | 96 | - `... func(RawParam)` - argument has zero or more values. Values specified in command line are provided into parameter. 97 | 98 | Example: 99 | 100 | 101 | 102 | ## Custom types 103 | 104 | `argparse` can actually work with any arbitrary type - just provide parsing function (see [Parsing customization](Parsing-customization.md#Parse) 105 | for details): 106 | 107 | 108 | -------------------------------------------------------------------------------- /docs/topics/reference/AllowedValues.md: -------------------------------------------------------------------------------- 1 | # AllowedValues 2 | 3 | `AllowedValues` UDA is used to list all values that an argument can accept. This is very useful in the cases when an argument 4 | must have a value from a specific list, for example, when argument type is `enum`. 5 | 6 | **Signature** 7 | 8 | ```C++ 9 | AllowedValues(string[] values...) 10 | ``` 11 | 12 | **Parameters** 13 | 14 | - `values` 15 | 16 | Values that argument can have. 17 | 18 | **Usage example** 19 | 20 | ```C++ 21 | enum Fruit { 22 | apple, 23 | @AllowedValues("no-apple","noapple") 24 | noapple 25 | }; 26 | ``` 27 | -------------------------------------------------------------------------------- /docs/topics/reference/ArgumentGroup.md: -------------------------------------------------------------------------------- 1 | # ArgumentGroup 2 | 3 | `ArgumentGroup` UDA is used to group arguments on help screen. 4 | 5 | ## Usage 6 | 7 | **Signature** 8 | ```C++ 9 | ArgumentGroup(string name) 10 | ``` 11 | 12 | **Usage example** 13 | 14 | ```C++ 15 | @ArgumentGroup("my group") 16 | { 17 | ... 18 | } 19 | ``` 20 | 21 | 22 | ## Public members 23 | 24 | ### Description 25 | 26 | `Description` is used to add description text to a group. 27 | 28 | **Signature** 29 | 30 | ```C++ 31 | ... Description(auto ref ... group, string text) 32 | ... Description(auto ref ... group, string function() text) 33 | ``` 34 | 35 | **Parameters** 36 | 37 | - `text` 38 | 39 | Text that contains group description or a function that returns such text. 40 | 41 | **Usage example** 42 | 43 | ```C++ 44 | @(ArgumentGroup("my group").Description("custom description")) 45 | { 46 | ... 47 | } 48 | ``` 49 | -------------------------------------------------------------------------------- /docs/topics/reference/CLI-API.md: -------------------------------------------------------------------------------- 1 | # CLI API 2 | 3 | `CLI` is a template that provides entry-point functions to call `argparse`. 4 | 5 | Here are the signatures that `CLI` template has: 6 | ```c++ 7 | template CLI(Config config, COMMAND) 8 | template CLI(Config config, COMMANDS...) 9 | ``` 10 | 11 | The second template with multiple `COMMANDS...` has only `main` function which wraps all `COMMANDS` inside internal 12 | struct with only data member of type `SubCommand!COMMANDS` and calls `CLI(Config config, CMD).main` with that. 13 | 14 | There is also an `alias` that uses default `Config.init` to simplify default behavior: 15 | ```c++ 16 | alias CLI(COMMANDS...) = CLI!(Config.init, COMMANDS); 17 | ``` 18 | 19 | ## Public members 20 | 21 | ### parseKnownArgs 22 | 23 | `CLI.parseKnownArgs` is a function that parses only known arguments from the command line. 24 | 25 | All arguments that were not recognized during parsing are returned to a caller. 26 | 27 | **Signature** 28 | 29 | ```c++ 30 | Result parseKnownArgs(ref COMMAND receiver, string[] args, out string[] unrecognizedArgs) 31 | Result parseKnownArgs(ref COMMAND receiver, ref string[] args) 32 | ``` 33 | 34 | **Parameters** 35 | 36 | - `receiver` 37 | 38 | Object that receives parsed command line arguments. 39 | 40 | - `args` 41 | 42 | Command line arguments to parse (excluding `argv[0]` – first command line argument in `main` function). 43 | 44 | - `unrecognizedArgs` 45 | 46 | Command line arguments that were not parsed. 47 | 48 | **Notes** 49 | 50 | - The second signature (without `unrecognizedArgs` parameter) returns not parsed arguments through `args` reference parameter. 51 | 52 | **Return value** 53 | 54 | `Result` object that can be cast to `bool` to check whether the parsing was successful or not. 55 | Successful parsing for `parseKnownArgs` function means that there are no error during parsing of known arguments. 56 | This means that having unrecognized arguments in a command line is not an error. 57 | 58 | ### parseArgs 59 | 60 | `CLI.parseArgs` is a function that parses command line arguments and validates that there are no unknown ones. 61 | 62 | **Signature** 63 | 64 | ```c++ 65 | Result parseArgs(ref COMMAND receiver, string[] args) 66 | ``` 67 | 68 | **Parameters** 69 | 70 | - `receiver` 71 | 72 | Object that receives parsed command line arguments. 73 | 74 | - `args` 75 | 76 | Command line arguments to parse (excluding `argv[0]` – first command line argument in `main` function). 77 | 78 | **Return value** 79 | 80 | - In case of parsing error - `Result.exitCode` (`1` by default). 81 | - In case of success - `0`. 82 | 83 | ### complete 84 | 85 | `CLI.complete` is a function that performs shell completion for command line arguments. 86 | 87 | **Signature** 88 | 89 | ```c++ 90 | int complete()(string[] args) 91 | ``` 92 | 93 | **Parameters** 94 | 95 | - `args` 96 | 97 | Command line arguments (excluding `argv[0]` – first command line argument in `main` function). 98 | 99 | **Notes** 100 | 101 | This function provides completion for the last argument in the command line: 102 | - If the last entry in command line is an empty string (`""`) then it provides all available argument names prepended 103 | with [`Config.shortNamePrefix`](Config.md#shortNamePrefix) or [`Config.longNamePrefix`](Config.md#longNamePrefix). 104 | - If the last entry in command line contains characters then `complete` provides completion only with those arguments 105 | that have names starting with specified characters. 106 | 107 | **Return value** 108 | 109 | - `0` in case of successful parsing. 110 | - Non-zero otherwise. 111 | 112 | ### mainComplete 113 | 114 | `CLI.mainComplete` is a mixin template that provides global `main` function which calls [`CLI.complete`](#complete). 115 | 116 | **Signature** 117 | 118 | ```c++ 119 | template mainComplete() 120 | ``` 121 | 122 | **Notes** 123 | 124 | Ingested `main` function is a simple wrapper of [`CLI.complete`](#complete) function that removes `argv[0]` from command line. 125 | 126 | **Return value** 127 | 128 | Value returned from [`CLI.complete`](#complete) function. 129 | 130 | ### main 131 | 132 | `CLI.main` is a mixin template that does one of these: 133 | 134 | - If `argparse_completion` version is defined then it instantiates `CLI.mainComplete` template mixin. 135 | - Otherwise it provides global `main` function that calls [`CLI.parseArgs`](#parseargs) function. 136 | 137 | **Signature** 138 | 139 | ```c++ 140 | template main(alias newMain) 141 | ``` 142 | 143 | **Parameters** 144 | 145 | - `newMain` 146 | 147 | Function that is called after successful command line parsing. See [`newMain`](#newMain) for details. 148 | 149 | **Notes** 150 | 151 | - `newMain` parameter is not used in case if `argparse_completion` version is defined. 152 | 153 | **Return value** 154 | 155 | See [`CLI.mainComplete`](#maincomplete) and [`CLI.parseArgs`](#parseargs). 156 | 157 | ## newMain parameter {id="newMain"} 158 | 159 | `newMain` parameter in `CLI` API is a substitution for classic `main` function with the following differences: 160 | - Its first parameter has type of a command struct that is passed to `CLI` API. This parameter is filled with the data 161 | parsed from actual command line. 162 | 163 | `... newMain(COMMAND command)` 164 | 165 | - It might have optional second parameter of type `string[]` that receives unknown command line arguments. 166 | 167 | `... newMain(COMMAND command, string[] unrecognizedArgs)` 168 | 169 | - `newMain` can optionally return `int` value. In this case, `argparse` will return that value from `main` function. 170 | 171 | > If `newMain` has only one parameter, `argparse` will error out when command line contains unrecognized arguments. 172 | > 173 | {style="warning"} -------------------------------------------------------------------------------- /docs/topics/reference/Command.md: -------------------------------------------------------------------------------- 1 | # Command 2 | 3 | `Command` UDA is used to customize **top-level command** as well as **subcommands**. 4 | 5 | This UDA can be chained with functions listed below to adjust different settings. 6 | 7 | **Signature** 8 | 9 | ```c++ 10 | Command(string[] names...) 11 | ``` 12 | 13 | **Parameters** 14 | 15 | - `names` 16 | 17 | For **subcommands**, these are the names of the subcommand that can appear in the command line. If no name is provided then 18 | the name of the type that represents the command is used. 19 | 20 | For **top-level command**, this is a name of a program/tool that is appeared on help screen. If multiple names are passed, only first is used. 21 | If no name is provided then `Runtime.args[0]` (a.k.a. `argv[0]` from `main` function) is used. 22 | 23 | 24 | ## Public members 25 | 26 | ### Usage 27 | 28 | `Usage` allows customize the usage text. By default, the parser calculates the usage message from the arguments it contains 29 | but this can be overridden with `Usage` call. If the custom text contains `%(PROG)` then it will be replaced by the 30 | `argv[0]` (from `main` function) in case of top-level command or by a list of commands (all parent commands and current one) 31 | in case of subcommand. 32 | 33 | 34 | **Signature** 35 | 36 | ```C++ 37 | Usage(auto ref ... command, string text) 38 | Usage(auto ref ... command, string function() text) 39 | ``` 40 | 41 | **Parameters** 42 | 43 | - `text` 44 | 45 | Usage text or a function that returns such text. 46 | 47 | **Usage example** 48 | 49 | ```C++ 50 | @(Command.Usage("%(PROG) [...]")) 51 | struct my_command 52 | { 53 | ... 54 | } 55 | ``` 56 | 57 | ### Description 58 | 59 | `Description` can be used to provide a description of what the command/program does and how it works. In help messages, the 60 | description is displayed between the usage string and the list of the command arguments. 61 | 62 | **Signature** 63 | 64 | ```C++ 65 | Description(auto ref ... command, string text) 66 | Description(auto ref ... command, string function() text) 67 | ``` 68 | 69 | **Parameters** 70 | 71 | - `text` 72 | 73 | Text that contains command description or a function that returns such text. 74 | 75 | **Usage example** 76 | 77 | ```C++ 78 | @(Command.Description("custom description")) 79 | struct my_command 80 | { 81 | ... 82 | } 83 | ``` 84 | 85 | ### ShortDescription 86 | 87 | `ShortDescription` can be used to provide a brief description of what the subcommand does. It is applicable to subcommands 88 | only and is displayed in *Available commands* section on help screen of the parent command. 89 | 90 | **Signature** 91 | 92 | ```C++ 93 | ShortDescription(auto ref ... command, string text) 94 | ShortDescription(auto ref ... command, string function() text) 95 | ``` 96 | 97 | **Parameters** 98 | 99 | - `text` 100 | 101 | Text that contains short description for a subcommand or a function that returns such text. 102 | 103 | **Usage example** 104 | 105 | ```C++ 106 | @(Command.ShortDescription("custom description")) 107 | struct my_command 108 | { 109 | ... 110 | } 111 | ``` 112 | 113 | 114 | ### Epilog 115 | 116 | `Epilog` can be used to provide some custom text that is printed at the end after the list of command arguments. 117 | 118 | **Signature** 119 | 120 | ```C++ 121 | Epilog(auto ref ... command, string text) 122 | Epilog(auto ref ... command, string function() text) 123 | ``` 124 | 125 | **Parameters** 126 | 127 | - `text` 128 | 129 | Epilog text or a function that returns such text. 130 | 131 | **Usage example** 132 | 133 | ```C++ 134 | @(Command.Epilog("extra info about the command")) 135 | struct my_command 136 | { 137 | ... 138 | } 139 | ``` 140 | -------------------------------------------------------------------------------- /docs/topics/reference/Config.md: -------------------------------------------------------------------------------- 1 | # Config 2 | 3 | `argparse` provides decent amount of settings to customize the parser. All customizations can be done by creating 4 | `Config` object with required settings (see below) and passing it to [CLI API](CLI-API.md). 5 | 6 | ## Assign character {id="assignChar"} 7 | 8 | `Config.assignChar` is an assignment character used in arguments with value: `-a=5`, `-boo=foo`. 9 | 10 | Default is equal sign `=`. 11 | 12 | Example: 13 | 14 | 15 | 16 | 17 | ## Assign character {id="assignKeyValueChar"} 18 | 19 | `Config.assignKeyValueChar` is an assignment character used in arguments that have associative array type: `-a=key=value`, `-boo=key=value`. 20 | 21 | Default is equal sign `=`. 22 | 23 | Example: 24 | 25 | 26 | 27 | ## Value separator {id="valueSep"} 28 | 29 | `Config.valueSep` is a separator that is used to extract argument values: `-a=5,6,7`, `--boo=foo,far,zoo`. 30 | 31 | Default is `,`. 32 | 33 | Example: 34 | 35 | 36 | 37 | ## Prefix for short argument name {id="shortNamePrefix"} 38 | 39 | `Config.shortNamePrefix` is a string that short names of arguments begin with. 40 | 41 | Default is dash (`-`). 42 | 43 | Example: 44 | 45 | 46 | 47 | ## Prefix for long argument name {id="longNamePrefix"} 48 | 49 | `Config.longNamePrefix` is a string that long names of arguments begin with. 50 | 51 | Default is double dash (`--`). 52 | 53 | Example: 54 | 55 | 56 | 57 | ## Variadic named arguments {id="variadicNamedArgument"} 58 | 59 | `Config.variadicNamedArgument` flag controls whether named arguments should be follow [POSIX.1-2024](https://pubs.opengroup.org/onlinepubs/9799919799/) 60 | guidelines which allows only one value per named argument: `-a value1 -a value2`. 61 | 62 | Setting this flag to `true` allows multiple values to be passed to a named argument: `-a value1 value2`. 63 | 64 | Default is `false`. 65 | 66 | Example: 67 | 68 | 69 | 70 | ## End of named arguments {id="endOfNamedArgs"} 71 | 72 | `Config.endOfNamedArgs` is a string that marks the end of all named arguments. All arguments that are specified 73 | after this one are treated as positional regardless to the value which can start with `Config.shortNamePrefix` or 74 | `Config.longNamePrefix` or be a subcommand. 75 | 76 | Default is double dash (`--`). 77 | 78 | Example: 79 | 80 | 81 | 82 | ## Case sensitivity {id="caseSensitive"} 83 | 84 | `Config` type hase three data members to allow fine-grained tuning of case sensitivity: 85 | - `Config.caseSensitiveShortName` to control case sensitivity for short argument names. 86 | - `Config.caseSensitiveLongName` to control case sensitivity for long argument names. 87 | - `Config.caseSensitiveSubCommand` to control case sensitivity for subcommands. 88 | 89 | Default value for all of them is `true`. 90 | 91 | > There is `Config.caseSensitive` helper function/property that sets all settings above to a specific value. 92 | > 93 | {style="note"} 94 | 95 | 96 | Example: 97 | 98 | 99 | 100 | ## Bundling of single-character arguments {id="bundling"} 101 | 102 | `Config.bundling` controls whether single-character arguments (usually boolean flags) can be bundled together. 103 | If it is set to `true` then `-abc` is the same as `-a -b -c`. 104 | 105 | Default is `false`. 106 | 107 | Example: 108 | 109 | 110 | 111 | ## Adding help generation {id="addHelpArgument"} 112 | 113 | `Config.addHelpArgument` can be used to add (if `true`) or not (if `false`) `-h`/`--help` argument. 114 | In case if the command line has `-h` or `--help`, then the corresponding help text is printed and the parsing is stopped. 115 | If `CLI!(...).parseArgs(alias newMain)` or `CLI!(...).main(alias newMain)` is used, then provided `newMain` function will 116 | not be called. 117 | 118 | Default is `true`. 119 | 120 | Example: 121 | 122 | 123 | 124 | Help text from the first part of the example code above: 125 | 126 | Config help example 127 | 128 | 129 | ## Styling mode {id="stylingMode"} 130 | 131 | `Config.stylingMode` controls whether styling for help text and errors should be enabled. 132 | It has the following type: `enum StylingMode { autodetect, on, off }`: 133 | - `Config.StylingMode.on`: styling is **always enabled**. 134 | - `Config.StylingMode.off`: styling is **always disabled**. 135 | - `Config.StylingMode.autodetect`: styling will be enabled when possible. 136 | 137 | See [ANSI coloring and styling](ANSI-coloring-and-styling.md) for details. 138 | 139 | Default value is `Config.StylingMode.autodetect`. 140 | 141 | Example: 142 | 143 | 144 | 145 | Help text from the first part of the example code above: 146 | 147 | Config stylingMode example 148 | 149 | ## Styling scheme {id="styling"} 150 | 151 | `Config.styling` contains style for the text output (error messages and help text). It has the following members: 152 | 153 | - `programName`: style for the program name. Default is `bold`. 154 | - `subcommandName`: style for the subcommand name. Default is `bold`. 155 | - `argumentGroupTitle`: style for the title of argument group. Default is `bold.underline`. 156 | - `argumentName`: style for the argument name. Default is `lightYellow`. 157 | - `namedArgumentValue`: style for the value of named argument. Default is `italic`. 158 | - `positionalArgumentValue`: style for the value of positional argument. Default is `lightYellow`. 159 | - `errorMessagePrefix`: style for *Error:* prefix in error messages. Default is `red`. 160 | 161 | See [ANSI coloring and styling](ANSI-coloring-and-styling.md) for details. 162 | 163 | Example: 164 | 165 | 166 | 167 | Help text from the first part of the example code above: 168 | 169 | Config styling example 170 | 171 | ## Error handling {id="errorHandler"} 172 | 173 | `Config.errorHandler` is a handler function for all errors occurred during command line parsing. 174 | It is a function that receives `string` parameter which would contain an error message. 175 | 176 | > Function must ne `nothrow` 177 | > 178 | {style="warning"} 179 | 180 | The default behavior is to print error message to `stderr`. 181 | 182 | Example: 183 | 184 | 185 | 186 | This code prints `Detected an error: Unrecognized arguments: ["-b"]` to `stderr`. 187 | -------------------------------------------------------------------------------- /docs/topics/reference/MutuallyExclusive.md: -------------------------------------------------------------------------------- 1 | # MutuallyExclusive 2 | 3 | `MutuallyExclusive` UDA is used to mark a set of arguments as mutually exclusive. This means that as soon as one argument from 4 | this group is specified in the command line then no other arguments from the same group can be specified. 5 | 6 | See ["Argument dependencies"](Argument-dependencies.md#MutuallyExclusive) section for more details. 7 | 8 | ## Required 9 | 10 | "Mutually exclusive" group can be marked as required in order to require exactly one argument from the group: 11 | 12 | ```C++ 13 | @(MutuallyExclusive.Required) 14 | { 15 | int a; 16 | int b; 17 | } 18 | ``` 19 | -------------------------------------------------------------------------------- /docs/topics/reference/Param-RawParam.md: -------------------------------------------------------------------------------- 1 | # Param / RawParam 2 | 3 | [Parsing customization API](Parsing-customization.md) works with Param/RawParam struct under the hood which is publicly available for usage. 4 | 5 | `Param` is a template struct parametrized by `VALUE_TYPE` (see below) which is usually a `string[]` or a type of destination data member. 6 | 7 | > `RawParam` is an alias where `VALUE_TYPE` is `string[]`. This alias represents "raw" values from command line. 8 | > 9 | {style="note"} 10 | 11 | `Param` struct has the following fields: 12 | 13 | - `const(Config)*` `config`- The content is the same as the [`Config`](Config.md) object that was passed into [CLI API](CLI-API.md). 14 | - `string` `name` – For named argument, it contains a name that is specified in command line exactly including prefix(es) 15 | ([`Config.shortNamePrefix`](Config.md#shortNamePrefix)/[`Config.longNamePrefix`](Config.md#longNamePrefix)). 16 | For positional arguments, it contains placeholder value. 17 | - `VALUE_TYPE` `value` – Argument values that are provided in command line. 18 | -------------------------------------------------------------------------------- /docs/topics/reference/RequiredTogether.md: -------------------------------------------------------------------------------- 1 | # RequiredTogether 2 | 3 | `RequiredTogether` UDA is used to mark a set of arguments as mutually required. This means that as soon as one argument from 4 | this group is specified in the command line then all arguments from the same group must also be specified. 5 | 6 | See ["Argument dependencies"](Argument-dependencies.md#RequiredTogether) section for more details. 7 | 8 | ## Required 9 | 10 | "Required together" group can be marked as required in order to require all arguments: 11 | 12 | ```C++ 13 | @(RequiredTogether.Required) 14 | { 15 | ... 16 | } 17 | ``` 18 | -------------------------------------------------------------------------------- /docs/topics/reference/Result.md: -------------------------------------------------------------------------------- 1 | # Result struct 2 | 3 | `Result` is a struct that is used to communicate between `argparse` and user functions. 4 | Its main responsibility is to hold the result of an operation: success or failure. 5 | 6 | ## Public members 7 | 8 | ### Success 9 | 10 | `Result.Success` is an compile-time constant (`enum`) that represents a successful result of an operation. 11 | 12 | **Signature** 13 | ```c++ 14 | static enum Success 15 | ``` 16 | 17 | ### Error 18 | 19 | `Result.Error` is a function that returns a failed result of an operation. 20 | 21 | **Signature** 22 | 23 | ```c++ 24 | static auto Error(T...)(int resultCode, string msg, T extraArgs) 25 | ``` 26 | 27 | **Parameters** 28 | 29 | - `resultCode` 30 | 31 | Result/exit code of an operation. 32 | 33 | - `msg` 34 | 35 | Text of an error message. 36 | 37 | - `extraArgs` 38 | 39 | Additional arguments that are added to the text of an error message. 40 | 41 | **Notes** 42 | 43 | - `msg` and `extraArgs` are converted to a single error message string using `std.conv.text(msg, extraArgs)`. 44 | - Error message supports ANSI styling. See [ANSI coloring and styling](ANSI-coloring-and-styling.md) how to use. 45 | - Error message is passed to [`Config.errorHandler`](Config.md#errorHandler) if it's set or printed to `stderr` otherwise 46 | by [CLI API](CLI-API.md) at the end of parsing. 47 | 48 | **Return value** 49 | 50 | `Result` object that represents the failed result of an operation. 51 | 52 | ### exitCode 53 | 54 | `Result.exitCode` is a property that returns the result/exit code. It's supposed to be returned from `main()` function. 55 | 56 | **Signature** 57 | 58 | ```c++ 59 | int exitCode() const 60 | ``` 61 | 62 | **Return value** 63 | 64 | Result/exit code of an operation. 65 | 66 | ### isSuccess 67 | 68 | `Result.isSuccess` can be used to determine whether result of an operation is successful. 69 | 70 | **Signature** 71 | 72 | ```c++ 73 | bool isSuccess() const 74 | ``` 75 | 76 | **Return value** 77 | 78 | - `true` if operation is successful. 79 | - `false` otherwise. 80 | 81 | ### isError 82 | 83 | `Result.isError` can be used to determine whether result of an operation is error. 84 | 85 | **Signature** 86 | 87 | ```c++ 88 | bool isError() const 89 | ``` 90 | 91 | **Return value** 92 | 93 | - `true` if the result is error. 94 | - `false` otherwise. 95 | 96 | ### isHelpWanted 97 | 98 | `Result.isHelpWanted` can be used to determine whether the help text was requested from command line (i.e. `-h`/`--help` argument was provided). 99 | 100 | **Signature** 101 | 102 | ```c++ 103 | bool isHelpWanted() const 104 | ``` 105 | 106 | **Return value** 107 | 108 | - `true` if help text was requested. 109 | - `false` otherwise. 110 | 111 | ### opCast 112 | 113 | `Result.opCast` is the same as `Result.isSuccess`. 114 | 115 | **Signature** 116 | 117 | ```c++ 118 | bool opCast(T : bool)() const 119 | ``` 120 | 121 | **Return value** 122 | 123 | - `true` if operation is successful. 124 | - `false` otherwise. 125 | -------------------------------------------------------------------------------- /docs/topics/reference/SubCommand.md: -------------------------------------------------------------------------------- 1 | # SubCommand 2 | 3 | `SubCommand` type can be used to enumerate type for subcommands. This is a wrapper of `SumType`: 4 | 5 | ```c++ 6 | struct SubCommand(Commands...) 7 | ``` 8 | 9 | ## Public members 10 | 11 | ### Copy constructor 12 | 13 | **Signature** 14 | 15 | ```c++ 16 | this(T)(T value) 17 | ``` 18 | 19 | **Parameters** 20 | 21 | - `T value` 22 | 23 | Value that is copied to a new object. Its type `T` must be one of the `Commands`. 24 | 25 | ### Assignment operator 26 | 27 | **Signature** 28 | 29 | ```c++ 30 | ref SubCommand opAssign(T)(T value) 31 | ``` 32 | 33 | **Parameters** 34 | 35 | - `T value` 36 | 37 | Value to be assigned. Its type `T` must be one of the `Commands`. 38 | 39 | ### isSetTo 40 | 41 | Checks whether the object is set to a specific command type; 42 | 43 | **Signature** 44 | 45 | ```c++ 46 | bool isSetTo(T)() const 47 | ``` 48 | 49 | **Parameters** 50 | 51 | - `T` 52 | 53 | Type `T` must be one of the `Commands`. 54 | 55 | **Return value** 56 | 57 | `true` if object contains `T` type, `false` otherwise. 58 | 59 | ### isSet 60 | 61 | Checks whether the object is set to any command type; 62 | 63 | **Signature** 64 | 65 | ```c++ 66 | bool isSet() const 67 | ``` 68 | 69 | **Return value** 70 | 71 | If one of the `Commands` is a default command then this function always returns `true`. 72 | 73 | In case if there is no default command, then: 74 | - `true` if object contains any type from `Commands`, `false` otherwise. 75 | 76 | ## Default 77 | 78 | `Default` type is a struct that can be used to mark a subcommand as default, i.e. it's chosen if no other subcommand is specified 79 | in command line explicitly. 80 | 81 | This struct has no members and is erased by `SubCommand` before passing to internal `SumType` member. 82 | 83 | **Signature** 84 | 85 | ```c++ 86 | struct Default(COMMAND) 87 | ``` 88 | 89 | ## matchCmd 90 | 91 | `matchCmd` is a function template that is similar to `std.sumtype.match` but adapted to work with `SubCommand`. 92 | 93 | **Signature** 94 | 95 | ```c++ 96 | template matchCmd(handlers...) 97 | { 98 | auto ref matchCmd(Sub : const SubCommand!Args, Args...)(auto ref Sub sc) 99 | ... 100 | } 101 | ``` 102 | 103 | **Parameters** 104 | 105 | - `handlers` 106 | 107 | Functions that have the same meaning as for `matchCmd` function in standard library with an exception that they must not use `Default` 108 | type because the latter is erased by `SubCommand` (i.e. just use `T` instead of `Default!T` here). 109 | 110 | - `sc` 111 | 112 | `SubCommand` parameter that the matching is applied to. 113 | 114 | **Return value** 115 | 116 | - If `sc` is set to any subcommand (or has default one) then function returns the result from `std.sumtype.match` function. 117 | - Otherwise, `init` value of the type that would be returned from `std.sumtype.match` function if that type is not `void`. 118 | - Otherwise, this function has no return value (i.e. it's `void`). 119 | -------------------------------------------------------------------------------- /docs/topics/reference/ansiStylingArgument.md: -------------------------------------------------------------------------------- 1 | # ansiStylingArgument 2 | 3 | Almost every command line tool that supports ANSI coloring and styling provides command line argument to control whether 4 | this coloring/styling should be forcefully enabled or disabled. 5 | 6 | `argparse` provides `ansiStylingArgument` function that returns an object which allows checking the status of styling/coloring. 7 | This function adds a command line argument that can have one of these values: 8 | - `always` or no value - coloring/styling should be enabled. 9 | - `never` - coloring/styling should be disabled. 10 | - `auto` - in this case, `argparse` will try to detect whether ANSI coloring/styling is supported by a system. 11 | 12 | See [ANSI coloring and styling](ANSI-coloring-and-styling.md) for details. 13 | 14 | **Signature** 15 | 16 | ```C++ 17 | ... ansiStylingArgument() 18 | ``` 19 | 20 | **Usage example** 21 | 22 | ```C++ 23 | static auto color = ansiStylingArgument; 24 | ``` 25 | 26 | > Explicit `static` is not required because returned object has only `static` data members. 27 | 28 | **Return value** 29 | 30 | Returned object that can be cast to boolean. Its value is `true` when the ANSI coloring/styling should be enabled in the output, 31 | otherwise it's `false`. 32 | -------------------------------------------------------------------------------- /docs/v.list: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /docs/writerside.cfg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /dub.json: -------------------------------------------------------------------------------- 1 | { 2 | "authors": [ 3 | "Andrey Zherikov" 4 | ], 5 | "copyright": "Copyright © 2021, Andrey Zherikov", 6 | "description": "Parser for command line arguments", 7 | "license": "BSL-1.0", 8 | "name": "argparse" 9 | } -------------------------------------------------------------------------------- /examples/.gitignore: -------------------------------------------------------------------------------- 1 | all_* 2 | -------------------------------------------------------------------------------- /examples/completion/separate_main/app/app.d: -------------------------------------------------------------------------------- 1 | // This example shows the usage of a separate main for completer 2 | 3 | import argparse; 4 | import cli; 5 | 6 | // This mixin defines standard main function that parses command line and prints completion result to stdout 7 | mixin CLI!Program.main!((prog) 8 | { 9 | // do something 10 | }); -------------------------------------------------------------------------------- /examples/completion/separate_main/app/dub.json: -------------------------------------------------------------------------------- 1 | { 2 | "license": "BSL-1.0", 3 | "name": "completion-separate_main-app", 4 | "targetType":"executable", 5 | "sourcePaths":[".", "../source"], 6 | "dependencies":{ "all:argparse":"*" } 7 | } -------------------------------------------------------------------------------- /examples/completion/separate_main/completer/app.d: -------------------------------------------------------------------------------- 1 | // This example shows the usage of a separate main for completer 2 | 3 | import argparse; 4 | import cli; 5 | 6 | // This mixin defines standard main function that parses command line and prints completion result to stdout 7 | mixin CLI!Program.mainComplete; -------------------------------------------------------------------------------- /examples/completion/separate_main/completer/dub.json: -------------------------------------------------------------------------------- 1 | { 2 | "license": "BSL-1.0", 3 | "name": "completion-separate_main-completer", 4 | "targetType":"executable", 5 | "sourcePaths":[".", "../source"], 6 | "dependencies":{ "all:argparse":"*" } 7 | } -------------------------------------------------------------------------------- /examples/completion/separate_main/source/cli.d: -------------------------------------------------------------------------------- 1 | module cli; 2 | 3 | import argparse: SubCommand; 4 | 5 | struct cmd1 6 | { 7 | string car; 8 | string can; 9 | string ban; 10 | } 11 | struct cmd2 {} 12 | 13 | struct Program 14 | { 15 | string foo, bar, baz; 16 | 17 | SubCommand!(cmd1, cmd2) cmd; 18 | } -------------------------------------------------------------------------------- /examples/completion/single_main/app/dub.json: -------------------------------------------------------------------------------- 1 | { 2 | "license": "BSL-1.0", 3 | "name": "completion-single_main-app", 4 | "targetType":"executable", 5 | "sourcePaths":["../source"], 6 | "dependencies":{ "all:argparse":"*" } 7 | } -------------------------------------------------------------------------------- /examples/completion/single_main/completer/dub.json: -------------------------------------------------------------------------------- 1 | { 2 | "license": "BSL-1.0", 3 | "name": "completion-single_main-completer", 4 | "targetType":"executable", 5 | "versions": ["argparse_completion"], 6 | "sourcePaths":["../source","../../../../source"] 7 | } -------------------------------------------------------------------------------- /examples/completion/single_main/source/app.d: -------------------------------------------------------------------------------- 1 | // This example shows the usage of a single entry for both a program and a completer 2 | 3 | import argparse; 4 | 5 | struct cmd1 6 | { 7 | string car; 8 | string can; 9 | string ban; 10 | } 11 | struct cmd2 {} 12 | 13 | struct Program 14 | { 15 | string foo, bar, baz; 16 | 17 | SubCommand!(cmd1, cmd2) cmd; 18 | } 19 | 20 | // This mixin defines standard main function that parses command line and prints completion result to stdout 21 | mixin CLI!Program.main!((prog) 22 | { 23 | version(argparse_completion) 24 | { 25 | // This function is never used when 'argparse_completion' version is defined 26 | static assert(false); 27 | } 28 | }); -------------------------------------------------------------------------------- /examples/dub.json: -------------------------------------------------------------------------------- 1 | { 2 | "license": "BSL-1.0", 3 | "name": "all", 4 | "targetType":"none", 5 | "dependencies":{ 6 | "all:completion-separate_main-app":"*", 7 | "all:completion-separate_main-completer":"*", 8 | "all:completion-single_main-app":"*", 9 | "all:completion-single_main-completer":"*", 10 | "all:getting_started-with_uda":"*", 11 | "all:getting_started-without_uda":"*", 12 | "all:sub_commands-advanced":"*", 13 | "all:sub_commands-basic":"*", 14 | "all:sub_commands-common_args":"*", 15 | "all:sub_commands-default":"*" 16 | }, 17 | "subPackages": [ 18 | "..", 19 | "completion/separate_main/app", 20 | "completion/separate_main/completer", 21 | "completion/single_main/app", 22 | "completion/single_main/completer", 23 | "getting_started/with_uda", 24 | "getting_started/without_uda", 25 | "sub_commands/advanced", 26 | "sub_commands/basic", 27 | "sub_commands/common_args", 28 | "sub_commands/default" 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /examples/getting_started/with_uda/app.d: -------------------------------------------------------------------------------- 1 | import argparse; 2 | 3 | struct Example 4 | { 5 | // Positional arguments are required by default 6 | @PositionalArgument(0) 7 | string name; 8 | 9 | // Named arguments can be attributed in bulk (parentheses can be omitted) 10 | @NamedArgument 11 | { 12 | // '--number' argument 13 | int number; 14 | 15 | // '--boolean' argument 16 | bool boolean; 17 | 18 | // Argument can have default value if it's not specified in command line 19 | // '--unused' argument 20 | string unused = "some default value"; 21 | } 22 | 23 | // Enums are also supported 24 | enum Enum { unset, foo, boo } 25 | 26 | // '--choice' argument 27 | @NamedArgument 28 | Enum choice; 29 | 30 | // Named argument can have specific or multiple names 31 | @NamedArgument("apple","appl") 32 | int apple; 33 | 34 | @NamedArgument("b","banana","ban") 35 | int banana; 36 | } 37 | 38 | // This mixin defines standard main function that parses command line and calls the provided function: 39 | mixin CLI!Example.main!((args) 40 | { 41 | // 'args' has 'Example' type 42 | static assert(is(typeof(args) == Example)); 43 | 44 | // do whatever you need 45 | import std.stdio: writeln; 46 | args.writeln; 47 | return 0; 48 | }); -------------------------------------------------------------------------------- /examples/getting_started/with_uda/dub.json: -------------------------------------------------------------------------------- 1 | { 2 | "license": "BSL-1.0", 3 | "name": "getting_started-with_uda", 4 | "targetType":"executable", 5 | "sourcePaths":["."], 6 | "dependencies":{ "all:argparse":"*" } 7 | } -------------------------------------------------------------------------------- /examples/getting_started/without_uda/app.d: -------------------------------------------------------------------------------- 1 | import argparse; 2 | 3 | // If struct has no UDA then all members are named arguments 4 | struct Example 5 | { 6 | // Basic data types are supported: 7 | // '--name' argument 8 | string name; 9 | 10 | // '--number' argument 11 | int number; 12 | 13 | // '--boolean' argument 14 | bool boolean; 15 | 16 | // Argument can have default value if it's not specified in command line 17 | // '--unused' argument 18 | string unused = "some default value"; 19 | 20 | 21 | // Enums are supported 22 | enum Enum { unset, foo, boo } 23 | // '--choice' argument 24 | Enum choice; 25 | 26 | // Use array to store multiple values 27 | // '--array' argument 28 | int[] array; 29 | 30 | // Callback with no args (flag) 31 | // '--callback' argument 32 | void callback() {} 33 | 34 | // Callback with single value 35 | // '--callback1' argument 36 | void callback1(string value) { assert(value == "cb-value"); } 37 | 38 | // Callback with zero or more values 39 | // '--callback2' argument 40 | void callback2(string[] value) { assert(value == ["cb-v1","cb-v2"]); } 41 | } 42 | 43 | // This mixin defines standard main function that parses command line and calls the provided function: 44 | mixin CLI!Example.main!((args) 45 | { 46 | // 'args' has 'Example' type 47 | static assert(is(typeof(args) == Example)); 48 | 49 | // do whatever you need 50 | import std.stdio: writeln; 51 | args.writeln; 52 | return 0; 53 | }); -------------------------------------------------------------------------------- /examples/getting_started/without_uda/dub.json: -------------------------------------------------------------------------------- 1 | { 2 | "license": "BSL-1.0", 3 | "name": "getting_started-without_uda", 4 | "targetType":"executable", 5 | "sourcePaths":["."], 6 | "dependencies":{ "all:argparse":"*" } 7 | } -------------------------------------------------------------------------------- /examples/sub_commands/advanced/app.d: -------------------------------------------------------------------------------- 1 | import argparse; 2 | import std.stdio: writeln; 3 | 4 | enum Filter { none, even, odd }; 5 | 6 | auto filter(R)(R numbers, Filter filt) 7 | { 8 | alias isOdd = n => (n&1); 9 | alias isEven = n => !isOdd(n); 10 | 11 | alias func = (n) 12 | { 13 | final switch(filt) 14 | { 15 | case Filter.none: return true; 16 | case Filter.even: return isEven(n); 17 | case Filter.odd : return isOdd(n); 18 | } 19 | }; 20 | 21 | import std.algorithm: filter; 22 | return numbers.filter!func; 23 | } 24 | 25 | @(Command // use "sum" (type name) as a command name 26 | .Usage("%(PROG) [...]") 27 | .Description(() => "Print sum of the numbers") 28 | ) 29 | struct sum 30 | { 31 | @PositionalArgument 32 | int[] numbers; 33 | } 34 | 35 | @(Command("minimum", "min") 36 | .Usage(() => "%(PROG) [...]") 37 | .Description(() => "Print the minimal number across provided") 38 | .ShortDescription(() => "Print the minimum") 39 | ) 40 | struct MinCmd 41 | { 42 | @PositionalArgument 43 | int[] numbers; 44 | } 45 | 46 | @(Command("maximum", "max") 47 | .Usage("%(PROG) [...]") 48 | .Description("Print the maximal number across provided") 49 | .ShortDescription("Print the maximum") 50 | ) 51 | struct MaxCmd 52 | { 53 | @PositionalArgument 54 | int[] numbers; 55 | } 56 | 57 | @(Command.Description("Description of main program")) 58 | struct Program 59 | { 60 | // Common arguments 61 | @ArgumentGroup("Common arguments") 62 | { 63 | @NamedArgument 64 | Filter filter; 65 | } 66 | 67 | // Sub-command 68 | // name of the command is the same as a name of the type 69 | SubCommand!(sum, MinCmd, MaxCmd) cmd; 70 | } 71 | 72 | 73 | // This mixin defines standard main function that parses command line and calls the provided function: 74 | mixin CLI!Program.main!((prog) 75 | { 76 | static assert(is(typeof(prog) == Program)); 77 | 78 | writeln("prog = ", prog); 79 | 80 | prog.cmd.matchCmd!( 81 | (MaxCmd cmd) 82 | { 83 | import std.algorithm: maxElement; 84 | writeln("max = ", cmd.numbers.filter(prog.filter).maxElement(int.min)); 85 | }, 86 | (MinCmd cmd) 87 | { 88 | import std.algorithm: minElement; 89 | writeln("min = ", cmd.numbers.filter(prog.filter).minElement(int.max)); 90 | }, 91 | (sum cmd) 92 | { 93 | import std.algorithm: sum; 94 | writeln("sum = ", cmd.numbers.filter(prog.filter).sum); 95 | } 96 | ); 97 | 98 | return 0; 99 | }); -------------------------------------------------------------------------------- /examples/sub_commands/advanced/dub.json: -------------------------------------------------------------------------------- 1 | { 2 | "license": "BSL-1.0", 3 | "name": "sub_commands-advanced", 4 | "targetType":"executable", 5 | "sourcePaths":["."], 6 | "dependencies":{ "all:argparse":"*" } 7 | } -------------------------------------------------------------------------------- /examples/sub_commands/basic/app.d: -------------------------------------------------------------------------------- 1 | import argparse; 2 | import std.stdio: writeln; 3 | 4 | 5 | struct sum 6 | { 7 | int[] numbers; // --numbers argument 8 | } 9 | 10 | struct min 11 | { 12 | int[] numbers; // --numbers argument 13 | } 14 | 15 | struct max 16 | { 17 | int[] numbers; // --numbers argument 18 | } 19 | 20 | int main_(max cmd) 21 | { 22 | import std.algorithm: maxElement; 23 | 24 | writeln("max = ", cmd.numbers.maxElement(int.min)); 25 | 26 | return 0; 27 | } 28 | 29 | int main_(min cmd) 30 | { 31 | import std.algorithm: minElement; 32 | 33 | writeln("min = ", cmd.numbers.minElement(int.max)); 34 | 35 | return 0; 36 | } 37 | 38 | int main_(sum cmd) 39 | { 40 | import std.algorithm: sum; 41 | 42 | writeln("sum = ", cmd.numbers.sum); 43 | 44 | return 0; 45 | } 46 | 47 | // This mixin defines standard main function that parses command line and calls the provided function: 48 | mixin CLI!(sum, min, max).main!main_; 49 | -------------------------------------------------------------------------------- /examples/sub_commands/basic/dub.json: -------------------------------------------------------------------------------- 1 | { 2 | "license": "BSL-1.0", 3 | "name": "sub_commands-basic", 4 | "targetType":"executable", 5 | "sourcePaths":["."], 6 | "dependencies":{ "all:argparse":"*" } 7 | } -------------------------------------------------------------------------------- /examples/sub_commands/common_args/app.d: -------------------------------------------------------------------------------- 1 | import argparse; 2 | import std.stdio: writeln; 3 | 4 | 5 | struct sum {} 6 | struct min {} 7 | struct max {} 8 | 9 | struct Program 10 | { 11 | int[] numbers; // --numbers argument 12 | 13 | // name of the command is the same as a name of the type 14 | SubCommand!(sum, min, max) cmd; 15 | } 16 | 17 | // This mixin defines standard main function that parses command line and calls the provided function: 18 | mixin CLI!Program.main!((prog) 19 | { 20 | static assert(is(typeof(prog) == Program)); 21 | 22 | writeln("prog = ", prog); 23 | 24 | prog.cmd.matchCmd!( 25 | (.max) 26 | { 27 | import std.algorithm: maxElement; 28 | writeln("max = ", prog.numbers.maxElement(int.min)); 29 | }, 30 | (.min) 31 | { 32 | import std.algorithm: minElement; 33 | writeln("min = ", prog.numbers.minElement(int.max)); 34 | }, 35 | (.sum) 36 | { 37 | import std.algorithm: sum; 38 | writeln("sum = ", prog.numbers.sum); 39 | } 40 | ); 41 | 42 | return 0; 43 | }); -------------------------------------------------------------------------------- /examples/sub_commands/common_args/dub.json: -------------------------------------------------------------------------------- 1 | { 2 | "license": "BSL-1.0", 3 | "name": "sub_commands-common_args", 4 | "targetType":"executable", 5 | "sourcePaths":["."], 6 | "dependencies":{ "all:argparse":"*" } 7 | } -------------------------------------------------------------------------------- /examples/sub_commands/default/app.d: -------------------------------------------------------------------------------- 1 | import argparse; 2 | import std.stdio: writeln; 3 | 4 | 5 | struct sum {} 6 | struct min {} 7 | struct max 8 | { 9 | string foo; // --foo argument 10 | } 11 | 12 | struct Program 13 | { 14 | int[] numbers; // --numbers argument 15 | 16 | // Default!T marks T as default command 17 | SubCommand!(sum, min, Default!max) cmd; 18 | } 19 | 20 | // This mixin defines standard main function that parses command line and calls the provided function: 21 | mixin CLI!Program.main!((prog) 22 | { 23 | static assert(is(typeof(prog) == Program)); 24 | 25 | writeln("prog = ", prog); 26 | 27 | prog.cmd.matchCmd!( 28 | (.max m) 29 | { 30 | import std.algorithm: maxElement; 31 | writeln("max = ", prog.numbers.maxElement(int.min), " foo = ", m.foo); 32 | }, 33 | (.min) 34 | { 35 | import std.algorithm: minElement; 36 | writeln("min = ", prog.numbers.minElement(int.max)); 37 | }, 38 | (.sum) 39 | { 40 | import std.algorithm: sum; 41 | writeln("sum = ", prog.numbers.sum); 42 | } 43 | ); 44 | 45 | return 0; 46 | }); -------------------------------------------------------------------------------- /examples/sub_commands/default/dub.json: -------------------------------------------------------------------------------- 1 | { 2 | "license": "BSL-1.0", 3 | "name": "sub_commands-default", 4 | "targetType":"executable", 5 | "sourcePaths":["."], 6 | "dependencies":{ "all:argparse":"*" } 7 | } -------------------------------------------------------------------------------- /source/argparse/api/ansi.d: -------------------------------------------------------------------------------- 1 | module argparse.api.ansi; 2 | 3 | import argparse.ansi; 4 | import argparse.config; 5 | import argparse.param; 6 | import argparse.result; 7 | import argparse.api.argument: NamedArgument, Description, NumberOfValues, AllowedValues, Parse, Action, ActionNoValue; 8 | 9 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 10 | /// Public API for ANSI coloring 11 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 12 | 13 | @(NamedArgument 14 | .Description("Colorize the output. If value is omitted then 'always' is used.") 15 | .AllowedValues("always","auto","never") 16 | .NumberOfValues(0, 1) 17 | .Parse((string _) => _) 18 | .Action(AnsiStylingArgument.action) 19 | .ActionNoValue(AnsiStylingArgument.actionNoValue) 20 | ) 21 | private struct AnsiStylingArgument 22 | { 23 | private static bool stdout; 24 | private static bool stderr; 25 | 26 | package(argparse) static bool stdoutStyling(bool v) { return stdout = v; } 27 | package(argparse) static bool stderrStyling(bool v) { return stderr = v; } 28 | 29 | package(argparse) static void initialize(Config.StylingMode mode) 30 | { 31 | final switch(mode) 32 | { 33 | case Config.StylingMode.on: stdout = stderr = true; break; 34 | case Config.StylingMode.off: stdout = stderr = false; break; 35 | case Config.StylingMode.autodetect: 36 | stdout = argparse.ansi.detectSupport(STDOUT); 37 | stderr = argparse.ansi.detectSupport(STDERR); 38 | break; 39 | } 40 | } 41 | 42 | ////////////////////////////////////////////////////////////////////////// 43 | 44 | public static bool stdoutStyling() { return stdout; } 45 | public static bool stderrStyling() { return stderr; } 46 | 47 | public bool opCast(T : bool)() const 48 | { 49 | return stdoutStyling(); 50 | } 51 | 52 | private enum action = (ref AnsiStylingArgument _, Param!string param) 53 | { 54 | switch(param.value) 55 | { 56 | case "always": stdout = stderr = true; return Result.Success; 57 | case "never": stdout = stderr = false; return Result.Success; 58 | case "auto": 59 | // force detection 60 | stdout = argparse.ansi.detectSupport(STDOUT); 61 | stderr = argparse.ansi.detectSupport(STDERR); 62 | return Result.Success; 63 | default: 64 | } 65 | return Result.Success; 66 | }; 67 | 68 | private enum actionNoValue = (ref AnsiStylingArgument _1, Param!void _2) 69 | { 70 | stdout = stderr = true; 71 | return Result.Success; 72 | }; 73 | } 74 | 75 | unittest 76 | { 77 | AnsiStylingArgument.initialize(Config.StylingMode.on); 78 | assert(ansiStylingArgument); 79 | assert(AnsiStylingArgument.stdoutStyling); 80 | assert(AnsiStylingArgument.stderrStyling); 81 | 82 | AnsiStylingArgument.initialize(Config.StylingMode.off); 83 | assert(!ansiStylingArgument); 84 | assert(!AnsiStylingArgument.stdoutStyling); 85 | assert(!AnsiStylingArgument.stderrStyling); 86 | } 87 | 88 | unittest 89 | { 90 | AnsiStylingArgument arg; 91 | arg.stdoutStyling = arg.stderrStyling = false; 92 | AnsiStylingArgument.actionNoValue(arg, Param!void.init); 93 | assert(arg); 94 | assert(arg.stdoutStyling); 95 | assert(arg.stderrStyling); 96 | } 97 | 98 | unittest 99 | { 100 | AnsiStylingArgument arg; 101 | arg.stdoutStyling = arg.stderrStyling = false; 102 | 103 | AnsiStylingArgument.action(arg, Param!string(null, "", "always")); 104 | assert(arg); 105 | assert(arg.stdoutStyling); 106 | assert(arg.stderrStyling); 107 | 108 | AnsiStylingArgument.action(arg, Param!string(null, "", "never")); 109 | assert(!arg); 110 | assert(!arg.stdoutStyling); 111 | assert(!arg.stderrStyling); 112 | } 113 | 114 | unittest 115 | { 116 | AnsiStylingArgument.initialize(Config.StylingMode.autodetect); 117 | 118 | auto stdout = AnsiStylingArgument.stdoutStyling; 119 | auto stderr = AnsiStylingArgument.stderrStyling; 120 | 121 | AnsiStylingArgument arg; 122 | 123 | arg.stdoutStyling = arg.stderrStyling = true; 124 | AnsiStylingArgument.action(arg, Param!string(null, "", "auto")); 125 | assert((cast(bool) arg) == stdout); 126 | assert(arg.stdoutStyling == stdout); 127 | assert(arg.stderrStyling == stderr); 128 | 129 | arg.stdoutStyling = arg.stderrStyling = true; 130 | AnsiStylingArgument.action(arg, Param!string(null, "", "auto")); 131 | assert((cast(bool) arg) == stdout); 132 | assert(arg.stdoutStyling == stdout); 133 | assert(arg.stderrStyling == stderr); 134 | } 135 | 136 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 137 | 138 | auto ansiStylingArgument() 139 | { 140 | return AnsiStylingArgument.init; 141 | } 142 | 143 | unittest 144 | { 145 | assert(ansiStylingArgument == AnsiStylingArgument.init); 146 | } 147 | -------------------------------------------------------------------------------- /source/argparse/api/argumentgroup.d: -------------------------------------------------------------------------------- 1 | module argparse.api.argumentgroup; 2 | 3 | import argparse.internal.arguments: Group; 4 | 5 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 6 | /// Public API for argument group UDA 7 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 8 | 9 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 10 | // Group of arguments 11 | 12 | auto ArgumentGroup(string name) 13 | { 14 | return Group(name); 15 | } 16 | 17 | auto ref Description()(auto ref Group group, string text) 18 | { 19 | group.description = text; 20 | return group; 21 | } 22 | 23 | auto ref Description()(auto ref Group group, string function() text) 24 | { 25 | group.description = text; 26 | return group; 27 | } 28 | 29 | unittest 30 | { 31 | auto g = ArgumentGroup("name").Description("description"); 32 | assert(g.name == "name"); 33 | assert(g.description.get == "description"); 34 | 35 | g = g.Description(() => "descr"); 36 | assert(g.description.get == "descr"); 37 | } 38 | 39 | -------------------------------------------------------------------------------- /source/argparse/api/command.d: -------------------------------------------------------------------------------- 1 | module argparse.api.command; 2 | 3 | import argparse.internal.commandinfo: CommandInfo; 4 | 5 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 6 | /// Public API for command and subcommand UDAs 7 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 8 | 9 | auto Command(string[] names...) 10 | { 11 | return CommandInfo(names.dup); 12 | } 13 | 14 | unittest 15 | { 16 | auto a = Command("MYPROG"); 17 | assert(a.names == ["MYPROG"]); 18 | } 19 | 20 | 21 | auto ref Usage()(auto ref CommandInfo cmd, string text) 22 | { 23 | cmd.usage = text; 24 | return cmd; 25 | } 26 | 27 | auto ref Usage()(auto ref CommandInfo cmd, string function() text) 28 | { 29 | cmd.usage = text; 30 | return cmd; 31 | } 32 | 33 | auto ref Description()(auto ref CommandInfo cmd, string text) 34 | { 35 | cmd.description = text; 36 | return cmd; 37 | } 38 | 39 | auto ref Description()(auto ref CommandInfo cmd, string function() text) 40 | { 41 | cmd.description = text; 42 | return cmd; 43 | } 44 | 45 | auto ref ShortDescription()(auto ref CommandInfo cmd, string text) 46 | { 47 | cmd.shortDescription = text; 48 | return cmd; 49 | } 50 | 51 | auto ref ShortDescription()(auto ref CommandInfo cmd, string function() text) 52 | { 53 | cmd.shortDescription = text; 54 | return cmd; 55 | } 56 | 57 | auto ref Epilog()(auto ref CommandInfo cmd, string text) 58 | { 59 | cmd.epilog = text; 60 | return cmd; 61 | } 62 | 63 | auto ref Epilog()(auto ref CommandInfo cmd, string function() text) 64 | { 65 | cmd.epilog = text; 66 | return cmd; 67 | } 68 | 69 | unittest 70 | { 71 | CommandInfo c; 72 | c = c.Usage("usg").Description("desc").ShortDescription("sum").Epilog("epi"); 73 | assert(c.names == []); 74 | assert(c.usage.get == "usg"); 75 | assert(c.description.get == "desc"); 76 | assert(c.shortDescription.get == "sum"); 77 | assert(c.epilog.get == "epi"); 78 | } 79 | 80 | unittest 81 | { 82 | CommandInfo c; 83 | c = c.Usage(() => "usg").Description(() => "desc").ShortDescription(() => "sum").Epilog(() => "epi"); 84 | assert(c.names == []); 85 | assert(c.usage.get == "usg"); 86 | assert(c.description.get == "desc"); 87 | assert(c.shortDescription.get == "sum"); 88 | assert(c.epilog.get == "epi"); 89 | } 90 | -------------------------------------------------------------------------------- /source/argparse/api/enums.d: -------------------------------------------------------------------------------- 1 | module argparse.api.enums; 2 | 3 | import argparse.internal.enumhelpers: EnumValue; 4 | 5 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 6 | /// Public API for enums 7 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 8 | 9 | auto AllowedValues(string[] values...) 10 | { 11 | return EnumValue(values.dup); 12 | } 13 | 14 | unittest 15 | { 16 | assert(AllowedValues("a","b").values == ["a","b"]); 17 | } 18 | -------------------------------------------------------------------------------- /source/argparse/api/restriction.d: -------------------------------------------------------------------------------- 1 | module argparse.api.restriction; 2 | 3 | import argparse.internal.restriction: RestrictionGroup; 4 | 5 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 6 | /// Public API for restrictions 7 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 8 | 9 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 10 | /// Required group 11 | 12 | auto ref Required()(auto ref RestrictionGroup group) 13 | { 14 | group.required = true; 15 | return group; 16 | } 17 | 18 | unittest 19 | { 20 | assert(RestrictionGroup.init.Required.required); 21 | } 22 | 23 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 24 | /// Arguments required together 25 | 26 | auto RequiredTogether(string file=__FILE__, uint line = __LINE__) 27 | { 28 | import std.conv: to; 29 | 30 | return RestrictionGroup(file~":"~line.to!string, RestrictionGroup.Type.together); 31 | } 32 | 33 | unittest 34 | { 35 | auto t = RequiredTogether(); 36 | assert(t.location.length > 0); 37 | assert(t.type == RestrictionGroup.Type.together); 38 | } 39 | 40 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 41 | /// Mutually exclusive arguments 42 | 43 | auto MutuallyExclusive(string file=__FILE__, uint line = __LINE__) 44 | { 45 | import std.conv: to; 46 | 47 | return RestrictionGroup(file~":"~line.to!string, RestrictionGroup.Type.exclusive); 48 | } 49 | 50 | unittest 51 | { 52 | auto e = MutuallyExclusive(); 53 | assert(e.location.length > 0); 54 | assert(e.type == RestrictionGroup.Type.exclusive); 55 | } 56 | -------------------------------------------------------------------------------- /source/argparse/api/subcommand.d: -------------------------------------------------------------------------------- 1 | module argparse.api.subcommand; 2 | 3 | 4 | import std.sumtype: SumType, sumtype_match = match; 5 | import std.meta; 6 | import std.typecons: Nullable; 7 | 8 | 9 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 10 | /// Default subcommand 11 | public struct Default(COMMAND) 12 | { 13 | } 14 | 15 | private enum isDefaultCommand(T) = is(T == Default!TYPE, TYPE); 16 | 17 | private alias RemoveDefaultAttribute(T : Default!ORIG_TYPE, ORIG_TYPE) = ORIG_TYPE; 18 | private alias RemoveDefaultAttribute(T) = T; 19 | 20 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 21 | 22 | public struct SubCommand(Commands...) 23 | if(Commands.length > 0) 24 | { 25 | private alias DefaultCommands = Filter!(isDefaultCommand, Commands); 26 | 27 | static assert(DefaultCommands.length <= 1, "Multiple default subcommands: "~DefaultCommands.stringof); 28 | 29 | static if(DefaultCommands.length > 0) 30 | { 31 | package(argparse) alias DefaultCommand = RemoveDefaultAttribute!(DefaultCommands[0]); 32 | 33 | package(argparse) alias Types = AliasSeq!(DefaultCommand, Filter!(templateNot!isDefaultCommand, Commands)); 34 | 35 | private SumType!Types impl = DefaultCommand.init; 36 | } 37 | else 38 | { 39 | package(argparse) alias Types = Commands; 40 | 41 | private Nullable!(SumType!Types) impl; 42 | } 43 | 44 | public this(T)(T value) 45 | if(staticIndexOf!(T, Types) >= 0) 46 | { 47 | impl = SumType!Types(value); 48 | } 49 | 50 | 51 | public ref SubCommand opAssign(T)(T value) 52 | if(staticIndexOf!(T, Types) >= 0) 53 | { 54 | impl = SumType!Types(value); 55 | return this; 56 | } 57 | 58 | 59 | public bool isSetTo(T)() const 60 | if(staticIndexOf!(T, Types) >= 0) 61 | { 62 | if(!isSet()) 63 | return false; 64 | 65 | static if(Types.length == 1) 66 | return true; 67 | else 68 | return this.matchCmd!((const ref T _) => true, (const ref _) => false); 69 | } 70 | 71 | public bool isSet() const 72 | { 73 | static if(is(DefaultCommand)) 74 | return true; 75 | else 76 | return !impl.isNull; 77 | } 78 | } 79 | 80 | 81 | package(argparse) enum bool isSubCommand(T) = is(T : SubCommand!Args, Args...); 82 | 83 | 84 | public template matchCmd(handlers...) 85 | { 86 | auto ref matchCmd(Sub : const SubCommand!Args, Args...)(auto ref Sub sc) 87 | { 88 | static if(is(Sub.DefaultCommand)) 89 | return sc.impl.sumtype_match!(handlers); 90 | else 91 | { 92 | if (!sc.impl.isNull) 93 | return sc.impl.get.sumtype_match!(handlers); 94 | else 95 | { 96 | alias RETURN_TYPE = typeof(sc.impl.get.init.sumtype_match!handlers); 97 | 98 | static if(!is(RETURN_TYPE == void)) 99 | return RETURN_TYPE.init; 100 | } 101 | } 102 | } 103 | } 104 | 105 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 106 | 107 | unittest 108 | { 109 | struct A 110 | { 111 | int i; 112 | } 113 | 114 | immutable sub = SubCommand!(A)(A(1)); 115 | static assert(isSubCommand!(typeof(sub))); 116 | assert(sub.matchCmd!(_ => _.i) == 1); 117 | } 118 | 119 | unittest 120 | { 121 | struct A { int i; } 122 | struct B { int i; } 123 | 124 | static assert(!__traits(compiles, SubCommand!())); 125 | static assert(!__traits(compiles, SubCommand!(A,A))); 126 | static assert(!__traits(compiles, SubCommand!(Default!A,Default!B))); 127 | 128 | static assert(!isSubCommand!(SumType!(A,B))); 129 | 130 | static foreach(SUBCMD; AliasSeq!(SubCommand!(A,B), SubCommand!(Default!A,B), SubCommand!(A,Default!B))) 131 | { 132 | static assert(isSubCommand!SUBCMD); 133 | 134 | static assert(Filter!(isDefaultCommand, SUBCMD.Types).length == 0); // underlying types have no `Default` 135 | 136 | static if(is(SUBCMD.DefaultCommand)) 137 | static assert(SUBCMD.init.isSet); 138 | else 139 | static assert(!SUBCMD.init.isSet); 140 | 141 | static foreach(CMD; AliasSeq!(A, B)) 142 | { 143 | static if(is(SUBCMD.DefaultCommand == CMD)) 144 | static assert(SUBCMD.init.isSetTo!CMD); 145 | else 146 | static assert(!SUBCMD.init.isSetTo!CMD); 147 | 148 | // can initialize with command 149 | static assert(SUBCMD(CMD.init).isSet); 150 | static assert(SUBCMD(CMD.init).isSetTo!CMD); 151 | 152 | { 153 | // can assign a command 154 | SUBCMD s; 155 | assert(s.matchCmd!(_ => _.i) == 0); 156 | 157 | s = CMD.init; 158 | assert(s.isSet); 159 | assert(s.isSetTo!CMD); 160 | 161 | // match without returning value 162 | s.matchCmd!((ref CMD _) { _.i = 123; },(_){}); 163 | 164 | // match with returning value 165 | assert(s.matchCmd!(_ => _.i) == 123); 166 | } 167 | } 168 | } 169 | } -------------------------------------------------------------------------------- /source/argparse/config.d: -------------------------------------------------------------------------------- 1 | module argparse.config; 2 | 3 | import argparse.internal.style: Style; 4 | 5 | 6 | struct Config 7 | { 8 | /** 9 | The assignment character used in arguments with parameters. 10 | Defaults to '='. 11 | */ 12 | char assignChar = '='; 13 | 14 | /** 15 | The assignment character used in "key=value" syntax for arguments that have associative array type. 16 | Defaults to '='. 17 | */ 18 | char assignKeyValueChar = '='; 19 | 20 | /** 21 | Value separator for "--arg=value1,value2,value3" syntax 22 | Defaults to ',' 23 | */ 24 | char valueSep = ','; 25 | 26 | /** 27 | The prefix for short argument name. 28 | Defaults to "-". 29 | */ 30 | string shortNamePrefix = "-"; 31 | 32 | /** 33 | The prefix for long argument name. 34 | Defaults to "--". 35 | */ 36 | string longNamePrefix = "--"; 37 | 38 | /** 39 | The string that conventionally marks the end of all named arguments. 40 | Assigning an empty string effectively disables it. 41 | Defaults to "--". 42 | */ 43 | string endOfNamedArgs = "--"; 44 | 45 | /** 46 | If set then argument short names are case-sensitive. 47 | Defaults to true. 48 | */ 49 | bool caseSensitiveShortName = true; 50 | 51 | /** 52 | If set then argument long names are case-sensitive. 53 | Defaults to true. 54 | */ 55 | bool caseSensitiveLongName = true; 56 | 57 | /** 58 | If set then subcommands are case-sensitive. 59 | Defaults to true. 60 | */ 61 | bool caseSensitiveSubCommand = true; 62 | 63 | /** 64 | Helper to set all case sensitivity settings to a specific value 65 | */ 66 | void caseSensitive(bool value) { caseSensitiveShortName = caseSensitiveLongName = caseSensitiveSubCommand = value; } 67 | 68 | /** 69 | Single-character arguments can be bundled together, i.e. "-abc" is the same as "-a -b -c". 70 | Disabled by default. 71 | */ 72 | bool bundling = false; 73 | 74 | /** 75 | By default, consume one value per appearance of named argument in command line. 76 | With `variadicNamedArgument` enabled (as was the default in v1), named arguments will 77 | consume all named arguments up to the next named argument if the receiving field is an array. 78 | */ 79 | bool variadicNamedArgument = false; 80 | 81 | /** 82 | Add a -h/--help argument to the parser. 83 | Defaults to true. 84 | */ 85 | bool addHelpArgument = true; 86 | 87 | /** 88 | Styling. 89 | */ 90 | Style styling = Style.Default; 91 | 92 | /** 93 | Styling mode. 94 | Defaults to auto-detectection of the capability. 95 | */ 96 | enum StylingMode { autodetect, on, off } 97 | StylingMode stylingMode = StylingMode.autodetect; 98 | 99 | /** 100 | Function that processes error messages if they happen during argument parsing. 101 | By default all errors are printed to stderr. 102 | */ 103 | void function(string s) nothrow errorHandler; 104 | 105 | /** 106 | Exit code that is returned from main() in case of parsing error. 107 | Defaults to 1. 108 | */ 109 | int errorExitCode = 1; 110 | } 111 | 112 | unittest 113 | { 114 | enum c = { 115 | Config cfg; 116 | cfg.errorHandler = (string s) { }; 117 | return cfg; 118 | }(); 119 | } 120 | 121 | unittest 122 | { 123 | Config cfg; 124 | assert(cfg.caseSensitiveShortName); 125 | assert(cfg.caseSensitiveLongName); 126 | assert(cfg.caseSensitiveSubCommand); 127 | cfg.caseSensitive = false; 128 | assert(!cfg.caseSensitiveShortName); 129 | assert(!cfg.caseSensitiveLongName); 130 | assert(!cfg.caseSensitiveSubCommand); 131 | cfg.caseSensitive = true; 132 | assert(cfg.caseSensitiveShortName); 133 | assert(cfg.caseSensitiveLongName); 134 | assert(cfg.caseSensitiveSubCommand); 135 | } 136 | -------------------------------------------------------------------------------- /source/argparse/internal/actionfunc.d: -------------------------------------------------------------------------------- 1 | module argparse.internal.actionfunc; 2 | 3 | import argparse.config; 4 | import argparse.param; 5 | import argparse.result; 6 | import argparse.internal.calldispatcher; 7 | import argparse.internal.errorhelpers; 8 | 9 | import std.traits: ForeachType; 10 | 11 | 12 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 13 | 14 | private struct Handler(RECEIVER, PARSE) 15 | { 16 | static Result opCall(bool function(ref RECEIVER receiver, PARSE value) func, ref RECEIVER receiver, Param!PARSE param) 17 | { 18 | return func(receiver, param.value) ? Result.Success : processingError(param); 19 | } 20 | static Result opCall(void function(ref RECEIVER receiver, PARSE value) func, ref RECEIVER receiver, Param!PARSE param) 21 | { 22 | func(receiver, param.value); 23 | return Result.Success; 24 | } 25 | static Result opCall(Result function(ref RECEIVER receiver, PARSE value) func, ref RECEIVER receiver, Param!PARSE param) 26 | { 27 | return func(receiver, param.value); 28 | } 29 | static Result opCall(bool function(ref RECEIVER receiver, Param!PARSE param) func, ref RECEIVER receiver, Param!PARSE param) 30 | { 31 | return func(receiver, param) ? Result.Success : processingError(param); 32 | } 33 | static Result opCall(void function(ref RECEIVER receiver, Param!PARSE param) func, ref RECEIVER receiver, Param!PARSE param) 34 | { 35 | func(receiver, param); 36 | return Result.Success; 37 | } 38 | static Result opCall(Result function(ref RECEIVER receiver, Param!PARSE param) func, ref RECEIVER receiver, Param!PARSE param) 39 | { 40 | return func(receiver, param); 41 | } 42 | } 43 | 44 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 45 | 46 | // bool action(ref T receiver, ParseType value) 47 | // void action(ref T receiver, ParseType value) 48 | // Result action(ref T receiver, ParseType value) 49 | // bool action(ref T receiver, Param!ParseType param) 50 | // void action(ref T receiver, Param!ParseType param) 51 | // Result action(ref T receiver, Param!ParseType param) 52 | package(argparse) struct ActionFunc(RECEIVER, PARSE) 53 | { 54 | alias CD = CallDispatcher!(Handler!(RECEIVER, PARSE)); 55 | CD dispatcher; 56 | alias this = dispatcher; 57 | 58 | static foreach(T; CD.TYPES) 59 | this(T f) 60 | { 61 | dispatcher = CD(f); 62 | } 63 | } 64 | 65 | 66 | unittest 67 | { 68 | auto test(T, F)(F func, T values) 69 | { 70 | T receiver; 71 | Config config; 72 | assert(ActionFunc!(T,T)(func)(receiver, Param!T(&config, "", values))); 73 | return receiver; 74 | } 75 | auto testErr(T, F)(F func, T values) 76 | { 77 | T receiver; 78 | Config config; 79 | return ActionFunc!(T,T)(func)(receiver, Param!T(&config, "", values)); 80 | } 81 | 82 | // Result action(ref T receiver, ParseType value) 83 | assert(test((ref string[] p, string[] a) { p=a; return Result.Success; }, ["1","2","3"]) == ["1","2","3"]); 84 | assert(testErr((ref string[] p, string[] a) => Result.Error(1, "error text"), ["1","2","3"]).isError("error text")); 85 | 86 | // bool action(ref T receiver, ParseType value) 87 | assert(test((ref string[] p, string[] a) { p=a; return true; }, ["1","2","3"]) == ["1","2","3"]); 88 | assert(testErr((ref string[] p, string[] a) => false, ["1","2","3"]).isError("Can't process value")); 89 | 90 | // void action(ref T receiver, ParseType value) 91 | assert(test((ref string[] p, string[] a) { p=a; }, ["1","2","3"]) == ["1","2","3"]); 92 | 93 | // Result action(ref T receiver, Param!ParseType param) 94 | assert(test((ref string[] p, Param!(string[]) a) { p=a.value; return Result.Success; }, ["1","2","3"]) == ["1","2","3"]); 95 | assert(testErr((ref string[] p, Param!(string[]) a) => Result.Error(1, "error text"), ["1","2","3"]).isError("error text")); 96 | 97 | // bool action(ref T receiver, Param!ParseType param) 98 | assert(test((ref string[] p, Param!(string[]) a) { p=a.value; return true; }, ["1","2","3"]) == ["1","2","3"]); 99 | assert(testErr((ref string[] p, Param!(string[]) a) => false, ["1","2","3"]).isError("Can't process value")); 100 | 101 | // void action(ref T receiver, Param!ParseType param) 102 | assert(test((ref string[] p, Param!(string[]) a) { p=a.value; }, ["1","2","3"]) == ["1","2","3"]); 103 | } 104 | 105 | unittest 106 | { 107 | alias test = (int[] v1, int[] v2) { 108 | int[] res; 109 | 110 | Param!(int[]) param; 111 | 112 | enum append = ActionFunc!(int[],int[])((ref int[] _1, int[] _2) { _1 ~= _2; }); 113 | 114 | param.value = v1; append(res, param); 115 | 116 | param.value = v2; append(res, param); 117 | 118 | return res; 119 | }; 120 | assert(test([1,2,3],[7,8,9]) == [1,2,3,7,8,9]); 121 | } 122 | 123 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 124 | 125 | package enum Assign(RECEIVER, PARSE = RECEIVER) = ActionFunc!(RECEIVER, PARSE) 126 | ((ref RECEIVER _1, PARSE _2) 127 | { 128 | _1 = _2; 129 | }); 130 | 131 | unittest 132 | { 133 | Config config; 134 | int i; 135 | Assign!int(i,Param!int(&config,"",7)); 136 | assert(i == 7); 137 | } 138 | 139 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 140 | 141 | package enum Append(TYPE) = ActionFunc!(TYPE, TYPE) 142 | ((ref TYPE _1, TYPE _2) 143 | { 144 | _1 ~= _2; 145 | }); 146 | 147 | unittest 148 | { 149 | Config config; 150 | int[] i; 151 | Append!(int[])(i,Param!(int[])(&config,"",[1,2,3])); 152 | Append!(int[])(i,Param!(int[])(&config,"",[7,8,9])); 153 | assert(i == [1, 2, 3, 7, 8, 9]); 154 | } 155 | 156 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 157 | 158 | package enum Extend(TYPE) = ActionFunc!(TYPE, ForeachType!TYPE) 159 | ((ref TYPE _1, ForeachType!TYPE _2) 160 | { 161 | _1 ~= _2; 162 | }); 163 | 164 | unittest 165 | { 166 | Config config; 167 | int[][] i; 168 | Extend!(int[][])(i,Param!(int[])(&config,"",[1,2,3])); 169 | Extend!(int[][])(i,Param!(int[])(&config,"",[7,8,9])); 170 | assert(i == [[1,2,3],[7,8,9]]); 171 | } 172 | -------------------------------------------------------------------------------- /source/argparse/internal/argumentuda.d: -------------------------------------------------------------------------------- 1 | module argparse.internal.argumentuda; 2 | 3 | import argparse.config; 4 | import argparse.param; 5 | import argparse.result; 6 | import argparse.internal.arguments: ArgumentInfo; 7 | import argparse.internal.valueparser: parseParameter; 8 | 9 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 10 | 11 | package(argparse) auto createArgumentUDA(ValueParser)(ArgumentInfo info, ValueParser valueParser) 12 | { 13 | return ArgumentUDA!ValueParser(info, valueParser); 14 | } 15 | 16 | package(argparse) struct ArgumentUDA(ValueParser) 17 | { 18 | package(argparse) ArgumentInfo info; 19 | 20 | package(argparse) ValueParser valueParser; 21 | 22 | package Result parse(COMMAND_STACK, RECEIVER)(const COMMAND_STACK cmdStack, ref RECEIVER receiver, RawParam param) 23 | { 24 | static assert(!is(RECEIVER == T*, T)); 25 | try 26 | { 27 | 28 | return valueParser.parseParameter(receiver, param); 29 | } 30 | catch(Exception e) 31 | { 32 | return Result.Error(param.config.errorExitCode, "Argument '", param.config.styling.argumentName(param.name), ": ", e.msg); 33 | } 34 | } 35 | 36 | package auto addDefaultUDA(T)(ArgumentUDA!T uda) 37 | { 38 | auto newInfo = info; 39 | 40 | if(newInfo.shortNames.length == 0) newInfo.shortNames = uda.info.shortNames; 41 | if(newInfo.longNames.length == 0) newInfo.longNames = uda.info.longNames; 42 | if(newInfo.placeholder.length == 0) newInfo.placeholder = uda.info.placeholder; 43 | if(!newInfo.description.isSet()) newInfo.description = uda.info.description; 44 | if(newInfo.position.isNull()) newInfo.position = uda.info.position; 45 | if(!newInfo.positional) newInfo.positional = uda.info.positional; 46 | if(newInfo.minValuesCount.isNull()) newInfo.minValuesCount = uda.info.minValuesCount; 47 | if(newInfo.maxValuesCount.isNull()) newInfo.maxValuesCount = uda.info.maxValuesCount; 48 | 49 | auto newValueParser = valueParser.addDefaults(uda.valueParser); 50 | 51 | return createArgumentUDA(newInfo, newValueParser); 52 | } 53 | 54 | auto addTypeDefaults(T)() 55 | { 56 | static assert(!is(T == P*, P)); 57 | 58 | static if(__traits(hasMember, valueParser, "addTypeDefaults")) 59 | return createArgumentUDA(info, valueParser.addTypeDefaults!T); 60 | else 61 | return this; 62 | } 63 | } 64 | 65 | unittest 66 | { 67 | struct S(string value) 68 | { 69 | string str = value; 70 | auto addDefaults(T)(T s) { 71 | static if(value.length == 0) 72 | return s; 73 | else 74 | return this; 75 | } 76 | } 77 | 78 | ArgumentUDA!(S!"foo1") arg1; 79 | arg1.info.shortNames = ["a1","b1"]; 80 | arg1.info.longNames = ["aa1","bb1"]; 81 | arg1.info.placeholder = "ph1"; 82 | arg1.info.description = "des1"; 83 | arg1.info.position = 1; 84 | arg1.info.positional = true; 85 | arg1.info.minValuesCount = 2; 86 | arg1.info.maxValuesCount = 3; 87 | 88 | ArgumentUDA!(S!"foo2") arg2; 89 | arg2.info.shortNames = ["a2","b2"]; 90 | arg2.info.longNames = ["aa2","bb2"]; 91 | arg2.info.placeholder = "ph2"; 92 | arg2.info.description = "des2"; 93 | arg2.info.position = 10; 94 | arg1.info.positional = true; 95 | arg2.info.minValuesCount = 20; 96 | arg2.info.maxValuesCount = 30; 97 | 98 | { 99 | // values shouldn't be changed 100 | auto res = arg1.addDefaultUDA(arg2); 101 | assert(res.info.shortNames == ["a1", "b1"]); 102 | assert(res.info.longNames == ["aa1", "bb1"]); 103 | assert(res.info.placeholder == "ph1"); 104 | assert(res.info.description.get == "des1"); 105 | assert(res.info.position.get == 1); 106 | assert(res.info.minValuesCount == 2); 107 | assert(res.info.maxValuesCount == 3); 108 | assert(res.valueParser.str == "foo1"); 109 | } 110 | 111 | { // values should be changed 112 | auto res = ArgumentUDA!(S!"").init.addDefaultUDA(arg1); 113 | assert(res.info.shortNames == ["a1", "b1"]); 114 | assert(res.info.longNames == ["aa1", "bb1"]); 115 | assert(res.info.placeholder == "ph1"); 116 | assert(res.info.description.get == "des1"); 117 | assert(res.info.position.get == 1); 118 | assert(res.info.minValuesCount == 2); 119 | assert(res.info.maxValuesCount == 3); 120 | assert(res.valueParser.str == "foo1"); 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /source/argparse/internal/argumentudahelpers.d: -------------------------------------------------------------------------------- 1 | module argparse.internal.argumentudahelpers; 2 | 3 | import argparse.api.argument: NamedArgument, PositionalArgument; 4 | import argparse.config; 5 | import argparse.internal.argumentuda: ArgumentUDA; 6 | import argparse.param; 7 | 8 | 9 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 10 | 11 | // `@NamedArgument` and `@PositionalArgument` (without parens) attach the functions as UDAs, 12 | // but we should treat it the same as `@NamedArgument()`/`@PositionalArgument()`. 13 | package enum isArgumentUDA(alias _ : NamedArgument) = true; 14 | package enum isArgumentUDA(alias _ : PositionalArgument) = true; 15 | package enum isArgumentUDA(alias uda) = is(typeof(uda) == ArgumentUDA!T, T); 16 | 17 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 18 | 19 | private template defaultValuesCount(T) 20 | { 21 | import std.traits; 22 | 23 | static if(isBoolean!T || 24 | // ... function() 25 | is(T == R function(), R) || 26 | is(T == R delegate(), R) || 27 | is(T == void function()) || 28 | is(T == void delegate())) 29 | { 30 | enum min = 0; 31 | enum max = 0; 32 | } 33 | else static if(isSomeString!T || 34 | isScalarType!T || 35 | // ... function(string value) 36 | is(T == R function(string), R) || 37 | is(T == R delegate(string), R) || 38 | is(T == void function(string)) || 39 | is(T == void delegate(string))) 40 | { 41 | enum min = 1; 42 | enum max = 1; 43 | } 44 | else static if(isStaticArray!T) 45 | { 46 | enum min = T.length; 47 | enum max = T.length; 48 | } 49 | else static if(isArray!T || 50 | isAssociativeArray!T || 51 | // ... function(string[] value) 52 | is(T == R function(string[]), R) || 53 | is(T == R delegate(string[]), R) || 54 | is(T == void function(string[])) || 55 | is(T == void delegate(string[])) || 56 | // ... function(RawParam value) 57 | is(T == R function(RawParam), R) || 58 | is(T == R delegate(RawParam), R) || 59 | is(T == void function(RawParam)) || 60 | is(T == void delegate(RawParam))) 61 | { 62 | enum min = 1; 63 | enum max = size_t.max; 64 | } 65 | else 66 | static assert(false, "Type is not supported: " ~ T.stringof); 67 | } 68 | 69 | unittest 70 | { 71 | struct T 72 | { 73 | bool b; 74 | string s; 75 | int i; 76 | int[7] sa; 77 | int[] da; 78 | int[string] aa; 79 | void f(); 80 | void fs(string); 81 | void fa(string[]); 82 | void fp(RawParam); 83 | int g(); 84 | int gs(string); 85 | int ga(string[]); 86 | int gp(RawParam); 87 | } 88 | void test(T)(size_t min, size_t max) 89 | { 90 | assert(defaultValuesCount!T.min == min); 91 | assert(defaultValuesCount!T.max == max); 92 | } 93 | test!(typeof(T.b))(0, 0); 94 | test!(typeof(T.s))(1, 1); 95 | test!(typeof(T.i))(1, 1); 96 | test!(typeof(T.sa))(7, 7); 97 | test!(typeof(T.da))(1, size_t.max); 98 | test!(typeof(T.aa))(1, size_t.max); 99 | test!(typeof(&T.f))(0, 0); 100 | test!(typeof(&T.fs))(1, 1); 101 | test!(typeof(&T.fa))(1, size_t.max); 102 | test!(typeof(&T.fp))(1, size_t.max); 103 | test!(typeof(&T.g))(0, 0); 104 | test!(typeof(&T.gs))(1, 1); 105 | test!(typeof(&T.ga))(1, size_t.max); 106 | test!(typeof(&T.gp))(1, size_t.max); 107 | } 108 | 109 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 110 | 111 | package auto getMemberArgumentUDA(TYPE, string symbol)(const Config config) 112 | { 113 | import argparse.internal.arguments: finalize; 114 | import std.meta: AliasSeq, Filter; 115 | 116 | alias member = __traits(getMember, TYPE, symbol); 117 | 118 | static if(is(typeof(member) == function) || is(typeof(member) == delegate)) 119 | alias MemberType = typeof(&__traits(getMember, TYPE.init, symbol)); 120 | else 121 | alias MemberType = typeof(member); 122 | 123 | alias udas = Filter!(isArgumentUDA, __traits(getAttributes, member)); 124 | static if(__traits(compiles, __traits(getAttributes, MemberType))) 125 | alias typeUDAs = Filter!(isArgumentUDA, __traits(getAttributes, MemberType)); 126 | else // On D <2.101, we are not allowed to query attributes of built-in types 127 | alias typeUDAs = AliasSeq!(); 128 | 129 | static assert(udas.length <= 1, "Member "~TYPE.stringof~"."~symbol~" has multiple '*Argument' UDAs"); 130 | static assert(typeUDAs.length <= 1, "Type "~MemberType.stringof~" has multiple '*Argument' UDAs"); 131 | 132 | static if(udas.length > 0) 133 | enum memberUDA = udas[0]; 134 | else 135 | enum memberUDA = NamedArgument(); 136 | 137 | static if(typeUDAs.length > 0) 138 | enum initUDA = memberUDA.addDefaultUDA(typeUDAs[0]).addTypeDefaults!MemberType; 139 | else 140 | enum initUDA = memberUDA.addTypeDefaults!MemberType; 141 | 142 | auto result = initUDA; 143 | 144 | result.info = result.info.finalize!MemberType(config, symbol); 145 | 146 | static if(initUDA.info.minValuesCount.isNull) result.info.minValuesCount = defaultValuesCount!MemberType.min; 147 | static if(initUDA.info.maxValuesCount.isNull) result.info.maxValuesCount = defaultValuesCount!MemberType.max; 148 | 149 | return result; 150 | } 151 | 152 | unittest 153 | { 154 | import argparse.api.argument: NumberOfValues; 155 | 156 | @(NamedArgument.NumberOfValues(5, 10)) 157 | struct FiveToTen {} 158 | 159 | @NamedArgument 160 | struct UnspecifiedA {} 161 | 162 | struct UnspecifiedB {} 163 | 164 | @(NamedArgument.NumberOfValues(5, 10)) 165 | @(NamedArgument.NumberOfValues(5, 10)) 166 | struct Multiple {} 167 | 168 | struct Args 169 | { 170 | @NamedArgument bool flag; 171 | @NamedArgument int count; 172 | @NamedArgument @NamedArgument() int incorrect; 173 | 174 | @NamedArgument FiveToTen fiveTen; 175 | @NamedArgument UnspecifiedA ua; 176 | @NamedArgument UnspecifiedB ub; 177 | @NamedArgument Multiple mult; 178 | 179 | @(NamedArgument.NumberOfValues(1, 2)) FiveToTen fiveTen1; 180 | @(NamedArgument.NumberOfValues(1, 2)) UnspecifiedA ua1; 181 | @(NamedArgument.NumberOfValues(1, 2)) UnspecifiedB ub1; 182 | @(NamedArgument.NumberOfValues(1, 2)) Multiple mult1; 183 | } 184 | 185 | auto getInfo(string symbol)() 186 | { 187 | return getMemberArgumentUDA!(Args, symbol)(Config.init).info; 188 | } 189 | 190 | // Built-in types: 191 | 192 | auto res = getInfo!"flag"; 193 | assert(res.minValuesCount == 0); 194 | assert(res.maxValuesCount == 0); 195 | 196 | res = getInfo!"count"; 197 | assert(res.minValuesCount == 1); 198 | assert(res.maxValuesCount == 1); 199 | 200 | assert(!__traits(compiles, getInfo!"incorrect")); 201 | 202 | // With type-inherited quantifiers: 203 | 204 | res = getInfo!"fiveTen"; 205 | assert(res.minValuesCount == 5); 206 | assert(res.maxValuesCount == 10); 207 | 208 | assert(!__traits(compiles, getInfo!"ua")); 209 | assert(!__traits(compiles, getInfo!"ub")); 210 | assert(!__traits(compiles, getInfo!"mult")); 211 | 212 | // With explicit quantifiers: 213 | 214 | res = getInfo!"fiveTen1"; 215 | assert(res.minValuesCount == 1); 216 | assert(res.maxValuesCount == 2); 217 | 218 | res = getInfo!"ua1"; 219 | assert(res.minValuesCount == 1); 220 | assert(res.maxValuesCount == 2); 221 | 222 | res = getInfo!"ub1"; 223 | assert(res.minValuesCount == 1); 224 | assert(res.maxValuesCount == 2); 225 | 226 | assert(!__traits(compiles, getInfo!"mult1")); 227 | } 228 | -------------------------------------------------------------------------------- /source/argparse/internal/calldispatcher.d: -------------------------------------------------------------------------------- 1 | module argparse.internal.calldispatcher; 2 | 3 | import std.traits; 4 | 5 | 6 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 7 | 8 | struct CallDispatcher(HANDLER) 9 | { 10 | alias getFirstParameter(T) = Parameters!T[0]; 11 | alias TYPES = staticMap!(getFirstParameter, typeof(__traits(getOverloads, HANDLER, "opCall"))); 12 | 13 | union { 14 | static foreach(i, T; TYPES) 15 | mixin("T value_", i, ";"); 16 | } 17 | 18 | size_t selection = -1; 19 | 20 | static foreach(i, T; TYPES) 21 | { 22 | this(T f) 23 | { 24 | mixin("value_",i) = f; 25 | selection = i; 26 | } 27 | } 28 | 29 | bool opCast(T : bool)() const 30 | { 31 | return selection != -1; 32 | } 33 | 34 | auto opCall(Args...)(auto ref Args args) const 35 | { 36 | import core.lifetime: forward; 37 | 38 | static foreach(i; 0 .. TYPES.length) 39 | if(i == selection) 40 | return HANDLER(mixin("value_",i), forward!args); 41 | 42 | assert(false); 43 | } 44 | } 45 | 46 | unittest 47 | { 48 | struct Handler 49 | { 50 | static string[] opCall(string function(int) f, string v) { return ["int", f(0), v]; } 51 | static string[] opCall(string function(string) f, string v) { return ["string", f(""), v]; } 52 | } 53 | 54 | assert(CallDispatcher!Handler((int) => "foo")("boo") == ["int","foo","boo"]); 55 | assert(CallDispatcher!Handler((string _) => "woo")("doo") == ["string","woo","doo"]); 56 | } -------------------------------------------------------------------------------- /source/argparse/internal/commandinfo.d: -------------------------------------------------------------------------------- 1 | module argparse.internal.commandinfo; 2 | 3 | import argparse.config; 4 | import argparse.internal.lazystring; 5 | 6 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 7 | 8 | package(argparse) struct CommandInfo 9 | { 10 | string[] names; 11 | string[] displayNames; 12 | bool caseSensitive = true; 13 | LazyString usage; 14 | LazyString description; 15 | LazyString shortDescription; 16 | LazyString epilog; 17 | } 18 | 19 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 20 | 21 | private auto finalize(const Config config, CommandInfo uda) 22 | { 23 | uda.displayNames = uda.names.dup; 24 | 25 | uda.caseSensitive = config.caseSensitiveSubCommand; 26 | if(!config.caseSensitiveSubCommand) 27 | { 28 | import std.algorithm: each; 29 | import std.uni : toUpper; 30 | 31 | uda.names.each!((ref _) => _ = _.toUpper); 32 | } 33 | 34 | return uda; 35 | } 36 | 37 | unittest 38 | { 39 | auto uda = finalize(Config.init, CommandInfo(["cmd-Name"])); 40 | assert(uda.displayNames == ["cmd-Name"]); 41 | assert(uda.names == ["cmd-Name"]); 42 | } 43 | 44 | unittest 45 | { 46 | enum Config config = { caseSensitiveShortName: false, caseSensitiveLongName: false, caseSensitiveSubCommand: false }; 47 | 48 | auto uda = finalize(config, CommandInfo(["cmd-Name"])); 49 | assert(uda.displayNames == ["cmd-Name"]); 50 | assert(uda.names == ["CMD-NAME"]); 51 | } 52 | 53 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 54 | 55 | private template getCommandInfo(TYPE) 56 | { 57 | import std.traits: getUDAs; 58 | 59 | enum udas = getUDAs!(TYPE, CommandInfo); 60 | static assert(udas.length <= 1, TYPE.stringof~" has multiple @Command UDA"); 61 | 62 | static if(udas.length > 0) 63 | enum getCommandInfo = udas[0]; 64 | else 65 | enum getCommandInfo = CommandInfo.init; 66 | } 67 | 68 | package auto getSubCommandInfo(COMMAND)(Config config) 69 | { 70 | struct SubCommand 71 | { 72 | CommandInfo info; 73 | alias info this; 74 | 75 | alias TYPE = COMMAND; 76 | } 77 | 78 | CommandInfo info = getCommandInfo!COMMAND; 79 | 80 | if(info.names.length == 0) 81 | info.names = [COMMAND.stringof]; 82 | 83 | return SubCommand(finalize(config, info)); 84 | } 85 | 86 | package(argparse) CommandInfo getTopLevelCommandInfo(COMMAND)(Config config) 87 | { 88 | return finalize(config, getCommandInfo!COMMAND); 89 | } 90 | 91 | unittest 92 | { 93 | @CommandInfo() 94 | struct T {} 95 | 96 | auto sc = getSubCommandInfo!T(Config.init); 97 | assert(sc.displayNames == ["T"]); 98 | assert(sc.names == ["T"]); 99 | assert(is(sc.TYPE == T)); 100 | } -------------------------------------------------------------------------------- /source/argparse/internal/commandstack.d: -------------------------------------------------------------------------------- 1 | module argparse.internal.commandstack; 2 | 3 | import argparse.config; 4 | import argparse.result; 5 | import argparse.internal.command; 6 | import argparse.internal.commandinfo: getTopLevelCommandInfo; 7 | 8 | import std.range: back, popBack; 9 | 10 | 11 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 12 | 13 | package struct FindResult 14 | { 15 | Command.Argument arg; 16 | 17 | Command[] cmdStack; 18 | } 19 | 20 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 21 | 22 | package struct CommandStack 23 | { 24 | Command[] stack; 25 | 26 | size_t idxCurPositionalCmd; 27 | 28 | private this(Command cmd) 29 | { 30 | stack = [cmd]; 31 | } 32 | 33 | void addCommand(Command cmd) 34 | { 35 | stack ~= cmd; 36 | } 37 | 38 | string[] getSuggestions(string arg) const 39 | { 40 | import std.algorithm: map, sort, uniq; 41 | import std.array: array; 42 | import std.range: join; 43 | 44 | return stack.map!((ref _) => _.suggestions(arg)).join.sort.uniq.array; 45 | } 46 | 47 | auto finalize(const Config config) 48 | { 49 | // Populate stack with default subcommands 50 | while(stack.back.defaultSubCommand) 51 | stack ~= stack.back.defaultSubCommand(); 52 | 53 | foreach(ref cmd; stack) 54 | { 55 | auto res = cmd.finalize(config, this); 56 | if(!res) 57 | return res; 58 | } 59 | 60 | return Result.Success; 61 | } 62 | 63 | auto findSubCommand(string name) 64 | { 65 | // Look up in comand stack 66 | foreach_reverse(ref cmd; stack) 67 | { 68 | auto res = cmd.getSubCommand(name); 69 | if(res) 70 | return res; 71 | } 72 | // Look up through default subcommands 73 | for(auto newStack = stack[]; newStack.back.defaultSubCommand !is null;) 74 | { 75 | newStack ~= newStack.back.defaultSubCommand(); 76 | 77 | auto res = newStack.back.getSubCommand(name); 78 | if(res) 79 | { 80 | // update stack 81 | stack = newStack; 82 | 83 | return res; 84 | } 85 | } 86 | return null; 87 | } 88 | 89 | 90 | FindResult getNextPositionalArgument(bool lookInDefaultSubCommands) 91 | { 92 | // Look up in current command stack 93 | while(true) 94 | { 95 | auto res = stack[idxCurPositionalCmd].getNextPositionalArgument(); 96 | if(res) 97 | return FindResult(res, stack[0..idxCurPositionalCmd+1]); 98 | 99 | if(idxCurPositionalCmd == stack.length-1) 100 | break; 101 | 102 | ++idxCurPositionalCmd; 103 | } 104 | 105 | if(lookInDefaultSubCommands) 106 | { 107 | for(auto newStack = stack[]; newStack.back.defaultSubCommand !is null;) 108 | { 109 | newStack ~= newStack.back.defaultSubCommand(); 110 | 111 | auto res = newStack.back.getNextPositionalArgument(); 112 | if(res) 113 | { 114 | // update stack 115 | stack = newStack; 116 | idxCurPositionalCmd = stack.length-1; 117 | 118 | return FindResult(res, newStack); 119 | } 120 | } 121 | } 122 | return FindResult.init; 123 | } 124 | 125 | 126 | private FindResult findNamedArgument(Command.Argument delegate(ref Command) find) 127 | { 128 | // Look up in command stack 129 | for(auto newStack = stack[]; newStack.length > 0; newStack.popBack) 130 | { 131 | auto res = find(newStack.back); 132 | if(res) 133 | return FindResult(res, newStack); 134 | } 135 | 136 | // Look up through default subcommands 137 | for(auto newStack = stack[]; newStack.back.defaultSubCommand !is null;) 138 | { 139 | newStack ~= newStack.back.defaultSubCommand(); 140 | 141 | auto res = find(newStack.back); 142 | if(res) 143 | { 144 | // update stack 145 | stack = newStack; 146 | 147 | return FindResult(res, newStack); 148 | } 149 | } 150 | 151 | return FindResult.init; 152 | } 153 | 154 | FindResult findShortArgument(string name) 155 | { 156 | return findNamedArgument((ref cmd) => cmd.findShortNamedArgument(name)); 157 | } 158 | 159 | FindResult findLongArgument(string name) 160 | { 161 | return findNamedArgument((ref cmd) => cmd.findLongNamedArgument(name)); 162 | } 163 | } 164 | 165 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 166 | 167 | package auto createCommandStack(Config config, COMMAND)(ref COMMAND receiver) 168 | { 169 | return CommandStack(createCommand!config(receiver, getTopLevelCommandInfo!COMMAND(config))); 170 | } 171 | -------------------------------------------------------------------------------- /source/argparse/internal/enumhelpers.d: -------------------------------------------------------------------------------- 1 | module argparse.internal.enumhelpers; 2 | 3 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 4 | 5 | package(argparse) struct EnumValue 6 | { 7 | string[] values; 8 | } 9 | 10 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 11 | 12 | private E[string] getEnumValuesMap(E)() 13 | { 14 | import std.traits: EnumMembers, getUDAs; 15 | alias members = EnumMembers!E; 16 | 17 | E[string] res; 18 | static foreach(i, mem; members) 19 | {{ 20 | enum valueUDAs = getUDAs!(mem, EnumValue); 21 | static assert(valueUDAs.length <= 1, E.stringof~"."~mem.stringof~" has multiple 'AllowedValues' UDAs"); 22 | 23 | static if(valueUDAs.length > 0) 24 | { 25 | static foreach(value; valueUDAs[0].values) 26 | res[value] = mem; 27 | } 28 | else 29 | res[mem.stringof] = mem; 30 | }} 31 | 32 | return res; 33 | } 34 | 35 | package enum getEnumValues(E) = getEnumValuesMap!E.keys; 36 | 37 | package E getEnumValue(E)(string value) 38 | { 39 | enum values = getEnumValuesMap!E; 40 | return values[value]; 41 | } 42 | 43 | 44 | unittest 45 | { 46 | enum E { abc, def, ghi } 47 | assert(getEnumValuesMap!E == ["abc":E.abc, "def":E.def, "ghi": E.ghi]); 48 | assert(getEnumValues!E == ["abc", "def", "ghi"]); 49 | assert(getEnumValue!E("abc") == E.abc); 50 | assert(getEnumValue!E("def") == E.def); 51 | assert(getEnumValue!E("ghi") == E.ghi); 52 | } 53 | 54 | unittest 55 | { 56 | enum E { 57 | @EnumValue(["a","b","c"]) 58 | abc, 59 | def, 60 | ghi, 61 | } 62 | assert(getEnumValuesMap!E == ["a":E.abc, "b":E.abc, "c":E.abc, "def":E.def, "ghi": E.ghi]); 63 | assert(getEnumValues!E == ["a","b","c","def","ghi"]); 64 | assert(getEnumValue!E("a") == E.abc); 65 | assert(getEnumValue!E("b") == E.abc); 66 | assert(getEnumValue!E("c") == E.abc); 67 | assert(getEnumValue!E("def") == E.def); 68 | assert(getEnumValue!E("ghi") == E.ghi); 69 | } 70 | 71 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 72 | 73 | -------------------------------------------------------------------------------- /source/argparse/internal/errorhelpers.d: -------------------------------------------------------------------------------- 1 | module argparse.internal.errorhelpers; 2 | 3 | import argparse.config; 4 | import argparse.param; 5 | import argparse.result; 6 | 7 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 8 | private auto createErrorMessage(T)(Param!T param, string prefix) 9 | { 10 | import std.array: appender; 11 | import std.conv: to; 12 | 13 | auto a = appender!string(prefix); 14 | 15 | static if(__traits(hasMember, param, "value")) 16 | { 17 | a ~= " '"; 18 | if(param.isNamedArg) 19 | a ~= param.config.styling.namedArgumentValue(param.value.to!string); 20 | else 21 | a ~= param.config.styling.positionalArgumentValue(param.value.to!string); 22 | a ~= "'"; 23 | } 24 | 25 | if(param.isNamedArg) 26 | { 27 | a ~= " for argument '"; 28 | a ~= param.config.styling.argumentName(param.name); 29 | a ~= "'"; 30 | } 31 | 32 | return a; 33 | } 34 | 35 | package Result processingError(T)(Param!T param) 36 | { 37 | return Result.Error(param.config.errorExitCode, createErrorMessage(param, "Can't process value")[]); 38 | } 39 | 40 | package Result invalidValueError(T)(Param!T param, string suffix = "") 41 | { 42 | auto msg = createErrorMessage(param, "Invalid value"); 43 | msg ~= suffix; 44 | 45 | return Result.Error(param.config.errorExitCode, msg[]); 46 | } 47 | 48 | 49 | unittest 50 | { 51 | Config config; 52 | assert(processingError(Param!void(&config, "--abc")).isError("Can't process value for argument","--abc")); 53 | assert(processingError(Param!void(&config, "")).isError("Can't process value")); 54 | assert(processingError(Param!(int[])(&config, "", [1,2])).isError("Can't process value '","[1, 2]")); 55 | assert(processingError(Param!(int[])(&config, "--abc", [1,2])).isError("Can't process value '","[1, 2]","' for argument '","--abc")); 56 | assert(invalidValueError(Param!(int[])(&config, "--abc", [1,2])).isError("Invalid value '","[1, 2]","' for argument '","--abc")); 57 | assert(invalidValueError(Param!(int[])(&config, "--abc", [1,2]), "custom suffix").isError("Invalid value '","[1, 2]","' for argument '","--abc", "custom suffix")); 58 | } 59 | -------------------------------------------------------------------------------- /source/argparse/internal/lazystring.d: -------------------------------------------------------------------------------- 1 | module argparse.internal.lazystring; 2 | 3 | import std.sumtype: SumType, match; 4 | 5 | package(argparse) struct LazyString 6 | { 7 | SumType!(string, string function()) value; 8 | 9 | this(string s) { value = s; } 10 | this(string function() fn) { value = fn; } 11 | 12 | void opAssign(string s) { value = s; } 13 | void opAssign(string function() fn) { value = fn; } 14 | 15 | @property string get() const 16 | { 17 | return value.match!( 18 | (string _) => _, 19 | (string function() fn) => fn() 20 | ); 21 | } 22 | 23 | bool isSet() const 24 | { 25 | return value.match!( 26 | (string s) => s.length > 0, 27 | (string function() fn) => fn != null 28 | ); 29 | } 30 | } 31 | 32 | unittest 33 | { 34 | LazyString s; 35 | assert(!s.isSet()); 36 | s = "asd"; 37 | assert(s.isSet()); 38 | assert(s.get == "asd"); 39 | s = () => "qwe"; 40 | assert(s.isSet()); 41 | assert(s.get == "qwe"); 42 | assert(LazyString("asd").get == "asd"); 43 | assert(LazyString(() => "asd").get == "asd"); 44 | } 45 | -------------------------------------------------------------------------------- /source/argparse/internal/novalueactionfunc.d: -------------------------------------------------------------------------------- 1 | module argparse.internal.novalueactionfunc; 2 | 3 | import argparse.config; 4 | import argparse.param; 5 | import argparse.result; 6 | import argparse.internal.errorhelpers; 7 | 8 | 9 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 10 | private struct ValueSetter(VALUE) 11 | { 12 | VALUE value; 13 | 14 | Result opCall(ref VALUE receiver, Param!void) const 15 | { 16 | import std.conv: to; 17 | 18 | receiver = value.to!VALUE; 19 | 20 | return Result.Success; 21 | } 22 | } 23 | 24 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 25 | 26 | package(argparse) struct NoValueActionFunc(RECEIVER) 27 | { 28 | union 29 | { 30 | Result function(ref RECEIVER, Param!void) func; 31 | ValueSetter!RECEIVER setter; 32 | } 33 | size_t selection = -1; 34 | 35 | 36 | this(Result function(ref RECEIVER, Param!void) f) 37 | { 38 | func = f; 39 | selection = 0; 40 | } 41 | 42 | this(ValueSetter!RECEIVER s) 43 | { 44 | setter = s; 45 | selection = 1; 46 | } 47 | 48 | bool opCast(T : bool)() const 49 | { 50 | return selection != -1; 51 | } 52 | 53 | Result opCall(ref RECEIVER receiver, Param!void param) const 54 | { 55 | switch(selection) 56 | { 57 | case 0: return func(receiver, param); 58 | case 1: return setter(receiver, param); 59 | default: assert(false); 60 | } 61 | } 62 | } 63 | 64 | unittest 65 | { 66 | auto test(T, F)(F func) 67 | { 68 | T receiver; 69 | assert(NoValueActionFunc!T(func)(receiver, Param!void.init)); 70 | return receiver; 71 | } 72 | auto testErr(T, F)(F func) 73 | { 74 | T receiver; 75 | return NoValueActionFunc!T(func)(receiver, Param!void.init); 76 | } 77 | 78 | // Result action(ref DEST receiver, Param!void param) 79 | assert(test!int((ref int r, Param!void p) { r=7; return Result.Success; }) == 7); 80 | assert(testErr!int((ref int r, Param!void p) => Result.Error(1, "error text")).isError("error text")); 81 | } 82 | 83 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 84 | 85 | package(argparse) auto SetValue(VALUE)(VALUE value) 86 | { 87 | ValueSetter!VALUE setter = { value }; 88 | 89 | return NoValueActionFunc!VALUE(setter); 90 | } 91 | 92 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 93 | 94 | package enum CallFunctionNoParam(FUNC) = NoValueActionFunc!FUNC 95 | ((ref FUNC func, Param!void param) 96 | { 97 | // ... func() 98 | static if(__traits(compiles, { func(); })) 99 | { 100 | func(); 101 | } 102 | // ... func(string value) 103 | else static if(__traits(compiles, { func(string.init); })) 104 | { 105 | func(string.init); 106 | } 107 | // ... func(string[] value) 108 | else static if(__traits(compiles, { func([]); })) 109 | { 110 | func([]); 111 | } 112 | // ... func(Param!void param) 113 | else static if(__traits(compiles, { func(param); })) 114 | { 115 | func(param); 116 | } 117 | // ... func(RawParam param) 118 | else static if(__traits(compiles, { func(RawParam.init); })) 119 | { 120 | func(RawParam(param.config, param.name)); 121 | } 122 | else 123 | static assert(false, "Unsupported callback: " ~ FUNC.stringof); 124 | 125 | return Result.Success; 126 | }); 127 | -------------------------------------------------------------------------------- /source/argparse/internal/parsefunc.d: -------------------------------------------------------------------------------- 1 | module argparse.internal.parsefunc; 2 | 3 | import argparse.config; 4 | import argparse.param; 5 | import argparse.result; 6 | import argparse.internal.calldispatcher; 7 | import argparse.internal.errorhelpers; 8 | 9 | 10 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 11 | 12 | private struct Handler(TYPE) 13 | { 14 | static Result opCall(TYPE function(string[] value) func, ref TYPE receiver, RawParam param) 15 | { 16 | receiver = func(param.value); 17 | return Result.Success; 18 | } 19 | static Result opCall(TYPE function(string value) func, ref TYPE receiver, RawParam param) 20 | { 21 | foreach (value; param.value) 22 | receiver = func(value); 23 | return Result.Success; 24 | } 25 | static Result opCall(TYPE function(RawParam param) func, ref TYPE receiver, RawParam param) 26 | { 27 | receiver = func(param); 28 | return Result.Success; 29 | } 30 | static Result opCall(Result function(ref TYPE receiver, RawParam param) func, ref TYPE receiver, RawParam param) 31 | { 32 | return func(receiver, param); 33 | } 34 | static Result opCall(bool function(ref TYPE receiver, RawParam param) func, ref TYPE receiver, RawParam param) 35 | { 36 | return func(receiver, param) ? Result.Success : processingError(param); 37 | } 38 | static Result opCall(void function(ref TYPE receiver, RawParam param) func, ref TYPE receiver, RawParam param) 39 | { 40 | func(receiver, param); 41 | return Result.Success; 42 | } 43 | } 44 | 45 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 46 | 47 | // T parse(string[] value) 48 | // T parse(string value) 49 | // T parse(RawParam param) 50 | // Result parse(ref T receiver, RawParam param) 51 | // bool parse(ref T receiver, RawParam param) 52 | // void parse(ref T receiver, RawParam param) 53 | package(argparse) struct ParseFunc(RECEIVER) 54 | { 55 | alias CD = CallDispatcher!(Handler!RECEIVER); 56 | CD dispatcher; 57 | alias this = dispatcher; 58 | 59 | static foreach(T; CD.TYPES) 60 | this(T f) 61 | { 62 | dispatcher = CD(f); 63 | } 64 | } 65 | 66 | unittest 67 | { 68 | auto test(T, F)(F func, string[] values) 69 | { 70 | T receiver; 71 | Config config; 72 | assert(ParseFunc!T(func)(receiver, RawParam(&config, "", values))); 73 | return receiver; 74 | } 75 | auto testErr(T, F)(F func, string[] values) 76 | { 77 | T receiver; 78 | Config config; 79 | return ParseFunc!T(func)(receiver, RawParam(&config, "", values)); 80 | } 81 | 82 | // T parse(string value) 83 | assert(test!string((string a) => a, ["1","2","3"]) == "3"); 84 | 85 | // T parse(string[] value) 86 | assert(test!(string[])((string[] a) => a, ["1","2","3"]) == ["1","2","3"]); 87 | 88 | // T parse(RawParam param) 89 | assert(test!string((RawParam p) => p.value[0], ["1","2","3"]) == "1"); 90 | 91 | // Result parse(ref T receiver, RawParam param) 92 | assert(test!(string[])((ref string[] r, RawParam p) { r = p.value; return Result.Success; }, ["1","2","3"]) == ["1","2","3"]); 93 | assert(testErr!(string[])((ref string[] r, RawParam p) => Result.Error(1, "error text"), ["1","2","3"]).isError("error text")); 94 | 95 | // bool parse(ref T receiver, RawParam param) 96 | assert(test!(string[])((ref string[] r, RawParam p) { r = p.value; return true; }, ["1","2","3"]) == ["1","2","3"]); 97 | assert(testErr!(string[])((ref string[] r, RawParam p) => false, ["1","2","3"]).isError("Can't process value")); 98 | 99 | // void parse(ref T receiver, RawParam param) 100 | assert(test!(string[])((ref string[] r, RawParam p) { r = p.value; }, ["1","2","3"]) == ["1","2","3"]); 101 | } 102 | 103 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 104 | 105 | package enum Convert(TYPE) = ParseFunc!TYPE 106 | ((ref TYPE receiver, RawParam param) 107 | { 108 | try 109 | { 110 | import std.conv: to; 111 | 112 | foreach (value; param.value) 113 | static if(is(TYPE == typeof(value))) 114 | receiver = value; 115 | else 116 | receiver = value.length > 0 ? value.to!TYPE : TYPE.init; 117 | 118 | return Result.Success; 119 | } 120 | catch(Exception e) 121 | { 122 | return Result.Error(param.config.errorExitCode, e.msg); 123 | } 124 | }); 125 | 126 | unittest 127 | { 128 | alias test(T) = (string value) 129 | { 130 | T receiver; 131 | assert(Convert!T(receiver, RawParam(null, "", [value]))); 132 | return receiver; 133 | }; 134 | 135 | assert(test!int("7") == 7); 136 | assert(test!string("7") == "7"); 137 | assert(test!char("7") == '7'); 138 | assert(test!string("") == ""); 139 | assert(test!string("") !is null); 140 | } 141 | 142 | unittest 143 | { 144 | alias testErr(T) = (string value) 145 | { 146 | Config config; 147 | T receiver; 148 | return Convert!T(receiver, RawParam(&config, "", [value])); 149 | }; 150 | 151 | assert(testErr!int("unknown").isError()); 152 | assert(testErr!bool("unknown").isError()); 153 | } 154 | -------------------------------------------------------------------------------- /source/argparse/internal/style.d: -------------------------------------------------------------------------------- 1 | module argparse.internal.style; 2 | 3 | import argparse.ansi; 4 | 5 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 6 | /// Styling options 7 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 8 | 9 | package(argparse) struct Style 10 | { 11 | TextStyle programName; 12 | TextStyle subcommandName; 13 | TextStyle argumentGroupTitle; 14 | TextStyle argumentName; 15 | TextStyle namedArgumentValue; 16 | TextStyle positionalArgumentValue; 17 | 18 | TextStyle errorMessagePrefix; 19 | 20 | enum None = Style.init; 21 | 22 | enum Style Default = { 23 | programName: bold, 24 | subcommandName: bold, 25 | argumentGroupTitle: bold.underline, 26 | argumentName: lightYellow, 27 | namedArgumentValue: italic, 28 | positionalArgumentValue: lightYellow, 29 | errorMessagePrefix: red, 30 | }; 31 | } 32 | 33 | unittest 34 | { 35 | assert(Style.Default.argumentGroupTitle("bbb") == bold.underline("bbb").toString); 36 | assert(Style.Default.argumentName("bbb") == lightYellow("bbb").toString); 37 | assert(Style.Default.namedArgumentValue("bbb") == italic("bbb").toString); 38 | } -------------------------------------------------------------------------------- /source/argparse/internal/typetraits.d: -------------------------------------------------------------------------------- 1 | module argparse.internal.typetraits; 2 | 3 | import argparse.config; 4 | import argparse.api.subcommand: isSubCommand; 5 | import argparse.internal.arguments: finalize; 6 | import argparse.internal.argumentuda: ArgumentUDA; 7 | import argparse.internal.argumentudahelpers: getMemberArgumentUDA, isArgumentUDA; 8 | import argparse.internal.commandinfo; 9 | import argparse.internal.help: HelpArgumentUDA; 10 | 11 | import std.meta; 12 | import std.string: startsWith; 13 | import std.traits; 14 | 15 | 16 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 17 | 18 | private enum isPositional(alias uda) = uda.info.positional; 19 | private enum hasPosition(alias uda) = !uda.info.position.isNull; 20 | 21 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 22 | 23 | private enum isOpFunction(alias mem) = is(typeof(mem) == function) && __traits(identifier, mem).length > 2 && __traits(identifier, mem)[0..2] == "op"; 24 | private enum isConstructor(alias mem) = is(typeof(mem) == function) && __traits(identifier, mem) == "__ctor"; 25 | 26 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 27 | 28 | private template Arguments(Config config, TYPE) 29 | { 30 | // Discover potential arguments 31 | private template potentialArgument(string sym) 32 | { 33 | static if(is(TYPE == class) && __traits(hasMember, Object, sym)) 34 | // If this is a member of Object class then skip it 35 | enum potentialArgument = false; 36 | else 37 | { 38 | alias mem = __traits(getMember, TYPE, sym); 39 | 40 | // Potential argument is: 41 | enum potentialArgument = 42 | !is(mem) && // not a type -- and -- 43 | !isOpFunction!mem && // not operator function -- and -- 44 | !isConstructor!mem && // not a constructor -- and -- 45 | !isSubCommand!(typeof(mem)); // not subcommand 46 | } 47 | } 48 | 49 | private enum allMembers = Filter!(potentialArgument, __traits(allMembers, TYPE)); 50 | 51 | private alias hasArgumentUDA(string sym) = anySatisfy!(isArgumentUDA, __traits(getAttributes, __traits(getMember, TYPE, sym))); 52 | 53 | private enum membersWithUDA = Filter!(hasArgumentUDA, allMembers); 54 | 55 | static if(config.addHelpArgument) 56 | private enum helpUDA = HelpArgumentUDA(HelpArgumentUDA.init.info.finalize!bool(config, null)); 57 | else 58 | private alias helpUDA = AliasSeq!(); 59 | 60 | // Get argument UDAs: if there are no members attributed with UDA then get all members, otherwise only attributed ones 61 | private enum getArgumentUDA(string sym) = getMemberArgumentUDA!(TYPE, sym)(config); 62 | 63 | static if(membersWithUDA.length == 0) 64 | { 65 | // No argument UDAs are used so all arguments are named and we can skip processing of positional UDAs 66 | enum UDAs = AliasSeq!(staticMap!(getArgumentUDA, allMembers), helpUDA); 67 | 68 | private enum hasOptionalPositional = false; 69 | } 70 | else 71 | { 72 | private enum originalUDAs = staticMap!(getArgumentUDA, membersWithUDA); 73 | 74 | // Ensure that we don't have a mix of user-dfined and automatic positions of arguments 75 | private enum positionalWithPos(alias uda) = isPositional!uda && hasPosition!uda; 76 | private enum positionalWithoutPos(alias uda) = isPositional!uda && !hasPosition!uda; 77 | 78 | private enum positionalWithPosNum = Filter!(positionalWithPos, originalUDAs).length; 79 | 80 | static assert(positionalWithPosNum == 0 || Filter!(positionalWithoutPos, originalUDAs).length == 0, 81 | TYPE.stringof~": Positions must be specified for all or none positional arguments"); 82 | 83 | enum UDAs = AliasSeq!(originalUDAs, helpUDA); 84 | 85 | static if(positionalWithPosNum == 0) 86 | { 87 | private enum positionalUDAs = Filter!(isPositional, UDAs); 88 | } 89 | else 90 | { 91 | private enum sortByPosition(alias uda1, alias uda2) = uda1.info.position.get - uda2.info.position.get; 92 | 93 | private enum positionalUDAs = staticSort!(sortByPosition, Filter!(isPositional, UDAs)); 94 | 95 | static foreach(i, uda; positionalUDAs) 96 | { 97 | static if(i < uda.info.position.get) 98 | static assert(false, TYPE.stringof~": Positional argument with index "~i.stringof~" is missed"); 99 | else static if(i > uda.info.position.get) 100 | static assert(false, TYPE.stringof~": Positional argument with index "~uda.info.position.get.stringof~" is duplicated"); 101 | } 102 | } 103 | 104 | static foreach(i, uda; positionalUDAs) 105 | { 106 | static if(i < positionalUDAs.length - 1) 107 | static assert(uda.info.minValuesCount.get == uda.info.maxValuesCount.get, 108 | TYPE.stringof~": Positional argument with index "~i.stringof~" has variable number of values - only last one is allowed to have it"); 109 | 110 | static if(i > 0 && uda.info.required) 111 | static assert(positionalUDAs[i-1].info.required, 112 | TYPE.stringof~": Required positional argument with index "~i.stringof~" can't come after optional positional argument"); 113 | } 114 | 115 | private enum hasOptionalPositional = positionalUDAs.length > 0 && !positionalUDAs[$-1].info.required; 116 | } 117 | 118 | private enum getInfo(alias uda) = uda.info; 119 | enum infos = staticMap!(getInfo, UDAs); 120 | 121 | // Check whether argument names do not violate argparse requirements 122 | static foreach(info; infos) 123 | { 124 | static foreach (name; info.shortNames) 125 | static assert(!name.startsWith(config.shortNamePrefix), TYPE.stringof~": Argument name should not begin with '"~config.shortNamePrefix~"': "~name); 126 | static foreach (name; info.longNames) 127 | static assert(!name.startsWith(config.longNamePrefix), TYPE.stringof~": Argument name should not begin with '"~config.longNamePrefix~"': "~name); 128 | } 129 | } 130 | 131 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 132 | 133 | private template SubCommands(Config config, TYPE) 134 | { 135 | private template isSubCommandSymbol(string sym) 136 | { 137 | alias mem = __traits(getMember, TYPE, sym); 138 | 139 | enum isSubCommandSymbol = 140 | !is(mem) && // not a type -- and -- 141 | .isSubCommand!(typeof(mem)); // subcommand 142 | } 143 | 144 | private alias symbols = Filter!(isSubCommandSymbol, __traits(allMembers, TYPE)); 145 | static assert(symbols.length <= 1, TYPE.stringof~": Multiple subcommand members: "~symbols.stringof); 146 | 147 | static if(symbols.length == 1) 148 | { 149 | enum symbol = symbols[0]; 150 | 151 | private alias memberType = typeof(__traits(getMember, TYPE, symbol)); 152 | 153 | private enum getCommandInfo(CMD) = .getSubCommandInfo!CMD(config); 154 | enum commands = staticMap!(getCommandInfo, memberType.Types); 155 | 156 | static if(is(memberType.DefaultCommand)) 157 | { 158 | private enum isDefault(alias CMD) = is(memberType.DefaultCommand == CMD.TYPE); 159 | 160 | enum defaultSubCommand = Filter!(isDefault, commands)[0]; 161 | } 162 | 163 | // Check whether names of subcommands do not violate argparse requirements 164 | static foreach(cmd; commands) 165 | static foreach(name; cmd.names) 166 | { 167 | static assert(!name.startsWith(config.shortNamePrefix), TYPE.stringof~": Subcommand name should not begin with '"~config.shortNamePrefix~"': "~name); 168 | static assert(!name.startsWith(config.longNamePrefix), TYPE.stringof~": Subcommand name should not begin with '"~config.longNamePrefix~"': "~name); 169 | } 170 | } 171 | } 172 | 173 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 174 | 175 | package template TypeTraits(Config config, TYPE) 176 | { 177 | ///////////////////////////////////////////////////////////////////// 178 | /// Arguments 179 | 180 | private alias arguments = .Arguments!(config, TYPE); 181 | 182 | alias argumentUDAs = arguments.UDAs; 183 | alias argumentInfos = arguments.infos; 184 | 185 | ///////////////////////////////////////////////////////////////////// 186 | /// Subcommands 187 | 188 | private alias SC = .SubCommands!(config, TYPE); 189 | 190 | static if(is(typeof(SC.commands))) 191 | { 192 | alias subCommandSymbol = SC.symbol; 193 | alias subCommands = SC.commands; 194 | 195 | static if(is(typeof(SC.defaultSubCommand))) 196 | { 197 | alias defaultSubCommand = SC.defaultSubCommand; 198 | 199 | static assert(!arguments.hasOptionalPositional, TYPE.stringof~": Optional positional arguments and default subcommand can't be used together in one command"); 200 | } 201 | } 202 | } 203 | -------------------------------------------------------------------------------- /source/argparse/internal/utils.d: -------------------------------------------------------------------------------- 1 | module argparse.internal.utils; 2 | 3 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 4 | 5 | package(argparse) string formatAllowedValues(T)(const(T)[] names) 6 | { 7 | import std.format: format; 8 | return "{%-(%s,%)}".format(names); 9 | } 10 | 11 | unittest 12 | { 13 | assert(formatAllowedValues(["abc", "def", "ghi"]) == "{abc,def,ghi}"); 14 | } -------------------------------------------------------------------------------- /source/argparse/param.d: -------------------------------------------------------------------------------- 1 | module argparse.param; 2 | 3 | import argparse.config; 4 | 5 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 6 | 7 | struct Param(VALUE_TYPE) 8 | { 9 | const(Config)* config; 10 | immutable string name; 11 | 12 | static if(!is(VALUE_TYPE == void)) 13 | VALUE_TYPE value; 14 | 15 | package bool isNamedArg() const 16 | { 17 | import std.string: startsWith; 18 | return name.startsWith(config.shortNamePrefix) || name.startsWith(config.longNamePrefix); 19 | } 20 | } 21 | 22 | alias RawParam = Param!(string[]); 23 | 24 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 25 | 26 | unittest 27 | { 28 | Config config; 29 | RawParam p1; 30 | auto p2 = p1; 31 | } 32 | 33 | unittest 34 | { 35 | Config config; 36 | assert(!RawParam(&config).isNamedArg); 37 | assert(!RawParam(&config,"a").isNamedArg); 38 | assert(RawParam(&config,"-a").isNamedArg); 39 | } 40 | -------------------------------------------------------------------------------- /source/argparse/result.d: -------------------------------------------------------------------------------- 1 | module argparse.result; 2 | 3 | 4 | struct Result 5 | { 6 | //////////////////////////////////////////////////////////////// 7 | /// Public API 8 | //////////////////////////////////////////////////////////////// 9 | 10 | int exitCode() const 11 | { 12 | return resultCode; 13 | } 14 | 15 | bool isSuccess () const { return status == Status.success; } 16 | bool isError () const { return status == Status.error; } 17 | bool isHelpWanted() const { return status == Status.helpWanted; } 18 | 19 | bool opCast(T : bool)() const 20 | { 21 | return isSuccess; 22 | } 23 | 24 | 25 | static enum Success = Result(0, Status.success); 26 | 27 | static auto Error(T...)(int resultCode, string msg, T extraArgs) 28 | { 29 | import std.conv: text; 30 | 31 | return Result(resultCode, Status.error, text(msg, extraArgs)); 32 | } 33 | 34 | //////////////////////////////////////////////////////////////// 35 | /// Private API 36 | //////////////////////////////////////////////////////////////// 37 | 38 | private this(int i, Status s, string err = "") { resultCode = i; status = s; errorMsg = err; } 39 | 40 | package static enum HelpWanted = Result(0, Status.helpWanted); 41 | 42 | private int resultCode; 43 | 44 | private enum Status { error, success, helpWanted }; 45 | private Status status; 46 | 47 | private string errorMsg; 48 | 49 | package string errorMessage() const { return errorMsg; } 50 | 51 | version(unittest) 52 | { 53 | package bool isError(string text0, string[] text...) 54 | { 55 | import std.algorithm: canFind; 56 | 57 | if(status != Status.error) 58 | return false; // success is not an error 59 | 60 | foreach(s; [text0] ~ text) 61 | if(!errorMsg.canFind(s)) 62 | return false; // can't find required text 63 | 64 | return true; // all required text is found 65 | } 66 | } 67 | } 68 | 69 | unittest 70 | { 71 | assert(Result.Success); 72 | assert(Result.Success.isSuccess); 73 | assert(!Result.Success.isError); 74 | assert(!Result.Success.isError("text")); 75 | assert(!Result.Success.isHelpWanted); 76 | 77 | assert(!Result.Error(5, "")); 78 | assert(Result.Error(5, "").exitCode == 5); 79 | assert(Result.Error(5, "").isError); 80 | assert(!Result.Error(5, "").isSuccess); 81 | assert(!Result.Error(5, "").isHelpWanted); 82 | 83 | assert(Result.HelpWanted.isHelpWanted); 84 | assert(!Result.HelpWanted.isSuccess); 85 | assert(!Result.HelpWanted.isError); 86 | 87 | auto r = Result.Error(1, "some text",",","more text"); 88 | assert(r.isError("some", "more")); 89 | assert(!r.isError("other text")); 90 | } -------------------------------------------------------------------------------- /tools/deps.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -euo pipefail 4 | IFS=$'\t\n' 5 | [[ $# -le 1 ]] || exit 2 6 | 7 | { 8 | cd -- "${0%[/\\]*}/../source" 9 | 10 | echo 'digraph {' 11 | 12 | grep -rw 'import \+argparse' --include='*.d' | sed -E ' 13 | s/^(.+)\.d: *(public +)?import +([^:; ]+).*/"\3" -> "\1"/ 14 | s|/|.|g 15 | s/\.package//g 16 | ' | sort -u 17 | 18 | echo '}' 19 | } | dot -Tsvg -o "${1-deps.svg}" 20 | --------------------------------------------------------------------------------