diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index e87efed..7bdc196 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -1,4 +1,4 @@ -name: Build +name: Build sources on: push: paths-ignore: diff --git a/.github/workflows/docs.yaml b/.github/workflows/docs.yaml new file mode 100644 index 0000000..1c37d3d --- /dev/null +++ b/.github/workflows/docs.yaml @@ -0,0 +1,99 @@ +name: Build documentation + +on: + push: + branches: ["master"] + pull_request: + +# Gives the workflow permissions to clone the repo and create a page deployment +permissions: + id-token: write + pages: write + +env: + # Name of help module and instance id separated by a slash + INSTANCE: docs/ad + # AD = the ID of the instance in capital letters + ARTIFACT: webHelpAD2-all.zip + # Writerside docker image version + DOCKER_VERSION: 241.15989 + # Add the variable below to upload Algolia indexes + # AD = the ID of the instance in capital letters + ALGOLIA_ARTIFACT: algolia-indexes-AD.zip + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Build Writerside docs using Docker + uses: JetBrains/writerside-github-action@v4 + with: + instance: ${{ env.INSTANCE }} + artifact: ${{ env.ARTIFACT }} + docker-version: ${{ env.DOCKER_VERSION }} + + - name: Upload documentation + uses: actions/upload-artifact@v4 + with: + name: docs + retention-days: 7 + path: | + artifacts/${{ env.ARTIFACT }} + artifacts/report.json + + # Add the step below to upload Algolia indexes + - name: Upload algolia-indexes + uses: actions/upload-artifact@v4 + with: + name: algolia-indexes + retention-days: 7 + path: artifacts/${{ env.ALGOLIA_ARTIFACT }} + + # fail the build when documentation contains errors + test: + # Requires build job results + needs: build + runs-on: ubuntu-latest + steps: + - name: Download artifacts + uses: actions/download-artifact@v4 + with: + name: docs + path: artifacts + + - name: Test documentation + uses: JetBrains/writerside-checker-action@v1 + with: + instance: ${{ env.INSTANCE }} + + deploy: + if: ${{ github.ref_name == 'master' }} + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + # Requires the test job results + needs: test + runs-on: ubuntu-latest + steps: + - name: Download artifact + uses: actions/download-artifact@v4 + with: + name: docs + + - name: Unzip artifact + run: unzip -O UTF-8 -qq ${{ env.ARTIFACT }} -d dir + + - name: Setup Pages + uses: actions/configure-pages@v4 + + - name: Upload artifact + uses: actions/upload-pages-artifact@v3 + with: + path: dir + + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 \ No newline at end of file diff --git a/.gitignore b/.gitignore index b9c2183..fc0bff6 100644 --- a/.gitignore +++ b/.gitignore @@ -3,7 +3,6 @@ out/ docs.json __dummy.html -docs/ /argparse argparse.iml *argparse.so diff --git a/docs/ad.tree b/docs/ad.tree new file mode 100644 index 0000000..d4b0130 --- /dev/null +++ b/docs/ad.tree @@ -0,0 +1,45 @@ + + + + \ No newline at end of file diff --git a/docs/c.list b/docs/c.list new file mode 100644 index 0000000..c4c77a2 --- /dev/null +++ b/docs/c.list @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/docs/cfg/buildprofiles.xml b/docs/cfg/buildprofiles.xml new file mode 100644 index 0000000..dac9c3c --- /dev/null +++ b/docs/cfg/buildprofiles.xml @@ -0,0 +1,12 @@ + + + + + + + false + + + + diff --git a/docs/code_snippets/allowed_values.d b/docs/code_snippets/allowed_values.d new file mode 100644 index 0000000..24f781b --- /dev/null +++ b/docs/code_snippets/allowed_values.d @@ -0,0 +1,14 @@ +import argparse; + +struct T +{ + @(NamedArgument.AllowedValues!(["apple","pear","banana"])) + string fruit; +} + +T t; +assert(CLI!T.parseArgs(t, ["--fruit", "apple"])); +assert(t == T("apple")); + +// "kiwi" is not allowed +assert(!CLI!T.parseArgs(t, ["--fruit", "kiwi"])); diff --git a/docs/code_snippets/ansiStylingArgument.d b/docs/code_snippets/ansiStylingArgument.d new file mode 100644 index 0000000..0230332 --- /dev/null +++ b/docs/code_snippets/ansiStylingArgument.d @@ -0,0 +1,20 @@ +import argparse; + +struct T +{ + static auto color = ansiStylingArgument; +} + +T t; +CLI!T.parseArgs(t, ["-h"]); + +// This is a way to detect whether `--color` argument was specified in the command line +// Note that 'autodetect' is converted to either 'on' or 'off' +assert(CLI!T.parseArgs(t, ["--color"])); +assert(t.color); + +assert(CLI!T.parseArgs(t, ["--color=always"])); +assert(t.color); + +assert(CLI!T.parseArgs(t, ["--color=never"])); +assert(!t.color); diff --git a/docs/code_snippets/arity.d b/docs/code_snippets/arity.d new file mode 100644 index 0000000..5a9d7cd --- /dev/null +++ b/docs/code_snippets/arity.d @@ -0,0 +1,13 @@ +import argparse; + +struct T +{ + @(NamedArgument.NumberOfValues(1,3)) + int[] a; + @(NamedArgument.NumberOfValues(2)) + int[] b; +} + +T t; +assert(CLI!T.parseArgs(t, ["-a","1","2","3","-b","4","5"])); +assert(t == T([1,2,3], [4,5])); diff --git a/docs/code_snippets/arity_no_values.d b/docs/code_snippets/arity_no_values.d new file mode 100644 index 0000000..3cb20fc --- /dev/null +++ b/docs/code_snippets/arity_no_values.d @@ -0,0 +1,19 @@ +import argparse; + +struct T +{ + @(NamedArgument.AllowNoValue !10) int a; + @(NamedArgument.RequireNoValue!20) int b; +} + +T t; +assert(CLI!T.parseArgs(t, ["-a"])); +assert(t.a == 10); + +assert(CLI!T.parseArgs(t, ["-b"])); +assert(t.b == 20); + +assert(CLI!T.parseArgs(t, ["-a","30"])); +assert(t.a == 30); + +assert(!CLI!T.parseArgs(t, ["-b","30"])); // Unrecognized arguments: ["30"] \ No newline at end of file diff --git a/docs/code_snippets/call_parser1.d b/docs/code_snippets/call_parser1.d new file mode 100644 index 0000000..3f8d66e --- /dev/null +++ b/docs/code_snippets/call_parser1.d @@ -0,0 +1,18 @@ +import argparse; + +struct T +{ + string a; + string b; +} + +mixin CLI!T.main!((args) +{ + // 'args' has 'T' type + static assert(is(typeof(args) == T)); + + // do whatever you need + import std.stdio: writeln; + args.writeln; + return 0; +}); diff --git a/docs/code_snippets/call_parser2.d b/docs/code_snippets/call_parser2.d new file mode 100644 index 0000000..11b3488 --- /dev/null +++ b/docs/code_snippets/call_parser2.d @@ -0,0 +1,27 @@ +import argparse; + +struct cmd1 +{ + string a; +} + +struct cmd2 +{ + string b; +} + +mixin CLI!(cmd1, cmd2).main!((args, unparsed) +{ + // 'args' has either 'cmd1' or 'cmd2' type + static if(is(typeof(args) == cmd1)) + writeln("cmd1: ", args); + else static if(is(typeof(args) == cmd2)) + writeln("cmd2: ", args); + else + static assert(false); // this would never happen + + // unparsed arguments has 'string[]' type + static assert(is(typeof(unparsed) == string[])); + + return 0; +}); diff --git a/docs/code_snippets/call_parser3.d b/docs/code_snippets/call_parser3.d new file mode 100644 index 0000000..87d2cdd --- /dev/null +++ b/docs/code_snippets/call_parser3.d @@ -0,0 +1,21 @@ +import argparse; + +struct COMMAND +{ + string a; + string b; +} + +static int my_main(COMMAND command) +{ + // Do whatever is needed + return 0; +} + +int main(string[] args) +{ + // Do initialization here + // If needed, termination code can be done as 'scope(exit) { ...code... }' here as well + + return CLI!COMMAND.parseArgs!my_main(args[1..$]); +} diff --git a/docs/code_snippets/call_parser4.d b/docs/code_snippets/call_parser4.d new file mode 100644 index 0000000..63cdb25 --- /dev/null +++ b/docs/code_snippets/call_parser4.d @@ -0,0 +1,19 @@ +import argparse; + +struct COMMAND +{ + string a; + string b; +} + +int main(string[] argv) +{ + COMMAND cmd; + + if(!CLI!COMMAND.parseArgs(cmd, argv[1..$])) + return 1; // parsing failure + + // Do whatever is needed + + return 0; +} diff --git a/docs/code_snippets/call_parser5.d b/docs/code_snippets/call_parser5.d new file mode 100644 index 0000000..70dcb4c --- /dev/null +++ b/docs/code_snippets/call_parser5.d @@ -0,0 +1,13 @@ +import argparse; + +struct T +{ + string a; +} + +auto arguments = [ "-a", "A", "-c", "C" ]; + +T result; +assert(CLI!T.parseKnownArgs(result, arguments)); +assert(result == T("A")); +assert(arguments == ["-c", "C"]); diff --git a/docs/code_snippets/config_addHelp.d b/docs/code_snippets/config_addHelp.d new file mode 100644 index 0000000..0b8a808 --- /dev/null +++ b/docs/code_snippets/config_addHelp.d @@ -0,0 +1,18 @@ +import argparse; + +struct T +{ + string a, b; +} + +T t1; +CLI!T.parseArgs(t1, ["-a", "A", "-h", "-b", "B"]); +assert(t1 == T("A")); + +enum Config cfg = { addHelp: false }; + +T t2; +string[] unrecognizedArgs; +CLI!(cfg, T).parseKnownArgs(t2, ["-a", "A", "-h", "-b", "B"], unrecognizedArgs); +assert(t2 == T("A","B")); +assert(unrecognizedArgs == ["-h"]); diff --git a/docs/code_snippets/config_arraySep.d b/docs/code_snippets/config_arraySep.d new file mode 100644 index 0000000..71e03dc --- /dev/null +++ b/docs/code_snippets/config_arraySep.d @@ -0,0 +1,16 @@ +import argparse; + +struct T +{ + string[] a; +} + +T t1; +assert(CLI!T.parseArgs(t1, ["-a","1:2:3","-a","4","5"])); +assert(t1 == T(["1:2:3","4","5"])); + +enum Config cfg = { arraySep: ':' }; + +T t2; +assert(CLI!(cfg, T).parseArgs(t2, ["-a","1:2:3","-a","4","5"])); +assert(t2 == T(["1","2","3","4","5"])); diff --git a/docs/code_snippets/config_assignChar.d b/docs/code_snippets/config_assignChar.d new file mode 100644 index 0000000..7acda42 --- /dev/null +++ b/docs/code_snippets/config_assignChar.d @@ -0,0 +1,12 @@ +import argparse; + +struct T +{ + string[] a; +} + +enum Config cfg = { assignChar: ':' }; + +T t; +assert(CLI!(cfg, T).parseArgs(t, ["-a:1","-a:2","-a:3"])); +assert(t == T(["1","2","3"])); diff --git a/docs/code_snippets/config_bundling.d b/docs/code_snippets/config_bundling.d new file mode 100644 index 0000000..991104a --- /dev/null +++ b/docs/code_snippets/config_bundling.d @@ -0,0 +1,24 @@ +import argparse; + +struct T +{ + bool a; + bool b; + string c; +} + +enum Config cfg = { bundling: true }; + +T t; + +assert(CLI!(cfg, T).parseArgs(t, ["-ab"])); +assert(t == T(true, true)); + +assert(CLI!(cfg, T).parseArgs(t, ["-abc=foo"])); +assert(t == T(true, true, "foo")); + +assert(CLI!(cfg, T).parseArgs(t, ["-a","-bc=foo"])); +assert(t == T(true, true, "foo")); + +assert(CLI!(cfg, T).parseArgs(t, ["-a","-bcfoo"])); +assert(t == T(true, true, "foo")); diff --git a/docs/code_snippets/config_caseSensitive.d b/docs/code_snippets/config_caseSensitive.d new file mode 100644 index 0000000..9ef060c --- /dev/null +++ b/docs/code_snippets/config_caseSensitive.d @@ -0,0 +1,12 @@ +import argparse; + +struct T +{ + string[] param; +} + +enum Config cfg = { caseSensitive: false }; + +T t; +assert(CLI!(cfg, T).parseArgs(t, ["--param","1","--PARAM","2","--PaRaM","3"])); +assert(t == T(["1","2","3"])); diff --git a/docs/code_snippets/config_endOfNamedArgs.d b/docs/code_snippets/config_endOfNamedArgs.d new file mode 100644 index 0000000..f6a238e --- /dev/null +++ b/docs/code_snippets/config_endOfNamedArgs.d @@ -0,0 +1,14 @@ +import argparse; + +struct T +{ + @NamedArgument string a; + @PositionalArgument(0) string b; + @PositionalArgument(1) string[] c; +} + +enum Config cfg = { endOfNamedArgs: "---" }; + +T t; +assert(CLI!(cfg, T).parseArgs(t, ["B","-a","foo","---","--","-a","boo"])); +assert(t == T("foo","B",["--","-a","boo"])); diff --git a/docs/code_snippets/config_errorHandler.d b/docs/code_snippets/config_errorHandler.d new file mode 100644 index 0000000..359db9c --- /dev/null +++ b/docs/code_snippets/config_errorHandler.d @@ -0,0 +1,24 @@ +import argparse; + +struct T +{ + string a; +} + +enum Config cfg = { + errorHandler: + (text) + { + try { + import std.stdio : stderr; + stderr.writeln("Detected an error: ", text); + } + catch(Exception e) + { + throw new Error(e.msg); + } + } +}; + +T t; +assert(!CLI!(cfg, T).parseArgs(t, ["-b"])); diff --git a/docs/code_snippets/config_namedArgPrefix.d b/docs/code_snippets/config_namedArgPrefix.d new file mode 100644 index 0000000..230e57d --- /dev/null +++ b/docs/code_snippets/config_namedArgPrefix.d @@ -0,0 +1,13 @@ +import argparse; + +struct T +{ + string a; + string baz; +} + +enum Config cfg = { namedArgPrefix: '+' }; + +T t; +assert(CLI!(cfg, T).parseArgs(t, ["+a","foo","++baz","BAZZ"])); +assert(t == T("foo","BAZZ")); diff --git a/docs/code_snippets/config_styling.d b/docs/code_snippets/config_styling.d new file mode 100644 index 0000000..9379785 --- /dev/null +++ b/docs/code_snippets/config_styling.d @@ -0,0 +1,13 @@ +import argparse; +import argparse.ansi; + +struct T +{ + string a, b; +} + +enum Config cfg = { styling: { programName: blue, argumentName: green.italic } }; + +T t; +CLI!(cfg, T).parseArgs(t, ["-a", "A", "-h", "-b", "B"]); +assert(t == T("A")); diff --git a/docs/code_snippets/config_stylingMode.d b/docs/code_snippets/config_stylingMode.d new file mode 100644 index 0000000..683378d --- /dev/null +++ b/docs/code_snippets/config_stylingMode.d @@ -0,0 +1,12 @@ +import argparse; + +struct T +{ + string a, b; +} + +enum Config cfg = { stylingMode: Config.StylingMode.off }; + +T t; +CLI!(cfg, T).parseArgs(t, ["-a", "A", "-h", "-b", "B"]); +assert(t == T("A")); diff --git a/docs/code_snippets/double_dash.d b/docs/code_snippets/double_dash.d new file mode 100644 index 0000000..bccd477 --- /dev/null +++ b/docs/code_snippets/double_dash.d @@ -0,0 +1,16 @@ +import argparse; + +struct T +{ + @NamedArgument + string a; + @NamedArgument + string b; + + @PositionalArgument(0) + string[] args; +} + +T t; +assert(CLI!T.parseArgs(t, ["-a","A","--","-b","B"])); +assert(t == T("A","",["-b","B"])); diff --git a/docs/code_snippets/help_argument_group.d b/docs/code_snippets/help_argument_group.d new file mode 100644 index 0000000..506c74e --- /dev/null +++ b/docs/code_snippets/help_argument_group.d @@ -0,0 +1,29 @@ +import argparse; + +@(Command("MYPROG") +.Description("custom description") +.Epilog("custom epilog") +) +struct T +{ + @(ArgumentGroup("group1").Description("group1 description")) + { + @NamedArgument + { + string a; + string b; + } + @PositionalArgument(0) string p; + } + + @(ArgumentGroup("group2").Description("group2 description")) + @NamedArgument + { + string c; + string d; + } + @PositionalArgument(1) string q; +} + +T t; +CLI!T.parseArgs(t, ["-h"]); diff --git a/docs/code_snippets/help_example.d b/docs/code_snippets/help_example.d new file mode 100644 index 0000000..48ee652 --- /dev/null +++ b/docs/code_snippets/help_example.d @@ -0,0 +1,24 @@ +import argparse; + +@(Command("MYPROG") +.Description("custom description") +.Epilog("custom epilog") +) +struct T +{ + @NamedArgument string s; + @(NamedArgument.Placeholder("VALUE")) string p; + + @(NamedArgument.HideFromHelp) string hidden; + + enum Fruit { apple, pear }; + @(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; + + @(NamedArgument.AllowedValues!([1,4,16,8])) int i; + + @(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; + @(PositionalArgument(1).AllowedValues!(["q","a"])) string param1; +} + +T t; +CLI!T.parseArgs(t, ["-h"]); diff --git a/docs/code_snippets/mutually_exclusive.d b/docs/code_snippets/mutually_exclusive.d new file mode 100644 index 0000000..e5a6f8a --- /dev/null +++ b/docs/code_snippets/mutually_exclusive.d @@ -0,0 +1,20 @@ +import argparse; + +struct T +{ + @MutuallyExclusive() + { + string a; + string b; + } +} + +T t; + +// One of them or no argument is allowed +assert(CLI!T.parseArgs(t, ["-a","a"])); +assert(CLI!T.parseArgs(t, ["-b","b"])); +assert(CLI!T.parseArgs(t, [])); + +// Both arguments or no argument is not allowed +assert(!CLI!T.parseArgs(t, ["-a","a","-b","b"])); diff --git a/docs/code_snippets/mutually_exclusive_required.d b/docs/code_snippets/mutually_exclusive_required.d new file mode 100644 index 0000000..cf7b6a0 --- /dev/null +++ b/docs/code_snippets/mutually_exclusive_required.d @@ -0,0 +1,20 @@ +import argparse; + +struct T +{ + @(MutuallyExclusive.Required) + { + string a; + string b; + } +} + +T t; + +// Either argument is allowed +assert(CLI!T.parseArgs(t, ["-a","a"])); +assert(CLI!T.parseArgs(t, ["-b","b"])); + +// Both or no arguments are not allowed +assert(!CLI!T.parseArgs(t, ["-a","a","-b","b"])); +assert(!CLI!T.parseArgs(t, [])); diff --git a/docs/code_snippets/mutually_required.d b/docs/code_snippets/mutually_required.d new file mode 100644 index 0000000..90b74a2 --- /dev/null +++ b/docs/code_snippets/mutually_required.d @@ -0,0 +1,20 @@ +import argparse; + +struct T +{ + @RequiredTogether() + { + string a; + string b; + } +} + +T t; + +// Both or no argument is allowed +assert(CLI!T.parseArgs(t, ["-a","a","-b","b"])); +assert(CLI!T.parseArgs(t, [])); + +// Only one argument is not allowed +assert(!CLI!T.parseArgs(t, ["-a","a"])); +assert(!CLI!T.parseArgs(t, ["-b","b"])); diff --git a/docs/code_snippets/mutually_required_required.d b/docs/code_snippets/mutually_required_required.d new file mode 100644 index 0000000..bc9ed61 --- /dev/null +++ b/docs/code_snippets/mutually_required_required.d @@ -0,0 +1,20 @@ +import argparse; + +struct T +{ + @(RequiredTogether.Required) + { + string a; + string b; + } +} + +T t; + +// Both arguments are allowed +assert(CLI!T.parseArgs(t, ["-a","a","-b","b"])); + +// Single argument or no argument is not allowed +assert(!CLI!T.parseArgs(t, ["-a","a"])); +assert(!CLI!T.parseArgs(t, ["-b","b"])); +assert(!CLI!T.parseArgs(t, [])); diff --git a/docs/code_snippets/named_arguments.d b/docs/code_snippets/named_arguments.d new file mode 100644 index 0000000..890efae --- /dev/null +++ b/docs/code_snippets/named_arguments.d @@ -0,0 +1,21 @@ +import argparse; + +struct Params +{ + // If name is not provided then member name is used: "--greeting" + @NamedArgument + string greeting; + + // If member name is single character then it becomes a short name: "-a" + @NamedArgument + string a; + + // Argument with multiple names: "--name", "--first-name", "-n" + // Note that single character becomes a short name + @NamedArgument(["name", "first-name", "n"]) + string name; + + // Another way to specify multiple names: "--family", "--last-name" + @NamedArgument("family", "last-name") + string family; +} diff --git a/docs/code_snippets/optional_required_arguments.d b/docs/code_snippets/optional_required_arguments.d new file mode 100644 index 0000000..01796b6 --- /dev/null +++ b/docs/code_snippets/optional_required_arguments.d @@ -0,0 +1,14 @@ +import argparse; + +struct T +{ + @(PositionalArgument(0, "a").Optional) + string a = "not set"; + + @(NamedArgument.Required) + int b; +} + +T t; +assert(CLI!T.parseArgs(t, ["-b", "4"])); +assert(t == T("not set", 4)); diff --git a/docs/code_snippets/parsing_customization.d b/docs/code_snippets/parsing_customization.d new file mode 100644 index 0000000..8d7d721 --- /dev/null +++ b/docs/code_snippets/parsing_customization.d @@ -0,0 +1,16 @@ +import argparse; + +struct T +{ + @(NamedArgument + .PreValidation!((string s) { return s.length > 1 && s[0] == '!'; }) + .Parse !((string s) { return s[1]; }) + .Validation !((char v) { return v >= '0' && v <= '9'; }) + .Action !((ref int a, char v) { a = v - '0'; }) + ) + int a; +} + +T t; +assert(CLI!T.parseArgs(t, ["-a","!4"])); +assert(t == T(4)); diff --git a/docs/code_snippets/positional_arguments.d b/docs/code_snippets/positional_arguments.d new file mode 100644 index 0000000..2e09089 --- /dev/null +++ b/docs/code_snippets/positional_arguments.d @@ -0,0 +1,10 @@ +import argparse; + +struct Params +{ + @PositionalArgument(0) + string firstName; + + @PositionalArgument(1, "lastName") + string arg; +} diff --git a/docs/code_snippets/styling_helloworld.d b/docs/code_snippets/styling_helloworld.d new file mode 100644 index 0000000..3a82e3a --- /dev/null +++ b/docs/code_snippets/styling_helloworld.d @@ -0,0 +1,14 @@ +import argparse.ansi; + +void printText(bool enableStyle) +{ + // style is enabled at runtime when `enableStyle` is true + auto myStyle = enableStyle ? bold.italic.cyan.onRed : noStyle; + + // "Hello" is always printed in green; + // "world!" is printed in bold, italic, cyan and on red when `enableStyle` is true, or "as is" otherwise + writeln(green("Hello "), myStyle("world!")); +} + +printText(true); +printText(false); \ No newline at end of file diff --git a/docs/code_snippets/styling_help.d b/docs/code_snippets/styling_help.d new file mode 100644 index 0000000..4f30847 --- /dev/null +++ b/docs/code_snippets/styling_help.d @@ -0,0 +1,11 @@ +import argparse; +import argparse.ansi; + +struct T +{ + @(NamedArgument("red").Description(bold.underline("Colorize the output:")~" make everything "~red("red"))) + bool red_; +} + +T t; +CLI!T.parseArgs(t, ["-h"]); diff --git a/docs/code_snippets/subcommands.d b/docs/code_snippets/subcommands.d new file mode 100644 index 0000000..cf16b4d --- /dev/null +++ b/docs/code_snippets/subcommands.d @@ -0,0 +1,25 @@ +import argparse; + +struct cmd1 {} +struct cmd2 {} +struct cmd3 {} + +struct T +{ + // name of the subcommand is the same as a name of the type by default + SubCommand!(cmd1, cmd2, cmd3) cmd; +} + +T t; + +assert(CLI!T.parseArgs(t, [])); +assert(t == T.init); + +assert(CLI!T.parseArgs(t, ["cmd1"])); +assert(t == T(typeof(T.cmd)(cmd1.init))); + +assert(CLI!T.parseArgs(t, ["cmd2"])); +assert(t == T(typeof(T.cmd)(cmd2.init))); + +assert(CLI!T.parseArgs(t, ["cmd3"])); +assert(t == T(typeof(T.cmd)(cmd3.init))); diff --git a/docs/code_snippets/subcommands_common_args.d b/docs/code_snippets/subcommands_common_args.d new file mode 100644 index 0000000..119a4cd --- /dev/null +++ b/docs/code_snippets/subcommands_common_args.d @@ -0,0 +1,26 @@ +import argparse; + +struct min {} +struct max {} +struct sum {} + +struct T +{ + int[] n; // common argument for all subcommands + + // name of the subcommand is the same as a name of the type by default + SubCommand!(min, max, sum) cmd; +} + +T t; + +assert(CLI!T.parseArgs(t, ["min","-n","1","2","3"])); +assert(t == T([1,2,3],typeof(T.cmd)(min.init))); + +t = T.init; +assert(CLI!T.parseArgs(t, ["max","-n","4","5","6"])); +assert(t == T([4,5,6],typeof(T.cmd)(max.init))); + +t = T.init; +assert(CLI!T.parseArgs(t, ["sum","-n","7","8","9"])); +assert(t == T([7,8,9],typeof(T.cmd)(sum.init))); diff --git a/docs/code_snippets/subcommands_default.d b/docs/code_snippets/subcommands_default.d new file mode 100644 index 0000000..83c7720 --- /dev/null +++ b/docs/code_snippets/subcommands_default.d @@ -0,0 +1,22 @@ +import argparse; + +struct min {} +struct max {} +struct sum {} + +struct T +{ + int[] n; // common argument for all subcommands + + // name of the subcommand is the same as a name of the type by default + SubCommand!(min, max, Default!sum) cmd; +} + +T t; + +assert(CLI!T.parseArgs(t, ["-n","7","8","9"])); +assert(t == T([7,8,9],typeof(T.cmd)(sum.init))); + +t = T.init; +assert(CLI!T.parseArgs(t, ["max","-n","4","5","6"])); +assert(t == T([4,5,6],typeof(T.cmd)(max.init))); diff --git a/docs/code_snippets/subcommands_enumerate.d b/docs/code_snippets/subcommands_enumerate.d new file mode 100644 index 0000000..5540109 --- /dev/null +++ b/docs/code_snippets/subcommands_enumerate.d @@ -0,0 +1,12 @@ +import argparse; + +struct cmd1 {} +struct cmd2 {} +struct cmd3 {} + +// This mixin defines standard main function that parses command line and calls the provided function: +mixin CLI!(cmd1, cmd2, cmd3).main!((cmd) +{ + import std.stdio; + cmd.writeln; +}); diff --git a/docs/code_snippets/subcommands_names.d b/docs/code_snippets/subcommands_names.d new file mode 100644 index 0000000..e1f079a --- /dev/null +++ b/docs/code_snippets/subcommands_names.d @@ -0,0 +1,22 @@ +import argparse; + +@Command("minimum", "min") +struct min {} +@Command("maximum", "max") +struct max {} + +struct T +{ + int[] n; // common argument for all subcommands + + SubCommand!(min, max) cmd; +} + +T t; + +assert(CLI!T.parseArgs(t, ["minimum","-n","1","2","3"])); +assert(t == T([1,2,3],typeof(T.cmd)(min.init))); + +t = T.init; +assert(CLI!T.parseArgs(t, ["max","-n","4","5","6"])); +assert(t == T([4,5,6],typeof(T.cmd)(max.init))); diff --git a/docs/code_snippets/types_array.d b/docs/code_snippets/types_array.d new file mode 100644 index 0000000..f94b3d5 --- /dev/null +++ b/docs/code_snippets/types_array.d @@ -0,0 +1,14 @@ +import argparse; + +struct T +{ + int[] a; + int[][] b; +} + +T t; +assert(CLI!T.parseArgs(t, ["-a","1","2","3","-a","4","5"])); +assert(t.a == [1,2,3,4,5]); + +assert(CLI!T.parseArgs(t, ["-b","1","2","3","-b","4","5"])); +assert(t.b == [[1,2,3],[4,5]]); diff --git a/docs/code_snippets/types_array_comma.d b/docs/code_snippets/types_array_comma.d new file mode 100644 index 0000000..c1bf22e --- /dev/null +++ b/docs/code_snippets/types_array_comma.d @@ -0,0 +1,12 @@ +import argparse; + +struct T +{ + int[] a; +} + +enum Config cfg = { arraySep: ',' }; + +T t; +assert(CLI!(cfg, T).parseArgs(t, ["-a=1,2,3","-a","4,5"])); +assert(t == T([1,2,3,4,5])); diff --git a/docs/code_snippets/types_assoc_array.d b/docs/code_snippets/types_assoc_array.d new file mode 100644 index 0000000..2e11093 --- /dev/null +++ b/docs/code_snippets/types_assoc_array.d @@ -0,0 +1,10 @@ +import argparse; + +struct T +{ + int[string] a; +} + +T t; +assert(CLI!T.parseArgs(t, ["-a=foo=3","-a","boo=7"])); +assert(t == T(["foo":3,"boo":7])); diff --git a/docs/code_snippets/types_assoc_array_comma.d b/docs/code_snippets/types_assoc_array_comma.d new file mode 100644 index 0000000..d597ddf --- /dev/null +++ b/docs/code_snippets/types_assoc_array_comma.d @@ -0,0 +1,12 @@ +import argparse; + +struct T +{ + int[string] a; +} + +enum Config cfg = { arraySep: ',' }; + +T t; +assert(CLI!(cfg, T).parseArgs(t, ["-a=foo=3,boo=7","-a","bar=4,baz=9"])); +assert(t == T(["foo":3,"boo":7,"bar":4,"baz":9])); diff --git a/docs/code_snippets/types_counter.d b/docs/code_snippets/types_counter.d new file mode 100644 index 0000000..19da8ab --- /dev/null +++ b/docs/code_snippets/types_counter.d @@ -0,0 +1,11 @@ +import argparse; + +struct T +{ + @(NamedArgument.Counter) + int v; +} + +T t; +assert(CLI!T.parseArgs(t, ["-v","-v","-v"])); +assert(t == T(3)); diff --git a/docs/code_snippets/types_counter_bundling.d b/docs/code_snippets/types_counter_bundling.d new file mode 100644 index 0000000..ae0c991 --- /dev/null +++ b/docs/code_snippets/types_counter_bundling.d @@ -0,0 +1,13 @@ +import argparse; + +struct T +{ + @(NamedArgument.Counter) + int v; +} + +enum Config cfg = { bundling: true }; + +T t; +assert(CLI!(cfg, T).parseArgs(t, ["-vv","-v"])); +assert(t == T(3)); diff --git a/docs/code_snippets/types_custom.d b/docs/code_snippets/types_custom.d new file mode 100644 index 0000000..5b719f1 --- /dev/null +++ b/docs/code_snippets/types_custom.d @@ -0,0 +1,15 @@ +import argparse; + +struct Value +{ + string a; +} +struct T +{ + @(NamedArgument.Parse!((string s) { return Value(s); })) + Value s; +} + +T t; +assert(CLI!T.parseArgs(t, ["-s","foo"])); +assert(t == T(Value("foo"))); \ No newline at end of file diff --git a/docs/code_snippets/types_enum.d b/docs/code_snippets/types_enum.d new file mode 100644 index 0000000..a8677cb --- /dev/null +++ b/docs/code_snippets/types_enum.d @@ -0,0 +1,15 @@ +import argparse; + +struct T +{ + enum Fruit { apple, pear }; + + Fruit a; +} + +T t; +assert(CLI!T.parseArgs(t, ["-a","apple"])); +assert(t == T(T.Fruit.apple)); + +assert(CLI!T.parseArgs(t, ["-a=pear"])); +assert(t == T(T.Fruit.pear)); diff --git a/docs/code_snippets/types_enum_custom_values.d b/docs/code_snippets/types_enum_custom_values.d new file mode 100644 index 0000000..b488718 --- /dev/null +++ b/docs/code_snippets/types_enum_custom_values.d @@ -0,0 +1,19 @@ +import argparse; + +struct T +{ + enum Fruit { + apple, + @ArgumentValue("no-apple","noapple") + noapple + }; + + Fruit a; +} + +T t; +assert(CLI!T.parseArgs(t, ["-a=no-apple"])); +assert(t == T(T.Fruit.noapple)); + +assert(CLI!T.parseArgs(t, ["-a","noapple"])); +assert(t == T(T.Fruit.noapple)); diff --git a/docs/code_snippets/types_function.d b/docs/code_snippets/types_function.d new file mode 100644 index 0000000..b695478 --- /dev/null +++ b/docs/code_snippets/types_function.d @@ -0,0 +1,21 @@ +import argparse; + +struct T +{ + int a_; + string[] b_; + string[][] c_; + + @NamedArgument + { + void a() { a_++; } + void b(string s) { b_ ~= s; } + void c(string[] s) { c_ ~= s; } + } +} + +T t; +assert(CLI!T.parseArgs(t, ["-a","-b","1","-c","q","w", + "-a","-b","2","-c","e","r", + "-a","-b","3","-c","t","y",])); +assert(t == T(3, ["1","2","3"], [["q","w"],["e","r"],["t","y"]])); diff --git a/docs/code_snippets/types_number_string.d b/docs/code_snippets/types_number_string.d new file mode 100644 index 0000000..a5ec910 --- /dev/null +++ b/docs/code_snippets/types_number_string.d @@ -0,0 +1,15 @@ +import argparse; + +struct T +{ + int i; + uint u; + double f; + string s; + wstring w; + dstring d; +} + +T t; +assert(CLI!T.parseArgs(t, ["-i","-5","-u","8","-f","12.345","-s","sss","-w","www","-d","ddd"])); +assert(t == T(-5,8,12.345,"sss","www","ddd")); diff --git a/docs/images/allowed_values_error.png b/docs/images/allowed_values_error.png new file mode 100644 index 0000000..82ad184 Binary files /dev/null and b/docs/images/allowed_values_error.png differ diff --git a/docs/images/allowed_values_error_dark.png b/docs/images/allowed_values_error_dark.png new file mode 100644 index 0000000..56bba60 Binary files /dev/null and b/docs/images/allowed_values_error_dark.png differ diff --git a/docs/images/ansiStylingArgument.png b/docs/images/ansiStylingArgument.png new file mode 100644 index 0000000..fc3aca7 Binary files /dev/null and b/docs/images/ansiStylingArgument.png differ diff --git a/docs/images/ansiStylingArgument_dark.png b/docs/images/ansiStylingArgument_dark.png new file mode 100644 index 0000000..22109f3 Binary files /dev/null and b/docs/images/ansiStylingArgument_dark.png differ diff --git a/docs/images/config_help.png b/docs/images/config_help.png new file mode 100644 index 0000000..fc60413 Binary files /dev/null and b/docs/images/config_help.png differ diff --git a/docs/images/config_help_dark.png b/docs/images/config_help_dark.png new file mode 100644 index 0000000..15e34ec Binary files /dev/null and b/docs/images/config_help_dark.png differ diff --git a/docs/images/config_styling.png b/docs/images/config_styling.png new file mode 100644 index 0000000..3fbadcf Binary files /dev/null and b/docs/images/config_styling.png differ diff --git a/docs/images/config_stylingMode.png b/docs/images/config_stylingMode.png new file mode 100644 index 0000000..c82d168 Binary files /dev/null and b/docs/images/config_stylingMode.png differ diff --git a/docs/images/config_stylingMode_dark.png b/docs/images/config_stylingMode_dark.png new file mode 100644 index 0000000..6271ffd Binary files /dev/null and b/docs/images/config_stylingMode_dark.png differ diff --git a/docs/images/config_styling_dark.png b/docs/images/config_styling_dark.png new file mode 100644 index 0000000..010e585 Binary files /dev/null and b/docs/images/config_styling_dark.png differ diff --git a/docs/images/default_styling.png b/docs/images/default_styling.png new file mode 100644 index 0000000..43e1831 Binary files /dev/null and b/docs/images/default_styling.png differ diff --git a/docs/images/default_styling_dark.png b/docs/images/default_styling_dark.png new file mode 100644 index 0000000..2056906 Binary files /dev/null and b/docs/images/default_styling_dark.png differ diff --git a/docs/images/hello_world_with_uda.png b/docs/images/hello_world_with_uda.png new file mode 100644 index 0000000..f99d2f1 Binary files /dev/null and b/docs/images/hello_world_with_uda.png differ diff --git a/docs/images/hello_world_with_uda_dark.png b/docs/images/hello_world_with_uda_dark.png new file mode 100644 index 0000000..a1d18d9 Binary files /dev/null and b/docs/images/hello_world_with_uda_dark.png differ diff --git a/docs/images/hello_world_without_uda.png b/docs/images/hello_world_without_uda.png new file mode 100644 index 0000000..890c1c6 Binary files /dev/null and b/docs/images/hello_world_without_uda.png differ diff --git a/docs/images/hello_world_without_uda_dark.png b/docs/images/hello_world_without_uda_dark.png new file mode 100644 index 0000000..1a0661f Binary files /dev/null and b/docs/images/hello_world_without_uda_dark.png differ diff --git a/docs/images/help_argument_group.png b/docs/images/help_argument_group.png new file mode 100644 index 0000000..607ee3d Binary files /dev/null and b/docs/images/help_argument_group.png differ diff --git a/docs/images/help_argument_group_dark.png b/docs/images/help_argument_group_dark.png new file mode 100644 index 0000000..23be326 Binary files /dev/null and b/docs/images/help_argument_group_dark.png differ diff --git a/docs/images/help_example.png b/docs/images/help_example.png new file mode 100644 index 0000000..1ff3d8f Binary files /dev/null and b/docs/images/help_example.png differ diff --git a/docs/images/help_example1.png b/docs/images/help_example1.png new file mode 100644 index 0000000..08591e7 Binary files /dev/null and b/docs/images/help_example1.png differ diff --git a/docs/images/help_example1_dark.png b/docs/images/help_example1_dark.png new file mode 100644 index 0000000..7de7e97 Binary files /dev/null and b/docs/images/help_example1_dark.png differ diff --git a/docs/images/help_example_dark.png b/docs/images/help_example_dark.png new file mode 100644 index 0000000..dff12f8 Binary files /dev/null and b/docs/images/help_example_dark.png differ diff --git a/docs/images/styling_help.png b/docs/images/styling_help.png new file mode 100644 index 0000000..db2faa5 Binary files /dev/null and b/docs/images/styling_help.png differ diff --git a/docs/images/styling_help_dark.png b/docs/images/styling_help_dark.png new file mode 100644 index 0000000..09717ac Binary files /dev/null and b/docs/images/styling_help_dark.png differ diff --git a/docs/redirection-rules.xml b/docs/redirection-rules.xml new file mode 100644 index 0000000..e9206e6 --- /dev/null +++ b/docs/redirection-rules.xml @@ -0,0 +1,38 @@ + + + + + + Created after removal of "Param and RawParam" from argparse documentation + Param-and-RawParam.html + + + Created after removal of "ANSI styling" from argparse documentation + ANSI-styling.html + + + Created after removal of "ANSI colors and styles" from argparse documentation + ANSI-colors-and-styles.html + + + Created after removal of "Without User-Defined Attributes" from argparse documentation + + Without-User-Defined-Attributes.html + + + Created after removal of "With User-Defined Attributes" from argparse documentation + With-User-Defined-Attributes.html + + + Created after removal of "About argparse documentation" from argparse documentation + starter-topic.html + + + Created after removal of "Default" from argparse documentation + Default.html + + \ No newline at end of file diff --git a/docs/topics/ANSI-coloring-and-styling.md b/docs/topics/ANSI-coloring-and-styling.md new file mode 100644 index 0000000..efae02c --- /dev/null +++ b/docs/topics/ANSI-coloring-and-styling.md @@ -0,0 +1,98 @@ +# ANSI coloring and styling + +Using colors in the command line tool’s output does not just look good: **contrasting** important elements like argument +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) +to add coloring and styling to the error messages and help text. In addition, `argparse` offers public API to apply +colors and styles to any text printed to the console (see below). + +> Coloring and styling API is provided in a separate `argparse.ansi` submodule. It has no dependencies on other parts of +> `argparse` so can be easily used in any other parts of a program unrelated to command line parsing. +> +{style="tip"} + +Default styling + +## Styles and colors + +The following styles and colors are available in `argparse.ansi` submodule: + +**Font styles:** +- `bold` +- `italic` +- `underline` + +**Colors:** + +| **Foreground** | **Background** | +|----------------|------------------| +| `black` | `onBlack` | +| `red` | `onRed` | +| `green` | `onGreen` | +| `yellow` | `onYellow` | +| `blue` | `onBlue` | +| `magenta` | `onMagenta` | +| `cyan` | `onCyan` | +| `lightGray` | `onLightGray` | +| `darkGray` | `onDarkGray` | +| `lightRed` | `onLightRed` | +| `lightGreen` | `onLightGreen` | +| `lightYellow` | `onLightYellow` | +| `lightBlue` | `onLightBlue` | +| `lightMagenta` | `onLightMagenta` | +| `lightCyan` | `onLightCyan` | +| `white` | `onWhite` | + +There is also a “virtual” style `noStyle` that means no styling is applied. It’s useful in ternary operations as a fallback +for the case when styling is disabled. See below example for details. + +All styles above can be combined using `.` and even be used in regular output: + + + +The following example shows how styling can be used in custom help text (`Usage`, `Description`, `ShortDescription`, `Epilog` API): + + + +Here is how help screen will look like: + +Config help example + + +## Enable/disable the styling + +By default `argparse` will try to detect whether ANSI styling is supported, and if so, it will apply styling to the help text. + +There is `Config.stylingMode` parameter that can be used to override default behavior: +- If it’s set to `Config.StylingMode.on`, then styling is **always enabled**. +- If it’s set to `Config.StylingMode.off`, then styling is **always disabled**. +- If it’s set to `Config.StylingMode.autodetect`, then [heuristics](#heuristic) are used to determine + whether styling will be applied. + +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). +`argparse` supports this use case – just add an argument to a command (it can be customized with `@NamedArgument` UDA): + + + +This will add the following argument: + +ansiStylingArgument + + +## Heuristics for enabling styling {id="heuristic"} + +Below is the exact sequence of steps `argparse` uses to determine whether or not to emit ANSI escape codes +(see detectSupport() function [here](https://github.com/andrey-zherikov/argparse/blob/master/source/argparse/ansi.d) for details): + +1. If environment variable `NO_COLOR != ""`, then styling is **disabled**. See [here](https://no-color.org/) for details. +2. If environment variable `CLICOLOR_FORCE != "0"`, then styling is **enabled**. See [here](https://bixense.com/clicolors/) for details. +3. If environment variable `CLICOLOR == "0"`, then styling is **disabled**. See [here](https://bixense.com/clicolors/) for details. +4. If environment variable `ConEmuANSI == "OFF"`, then styling is **disabled**. See [here](https://conemu.github.io/en/AnsiEscapeCodes.html#Environment_variable) for details. +5. If environment variable `ConEmuANSI == "ON"`, then styling is **enabled**. See [here](https://conemu.github.io/en/AnsiEscapeCodes.html#Environment_variable) for details. +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. +7. **Windows only** (`version(Windows)`): + 1. If environment variable `TERM` contains `"cygwin"` or starts with `"xterm"`, then styling is **enabled**. + 2. If `GetConsoleMode` call for `STD_OUTPUT_HANDLE` returns a mode that has `ENABLE_VIRTUAL_TERMINAL_PROCESSING` set, then styling is **enabled**. + 3. If `SetConsoleMode` call for `STD_OUTPUT_HANDLE` with `ENABLE_VIRTUAL_TERMINAL_PROCESSING` mode was successful, then styling is **enabled**. +8. **Posix only** (`version(Posix)`): + 1. If `STDOUT` is **not** redirected, then styling is **enabled**. +9. If none of the above applies, then styling is **disabled**. diff --git a/docs/topics/Argument-dependencies.md b/docs/topics/Argument-dependencies.md new file mode 100644 index 0000000..dcf2115 --- /dev/null +++ b/docs/topics/Argument-dependencies.md @@ -0,0 +1,30 @@ +# Argument dependencies + +## Mutually exclusive arguments {id="MutuallyExclusive"} + +Mutually exclusive arguments (i.e., those that can’t be used together) can be declared using `MutuallyExclusive()` UDA: + + + +> Parentheses `()` are required for this UDA to work correctly. +> +{style="warning"} + +Set of mutually exclusive arguments can be marked as required in order to require exactly one of the arguments: + + + + +## Mutually required arguments {id="RequiredTogether"} + +Mutually required arguments (i.e., those that require other arguments) can be declared using `RequiredTogether()` UDA: + + + +> Parentheses `()` are required for this UDA to work correctly. +> +{style="warning"} + +Set of mutually required arguments can be marked as required in order to require all arguments: + + diff --git a/docs/topics/Arguments-bundling.md b/docs/topics/Arguments-bundling.md new file mode 100644 index 0000000..ac0e668 --- /dev/null +++ b/docs/topics/Arguments-bundling.md @@ -0,0 +1,10 @@ +# Arguments bundling + +Some command line tools allow bundling of single-character argument names in a form of `-abc` where `a`, `b` and `c` are +separate arguments. `argparse` supports this through [`Config.bundling`](Config.md#bundling) setting and allows the following usages: + + + +To explain what happens under the hood, let's consider that a command line has `-abc` entry and there is no `abc` argument. +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` +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. diff --git a/docs/topics/Arguments.md b/docs/topics/Arguments.md new file mode 100644 index 0000000..d217ffa --- /dev/null +++ b/docs/topics/Arguments.md @@ -0,0 +1,4 @@ +# Arguments + +Command line usually consists of arguments that can be separated into _named arguments_ and _positional arguments_. +There is no restriction that one should be before another one so they may be mixed in command line. diff --git a/docs/topics/Arity.md b/docs/topics/Arity.md new file mode 100644 index 0000000..03a7f68 --- /dev/null +++ b/docs/topics/Arity.md @@ -0,0 +1,49 @@ +# Arity + +Sometimes an argument might accept more than one value. This is especially a case when a data member is an array. + +`argparse` supports these use cases: +- Exact number of values. +- Limited range of minimum-maximum number of values. +- Unlimited range where only minimum number of values is provided (e.g. argument accepts _any number_ of values). + +To adjust the arity, use one the following API: +- `NumberOfValues(ulong min, ulong max)` – sets both minimum and maximum number of values. +- `NumberOfValues(ulong num)` – sets the exact number of values. +- `MinNumberOfValues(ulong min)` – sets minimum number of values. +- `MaxNumberOfValues(ulong max)` – sets maximum number of values. + +> Positional argument must have at least one value. +> +{style="warning"} + +Example: + + + +## Default arity + +| Type | Default arity | Notes | +|-----------------------|:---------------:|-----------------------------------------------------------------------------------------------------------------------------| +| `bool` | 0 | Boolean flags do not accept values with the only exception when they are specified in `--flag=true` format in command line. | +| String or scalar | 1 | Exactly one value is accepted. | +| Static array | Length of array | If a range is desired then use provided API to adjust arity. | +| Dynamic array | 1 ... ∞ | | +| Associative array | 1 ... ∞ | | +| `function ()` | 0 | Same as boolean flag. | +| `function (string)` | 1 | Same as `string`. | +| `function (string[])` | 1 ... ∞ | Same as `string[]` array. | +| `function (RawParam)` | 1 ... ∞ | Same as `string[]` array. | + +## Named arguments with no values + +Sometimes named arguments are can have no values in command line. Here are two cases that arise in this situation: + +- Argument should get specific value if there is no value provided in command line. Use `AllowNoValue` in this case. + +- Argument must not have any values in command line. Use `RequireNoValue` in this case. + +Both `AllowNoValue` and `RequireNoValue` accept a value that should be used when no value is provided in the command line. +The difference between them can be seen in this example: + + diff --git a/docs/topics/Calling-the-parser.md b/docs/topics/Calling-the-parser.md new file mode 100644 index 0000000..66aebe0 --- /dev/null +++ b/docs/topics/Calling-the-parser.md @@ -0,0 +1,119 @@ +# Calling the parser + +`argparse` provides `CLI` template to call the parser covering different use cases. It has the following signatures: +- `template CLI(Config config, COMMAND)` – this is main template that provides multiple API (see below) for all + supported use cases. +- `template CLI(Config config, COMMANDS...)` – convenience wrapper of the previous template that provides `main` + template mixin only for the simplest use case with subcommands. See [Subcommands](Subcommands.md) section for details. +- `alias CLI(COMMANDS...) = CLI!(Config.init, COMMANDS)` – alias provided for convenience that allows using default + `Config`, i.e., `config = Config.init`. + +## Wrapper for `main` function + +The recommended and most convenient way to use `argparse` is through `CLI!(...).main(alias newMain)` mixin template. +It declares the standard `main` function that parses command line arguments and calls provided `newMain` function with +an object that contains parsed arguments. + +`newMain` function must satisfy these requirements: +- It must accept `COMMAND` type as a first parameter if `CLI` template is used with one `COMMAND`. +- It must accept all `COMMANDS` types as a first parameter if `CLI` template is used with multiple `COMMANDS...`. + `argparse` uses `std.sumtype.match` for matching. Possible implementation of such `newMain` function would be a + function that is overridden for every command type from `COMMANDS`. Another example would be a lambda that does + compile-time checking of the type of the first parameter (see examples below for details). +- Optionally `newMain` function can take a `string[]` parameter as a second argument. Providing such a function will + mean that `argparse` will parse known arguments only and all unknown ones will be passed into the second parameter of + `newMain` function. If `newMain` function doesn’t have such parameter, then `argparse` will error out if there is an + unknown argument provided in command line. +- Optionally `newMain` can return a result that can be cast to `int`. In this case, this result will be returned from + standard `main` function. + +**Usage examples:** + + + + + + +## Call parser with custom `main` function + +In the case when wrapping of standard `main` function does not fit the needs (e.g., some initialization has to be done before parsing +the command line), `argparse` offers `CLI!(...).parseArgs` function: + +`int parseArgs(alias newMain)(string[] args, COMMAND initialValue = COMMAND.init)` + +**Parameters:** + +- `newMain` – function that’s called with object of type `COMMAND` as a first parameter that is filled with the data parsed from + command line; optionally it can take `string[]` as a second parameter which will contain unknown arguments + (see [Wrapper for main function](#wrapper-for-main-function) section for details). +- `args` – raw command line arguments (excluding `argv[0]` – first command line argument in `main` function). +- `initialValue` – initial value for the object passed to `newMain` function. + +**Return value:** + +If there is an error happened during the parsing, then non-zero value is returned. In case of no error, if `newMain` +function returns a value that can be cast to `int`, then this value is returned, or `0` otherwise. + +**Usage example:** + + + + +## Low-level calling of parser + +For the cases when providing `newMain` function is not possible or feasible, `parseArgs` function can accept a reference +to an object that receives the values of command line arguments: + +`Result parseArgs(ref COMMAND receiver, string[] args)` + +**Parameters:** + +- `receiver` – object that is populated with parsed values. +- `args` – raw command line arguments (excluding `argv[0]` – first command line argument in `main` function). + +**Return value:** + +An object that can be cast to `bool` to check whether the parsing was successful or not. + +> Note that this function will error out if command line contains unknown arguments. +> +{style="warning"} + +**Usage example:** + + + + +## Partial argument parsing + +Sometimes a program may only parse a few of the command line arguments and process the remaining arguments in some different +way. In these cases, `CLI!(...).parseKnownArgs` function can be used. It works much like `CLI!(...).parseArgs` except +that it does not produce an error when unknown arguments are present. It has the following signatures: + +- `Result parseKnownArgs(ref COMMAND receiver, string[] args, out string[] unrecognizedArgs)` + + **Parameters:** + + - `receiver` – the object that’s populated with parsed values. + - `args` – raw command line arguments (excluding `argv[0]` – first command line argument in `main` function). + - `unrecognizedArgs` – raw command line arguments that were not parsed. + + **Return value:** + + An object that can be cast to `bool` to check whether the parsing was successful or not. + +- `Result parseKnownArgs(ref COMMAND receiver, ref string[] args)` + + **Parameters:** + + - `receiver` – the object that’s populated with parsed values. + - `args` – raw command line arguments that are modified to have parsed arguments removed (excluding `argv[0]` – first + command line argument in `main` function). + + **Return value:** + + An object that can be cast to `bool` to check whether the parsing was successful or not. + +**Usage example:** + + diff --git a/docs/topics/End-of-named-arguments.md b/docs/topics/End-of-named-arguments.md new file mode 100644 index 0000000..ea76ff4 --- /dev/null +++ b/docs/topics/End-of-named-arguments.md @@ -0,0 +1,7 @@ +# End of named arguments + +When the command line contains an entry that is equal to [`Config.endOfNamedArgs`](Config.md#endOfNamedArgs) +(double dash `--` by default), `argparse` interprets all following command line entries as positional arguments, even +if they can match a named argument or a subcommand. + + diff --git a/docs/topics/Getting-started.md b/docs/topics/Getting-started.md new file mode 100644 index 0000000..3a64eea --- /dev/null +++ b/docs/topics/Getting-started.md @@ -0,0 +1,25 @@ +# Getting started + +`argparse` provides User-Defined Attributes (UDA) that can be used to annotate `struct` members that are part of +command line interface. + +## Without User-Defined Attributes + +Using UDAs is not required and if a `struct` has no UDAs then all data members are treated as named command line +arguments: + + + +Running the program above with `-h` argument will have the following output: + +Hello world example + +## With User-Defined Attributes + +Although UDA-less approach is useful as a starting point, it's not enough for real command line tool: + + + +Running the program above with `-h` argument will have the following output: + +Hello world example diff --git a/docs/topics/Help-generation.md b/docs/topics/Help-generation.md new file mode 100644 index 0000000..117ec0c --- /dev/null +++ b/docs/topics/Help-generation.md @@ -0,0 +1,67 @@ +# Help generation + + +## Command + +`Command` UDA provides few customizations that affect help text. It can be used for **top-level command** and **subcommands**. + +- Program name (i.e., the name of top-level command) and subcommand name can be provided to `Command` UDA as a parameter. + If program name is not provided, then `Runtime.args[0]` (a.k.a. `argv[0]` from `main` function) is used. + If subcommand name is not provided (e.g., `@(Command.Description(...))`), then the name of the type that represents the command is used. +- `Usage` – allows custom usage text. By default, the parser calculates the usage message from the arguments it contains + but this can be overridden with `Usage` call. If the custom text contains `%(PROG)` then it will be replaced by the + command/program name. +- `Description` – used to provide a description of what the command/program does and how it works. In help messages, the + description is displayed between the usage string and the list of the command arguments. +- `ShortDescription` – used to provide a brief description of what the subcommand does. It is applicable to subcommands + only and is displayed in *Available commands* section on help screen of the parent command. +- `Epilog` – custom text that is printed after the list of the arguments. + +`Usage`, `Description`, `ShortDescription` and `Epilog` modifiers take either `string` or `string function()` +value – the latter can be used to return a value that is not known at compile time. + +## Argument + +There are some customizations supported on argument level for both `PositionalArgument` and `NamedArgument` UDAs: + +- `Description` – provides brief description of the argument. This text is printed next to the argument + in the argument-list section of a help message. `Description` takes either `string` or `string function()` + value – the latter can be used to return a value that is not known at compile time. +- `HideFromHelp` – can be used to indicate that the argument shouldn’t be printed in help message. +- `Placeholder` – provides custom text that is used to indicate the value of the argument in help message. + +## Help text styling + +`argparse` uses `Config.styling` to determine what style should be applied to different parts of the help text. +Please refer to [ANSI coloring and styling](ANSI-coloring-and-styling.md) section for details. + +## Example + +Here is an example of how this customization can be used: + + + +This example will print the following help message: + +Help example + +## Argument groups + +By default, parser groups command line arguments into “required arguments” and “optional arguments” when displaying help +message. When there is a better conceptual grouping of arguments than this default one, appropriate groups can be +created using `ArgumentGroup` UDA. + +This UDA has some customization for displaying help text: + +- `Description` – provides brief description of the group. This text is printed right after group name. + It takes either `string` or `string function()` value – the latter can be used to return a value that is not known + at compile time. + +Example: + + + +When an argument is attributed with a group, the parser treats it just like a normal argument, but displays the argument +in a separate group for help messages: + +Help argument group diff --git a/docs/topics/Introduction.md b/docs/topics/Introduction.md new file mode 100644 index 0000000..ce8f1f2 --- /dev/null +++ b/docs/topics/Introduction.md @@ -0,0 +1,20 @@ +# Introduction + +`argparse` is a flexible parser of command line arguments that allows easy creation of terminal applications with rich +command line interface. + +## Features + +`argparse` provides a lot of features to support different use cases: +- Positional arguments +- Named arguments +- Argument grouping +- Subcommands +- Built-in help screen +- ANSI colors and styles +- Shell completion +- Support for different types, for example: scalars, enums, arrays, associative arrays, callbacks + +Example of help screen with ANSI colors and styles: + +Help example diff --git a/docs/topics/Named-arguments.md b/docs/topics/Named-arguments.md new file mode 100644 index 0000000..cba98ac --- /dev/null +++ b/docs/topics/Named-arguments.md @@ -0,0 +1,30 @@ +# Named arguments + +_Named arguments_ (they are also called as flags or options) have one or more name that can be separated into two categories: +- _Short names_ are those that start with single dash `-` (see [`Config.namedArgPrefix`](Config.md#namedArgPrefix) for customization). +- _Long names_ start with double dash `--` (see [`Config.namedArgPrefix`](Config.md#namedArgPrefix) for customization). + +Both cases are fully supported with one caveat: +if a single-character argument is used with a double dash (e.g., `--n`) in command line, then it behaves the same as a +multi-character argument. + +The following usages of the argument in the command line are equivalent: +- `--name John` +- `--name=John` +- `--n John` +- `--n=John` +- `-n John` +- `-n=John` +- `-nJohn` - this works for single-character names only + +> Any other character can be used instead of `=` – see [`Config.assignChar`](Config.md#assignChar) for details. + +_Named arguments_ can be declared using `NamedArgument` UDA which has the following parameters: + +| # | Name | Type | Optional/
Required | Description | +|---|--------|------------------------|------------------------|------------------------------------------------------------| +| 1 | `name` | `string` or `string[]` | optional | Name(s) of this argument that can show up in command line. | + +Example: + + diff --git a/docs/topics/Optional-and-required-arguments.md b/docs/topics/Optional-and-required-arguments.md new file mode 100644 index 0000000..830ceb6 --- /dev/null +++ b/docs/topics/Optional-and-required-arguments.md @@ -0,0 +1,10 @@ +# Optional and required arguments + +Arguments can be marked as required or optional by adding `.Required` or `.Optional` to UDA. If required argument is +not present in command line, `argparse` will error out. + +By default, _positional arguments_ are **required** and _named arguments_ are **optional**. + +Example: + + diff --git a/docs/topics/Parsing-customization.md b/docs/topics/Parsing-customization.md new file mode 100644 index 0000000..c503c3c --- /dev/null +++ b/docs/topics/Parsing-customization.md @@ -0,0 +1,135 @@ +# Parsing customization + +Sometime the functionality that is provided out of the box is not enough and needs to be tuned. +`argparse` allows customizing of every step of command line parsing: + +- **Pre-validation** – argument values are validated as raw strings. +- **Parsing** – raw argument values are converted to a different type (usually the type of the receiving data member). +- **Validation** – converted value is validated. +- **Action** – depending on a type of the receiving data member, for example, it can be an assignment of converted value to a + data member, or appending value if type is an array. + +In case if argument does not have any value to parse, then the only one step is involved in parsing: + +- **Action if no value** – similar to **Action** step above but without converted value. + +> If any of the steps above fails, then the command line parsing fails as well. +> +{style="note"} + +Each of the steps above can be customized with UDA modifiers below. + +## Pre-validation {id="PreValidation"} + +`PreValidation` modifier can be used to customize the validation of raw string values. It accepts a function with one of +the following signatures: + +- `bool validate(string value)` + + `Result validate(string value)` + + > In this case, function will be called once for every value specified in command line for an argument in case of multiple values. + > + {style="note"} + +- `bool validate(string[] value)` + + `Result validate(string[] value)` + +- `bool validate(RawParam param)` + + `Result validate(RawParam param)` + +Parameters: + +- `value`/`param` values to be parsed. + +Return value: + +- `true`/`Result.Success` if validation passed or +- `false`/`Result.Error` otherwise. + +## Parsing {id="Parse"} + +`Parse` modifier allows providing custom conversion from raw string to a typed value. It accepts a function with one of +the following signatures: + +- `ParseType parse(string value)` +- `ParseType parse(string[] value)` +- `ParseType parse(RawParam param)` +- `bool parse(ref ParseType receiver, RawParam param)` +- `void parse(ref ParseType receiver, RawParam param)` +- `Result parse(ref ParseType receiver, RawParam param)` + +> `ParseType` is a type that a string value is supposed to be parsed to and it is not required be the same as +a type of destination - `argparse` tries to detect this type from provided function. +> +{title="ParseType"} + +Parameters: + +- `value`/`param` raw (string) values to be parsed. +- `receiver` is an output variable for parsed value. + +Return value for functions that return `bool` or `Result` (in other cases parsing is always considered successful): +- `true`/`Result.Success` if parsing was successful or +- `false`/`Result.Error` otherwise. + +## Validation {id="Validation"} + +`Validation` modifier can be used to validate parsed value. It accepts a function with one of the following +signatures: + +- `bool validate(ParseType value)` + + `Result validate(ParseType value)` + +- `bool validate(ParseType[] value)` + + `Result validate(ParseType[] value)` + +- `bool validate(Param!ParseType param)` + + `Result validate(Param!ParseType param)` + +Parameters: + +- `value`/`param` contains a value returned from `Parse` step. + +Return value: + +- `true`/`Result.Success` if validation passed or +- `false`/`Result.Error` otherwise. + +## Action {id="Action"} + +`Action` modifier allows customizing a logic of how "destination" should be changed when argument has a value in +command line. It accepts a function with one of the following signatures: + +- `bool action(ref T receiver, ParseType value)` + + `void action(ref T receiver, ParseType value)` + +- `Result action(ref T receiver, ParseType value)` + + `bool action(ref T receiver, Param!ParseType param)` + +- `void action(ref T receiver, Param!ParseType param)` + + `Result action(ref T receiver, Param!ParseType param)` + +Parameters: + +- `receiver` is a receiver (destination) which is supposed to be changed based on a `value`/`param`. +- `value`/`param` has a value returned from `Parse` step. + +Return value: + +- `true`/`Result.Success` if operation was successful or +- `false`/`Result.Error` otherwise. + +## Example + +All the above modifiers can be combined in any way: + + diff --git a/docs/topics/Positional-arguments.md b/docs/topics/Positional-arguments.md new file mode 100644 index 0000000..e586ee5 --- /dev/null +++ b/docs/topics/Positional-arguments.md @@ -0,0 +1,21 @@ +# Positional arguments + +_Positional arguments_ are arguments that have specific position within the command line. This argument can be declared +using `PositionalArgument` UDA. It has the following parameters: + +| # | Name | Type | Optional/
Required | Description | +|---|---------------|----------|------------------------|--------------------------------------------------------------------------------------------------------------| +| 1 | `position` | `uint` | required | Zero-based unsigned position of the argument. | +| 2 | `placeholder` | `string` | optional | Name of this argument that is shown in help text.
If not provided, then the name of data member is used. | + +Since that both _named_ and _positional arguments_ can be mixed in the command line, `argparse` enforces the following +restrictions to be able to parse a command line unambiguously: +- Positions of _positional arguments_ must be consecutive starting with zero without missing or repeating. +- _Positional argument_ must not have variable number of values except for the last argument. +- Optional _positional argument_ must have index greater than required _positional arguments_. +- If a command has default subcommand (see Subcommand section for details) the optional _positional argument_ is not + allowed in this command. + +Example: + + diff --git a/docs/topics/Restrict-allowed-values.md b/docs/topics/Restrict-allowed-values.md new file mode 100644 index 0000000..3a690ad --- /dev/null +++ b/docs/topics/Restrict-allowed-values.md @@ -0,0 +1,17 @@ +# Restrict allowed values + +In some cases an argument can receive one of the limited set of values. This can be achieved by adding `.AllowedValues!()` +to UDA: + + + +For the value that is not in the allowed list, this error will be printed: + +Allowed values error + +> If the type of destination data member is `enum`, then the allowed values are automatically limited to those +> listed in the `enum`. +> +> See [Enum](Supported-types.md#enum) section for details. +> +{style="note"} \ No newline at end of file diff --git a/docs/topics/Shell-completion.md b/docs/topics/Shell-completion.md new file mode 100644 index 0000000..95195a4 --- /dev/null +++ b/docs/topics/Shell-completion.md @@ -0,0 +1,90 @@ +# Shell completion + +`argparse` supports tab completion of last argument for certain shells (see below). However, this support is limited +to the names of arguments and subcommands. + +## Wrappers for main function + +If you are using `CLI!(...).main(alias newMain)` mixin template in your code then you can easily build a completer +(program that provides completion) by defining `argparse_completion` version (`-version=argparse_completion` option of +`dmd`). Don’t forget to use different output file for completer than your main program (`-of` option in `dmd`). No other +changes are necessary to generate completer, but you should consider minimizing the set of imported modules when +`argparse_completion` version is defined. For example, you can put all imports into your main function that is passed to +`CLI!(...).main(alias newMain)` – `newMain` parameter is not used in completer. + +If you prefer having separate main module for completer, then you can use `CLI!(...).mainComplete` mixin template: +```c++ +mixin CLI!(...).mainComplete; +``` + +In case if you prefer to have your own `main` function and would like to call completer by yourself, you can use +`int CLI!(...).complete(string[] args)` function. This function executes the completer by parsing provided `args` (note +that you should remove the first argument from `argv` passed to `main` function). The returned value is meant to be +returned from `main` function, having zero value in case of success. + +## Low level completion + +In case if none of the above methods is suitable, `argparse` provides `string[] CLI!(...).completeArgs(string[] args)` +function. It takes arguments that should be completed and returns all possible completions. + +`completeArgs` function expects to receive all command line arguments (excluding `argv[0]` – first command line argument +in `main` function) in order to provide completions correctly (set of available arguments depends on subcommand). This +function supports two workflows: +- If the last argument in `args` is empty and it’s not supposed to be a value for a command line argument, then all + available arguments and subcommands (if any) are returned. +- 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 + those arguments and subcommands (if any) are returned that start with the same text as the last argument in `args`. + +For example, if there are `--foo`, `--bar` and `--baz` arguments available, then: +- Completion for `args=[""]` will be `["--foo", "--bar", "--baz"]`. +- Completion for `args=["--b"]` will be `["--bar", "--baz"]`. + +## Using the completer + +Completer that is provided by `argparse` supports the following shells: +- bash +- zsh +- tcsh +- fish + +Its usage consists of two steps: completion setup and completing of the command line. Both are implemented as +subcommands (`init` and `complete` accordingly). + +### Completion setup + +Before using completion, completer should be added to the shell. This can be achieved by using `init` subcommand. It +accepts the following arguments (you can get them by running ` init --help`): +- `--bash`: provide completion for bash. +- `--zsh`: provide completion for zsh. Note: zsh completion is done through bash completion so you should execute `bashcompinit` first. +- `--tcsh`: provide completion for tcsh. +- `--fish`: provide completion for fish. +- `--completerPath `: path to completer. By default, the path to itself is used. +- `--commandName `: command name that should be completed. By default, the first name of your main command is used. + +Either `--bash`, `--zsh`, `--tcsh` or `--fish` is expected. + +As a result, completer prints the script to setup completion for requested shell into standard output (`stdout`) +which should be executed. To make this more streamlined, you can execute the output inside the current shell or to do +this during shell initialization (e.g., in `.bashrc` for bash). To help doing so, completer also prints sourcing +recommendation to standard output as a comment. + +Example of completer output for ` init --bash --commandName mytool --completerPath /path/to/completer` arguments: +```bash +# Add this source command into .bashrc: +# source <(/path/to/completer init --bash --commandName mytool) +complete -C 'eval /path/to/completer --bash -- $COMP_LINE ---' mytool +``` + +Recommended workflow is to install completer into a system according to your installation policy and update shell +initialization/config file to source the output of `init` command. + +### Completing of the command line + +Argument completion is done by `complete` subcommand (it’s default one). It accepts the following arguments (you can get them by running ` complete --help`): +- `--bash`: provide completion for bash. +- `--tcsh`: provide completion for tcsh. +- `--fish`: provide completion for fish. + +As a result, completer prints all available completions, one per line, assuming that it’s called according to the output +of `init` command. + diff --git a/docs/topics/Subcommands.md b/docs/topics/Subcommands.md new file mode 100644 index 0000000..c2b16ac --- /dev/null +++ b/docs/topics/Subcommands.md @@ -0,0 +1,46 @@ +# Subcommands + +Sophisticated command line tools, like `git`, have many subcommands (e.g., `git clone`, `git commit`, `git push`, etc.), +each with its own set of arguments. There are few ways how to use subcommands with `argparse`. + +## `Subcommand` type + +General approach to declare subcommands is to use `SubCommand` type. This type is behaving like a `SumType` from standard library +with few additions: +- It allows no command to be chosen. +- It supports at most one default subcommand (see [below](#default-subcommand) for details). + +> See [SubCommand](SubCommand.md) section in the Reference for more detailed description of `SubCommand` type. +> +{style="note"} + + + +## Subcommands with shared common arguments + +In some cases command line tool has arguments that are common across all subcommands. They can be specified as regular +arguments in a struct that represents the whole program: + + + +## Subcommand name and aliases + +Using type name as a subcommand name in command line might not be convenient, moreover, the same subcommand might have +multiple names in command line (e.g. short and long versions). `Command` UDA can be used to list all acceptable names for +a subcommand: + + + +## Default subcommand + +Default subcommand is one that is selected when user does not specify any subcommand in the command line. +To mark a subcommand as default, use `Default` template: + + + +## Enumerating subcommands in CLI mixin + +One of the possible ways to use subcommands with `argparse` is to list all subcommands in `CLI` mixin. Although this might +be a useful feature, it is very limited: `CLI` mixin only allows overriding of the `main` function for this case: + + diff --git a/docs/topics/Supported-types.md b/docs/topics/Supported-types.md new file mode 100644 index 0000000..9f4fc4c --- /dev/null +++ b/docs/topics/Supported-types.md @@ -0,0 +1,115 @@ +# Supported types + +When command line entries are mapped to the annotated data members, the text value is converted to the type of the +data member. + +## Boolean flags + +Boolean types usually represent command line flags. `argparse` supports multiple ways of providing flag value including +negation (i.e., `--no-flag`): + +| Command line entries | Result | +|--------------------------------|-----------| +| `-b` / `--boo` | `true` | +| `-b=` / `--boo=` | `` | +| `--no-b` / `--no-boo` | `false` | +| `-b ` / `--boo ` | error | + +> `` is accepted only if it's provided with assignment `=` character ([`Config.assignChar`](Config.md#assignChar)), +> not as a separate command line entry. +> +{style="warning"} + +`argparse` supports the following strings as a `` (comparison is case-insensitive): + +| `` | Result | +|------------------|--------| +| `true`,`yes`,`y` | true | +| `false`,`no`,`n` | false | + +## Numbers and strings + +Numeric (according to `std.traits.isNumeric`) and string (according to `std.traits.isSomeString`) data types are +seamlessly converted to destination type using `std.conv.to`: + + + +## Arrays + +`argparse` supports 1D and 2D arrays: +- If an argument is bound to 1D array, a new element is appended to this array each time the argument is provided in +command line. +- In case of 2D array, new elements are grouped in a way as they appear in +command line and then each group is appended to this array. + +The difference can be easily shown in the following example: + + + +Alternatively one can set [`Config.arraySep`](Config.md#arraySep) to allow multiple elements in one command line entry: + + + +## Associative arrays + +`argparse` also supports associative array where simple value type (e.g. numbers, strings etc.). In this case, expected +format of the value is `key=value` (equal sign can be customized with [`Config.assignChar`](Config.md#assignChar)): + + + +Alternatively one can set [`Config.arraySep`](Config.md#arraySep) to allow multiple elements in one command line entry: + + + +## Enums {id="enum"} + +It is encouraged to use `enum` types for arguments that have a limited set of valid values. In this case, `argparse` +validates that the value specified in command line matches one of enum identifiers: + + + +In some cases the value for command line argument might have characters that are not allowed in enum identifiers. +Actual values that are allowed in command line can be adjusted with `ArgumentValue` UDA: + + + +> When `ArgumentValue` UDA is used, enum identifier is ignored so if argument is supposed to accept it, identifier +> must be listed in the UDA as well - see `"noapple"` in the example above. +> +{style="note"} + +## Counter + +Counter is an argument that tracks the number of times it's specified in the command line: + + + +The same example with enabled [bundling](Arguments-bundling.md): + + + +## Callback + +If member type is a function, `argparse` will try to call it when the corresponding argument is specified in the +command line. + +`argparse` supports the following function signatures (return value is ignored, if any): + +- `... func()` - argument is treated as a boolean flag. + +- `... func(string)` - argument has exactly one value. The value specified in command line is provided into `string` parameter. + +- `... func(string[])` - argument has zero or more values. Values specified in command line are provided into `string[]` parameter. + +- `... func(RawParam)` - argument has zero or more values. Values specified in command line are provided into parameter. + +Example: + + + +## Custom types + +`argparse` can actually work with any arbitrary type - just provide parsing function (see [Parsing customization](Parsing-customization.md#Parse) +for details): + + diff --git a/docs/topics/reference/ArgumentGroup.md b/docs/topics/reference/ArgumentGroup.md new file mode 100644 index 0000000..19f08fc --- /dev/null +++ b/docs/topics/reference/ArgumentGroup.md @@ -0,0 +1,48 @@ +# ArgumentGroup + +`ArgumentGroup` UDA is used to group arguments on help screen. + +## Usage + +**Signature** +```C++ +ArgumentGroup(string name) +``` + +**Usage example** + +```C++ +@ArgumentGroup("my group") +{ +... +} +``` + + +## Public members + +### Description + +`Description` is used to add description text to a group. + +**Signature** + +```C++ +... Description(auto ref ... group, string text) +... Description(auto ref ... group, string function() text) +``` + +**Parameters** + +- `text` + + Text that contains group description or a function that returns such text. + +**Usage example** + +```C++ +@(ArgumentGroup("my group").Description("custom description")) +{ +... +} +``` diff --git a/docs/topics/reference/ArgumentValue.md b/docs/topics/reference/ArgumentValue.md new file mode 100644 index 0000000..38a684e --- /dev/null +++ b/docs/topics/reference/ArgumentValue.md @@ -0,0 +1,26 @@ +# ArgumentValue + +`ArgumentValue` UDA is used to list all values that an argument can accept. This is very useful in the cases when an argument +must have a value from a specific list, for example, when argument type is `enum`. + +**Signature** + +```C++ +ArgumentValue(string[] values...) +``` + +**Parameters** + +- `values` + + Values that argument can have. + +**Usage example** + +```C++ +enum Fruit { + apple, + @ArgumentValue("no-apple","noapple") + noapple +}; +``` diff --git a/docs/topics/reference/CLI-API.md b/docs/topics/reference/CLI-API.md new file mode 100644 index 0000000..cee7c0d --- /dev/null +++ b/docs/topics/reference/CLI-API.md @@ -0,0 +1,191 @@ +# CLI API + +`CLI` is a template that provides entry-point functions to call `argparse`. + +Here are the signatures that `CLI` template has: +```c++ +template CLI(Config config, COMMAND) +template CLI(Config config, COMMANDS...) +``` + +The second template with multiple `COMMANDS...` has only `main` function which wraps all `COMMANDS` inside internal +struct with only data member of type `SubCommand!COMMANDS` and calls `CLI(Config config, CMD).main` with that. + +There is also an `alias` that uses default `Config.init` to simplify default behavior: +```c++ +alias CLI(COMMANDS...) = CLI!(Config.init, COMMANDS); +``` + +## Public members + +### parseKnownArgs + +`CLI.parseKnownArgs` is a function that parses only known arguments from the command line. + +All arguments that were not recognized during parsing are returned to a caller. + +**Signature** + +```c++ +Result parseKnownArgs(ref COMMAND receiver, string[] args, out string[] unrecognizedArgs) +Result parseKnownArgs(ref COMMAND receiver, ref string[] args) +``` + +**Parameters** + +- `receiver` + + Object that receives parsed command line arguments. + +- `args` + + Command line arguments to parse (excluding `argv[0]` – first command line argument in `main` function). + +- `unrecognizedArgs` + + Command line arguments that were not parsed. + +**Notes** + +- The second signature (without `unrecognizedArgs` parameter) returns not parsed arguments through `args` reference parameter. + +**Return value** + +`Result` object that can be cast to `bool` to check whether the parsing was successful or not. +Successful parsing for `parseKnownArgs` function means that there are no error during parsing of known arguments. +This means that having unrecognized arguments in a command line is not an error. + +### parseArgs + +`CLI.parseArgs` is a function that parses command line arguments and validates that there are no unknown ones. + +**Signature** + +```c++ +Result parseArgs(ref COMMAND receiver, string[] args) +int parseArgs(alias newMain)(string[] args, COMMAND initialValue = COMMAND.init) +``` + +**Parameters** + +- `receiver` + + Object that receives parsed command line arguments. + +- `args` + + Command line arguments to parse (excluding `argv[0]` – first command line argument in `main` function). + +- `newMain` + + Function that is called after successful command line parsing. See [`newMain`](#newMain) for details. + +- `initialValue` + + Initial value for the object passed to `newMain` function. + + +**Notes** + +- `newMain` will not be called in case of parsing error. + +**Return value** + +- In case of parsing error - `Result.exitCode` (`1` by default). +- In case of success: + - `0` for the `parseArgs` version that doesn't accept `newMain` function. + - `0` if `newMain` doesn't return a value that can be cast to `int`. + - Value returned by `newMain` that is cast to `int`. + +### complete + +`CLI.complete` is a function that performs shell completion for command line arguments. + +**Signature** + +```c++ +int complete()(string[] args) +``` + +**Parameters** + +- `args` + + Command line arguments (excluding `argv[0]` – first command line argument in `main` function). + +**Notes** + +This function provides completion for the last argument in the command line: +- If the last entry in command line is an empty string (`""`) then it provides all available argument names prepended + with [`Config.namedArgPrefix`](Config.md#namedArgPrefix). +- If the last entry in command line contains characters then `complete` provides completion only with those arguments + that have names starting with specified characters. + +**Return value** + +- `0` in case of successful parsing. +- Non-zero otherwise. + +### mainComplete + +`CLI.mainComplete` is a mixin template that provides global `main` function which calls [`CLI.complete`](#complete). + +**Signature** + +```c++ +template mainComplete() +``` + +**Notes** + +Ingested `main` function is a simple wrapper of [`CLI.complete`](#complete) function that removes `argv[0]` from command line. + +**Return value** + +Value returned from [`CLI.complete`](#complete) function. + +### main + +`CLI.main` is a mixin template that does one of these: + +- If `argparse_completion` version is defined then it instantiates `CLI.mainComplete` template mixin. +- Otherwise it provides global `main` function that calls [`CLI.parseArgs`](#parseargs) function. + +**Signature** + +```c++ +template main(alias newMain) +``` + +**Parameters** + +- `newMain` + + Function that is called after successful command line parsing. See [`newMain`](#newMain) for details. + +**Notes** + +- `newMain` parameter is not used in case if `argparse_completion` version is defined. + +**Return value** + +See [`CLI.mainComplete`](#maincomplete) and [`CLI.parseArgs`](#parseargs). + +## newMain parameter {id="newMain"} + +`newMain` parameter in `CLI` API is a substitution for classic `main` function with the following differences: +- Its first parameter has type of a command struct that is passed to `CLI` API. This parameter is filled with the data + parsed from actual command line. + + `... newMain(COMMAND command)` + +- It might have optional second parameter of type `string[]` that receives unknown command line arguments. + + `... newMain(COMMAND command, string[] unrecognizedArgs)` + +- `newMain` can optionally return anything that can be cast to `int`. In this case, `argparse` will return that value from `CLI` API + or from injected `main` function in case of `CLI.main`. + +> If `newMain` has only one parameter, `argparse` will error out when command line contains unrecognized arguments. +> +{style="warning"} \ No newline at end of file diff --git a/docs/topics/reference/Command.md b/docs/topics/reference/Command.md new file mode 100644 index 0000000..55e77ee --- /dev/null +++ b/docs/topics/reference/Command.md @@ -0,0 +1,139 @@ +# Command + +`Command` UDA is used to customize **top-level command** as well as **subcommands**. + +This UDA can be chained with functions listed below to adjust different settings. + +**Signature** + +```c++ +Command(string[] names...) +``` + +**Parameters** + +- `names` + + For **subcommands**, these are the names of the subcommand that can appear in the command line. If no name is provided then + the name of the type that represents the command is used. + + 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. + If no name is provided then `Runtime.args[0]` (a.k.a. `argv[0]` from `main` function) is used. + + +## Public members + +### Usage + +`Usage` allows customize the usage text. By default, the parser calculates the usage message from the arguments it contains +but this can be overridden with `Usage` call. If the custom text contains `%(PROG)` then it will be replaced by the +`argv[0]` (from `main` function) in case of top-level command or by a list of commands (all parent commands and current one) +in case of subcommand. + + +**Signature** + +```C++ +Usage(auto ref ... command, string text) +Usage(auto ref ... command, string function() text) +``` + +**Parameters** + +- `text` + + Usage text or a function that returns such text. + +**Usage example** + +```C++ +@(Command.Usage("%(PROG) [...]")) +struct my_command +{ +... +} +``` + +### Description + +`Description` can be used to provide a description of what the command/program does and how it works. In help messages, the + description is displayed between the usage string and the list of the command arguments. + +**Signature** + +```C++ +Description(auto ref ... command, string text) +Description(auto ref ... command, string function() text) +``` + +**Parameters** + +- `text` + + Text that contains command description or a function that returns such text. + +**Usage example** + +```C++ +@(Command.Description("custom description")) +struct my_command +{ +... +} +``` + +### ShortDescription + +`ShortDescription` can be used to provide a brief description of what the subcommand does. It is applicable to subcommands + only and is displayed in *Available commands* section on help screen of the parent command. + +**Signature** + +```C++ +ShortDescription(auto ref ... command, string text) +ShortDescription(auto ref ... command, string function() text) +``` + +**Parameters** + +- `text` + + Text that contains short description for a subcommand or a function that returns such text. + +**Usage example** + +```C++ +@(Command.ShortDescription("custom description")) +struct my_command +{ +... +} +``` + + +### Epilog + +`Epilog` can be used to provide some custom text that is printed at the end after the list of command arguments. + +**Signature** + +```C++ +Epilog(auto ref ... command, string text) +Epilog(auto ref ... command, string function() text) +``` + +**Parameters** + +- `text` + + Epilog text or a function that returns such text. + +**Usage example** + +```C++ +@(Command.Epilog("extra info about the command")) +struct my_command +{ +... +} +``` diff --git a/docs/topics/reference/Config.md b/docs/topics/reference/Config.md new file mode 100644 index 0000000..80ce66d --- /dev/null +++ b/docs/topics/reference/Config.md @@ -0,0 +1,148 @@ +# Config + +`argparse` provides decent amount of settings to customize the parser. All customizations can be done by creating +`Config` object with required settings (see below) and passing it to [CLI API](CLI-API.md). + +## Assign character {id="assignChar"} + +`Config.assignChar` is an assignment character used in arguments with value: `-a=5`, `-b=foo`. + +Default is equal sign `=`. + +Example: + + + +## Array separator {id="arraySep"} + +When `Config.arraySep` is set to `char.init`, values to array and associative-array receivers are treated as an individual +value. That is, only one argument is appended/inserted per appearance of the argument. If `arraySep` is set to something +else, then each value is first split by the separator, and the individual pieces are treated as values to the same +argument. + +Default is `char.init`. + +Example: + + + +## Named argument prefix {id="namedArgPrefix"} + +`Config.namedArgPrefix` is a character that named arguments begin with. + +Default is dash (`-`). + +Example: + + + +## End of named arguments {id="endOfNamedArgs"} + +`Config.endOfNamedArgs` is a string that marks the end of all named arguments. All arguments that are specified +after this one are treated as positional regardless to the value which can start with `namedArgPrefix` (dash `-` by default) +or be a subcommand. + +Default is double dash (`--`). + +Example: + + + +## Case sensitivity {id="caseSensitive"} + +`Config.caseSensitive` controls whether the argument names are case-sensitive. By default they are and it can be changed +by setting this member to `false`. + +Default is `true`. + +Example: + + + +## Bundling of single-character arguments {id="bundling"} + +`Config.bundling` controls whether single-character arguments (usually boolean flags) can be bundled together. +If it is set to `true` then `-abc` is the same as `-a -b -c`. + +Default is `false`. + +Example: + + + +## Adding help generation {id="addHelp"} + +`Config.addHelp` can be used to add (if `true`) or not (if `false`) `-h`/`--help` argument. +In case if the command line has `-h` or `--help`, then the corresponding help text is printed and the parsing is stopped. +If `CLI!(...).parseArgs(alias newMain)` or `CLI!(...).main(alias newMain)` is used, then provided `newMain` function will +not be called. + +Default is `true`. + +Example: + + + +Help text from the first part of the example code above: + +Config help example + + +## Styling mode {id="stylingMode"} + +`Config.stylingMode` controls whether styling for help text and errors should be enabled. +It has the following type: `enum StylingMode { autodetect, on, off }`: +- `Config.StylingMode.on`: styling is **always enabled**. +- `Config.StylingMode.off`: styling is **always disabled**. +- `Config.StylingMode.autodetect`: styling will be enabled when possible. + +See [ANSI coloring and styling](ANSI-coloring-and-styling.md) for details. + +Default value is `Config.StylingMode.autodetect`. + +Example: + + + +Help text from the first part of the example code above: + +Config stylingMode example + +## Styling scheme {id="styling"} + +`Config.styling` contains style for the text output (error messages and help text). It has the following members: + +- `programName`: style for the program name. Default is `bold`. +- `subcommandName`: style for the subcommand name. Default is `bold`. +- `argumentGroupTitle`: style for the title of argument group. Default is `bold.underline`. +- `argumentName`: style for the argument name. Default is `lightYellow`. +- `namedArgumentValue`: style for the value of named argument. Default is `italic`. +- `positionalArgumentValue`: style for the value of positional argument. Default is `lightYellow`. +- `errorMessagePrefix`: style for *Error:* prefix in error messages. Default is `red`. + +See [ANSI coloring and styling](ANSI-coloring-and-styling.md) for details. + +Example: + + + +Help text from the first part of the example code above: + +Config styling example + +## Error handling {id="errorHandler"} + +`Config.errorHandler` is a handler function for all errors occurred during command line parsing. +It is a function that receives `string` parameter which would contain an error message. + +> Function must ne `nothrow` +> +{style="warning"} + +The default behavior is to print error message to `stderr`. + +Example: + + + +This code prints `Detected an error: Unrecognized arguments: ["-b"]` to `stderr`. diff --git a/docs/topics/reference/MutuallyExclusive.md b/docs/topics/reference/MutuallyExclusive.md new file mode 100644 index 0000000..bc25b4b --- /dev/null +++ b/docs/topics/reference/MutuallyExclusive.md @@ -0,0 +1,18 @@ +# MutuallyExclusive + +`MutuallyExclusive` UDA is used to mark a set of arguments as mutually exclusive. This means that as soon as one argument from +this group is specified in the command line then no other arguments from the same group can be specified. + +See ["Argument dependencies"](Argument-dependencies.md#MutuallyExclusive) section for more details. + +## Required + +"Mutually exclusive" group can be marked as required in order to require exactly one argument from the group: + +```C++ +@(MutuallyExclusive.Required) +{ + int a; + int b; +} +``` diff --git a/docs/topics/reference/Param-RawParam.md b/docs/topics/reference/Param-RawParam.md new file mode 100644 index 0000000..051a02b --- /dev/null +++ b/docs/topics/reference/Param-RawParam.md @@ -0,0 +1,18 @@ +# Param / RawParam + +[Parsing customization API](Parsing-customization.md) works with Param/RawParam struct under the hood which is publicly available for usage. + +`Param` is a template struct parametrized by `VALUE_TYPE` (see below) which is usually a `string[]` or a type of destination data member. + +> `RawParam` is an alias where `VALUE_TYPE` is `string[]`. This alias represents "raw" values from command line. +> +{style="note"} + +`Param` struct has the following fields: + +- `const(Config)*` `config`- The content is almost the same as the [`Config`](Config.md) object that was passed into [CLI API](CLI-API.md). + The only difference is in [`Config.stylingMode`](Config.md#stylingMode) - it is either `Config.StylingMode.on` or `Config.StylingMode.off` + based on [auto-detection](ANSI-coloring-and-styling.md#heuristic) results. +- `string` `name` – For named argument, it contains a name that is specified in command line exactly including prefix(es) + ([`Config.namedArgPrefix`](Config.md#namedArgPrefix)). For positional arguments, it contains placeholder value. +- `VALUE_TYPE` `value` – Argument values that are provided in command line. diff --git a/docs/topics/reference/PositionalNamedArgument.md b/docs/topics/reference/PositionalNamedArgument.md new file mode 100644 index 0000000..ec5cc60 --- /dev/null +++ b/docs/topics/reference/PositionalNamedArgument.md @@ -0,0 +1,447 @@ +# PositionalArgument/NamedArgument + +`PositionalArgument` UDA is used to declare an argument that has specific position in the command line. + + +**Signature** + +```c++ +PositionalArgument(uint position) +PositionalArgument(uint position, string placeholder) +NamedArgument(string[] names...) +``` + +**Parameters** + +- `position` + + Zero-based unsigned position of the argument. + +- `placeholder` + + Name of this argument that is shown in help text. + By default, the name of data member is used. + +- `names` + + Name(s) of this argument that can be used in command line. + By default, the name of data member is used. + +## Public members + +### Description + +`Description` can be used to provide a description of the argument. This text is printed next to the argument +in the argument list section of a help message. + +**Signature** + +```C++ +Description(auto ref ... argument, string text) +Description(auto ref ... argument, string function() text) +``` + +**Parameters** + +- `text` + + Text that contains argument description or a function that returns such text. + +**Usage example** + +```C++ +struct my_command +{ + @(NamedArgument.Description("custom description")) + int a; +} +``` + +### HideFromHelp + +`HideFromHelp` can be used to indicate that the argument should not be printed in help message. + +**Signature** + +```C++ +HideFromHelp(auto ref ... argument, bool hide = true) +``` + +**Parameters** + +- `hide` + + If `true` then argument is not printed in help message. + +**Usage example** + +```C++ +struct my_command +{ + @(NamedArgument.HideFromHelp) + int a; +} +``` + +### Placeholder + +`Placeholder` provides custom text that is used to indicate the value of the argument in help message. + +**Signature** + +```C++ +Placeholder(auto ref ... argument, string value) +``` + +**Parameters** + +- `value` + + Text that is used as a placeholder for a value of an argument in help message. + +**Usage example** + +```C++ +struct my_command +{ + @(NamedArgument.Placeholder("VALUE")) + int a; +} +``` + +### Required + +Mark an argument as required so if it is not provided in command line, `argparse` will error out. + +By default all positional arguments are required. + +**Signature** + +```C++ +Required(auto ref ... argument) +``` + +**Usage example** + +```C++ +struct my_command +{ + @(NamedArgument.Required) + int a; +} +``` + +### Optional + +Mark an argument as optional so it can be omitted in command line without causing errors. + +By default all named arguments are optional. + +**Signature** + +```C++ +Optional(auto ref ... argument) +``` + +**Usage example** + +```C++ +struct my_command +{ + @(NamedArgument.Optional) + int a; +} +``` + +### NumberOfValues + +`NumberOfValues` is used to limit number of values that an argument can accept. + +**Signature** + +```C++ +NumberOfValues(auto ref ... argument, ulong min, ulong max) +NumberOfValues(auto ref ... argument, ulong num) +``` + +**Parameters** + +- `min` + + Minimum number of values. + +- `max` + + Maximum number of values. + +- `num` + + Exact number of values. + +**Usage example** + +```C++ +struct my_command +{ + @(NamedArgument.NumberOfValues(1,3)) + int[] a; + + @(NamedArgument.NumberOfValues(2)) + int[] b; +} +``` + +### MinNumberOfValues + +`MinNumberOfValues` is used to set minimum number of values that an argument can accept. + +**Signature** + +```C++ +MinNumberOfValues(auto ref ... argument, ulong min) +``` + +**Parameters** + +- `min` + + Minimum number of values. + +**Usage example** + +```C++ +struct my_command +{ + @(NamedArgument.MinNumberOfValues(2)) + int[] a; +} +``` + +### MaxNumberOfValues + +`MaxNumberOfValues` is used to set maximum number of values that an argument can accept. + +**Signature** + +```C++ +MaxNumberOfValues(auto ref ... argument, ulong max) +``` + +**Parameters** + +- `max` + + Maximum number of values. + +**Usage example** + +```C++ +struct my_command +{ + @(NamedArgument.MinNumberOfValues(3)) + int[] a; +} +``` + +### AllowNoValue + +`AllowNoValue` allows an argument to not have a value in the command line - in this case, the value provided to this function will be used. + +**Signature** + +```C++ +AllowNoValue(alias valueToUse)(auto ref ... argument) +``` + +**Parameters** + +- `valueToUse` + + Value that is used when argument has no value specified in command line. + +**Usage example** + +```C++ +struct my_command +{ + @(NamedArgument.AllowNoValue!10) + int a; +} +``` + +### RequireNoValue + +`RequireNoValue` requires an argument to have no value in the command line. The argument is behaving like a boolean flag +but instead of `true`/`false` values, there can be either a value provided to this function or a default one (`.init`). + +**Signature** + +```C++ +RequireNoValue(alias valueToUse)(auto ref ... argument) +``` + +**Parameters** + +- `valueToUse` + + Value that is used when argument is specified in command line. + +**Usage example** + +```C++ +struct my_command +{ + @(NamedArgument.RequireNoValue!10) + int a; +} +``` + +### AllowedValues + +`AllowedValues` can be used to restrict what value can be provided in the command line for an argument. + +**Signature** + +```C++ +AllowedValues(alias values)(auto ref ... argument) +``` + +**Parameters** + +- `values` + + List of values that an argument can have. + +**Usage example** + +```C++ +struct my_command +{ + @(NamedArgument.AllowedValues!([1,4,16,8])) + int a; +} +``` + +### Counter + +`Counter` can be used to mark an argument that tracks the number of times it's specified in the command line. + +**Signature** + +```C++ +Counter(auto ref ... argument) +``` + +**Usage example** + +```C++ +struct my_command +{ + @(NamedArgument.Counter) + int a; +} +``` + +### PreValidation + +`PreValidation` can be used to customize the validation of raw string values. + +**Signature** + +```C++ +PreValidation(alias func)(auto ref ... argument) +``` + +**Parameters** + +- `func` + + Function that is called to validate raw value. See [parsing customization](Parsing-customization.md#PreValidation) for details. + +**Usage example** + +```C++ +struct my_command +{ + @(NamedArgument.PreValidation!((string s) { return s.length > 0;})) + int a; +} +``` + +### Parse + +`Parse` can be used to provide custom conversion from raw string to a value. + +**Signature** + +```C++ +Parse(alias func)(auto ref ... argument) +``` + +**Parameters** + +- `func` + + Function that is called to convert raw value. See [parsing customization](Parsing-customization.md#Parse) for details. + +**Usage example** + +```C++ +struct my_command +{ + @(NamedArgument.Parse!((string s) { return s[1]; })) + char a; +} +``` + +### Validation + +`Validation` can be used to validate parsed value. + +**Signature** + +```C++ +Validation(alias func)(auto ref ... argument) +``` + +**Parameters** + +- `func` + + Function that is called to validate the value. See [parsing customization](Parsing-customization.md#Validation) for details. + +**Usage example** + +```C++ +struct my_command +{ + @(NamedArgument.Validation!((int a) { return a >= 0 && a <= 9; })) + int a; +} +``` + +### Action + +`Action` can be used to customize a logic of how "destination" should be changed based on parsed argument value. + +**Signature** + +```C++ +Action(alias func)(auto ref ... argument) +``` + +**Parameters** + +- `func` + + Function that is called to update the destination. See [parsing customization](Parsing-customization.md#Action) for details. + +**Usage example** + +```C++ +struct my_command +{ + @(NamedArgument.Action!((ref int a, int v) { a += v; }) + int a; +} +``` diff --git a/docs/topics/reference/RequiredTogether.md b/docs/topics/reference/RequiredTogether.md new file mode 100644 index 0000000..72e2fc7 --- /dev/null +++ b/docs/topics/reference/RequiredTogether.md @@ -0,0 +1,17 @@ +# RequiredTogether + +`RequiredTogether` UDA is used to mark a set of arguments as mutually required. This means that as soon as one argument from +this group is specified in the command line then all arguments from the same group must also be specified. + +See ["Argument dependencies"](Argument-dependencies.md#RequiredTogether) section for more details. + +## Required + +"Required together" group can be marked as required in order to require all arguments: + +```C++ +@(RequiredTogether.Required) +{ +... +} +``` diff --git a/docs/topics/reference/Result.md b/docs/topics/reference/Result.md new file mode 100644 index 0000000..66c3c85 --- /dev/null +++ b/docs/topics/reference/Result.md @@ -0,0 +1,80 @@ +# Result struct + +`Result` is a struct that is used to communicate between `argparse` and user functions. +Its main responsibility is to hold the result of an operation: success or failure. + +## Public members + +### Success + +`Result.Success` is an compile-time constant (`enum`) that represents a successful result of an operation. + +**Signature** +```c++ +static enum Success +``` + +### Error + +`Result.Error` is a function that returns a failed result of an operation. + +**Signature** + +```c++ +static auto Error(T...)(string msg, T extraArgs) +static auto Error(T...)(int resultCode, string msg, T extraArgs) +``` + +**Parameters** + +- `resultCode` + + Result/exit code of an operation. If it's not specified then `1` is used. + +- `msg` + + Text of an error message. + +- `extraArgs` + + Additional arguments that are added to the text of an error message. + +**Notes** + +- `msg` and `extraArgs` are converted to a single error message string using `std.conv.text(msg, extraArgs)`. +- Error message supports ANSI styling. See [ANSI coloring and styling](ANSI-coloring-and-styling.md) how to use. +- Error message is passed to [`Config.errorHandler`](Config.md#errorHandler) if it's set or printed to `stderr` otherwise + by [CLI API](CLI-API.md) at the end of parsing. + +**Return value** + +`Result` object that represents the failed result of an operation. + +### exitCode + +`Result.exitCode` is a property that returns the result/exit code. It's supposed to be returned from `main()` function. + +**Signature** + +```c++ +int exitCode() const +``` + +**Return value** + +Result/exit code of an operation. + +### opCast + +`Result.opCast` can be used to determine whether result of an operation is successful. + +**Signature** + +```c++ +bool opCast(T : bool)() const +``` + +**Return value** + +- `true` if operation is successful. +- `false` otherwise. diff --git a/docs/topics/reference/SubCommand.md b/docs/topics/reference/SubCommand.md new file mode 100644 index 0000000..0621c1b --- /dev/null +++ b/docs/topics/reference/SubCommand.md @@ -0,0 +1,118 @@ +# SubCommand + +`SubCommand` type can be used to enumerate type for subcommands. This is a wrapper of `SumType`: + +```c++ +struct SubCommand(Commands...) +``` + +## Public members + +### Copy constructor + +**Signature** + +```c++ +this(T)(T value) +``` + +**Parameters** + +- `T value` + + Value that is copied to a new object. Its type `T` must be one of the `Commands`. + +### Assignment operator + +**Signature** + +```c++ +ref SubCommand opAssign(T)(T value) +``` + +**Parameters** + +- `T value` + + Value to be assigned. Its type `T` must be one of the `Commands`. + +### isSetTo + +Checks whether the object is set to a specific command type; + +**Signature** + +```c++ +bool isSetTo(T)() const +``` + +**Parameters** + +- `T` + + Type `T` must be one of the `Commands`. + +**Return value** + +`true` if object contains `T` type, `false` otherwise. + +### isSet + +Checks whether the object is set to any command type; + +**Signature** + +```c++ +bool isSet() const +``` + +**Return value** + +If one of the `Commands` is a default command then this function always returns `true`. + +In case if there is no default command, then: +- `true` if object contains any type from `Commands`, `false` otherwise. + +## Default + +`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 +in command line explicitly. + +This struct has no members and is erased by `SubCommand` before passing to internal `SumType` member. + +**Signature** + +```c++ +struct Default(COMMAND) +``` + +## match + +`match` is a function template that is similar to the one from standard library but adapted to work with `SubCommand`. + +**Signature** + +```c++ +template match(handlers...) +{ + auto ref match(Sub : const SubCommand!Args, Args...)(auto ref Sub sc) + ... +} +``` + +**Parameters** + +- `handlers` + + Functions that have the same meaning as for `match` function in standard library with an exception that they must not use `Default` + type because the latter is erased by `SubCommand` (i.e. just use `T` instead of `Default!T` here). + +- `sc` + + `SubCommand` parameter that the matching is applied to. + +**Return value** + +- If `SubCommand` is set to any subcommand (or has default one) then function returns the result from `match` function from standard library. +- Otherwise, `init` value of the type that would be returned from `match` function from standard library if that type is not `void`. +- Otherwise, this function has no return value (i.e. it's `void`). diff --git a/docs/topics/reference/ansiStylingArgument.md b/docs/topics/reference/ansiStylingArgument.md new file mode 100644 index 0000000..084bf71 --- /dev/null +++ b/docs/topics/reference/ansiStylingArgument.md @@ -0,0 +1,31 @@ +# ansiStylingArgument + +Almost every command line tool that supports ANSI coloring and styling provides command line argument to control whether +this coloring/styling should be forcefully enabled or disabled. + +`argparse` provides `ansiStylingArgument` function that returns an object which allows checking the status of styling/coloring. +This function adds a command line argument that can have one of these values: +- `always` or no value - coloring/styling should be enabled. +- `never` - coloring/styling should be disabled. +- `auto` - in this case, `argparse` will try to detect whether ANSI coloring/styling is supported by a system. + +See [ANSI coloring and styling](ANSI-coloring-and-styling.md) for details. + +**Signature** + +```C++ +... ansiStylingArgument() +``` + +**Usage example** + +```C++ +static auto color = ansiStylingArgument; +``` + +> Explicit `static` is not required because returned object has only `static` data members. + +**Return value** + +Returned object that can be cast to boolean. Its value is `true` when the ANSI coloring/styling should be enabled in the output, +otherwise it's `false`. diff --git a/docs/v.list b/docs/v.list new file mode 100644 index 0000000..2d12cb3 --- /dev/null +++ b/docs/v.list @@ -0,0 +1,5 @@ + + + + + diff --git a/docs/writerside.cfg b/docs/writerside.cfg new file mode 100644 index 0000000..a3c205b --- /dev/null +++ b/docs/writerside.cfg @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/examples/dub.json b/examples/dub.json index 323c765..001870b 100644 --- a/examples/dub.json +++ b/examples/dub.json @@ -7,8 +7,8 @@ "all:completion-separate_main-completer":"*", "all:completion-single_main-app":"*", "all:completion-single_main-completer":"*", - "all:getting_started-advanced":"*", - "all:getting_started-basic":"*", + "all:getting_started-with_uda":"*", + "all:getting_started-without_uda":"*", "all:sub_commands-advanced":"*", "all:sub_commands-basic":"*", "all:sub_commands-common_args":"*", @@ -20,8 +20,8 @@ "completion/separate_main/completer", "completion/single_main/app", "completion/single_main/completer", - "getting_started/advanced", - "getting_started/basic", + "getting_started/with_uda", + "getting_started/without_uda", "sub_commands/advanced", "sub_commands/basic", "sub_commands/common_args", diff --git a/examples/getting_started/advanced/app.d b/examples/getting_started/advanced/app.d deleted file mode 100644 index 316dbd6..0000000 --- a/examples/getting_started/advanced/app.d +++ /dev/null @@ -1,75 +0,0 @@ -import argparse; -import argparse.ansi; - -struct Advanced -{ - // Positional arguments are required by default - @PositionalArgument(0) - string name; - - // Named arguments can be attributed in bulk (parentheses can be omitted) - @NamedArgument - { - string unused = "some default value"; - int number; - bool boolean; - } - - // Named argument can have custom or multiple names - @NamedArgument("apple","appl") - int apple; - - @NamedArgument(["b","banana","ban"]) - int banana; - - // Enums can have a value that is not an identifier - enum Enum { - @ArgumentValue("value1","value-1","value.1") - value1, - value2, - } - @NamedArgument - Enum choice; - - // Custom types can also be used with custom parsing function - struct CustomType { - double d; - } - @(NamedArgument.Parse!((string value) { import std.conv: to; return CustomType(value.to!double); })) - CustomType custom; - - @(NamedArgument.Description(green.bold.underline("Colorize")~" the output. If value is omitted then '"~red("always")~"' is used.")) - static auto color = ansiStylingArgument; -} - -// Customize parsing config -auto config() -{ - Config cfg; - - cfg.styling.programName = blue.onYellow; - cfg.styling.argumentName = bold.italic.cyan.onRed; - - return cfg; -} - -// This mixin defines standard main function that parses command line and calls the provided function: -mixin CLI!(config(), Advanced).main!((args, unparsed) -{ - // 'args' has 'Advanced' type - static assert(is(typeof(args) == Advanced)); - - // unparsed arguments has 'string[]' type - static assert(is(typeof(unparsed) == string[])); - - // do whatever you need - import std.stdio: writeln; - args.writeln; - writeln("Unparsed args: ", unparsed); - - // use actual styling mode to print output - auto style = Advanced.color ? red.onWhite : noStyle; - writeln(style("Styling mode: "), Advanced.color ? "on" : "off"); - - return 0; -}); \ No newline at end of file diff --git a/examples/getting_started/with_uda/app.d b/examples/getting_started/with_uda/app.d new file mode 100644 index 0000000..40cd050 --- /dev/null +++ b/examples/getting_started/with_uda/app.d @@ -0,0 +1,48 @@ +import argparse; + +struct Example +{ + // Positional arguments are required by default + @PositionalArgument(0) + string name; + + // Named arguments can be attributed in bulk (parentheses can be omitted) + @NamedArgument + { + // '--number' argument + int number; + + // '--boolean' argument + bool boolean; + + // Argument can have default value if it's not specified in command line + // '--unused' argument + string unused = "some default value"; + } + + // Enums are also supported + enum Enum { unset, foo, boo } + + // '--choice' argument + @NamedArgument + Enum choice; + + // Named argument can have specific or multiple names + @NamedArgument("apple","appl") + int apple; + + @NamedArgument("b","banana","ban") + int banana; +} + +// This mixin defines standard main function that parses command line and calls the provided function: +mixin CLI!Example.main!((args) +{ + // 'args' has 'Example' type + static assert(is(typeof(args) == Example)); + + // do whatever you need + import std.stdio: writeln; + args.writeln; + return 0; +}); \ No newline at end of file diff --git a/examples/getting_started/advanced/dub.json b/examples/getting_started/with_uda/dub.json similarity index 75% rename from examples/getting_started/advanced/dub.json rename to examples/getting_started/with_uda/dub.json index 34a8805..cc05706 100644 --- a/examples/getting_started/advanced/dub.json +++ b/examples/getting_started/with_uda/dub.json @@ -1,6 +1,6 @@ { "license": "BSL-1.0", - "name": "getting_started-advanced", + "name": "getting_started-with_uda", "targetType":"executable", "sourcePaths":["."], "dependencies":{ "all:argparse":"*" } diff --git a/examples/getting_started/basic/app.d b/examples/getting_started/without_uda/app.d similarity index 88% rename from examples/getting_started/basic/app.d rename to examples/getting_started/without_uda/app.d index 4131aab..d06c152 100644 --- a/examples/getting_started/basic/app.d +++ b/examples/getting_started/without_uda/app.d @@ -1,7 +1,7 @@ import argparse; // If struct has no UDA then all members are named arguments -struct Basic +struct Example { // Basic data types are supported: // '--name' argument @@ -18,7 +18,7 @@ struct Basic string unused = "some default value"; - // Enums are also supported + // Enums are supported enum Enum { unset, foo, boo } // '--choice' argument Enum choice; @@ -41,10 +41,10 @@ struct Basic } // This mixin defines standard main function that parses command line and calls the provided function: -mixin CLI!Basic.main!((args) +mixin CLI!Example.main!((args) { - // 'args' has 'Baisc' type - static assert(is(typeof(args) == Basic)); + // 'args' has 'Example' type + static assert(is(typeof(args) == Example)); // do whatever you need import std.stdio: writeln; diff --git a/examples/getting_started/basic/dub.json b/examples/getting_started/without_uda/dub.json similarity index 74% rename from examples/getting_started/basic/dub.json rename to examples/getting_started/without_uda/dub.json index 4e4299b..07a0e3c 100644 --- a/examples/getting_started/basic/dub.json +++ b/examples/getting_started/without_uda/dub.json @@ -1,6 +1,6 @@ { "license": "BSL-1.0", - "name": "getting_started-basic", + "name": "getting_started-without_uda", "targetType":"executable", "sourcePaths":["."], "dependencies":{ "all:argparse":"*" } diff --git a/images/default_styling.png b/images/default_styling.png deleted file mode 100644 index a53edbe..0000000 Binary files a/images/default_styling.png and /dev/null differ