Coder Social home page Coder Social logo

andrey-zherikov / argparse Goto Github PK

View Code? Open in Web Editor NEW
30.0 2.0 6.0 752 KB

Parser for command-line arguments

Home Page: https://andrey-zherikov.github.io/argparse/

License: Boost Software License 1.0

D 99.84% Shell 0.16%
d-lang command-line dlang2 dlanguage argument-parsing argument-parser d cli

argparse's People

Contributors

andrey-zherikov avatar belka-ew avatar gizmomogwai avatar ik4tsu avatar rjkilpatrick avatar sirnickolas avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

argparse's Issues

Subcommands do not mix well with NamedArguments

import std;
import argparse;

@Command("c1")
struct command1
{
    bool c1data;
}
@Command("c2")
struct command2
{
    @NamedArgument
    bool c2data;
}
struct Program {
    @NamedArgument("numbers")
    bool numbers;
    SumType!(command1, command2) cmd;
}

void _main(Program args)
{
    writeln(args);
}
mixin CLI!(Program).main!((arguments) {
    _main(arguments);
});

This program does not show the subcommand when called with --help:

Usage: argparse-test [--numbers] [-h]

Optional arguments:
  --numbers
  -h, --help    Show this help message and exit

If I remove the @NamedArgument annotation, then its printing the expected output:

Usage: argparse-test [--numbers] [-h] <command> [<args>]

Available commands:
  c1
  c2

Optional arguments:
  --numbers
  -h, --help    Show this help message and exit

In some of my projects I get a cryptic compile error

Happens only with some versions of the compiler e.g dmd-2.100.0.
``
../../external/argparse/source/argparse/package.d(425,6): Error: typesafe variadic function parametername` of type `string[]` cannot be marked `return`


Positional arguments do not ignore executable itself on Windows

It seems, PositionalArgument(0) actually gets the name of the executable itself, at least on Windows. I have struct defined this way:

@(Command.Description("Simple tool that generates a D source module embedding specified resources into it"))
struct Options {
    @(
        PositionalArgument(0)
            .Description("Resource manifest to process")
    )
    string manifest;

    @(
        NamedArgument("d", "developer")
            .Description("Read resources from filesystem instead of actually embedding them")
    )
    bool developerMode = false;
}

Argument parsing is called this way:

void main(string[] args) {
    CLI!Options.parseArgs!typedMain(args);
}

And for the test I just output the parsed manifest option:

void typedMain(Options args) {
    info("Provided manifest file: " ~ args.manifest);
}

Seems to follow examples from docs, if I'm not missing something obvious... Well, results:

> embed-resources
Provided manifest file: embed-resources

> embed-resources manifest.json
Error: Unrecognized arguments: ["manifest.json"]

`errorHandler` is unusable

import argparse;

struct Args {
    bool banana;
}

mixin CLI!({
    Config cfg;
    cfg.errorHandler = (string) { };
    return cfg;
}(), Args).main!((in _) { });
argparse/config.d(90,9): Error: closures are not yet supported in CTFE

N.B. (string) { } is not a closure; return errorHandlerFunc = (string msg) { func(msg); }; (inside the library) is.

Suggestions on help formatting

I’m almost satisfied with the way argparse formats the help message. Just a couple of suggestions:

  1. -h/--help argument should be customizable. For example, one might want to reword its description or omit --help from help (what’s the point of having it there, in the first place?). Disabling it altogether and processing it manually is not an option: -h is special-cased in the parser (no errors are issued if -h is present, and it interacts with subcommands in a complex way). I suppose the approach of ansiStylingArgument can be used here.

  2. This code currently produces the following output:

    import argparse;
    
    struct Args {
        @(NamedArgument("b", "banana").Description("Description of the banana"))
        int banana;
    }
    
    mixin CLI!Args.main!((in _) { });
    Usage: myprog [-b BANANA] [-h]
    
    Optional arguments:
      -b BANANA, --banana BANANA
                    Description of the banana
      -h, --help    Show this help message and exit
    

    I’d like it to look as follows:

    Usage: myprog [-b BANANA] [-h]
    
    Optional arguments:
      -b, --banana BANANA
                    Description of the banana
      -h, --help    Show this help message and exit
    

    I.e., output a placeholder only once per set of alternatives (one BANANA at the end). Saying that several times a line just clutters the output, in my opinion. A field in the config will fit, I think.

  3. You may notice in the example above that columns are separated by 4 spaces. Is it a bug? (I saw a reference to 2 spaces somewhere in the source and would indeed prefer slightly more compact layout.)

  4. If epilog is empty (default), a blank line is emitted before it nevertheless. Therefore, the output of a program ends with \n\n, which feels wrong.

  5. I think it would be handy if a program, when invoked incorrectly (i.e., if parsing failed), printed its one-line usage after the error message. I’d appreciate if I could just enable that behaviour with a flag in the config; however, it would even be possible to implement it outside the library if we had access to computed usage and if errorHandler wasn’t broken.

Bump from d789306 to 2454a72 broke the build

Compilation flags used --mtriple=x86_64-linux-gnu -w --preview=dip1000 --preview=dtorfields --preview=fieldwise -O --release --boundscheck=off (this is a precheck compilation phase).

Example code for argparse options (the code that produces the error):

struct ViewerOptions
{
    @(MutuallyExclusive())
    {
        @(NamedArgument("local").Description("Choose a local connection").Optional())
        bool local = true;

        @(NamedArgument("remote").Description("Choose a remote connection").Optional())
        string remoteAddress = null;
    }

    @(NamedArgument("serverPath").Description("Path the server binary").Optional())
    string serverPath = null;

    @(NamedArgument("p", "path").Description("Path of the dumping blobs").Optional())
    string localPath = defaultPath;

    @(NamedArgument("namespace").Description("Namespace to interleave").Optional())
    string namespace = null;

    @(NamedArgument("d", "debug").Description("Enable debugging").Optional())
    bool debugging = false;
}
/home/runner/work/wtracer/wtracer/3rdparty/argparse/source/argparse/internal/command.d(278): Error: CTFE failed because of previous errors in `getMemberArgumentUDA`
/opt/hostedtoolcache/dc/ldc2-1.30.0/x64/ldc2-1.30.0-linux-x86_64/bin/../import/std/meta.d(652): Error: template instance `argparse.internal.command.TypeTraits!(Config('=', '\xff', '-', "--", true, false, true, Style(TextStyle("\x1b[1"), TextStyle("\x1b[1"), TextStyle("\x1b[1;4"), TextStyle("\x1b[93"), TextStyle("\x1b[3"), TextStyle("\x1b[93"), TextStyle("\x1b[31")), StylingMode.off, null), ViewerOptions).fun!"serverPath"` error instantiating
/home/runner/work/wtracer/wtracer/3rdparty/argparse/source/argparse/internal/command.d(286):        instantiated from here: `staticMap!(getArgumentUDA, "local", "remoteAddress", "serverPath", "localPath", "namespace", "debugging")`
/home/runner/work/wtracer/wtracer/3rdparty/argparse/source/argparse/internal/command.d(358):        instantiated from here: `TypeTraits!(Config('=', '\xff', '-', "--", true, false, true, Style(TextStyle("\x1b[1"), TextStyle("\x1b[1"), TextStyle("\x1b[1;4"), TextStyle("\x1b[93"), TextStyle("\x1b[3"), TextStyle("\x1b[93"), TextStyle("\x1b[31")), StylingMode.off, null), ViewerOptions)`
/home/runner/work/wtracer/wtracer/3rdparty/argparse/source/argparse/internal/parser.d(603):        instantiated from here: `createCommand!(Config('=', '\xff', '-', "--", true, false, true, Style(TextStyle("\x1b[1"), TextStyle("\x1b[1"), TextStyle("\x1b[1;4"), TextStyle("\x1b[93"), TextStyle("\x1b[3"), TextStyle("\x1b[93"), TextStyle("\x1b[31")), StylingMode.off, null), ViewerOptions)`
/home/runner/work/wtracer/wtracer/3rdparty/argparse/source/argparse/internal/parser.d(628):        ... (2 instantiations, -v to show) ...
/home/runner/work/wtracer/wtracer/3rdparty/argparse/source/argparse/api/cli.d(192):        instantiated from here: `CLI!(Config('=', '\xff', '-', "--", true, false, true, Style(TextStyle("\x1b[1"), TextStyle("\x1b[1"), TextStyle("\x1b[1;4"), TextStyle("\x1b[93"), TextStyle("\x1b[3"), TextStyle("\x1b[93"), TextStyle("\x1b[31")), StylingMode.autodetect, null), ViewerOptions)`
source/wtracer/viewer/main.d(273):        instantiated from here: `CLI!(ViewerOptions)`
/home/runner/work/wtracer/wtracer/3rdparty/argparse/source/argparse/internal/command.d(278): Error: CTFE failed because of previous errors in `getMemberArgumentUDA`
/opt/hostedtoolcache/dc/ldc2-1.30.0/x64/ldc2-1.30.0-linux-x86_64/bin/../import/std/meta.d(652): Error: template instance `argparse.internal.command.TypeTraits!(Config('=', '\xff', '-', "--", true, false, true, Style(TextStyle("\x1b[1"), TextStyle("\x1b[1"), TextStyle("\x1b[1;4"), TextStyle("\x1b[93"), TextStyle("\x1b[3"), TextStyle("\x1b[93"), TextStyle("\x1b[31")), StylingMode.off, null), ViewerOptions).fun!"localPath"` error instantiating
More complete log:
/home/runner/work/wtracer/wtracer/3rdparty/argparse/source/argparse/internal/argumentudahelpers.d(77): Error: first argument is not a symbol
/home/runner/work/wtracer/wtracer/3rdparty/argparse/source/argparse/internal/argumentudahelpers.d(80):        while evaluating: `static assert(typeUDAs.length <= 1)`
/opt/hostedtoolcache/dc/ldc2-1.30.0/x64/ldc2-1.30.0-linux-x86_64/bin/../import/std/meta.d(652): Error: template instance `argparse.internal.command.TypeTraits!(Config('=', '\xff', '-', "--", true, false, true, Style(TextStyle("\x1b[1"), TextStyle("\x1b[1"), TextStyle("\x1b[1;4"), TextStyle("\x1b[93"), TextStyle("\x1b[3"), TextStyle("\x1b[93"), TextStyle("\x1b[31")), StylingMode.on, null), ViewerOptions).fun!"local"` error instantiating
/home/runner/work/wtracer/wtracer/3rdparty/argparse/source/argparse/internal/command.d(286):        instantiated from here: `staticMap!(getArgumentUDA, "local", "remoteAddress", "serverPath", "localPath", "namespace", "debugging")`
/home/runner/work/wtracer/wtracer/3rdparty/argparse/source/argparse/internal/command.d(358):        instantiated from here: `TypeTraits!(Config('=', '\xff', '-', "--", true, false, true, Style(TextStyle("\x1b[1"), TextStyle("\x1b[1"), TextStyle("\x1b[1;4"), TextStyle("\x1b[93"), TextStyle("\x1b[3"), TextStyle("\x1b[93"), TextStyle("\x1b[31")), StylingMode.on, null), ViewerOptions)`
/home/runner/work/wtracer/wtracer/3rdparty/argparse/source/argparse/internal/parser.d(603):        instantiated from here: `createCommand!(Config('=', '\xff', '-', "--", true, false, true, Style(TextStyle("\x1b[1"), TextStyle("\x1b[1"), TextStyle("\x1b[1;4"), TextStyle("\x1b[93"), TextStyle("\x1b[3"), TextStyle("\x1b[93"), TextStyle("\x1b[31")), StylingMode.on, null), ViewerOptions)`
/home/runner/work/wtracer/wtracer/3rdparty/argparse/source/argparse/internal/parser.d(626):        ... (2 instantiations, -v to show) ...
/home/runner/work/wtracer/wtracer/3rdparty/argparse/source/argparse/api/cli.d(192):        instantiated from here: `CLI!(Config('=', '\xff', '-', "--", true, false, true, Style(TextStyle("\x1b[1"), TextStyle("\x1b[1"), TextStyle("\x1b[1;4"), TextStyle("\x1b[93"), TextStyle("\x1b[3"), TextStyle("\x1b[93"), TextStyle("\x1b[31")), StylingMode.autodetect, null), ViewerOptions)`
source/wtracer/viewer/main.d(273):        instantiated from here: `CLI!(ViewerOptions)`
/home/runner/work/wtracer/wtracer/3rdparty/argparse/source/argparse/internal/argumentudahelpers.d(77): Error: first argument is not a symbol
/home/runner/work/wtracer/wtracer/3rdparty/argparse/source/argparse/internal/argumentudahelpers.d(80):        while evaluating: `static assert(typeUDAs.length <= 1)`
/opt/hostedtoolcache/dc/ldc2-1.30.0/x64/ldc2-1.30.0-linux-x86_64/bin/../import/std/meta.d(652): Error: template instance `argparse.internal.command.TypeTraits!(Config('=', '\xff', '-', "--", true, false, true, Style(TextStyle("\x1b[1"), TextStyle("\x1b[1"), TextStyle("\x1b[1;4"), TextStyle("\x1b[93"), TextStyle("\x1b[3"), TextStyle("\x1b[93"), TextStyle("\x1b[31")), StylingMode.on, null), ViewerOptions).fun!"remoteAddress"` error instantiating
/home/runner/work/wtracer/wtracer/3rdparty/argparse/source/argparse/internal/command.d(286):        instantiated from here: `staticMap!(getArgumentUDA, "local", "remoteAddress", "serverPath", "localPath", "namespace", "debugging")`
/home/runner/work/wtracer/wtracer/3rdparty/argparse/source/argparse/internal/command.d(358):        instantiated from here: `TypeTraits!(Config('=', '\xff', '-', "--", true, false, true, Style(TextStyle("\x1b[1"), TextStyle("\x1b[1"), TextStyle("\x1b[1;4"), TextStyle("\x1b[93"), TextStyle("\x1b[3"), TextStyle("\x1b[93"), TextStyle("\x1b[31")), StylingMode.on, null), ViewerOptions)`
/home/runner/work/wtracer/wtracer/3rdparty/argparse/source/argparse/internal/parser.d(603):        instantiated from here: `createCommand!(Config('=', '\xff', '-', "--", true, false, true, Style(TextStyle("\x1b[1"), TextStyle("\x1b[1"), TextStyle("\x1b[1;4"), TextStyle("\x1b[93"), TextStyle("\x1b[3"), TextStyle("\x1b[93"), TextStyle("\x1b[31")), StylingMode.on, null), ViewerOptions)`
/home/runner/work/wtracer/wtracer/3rdparty/argparse/source/argparse/internal/parser.d(626):        ... (2 instantiations, -v to show) ...
/home/runner/work/wtracer/wtracer/3rdparty/argparse/source/argparse/api/cli.d(192):        instantiated from here: `CLI!(Config('=', '\xff', '-', "--", true, false, true, Style(TextStyle("\x1b[1"), TextStyle("\x1b[1"), TextStyle("\x1b[1;4"), TextStyle("\x1b[93"), TextStyle("\x1b[3"), TextStyle("\x1b[93"), TextStyle("\x1b[31")), StylingMode.autodetect, null), ViewerOptions)`
source/wtracer/viewer/main.d(273):        instantiated from here: `CLI!(ViewerOptions)`
/home/runner/work/wtracer/wtracer/3rdparty/argparse/source/argparse/internal/argumentudahelpers.d(77): Error: first argument is not a symbol
/home/runner/work/wtracer/wtracer/3rdparty/argparse/source/argparse/internal/argumentudahelpers.d(80):        while evaluating: `static assert(typeUDAs.length <= 1)`
/opt/hostedtoolcache/dc/ldc2-1.30.0/x64/ldc2-1.30.0-linux-x86_64/bin/../import/std/meta.d(652): Error: template instance `argparse.internal.command.TypeTraits!(Config('=', '\xff', '-', "--", true, false, true, Style(TextStyle("\x1b[1"), TextStyle("\x1b[1"), TextStyle("\x1b[1;4"), TextStyle("\x1b[93"), TextStyle("\x1b[3"), TextStyle("\x1b[93"), TextStyle("\x1b[31")), StylingMode.on, null), ViewerOptions).fun!"serverPath"` error instantiating
/home/runner/work/wtracer/wtracer/3rdparty/argparse/source/argparse/internal/command.d(286):        instantiated from here: `staticMap!(getArgumentUDA, "local", "remoteAddress", "serverPath", "localPath", "namespace", "debugging")`
/home/runner/work/wtracer/wtracer/3rdparty/argparse/source/argparse/internal/command.d(358):        instantiated from here: `TypeTraits!(Config('=', '\xff', '-', "--", true, false, true, Style(TextStyle("\x1b[1"), TextStyle("\x1b[1"), TextStyle("\x1b[1;4"), TextStyle("\x1b[93"), TextStyle("\x1b[3"), TextStyle("\x1b[93"), TextStyle("\x1b[31")), StylingMode.on, null), ViewerOptions)`
/home/runner/work/wtracer/wtracer/3rdparty/argparse/source/argparse/internal/parser.d(603):        instantiated from here: `createCommand!(Config('=', '\xff', '-', "--", true, false, true, Style(TextStyle("\x1b[1"), TextStyle("\x1b[1"), TextStyle("\x1b[1;4"), TextStyle("\x1b[93"), TextStyle("\x1b[3"), TextStyle("\x1b[93"), TextStyle("\x1b[31")), StylingMode.on, null), ViewerOptions)`
/home/runner/work/wtracer/wtracer/3rdparty/argparse/source/argparse/internal/parser.d(626):        ... (2 instantiations, -v to show) ...
/home/runner/work/wtracer/wtracer/3rdparty/argparse/source/argparse/api/cli.d(192):        instantiated from here: `CLI!(Config('=', '\xff', '-', "--", true, false, true, Style(TextStyle("\x1b[1"), TextStyle("\x1b[1"), TextStyle("\x1b[1;4"), TextStyle("\x1b[93"), TextStyle("\x1b[3"), TextStyle("\x1b[93"), TextStyle("\x1b[31")), StylingMode.autodetect, null), ViewerOptions)`
source/wtracer/viewer/main.d(273):        instantiated from here: `CLI!(ViewerOptions)`
/home/runner/work/wtracer/wtracer/3rdparty/argparse/source/argparse/internal/argumentudahelpers.d(77): Error: first argument is not a symbol
/home/runner/work/wtracer/wtracer/3rdparty/argparse/source/argparse/internal/argumentudahelpers.d(80):        while evaluating: `static assert(typeUDAs.length <= 1)`
/opt/hostedtoolcache/dc/ldc2-1.30.0/x64/ldc2-1.30.0-linux-x86_64/bin/../import/std/meta.d(652): Error: template instance `argparse.internal.command.TypeTraits!(Config('=', '\xff', '-', "--", true, false, true, Style(TextStyle("\x1b[1"), TextStyle("\x1b[1"), TextStyle("\x1b[1;4"), TextStyle("\x1b[93"), TextStyle("\x1b[3"), TextStyle("\x1b[93"), TextStyle("\x1b[31")), StylingMode.on, null), ViewerOptions).fun!"localPath"` error instantiating
/home/runner/work/wtracer/wtracer/3rdparty/argparse/source/argparse/internal/command.d(286):        instantiated from here: `staticMap!(getArgumentUDA, "local", "remoteAddress", "serverPath", "localPath", "namespace", "debugging")`
/home/runner/work/wtracer/wtracer/3rdparty/argparse/source/argparse/internal/command.d(358):        instantiated from here: `TypeTraits!(Config('=', '\xff', '-', "--", true, false, true, Style(TextStyle("\x1b[1"), TextStyle("\x1b[1"), TextStyle("\x1b[1;4"), TextStyle("\x1b[93"), TextStyle("\x1b[3"), TextStyle("\x1b[93"), TextStyle("\x1b[31")), StylingMode.on, null), ViewerOptions)`
/home/runner/work/wtracer/wtracer/3rdparty/argparse/source/argparse/internal/parser.d(603):        instantiated from here: `createCommand!(Config('=', '\xff', '-', "--", true, false, true, Style(TextStyle("\x1b[1"), TextStyle("\x1b[1"), TextStyle("\x1b[1;4"), TextStyle("\x1b[93"), TextStyle("\x1b[3"), TextStyle("\x1b[93"), TextStyle("\x1b[31")), StylingMode.on, null), ViewerOptions)`
/home/runner/work/wtracer/wtracer/3rdparty/argparse/source/argparse/internal/parser.d(626):        ... (2 instantiations, -v to show) ...
/home/runner/work/wtracer/wtracer/3rdparty/argparse/source/argparse/api/cli.d(192):        instantiated from here: `CLI!(Config('=', '\xff', '-', "--", true, false, true, Style(TextStyle("\x1b[1"), TextStyle("\x1b[1"), TextStyle("\x1b[1;4"), TextStyle("\x1b[93"), TextStyle("\x1b[3"), TextStyle("\x1b[93"), TextStyle("\x1b[31")), StylingMode.autodetect, null), ViewerOptions)`
source/wtracer/viewer/main.d(273):        instantiated from here: `CLI!(ViewerOptions)`
/home/runner/work/wtracer/wtracer/3rdparty/argparse/source/argparse/internal/argumentudahelpers.d(77): Error: first argument is not a symbol
/home/runner/work/wtracer/wtracer/3rdparty/argparse/source/argparse/internal/argumentudahelpers.d(80):        while evaluating: `static assert(typeUDAs.length <= 1)`
/opt/hostedtoolcache/dc/ldc2-1.30.0/x64/ldc2-1.30.0-linux-x86_64/bin/../import/std/meta.d(652): Error: template instance `argparse.internal.command.TypeTraits!(Config('=', '\xff', '-', "--", true, false, true, Style(TextStyle("\x1b[1"), TextStyle("\x1b[1"), TextStyle("\x1b[1;4"), TextStyle("\x1b[93"), TextStyle("\x1b[3"), TextStyle("\x1b[93"), TextStyle("\x1b[31")), StylingMode.on, null), ViewerOptions).fun!"namespace"` error instantiating
/home/runner/work/wtracer/wtracer/3rdparty/argparse/source/argparse/internal/command.d(286):        instantiated from here: `staticMap!(getArgumentUDA, "local", "remoteAddress", "serverPath", "localPath", "namespace", "debugging")`
/home/runner/work/wtracer/wtracer/3rdparty/argparse/source/argparse/internal/command.d(358):        instantiated from here: `TypeTraits!(Config('=', '\xff', '-', "--", true, false, true, Style(TextStyle("\x1b[1"), TextStyle("\x1b[1"), TextStyle("\x1b[1;4"), TextStyle("\x1b[93"), TextStyle("\x1b[3"), TextStyle("\x1b[93"), TextStyle("\x1b[31")), StylingMode.on, null), ViewerOptions)`
/home/runner/work/wtracer/wtracer/3rdparty/argparse/source/argparse/internal/parser.d(603):        instantiated from here: `createCommand!(Config('=', '\xff', '-', "--", true, false, true, Style(TextStyle("\x1b[1"), TextStyle("\x1b[1"), TextStyle("\x1b[1;4"), TextStyle("\x1b[93"), TextStyle("\x1b[3"), TextStyle("\x1b[93"), TextStyle("\x1b[31")), StylingMode.on, null), ViewerOptions)`
/home/runner/work/wtracer/wtracer/3rdparty/argparse/source/argparse/internal/parser.d(626):        ... (2 instantiations, -v to show) ...
/home/runner/work/wtracer/wtracer/3rdparty/argparse/source/argparse/api/cli.d(192):        instantiated from here: `CLI!(Config('=', '\xff', '-', "--", true, false, true, Style(TextStyle("\x1b[1"), TextStyle("\x1b[1"), TextStyle("\x1b[1;4"), TextStyle("\x1b[93"), TextStyle("\x1b[3"), TextStyle("\x1b[93"), TextStyle("\x1b[31")), StylingMode.autodetect, null), ViewerOptions)`
source/wtracer/viewer/main.d(273):        instantiated from here: `CLI!(ViewerOptions)`
/home/runner/work/wtracer/wtracer/3rdparty/argparse/source/argparse/internal/argumentudahelpers.d(77): Error: first argument is not a symbol
/home/runner/work/wtracer/wtracer/3rdparty/argparse/source/argparse/internal/argumentudahelpers.d(80):        while evaluating: `static assert(typeUDAs.length <= 1)`
/opt/hostedtoolcache/dc/ldc2-1.30.0/x64/ldc2-1.30.0-linux-x86_64/bin/../import/std/meta.d(652): Error: template instance `argparse.internal.command.TypeTraits!(Config('=', '\xff', '-', "--", true, false, true, Style(TextStyle("\x1b[1"), TextStyle("\x1b[1"), TextStyle("\x1b[1;4"), TextStyle("\x1b[93"), TextStyle("\x1b[3"), TextStyle("\x1b[93"), TextStyle("\x1b[31")), StylingMode.on, null), ViewerOptions).fun!"debugging"` error instantiating
/home/runner/work/wtracer/wtracer/3rdparty/argparse/source/argparse/internal/command.d(286):        instantiated from here: `staticMap!(getArgumentUDA, "local", "remoteAddress", "serverPath", "localPath", "namespace", "debugging")`
/home/runner/work/wtracer/wtracer/3rdparty/argparse/source/argparse/internal/command.d(358):        instantiated from here: `TypeTraits!(Config('=', '\xff', '-', "--", true, false, true, Style(TextStyle("\x1b[1"), TextStyle("\x1b[1"), TextStyle("\x1b[1;4"), TextStyle("\x1b[93"), TextStyle("\x1b[3"), TextStyle("\x1b[93"), TextStyle("\x1b[31")), StylingMode.on, null), ViewerOptions)`
/home/runner/work/wtracer/wtracer/3rdparty/argparse/source/argparse/internal/parser.d(603):        instantiated from here: `createCommand!(Config('=', '\xff', '-', "--", true, false, true, Style(TextStyle("\x1b[1"), TextStyle("\x1b[1"), TextStyle("\x1b[1;4"), TextStyle("\x1b[93"), TextStyle("\x1b[3"), TextStyle("\x1b[93"), TextStyle("\x1b[31")), StylingMode.on, null), ViewerOptions)`
/home/runner/work/wtracer/wtracer/3rdparty/argparse/source/argparse/internal/parser.d(626):        ... (2 instantiations, -v to show) ...
/home/runner/work/wtracer/wtracer/3rdparty/argparse/source/argparse/api/cli.d(192):        instantiated from here: `CLI!(Config('=', '\xff', '-', "--", true, false, true, Style(TextStyle("\x1b[1"), TextStyle("\x1b[1"), TextStyle("\x1b[1;4"), TextStyle("\x1b[93"), TextStyle("\x1b[3"), TextStyle("\x1b[93"), TextStyle("\x1b[31")), StylingMode.autodetect, null), ViewerOptions)`
source/wtracer/viewer/main.d(273):        instantiated from here: `CLI!(ViewerOptions)`
/home/runner/work/wtracer/wtracer/3rdparty/argparse/source/argparse/internal/command.d(278): Error: CTFE failed because of previous errors in `getMemberArgumentUDA`
/opt/hostedtoolcache/dc/ldc2-1.30.0/x64/ldc2-1.30.0-linux-x86_64/bin/../import/std/meta.d(652): Error: template instance `argparse.internal.command.TypeTraits!(Config('=', '\xff', '-', "--", true, false, true, Style(TextStyle("\x1b[1"), TextStyle("\x1b[1"), TextStyle("\x1b[1;4"), TextStyle("\x1b[93"), TextStyle("\x1b[3"), TextStyle("\x1b[93"), TextStyle("\x1b[31")), StylingMode.off, null), ViewerOptions).fun!"local"` error instantiating
/home/runner/work/wtracer/wtracer/3rdparty/argparse/source/argparse/internal/command.d(286):        instantiated from here: `staticMap!(getArgumentUDA, "local", "remoteAddress", "serverPath", "localPath", "namespace", "debugging")`
/home/runner/work/wtracer/wtracer/3rdparty/argparse/source/argparse/internal/command.d(358):        instantiated from here: `TypeTraits!(Config('=', '\xff', '-', "--", true, false, true, Style(TextStyle("\x1b[1"), TextStyle("\x1b[1"), TextStyle("\x1b[1;4"), TextStyle("\x1b[93"), TextStyle("\x1b[3"), TextStyle("\x1b[93"), TextStyle("\x1b[31")), StylingMode.off, null), ViewerOptions)`
/home/runner/work/wtracer/wtracer/3rdparty/argparse/source/argparse/internal/parser.d(603):        instantiated from here: `createCommand!(Config('=', '\xff', '-', "--", true, false, true, Style(TextStyle("\x1b[1"), TextStyle("\x1b[1"), TextStyle("\x1b[1;4"), TextStyle("\x1b[93"), TextStyle("\x1b[3"), TextStyle("\x1b[93"), TextStyle("\x1b[31")), StylingMode.off, null), ViewerOptions)`
/home/runner/work/wtracer/wtracer/3rdparty/argparse/source/argparse/internal/parser.d(628):        ... (2 instantiations, -v to show) ...
/home/runner/work/wtracer/wtracer/3rdparty/argparse/source/argparse/api/cli.d(192):        instantiated from here: `CLI!(Config('=', '\xff', '-', "--", true, false, true, Style(TextStyle("\x1b[1"), TextStyle("\x1b[1"), TextStyle("\x1b[1;4"), TextStyle("\x1b[93"), TextStyle("\x1b[3"), TextStyle("\x1b[93"), TextStyle("\x1b[31")), StylingMode.autodetect, null), ViewerOptions)`
source/wtracer/viewer/main.d(273):        instantiated from here: `CLI!(ViewerOptions)`
/home/runner/work/wtracer/wtracer/3rdparty/argparse/source/argparse/internal/command.d(278): Error: CTFE failed because of previous errors in `getMemberArgumentUDA`
/opt/hostedtoolcache/dc/ldc2-1.30.0/x64/ldc2-1.30.0-linux-x86_64/bin/../import/std/meta.d(652): Error: template instance `argparse.internal.command.TypeTraits!(Config('=', '\xff', '-', "--", true, false, true, Style(TextStyle("\x1b[1"), TextStyle("\x1b[1"), TextStyle("\x1b[1;4"), TextStyle("\x1b[93"), TextStyle("\x1b[3"), TextStyle("\x1b[93"), TextStyle("\x1b[31")), StylingMode.off, null), ViewerOptions).fun!"remoteAddress"` error instantiating
/home/runner/work/wtracer/wtracer/3rdparty/argparse/source/argparse/internal/command.d(286):        instantiated from here: `staticMap!(getArgumentUDA, "local", "remoteAddress", "serverPath", "localPath", "namespace", "debugging")`
/home/runner/work/wtracer/wtracer/3rdparty/argparse/source/argparse/internal/command.d(358):        instantiated from here: `TypeTraits!(Config('=', '\xff', '-', "--", true, false, true, Style(TextStyle("\x1b[1"), TextStyle("\x1b[1"), TextStyle("\x1b[1;4"), TextStyle("\x1b[93"), TextStyle("\x1b[3"), TextStyle("\x1b[93"), TextStyle("\x1b[31")), StylingMode.off, null), ViewerOptions)`
/home/runner/work/wtracer/wtracer/3rdparty/argparse/source/argparse/internal/parser.d(603):        instantiated from here: `createCommand!(Config('=', '\xff', '-', "--", true, false, true, Style(TextStyle("\x1b[1"), TextStyle("\x1b[1"), TextStyle("\x1b[1;4"), TextStyle("\x1b[93"), TextStyle("\x1b[3"), TextStyle("\x1b[93"), TextStyle("\x1b[31")), StylingMode.off, null), ViewerOptions)`
/home/runner/work/wtracer/wtracer/3rdparty/argparse/source/argparse/internal/parser.d(628):        ... (2 instantiations, -v to show) ...
/home/runner/work/wtracer/wtracer/3rdparty/argparse/source/argparse/api/cli.d(192):        instantiated from here: `CLI!(Config('=', '\xff', '-', "--", true, false, true, Style(TextStyle("\x1b[1"), TextStyle("\x1b[1"), TextStyle("\x1b[1;4"), TextStyle("\x1b[93"), TextStyle("\x1b[3"), TextStyle("\x1b[93"), TextStyle("\x1b[31")), StylingMode.autodetect, null), ViewerOptions)`
source/wtracer/viewer/main.d(273):        instantiated from here: `CLI!(ViewerOptions)`
/home/runner/work/wtracer/wtracer/3rdparty/argparse/source/argparse/internal/command.d(278): Error: CTFE failed because of previous errors in `getMemberArgumentUDA`
/opt/hostedtoolcache/dc/ldc2-1.30.0/x64/ldc2-1.30.0-linux-x86_64/bin/../import/std/meta.d(652): Error: template instance `argparse.internal.command.TypeTraits!(Config('=', '\xff', '-', "--", true, false, true, Style(TextStyle("\x1b[1"), TextStyle("\x1b[1"), TextStyle("\x1b[1;4"), TextStyle("\x1b[93"), TextStyle("\x1b[3"), TextStyle("\x1b[93"), TextStyle("\x1b[31")), StylingMode.off, null), ViewerOptions).fun!"serverPath"` error instantiating
/home/runner/work/wtracer/wtracer/3rdparty/argparse/source/argparse/internal/command.d(286):        instantiated from here: `staticMap!(getArgumentUDA, "local", "remoteAddress", "serverPath", "localPath", "namespace", "debugging")`
/home/runner/work/wtracer/wtracer/3rdparty/argparse/source/argparse/internal/command.d(358):        instantiated from here: `TypeTraits!(Config('=', '\xff', '-', "--", true, false, true, Style(TextStyle("\x1b[1"), TextStyle("\x1b[1"), TextStyle("\x1b[1;4"), TextStyle("\x1b[93"), TextStyle("\x1b[3"), TextStyle("\x1b[93"), TextStyle("\x1b[31")), StylingMode.off, null), ViewerOptions)`
/home/runner/work/wtracer/wtracer/3rdparty/argparse/source/argparse/internal/parser.d(603):        instantiated from here: `createCommand!(Config('=', '\xff', '-', "--", true, false, true, Style(TextStyle("\x1b[1"), TextStyle("\x1b[1"), TextStyle("\x1b[1;4"), TextStyle("\x1b[93"), TextStyle("\x1b[3"), TextStyle("\x1b[93"), TextStyle("\x1b[31")), StylingMode.off, null), ViewerOptions)`
/home/runner/work/wtracer/wtracer/3rdparty/argparse/source/argparse/internal/parser.d(628):        ... (2 instantiations, -v to show) ...
/home/runner/work/wtracer/wtracer/3rdparty/argparse/source/argparse/api/cli.d(192):        instantiated from here: `CLI!(Config('=', '\xff', '-', "--", true, false, true, Style(TextStyle("\x1b[1"), TextStyle("\x1b[1"), TextStyle("\x1b[1;4"), TextStyle("\x1b[93"), TextStyle("\x1b[3"), TextStyle("\x1b[93"), TextStyle("\x1b[31")), StylingMode.autodetect, null), ViewerOptions)`
source/wtracer/viewer/main.d(273):        instantiated from here: `CLI!(ViewerOptions)`
/home/runner/work/wtracer/wtracer/3rdparty/argparse/source/argparse/internal/command.d(278): Error: CTFE failed because of previous errors in `getMemberArgumentUDA`
/opt/hostedtoolcache/dc/ldc2-1.30.0/x64/ldc2-1.30.0-linux-x86_64/bin/../import/std/meta.d(652): Error: template instance `argparse.internal.command.TypeTraits!(Config('=', '\xff', '-', "--", true, false, true, Style(TextStyle("\x1b[1"), TextStyle("\x1b[1"), TextStyle("\x1b[1;4"), TextStyle("\x1b[93"), TextStyle("\x1b[3"), TextStyle("\x1b[93"), TextStyle("\x1b[31")), StylingMode.off, null), ViewerOptions).fun!"localPath"` error instantiating

README.md does not properly describe subcommands

e.g. for me --help does not print the subcommands if I do not Annotate their SumType with @SubCommands.
Also the first subsection reads Subcommands without UDA which indices for me, that there is a way to do subcommands without and with uda ... but its kind of the same, just the subcommands are configured with udas ...

Not providing a program name requires to pass an empty string

If program name is not provided, then Runtime.args[0] (a.k.a. argv[0] from main function) is used.

Here’s how we “not provide” a program name:

import argparse;

@(Command("").Description("A program's description."))
struct Args {
    bool banana;
}

mixin CLI!Args.main!((in _) { });

Command() should work as well, shouldn’t it? But it doesn’t; it throws an ArrayIndexError.

`--` is handled unconventionally

All ---aware programs I’ve encountered understand it the same way: it marks the end of named parameters; all arguments following it are bound to positional ones. To illustrate, these three invocations are equivalent:

cp -f -- a b
cp -f a -- b
cp -f a b

This is necessary in order to support binding an argument that starts with a dash to a positional one:

cp -- a -R # Copy the file named `a` to a file named `-R`.
cp a -- -R # Ditto.

This is important to a program user if the names aren’t known in advance:

cp -- "$from" "$to" # Will work predictably no matter what these variables contain.

If we were implementing cp in D, we’d like to declare it as follows:

struct Args {
    @PositionalArgument(0) string from;
    @PositionalArgument(1) string to;
}

…and expect everything to work out of the box.

A program should never need to know if it was invoked with -- or without. If it needs to accept a variable number of arguments, it just asks for @(PositionalArgument(0).Optional) string[ ] rest.

A lone `-` should be treated as a regular positional argument

It is a common idiom to accept a - parameter meaning stdin. E.g., cat - reads its whole stdin and sends it to stdout. However, argparse currently treats it as a named parameter, even though NamedArgument("") is not allowed—so it cannot be accessed in any way.

import argparse;

struct T {
    @NamedArgument bool c;
    @PositionalArgument(0) string fileName;
}

void main() {
    assert(CLI!T.parseArgs!((T t) { assert(t == T(true, "-")); })(["-", "-c"]) == 0);
}

Case insensitivity breaks `--help`

import argparse;

struct Args {
    bool banana;
}

mixin CLI!({
    Config cfg = { caseSensitive: false };
    return cfg;
}(), Args).main!((in _) { });
$ ./prog --help
Error: Unrecognized arguments: ["--help"]

`Param`s are non-assignable

struct Param(VALUE_TYPE) contains a const Config* config, which disables the assignment operator for the whole struct. Perhaps you meant const(Config)* config?

One does not simply pass a closure to a `LazyString`

Discovered this when I was trying to deduplicate Complete.InitCmd. A reduced example that should be easier to follow:

struct Description {
    string delegate() dg;
}

auto createCompleter(string desc) {
    @Description(() => desc) // Seems good so far.
    struct Complete { }

    return Complete.init; // Err... We haven't stored `desc` anywhere, have we?
}

void main() {
    import std.stdio: writeln;
    import std.traits: getUDAs;

    enum completer = createCompleter("Description.");
    writeln(getUDAs!(typeof(completer), Description)[0].dg()); // Here.
}

The compiler blames: delegate test.createCompleter.__lambda3 is a nested function and cannot be accessed from D main. And it is right. Therefore, a delegate can be used with a LazyString only if it does not escape its declaration scope.

UDAs are attached at struct-declaration time, but variables we want to close over are only available at instance-creation time. Unfortunately, I can’t think of a good solution here. One idea comes to my mind: add a third option to LazyString, which should be a struct with no members, to signify even more runtime strings. Then a caller can test for this and invoke cmd.getDescription. That should work in principle, but it requires patching every callsite to account for that, which I’m not happy with. Any better ideas?

Doesn't seem to be compilable with LDC 1.37.0 nor 1.38.0

This is almost probably a compiler/Phobos regression. CC @pbackus perhaps are you aware of some change on sumtype that affected this? There's no other informational error, so, I can't understand clearly where the issue resides exactly. All I have is this log, and I'm confident its from argparse given the type names:

/home/luis/.local/bin/../import/std/sumtype.d(418): Error: template instance `DeducedParameterType!(inout(Unknown))` does not match template declaration `DeducedParameterType(T)`
/home/luis/.local/bin/../import/std/sumtype.d(418): Error: template instance `DeducedParameterType!(inout(Argument))` does not match template declaration `DeducedParameterType(T)`
/home/luis/.local/bin/../import/std/sumtype.d(418): Error: template instance `DeducedParameterType!(inout(SubCommand))` does not match template declaration `DeducedParameterType(T)`
/home/luis/.local/bin/../import/std/sumtype.d(418): Error: template instance `DeducedParameterType!(inout(Unknown))` does not match template declaration `DeducedParameterType(T)`
/home/luis/.local/bin/../import/std/sumtype.d(418): Error: template instance `DeducedParameterType!(inout(Argument))` does not match template declaration `DeducedParameterType(T)`
/home/luis/.local/bin/../import/std/sumtype.d(418): Error: template instance `DeducedParameterType!(inout(SubCommand))` does not match template declaration `DeducedParameterType(T)`
/home/luis/.local/bin/../import/std/sumtype.d(418): Error: template instance `DeducedParameterType!(inout(Unknown))` does not match template declaration `DeducedParameterType(T)`
/home/luis/.local/bin/../import/std/sumtype.d(418): Error: template instance `DeducedParameterType!(inout(Argument))` does not match template declaration `DeducedParameterType(T)`
/home/luis/.local/bin/../import/std/sumtype.d(418): Error: template instance `DeducedParameterType!(inout(SubCommand))` does not match template declaration `DeducedParameterType(T)`
/home/luis/.local/bin/../import/std/sumtype.d(418): Error: template instance `DeducedParameterType!(inout(Unknown))` does not match template declaration `DeducedParameterType(T)`
/home/luis/.local/bin/../import/std/sumtype.d(418): Error: template instance `DeducedParameterType!(inout(Argument))` does not match template declaration `DeducedParameterType(T)`
/home/luis/.local/bin/../import/std/sumtype.d(418): Error: template instance `DeducedParameterType!(inout(SubCommand))` does not match template declaration `DeducedParameterType(T)`
/home/luis/.local/bin/../import/std/sumtype.d(418): Error: template instance `DeducedParameterType!(inout(Unknown))` does not match template declaration `DeducedParameterType(T)`
/home/luis/.local/bin/../import/std/sumtype.d(418): Error: template instance `DeducedParameterType!(inout(Argument))` does not match template declaration `DeducedParameterType(T)`
/home/luis/.local/bin/../import/std/sumtype.d(418): Error: template instance `DeducedParameterType!(inout(SubCommand))` does not match template declaration `DeducedParameterType(T)`
/home/luis/.local/bin/../import/std/sumtype.d(418): Error: template instance `DeducedParameterType!(inout(Unknown))` does not match template declaration `DeducedParameterType(T)`
/home/luis/.local/bin/../import/std/sumtype.d(418): Error: template instance `DeducedParameterType!(inout(Argument))` does not match template declaration `DeducedParameterType(T)`
/home/luis/.local/bin/../import/std/sumtype.d(418): Error: template instance `DeducedParameterType!(inout(SubCommand))` does not match template declaration `DeducedParameterType(T)`
/home/luis/.local/bin/../import/std/sumtype.d(418): Error: template instance `DeducedParameterType!(inout(Unknown))` does not match template declaration `DeducedParameterType(T)`
/home/luis/.local/bin/../import/std/sumtype.d(418): Error: template instance `DeducedParameterType!(inout(Argument))` does not match template declaration `DeducedParameterType(T)`

Is it possible to use an own type (struct) with the Parse annotations?

If I try something like this:

...
        @(NamedArgument
            .Description("Set of ponies to consider +- list of regexes")
            .Parse!((string s) { return 1; })
            .Action!((ref double result, int i) { result = 1.0; })
        )
        double set;
...

it works as described, but if I replace double with a custom type of mine:

...
struct MyType {
    double d;
}
...
        @(NamedArgument
            .Description("Set of ponies to consider +- list of regexes")
            .Parse!((string s) { return 1; })
            .Action!((ref MyType result, int i) { result = MyType(); result.d = 1.0; })
        )
        MyType set;
...

I get from the compiler:

../../../../../.dub/packages/argparse-master/argparse/source/argparse/internal.d(478,9): Error: static assert:  "Type is not supported: MyType"
../../../../../.dub/packages/argparse-master/argparse/source/argparse/internal.d(493,58):        instantiated from here: `defaultValuesCount!(MyType)`
../../../../../.dub/packages/argparse-master/argparse/source/argparse/internal.d(885,29):        instantiated from here: `setDefaults!(MyType, "set")`
../../../../../.dub/packages/argparse-master/argparse/source/argparse/internal.d(852,21):        instantiated from here: `addArgument!("set")`

Name of Positional Argument is ignored?

mixin CLI!Params.main!((arguments) { });

struct Params
{
    @PositionalArgument(0, "bar")
    string foo;
}

shows the help as

Usage: command [-h] foo

Required arguments:
  foo

while I would have expected "bar" instead of "foo" from the README.
(Especially in the case that the CLI parameter is a keyword of D.)

An argument with `=` bundles incorrectly

import argparse;

struct Args {
    bool b;
    string s;
}

mixin CLI!({
    Config cfg = { bundling: true };
    return cfg;
}(), Args).main!((in args) {
    import std.stdio;

    writeln(args.b, " <", args.s, '>');
});
./prog -bs=abc

Expected output: true <abc>.
Current output: true <>.

On the other hand, all of these work correctly:

./prog -bsabc
./prog -b -sabc
./prog -b -s=abc
./prog -b -s abc

Positional arguments are incorrectly name in the error messages

import argparse;

static struct Basic
{
    @PositionalArgument(0)
    int[] array;
}

mixin CLI!Basic.main!((args)
{
    return 0;
});

If I call this program without arguments, I get an error message:

Error: The following argument is required: --array

But there is no argument --array. The help message actually provides the correct name:

Required arguments:
array ...

A lazy string crashes the compiler

An example from the Readme, reduced:

import argparse;

struct T {
    @(PositionalArgument(0).Description(() => "Argument description"))
    string param0;
}

void main() {
    CLI!T.parseArgs!((T t) { })(["-h"]); // SIGILL
}

DMD 2.105.3.

No color in Visual Studio Code's Terminal

I have no idea why, but I don't see any colors in the help text in the bash terminal of Visual Studio Code.
I see colors in a separate terminal. And I see the red "Error:" text in error messages from dmd in Visual Studio Code.

`-unittest` requires an enormous amount of memory to compile

Having a mere import argparse; in a program and compiling it with -unittest compiles and runs all of the argparse’s tests. Or at least attempts to. If I run dmd -lowmem -unittest -i test.d on a file containing this single line, the compiler devours 2.8 GB of RAM; then I kill it, or else my system freezes. I have no idea how hungry it is in reality.

In my opinion, inspecting some UDAs shouldn’t involve so much work. I’ll have a look at the code and see if I can figure out what is going on there.

Show help as default command

Hello,

I am making a CLI which only has subcommands, and as such I thought that is should show help when no command is given, how can I do that. I didn't find any way to show the default help outside of the help switch.

Please print default values for parameters and commands (if they have one)

If there are default values for subcommands or parameters, those should be highlighted in the helptext.
e.g.

import std.stdio;
import argparse;

struct Cmd1 {}
struct Cmd2 {}
enum Mode {
    WALK,
    RUN,
}
struct Arguments {
    @(NamedArgument.Description("dsc for text1"))
    string text1 = "abc";
    @(NamedArgument.Description("dsc for text2"))
    string text2;
    @(NamedArgument.Description("mode to use"))
    Mode mode = Mode.WALK;
    SubCommand!(Default!Cmd1, Cmd2) subcommands;
}
mixin CLI!Arguments.main!(arguments => arguments.writeln);

outputs

Usage: argparse-issue [--text1 TEXT1] [--text2 TEXT2] [--mode {WALK,RUN}] [-h] <command> [<args>]

Available commands:
  Cmd1
  Cmd2

Optional arguments:
  --text1 TEXT1        dsc for text1
  --text2 TEXT2        dsc for text2
  --mode {WALK,RUN}    mode to use
  -h, --help           Show this help message and exit

at the moment when called with --help.
It would be great if something like

Usage: argparse-issue [--text1 TEXT1] [--text2 TEXT2] [--mode {WALK,RUN}] [-h] <command> [<args>]

Available commands:
  Cmd1 (default)
  Cmd2

Optional arguments:
  --text1 TEXT1        dsc for text1 (defaults to abc)
  --text2 TEXT2        dsc for text2
  --mode {WALK,RUN}    mode to use (defaults to WALK)
  -h, --help           Show this help message and exit

could be printed.

Keywords in enum values

I have a program with a debug output option, so it can be called with --format debug. Output options are defined as enum:

enum OutputFormat
{
    silent,
    debug_,
}

Obviously, I cannot define debug as enum member since it is a keyword. My current workaround looks like this:

import argparse;
import std.meta;
import std.traits;
import std.conv;
import std.algorithm;

enum OutputFormat
{
    silent,
    debug_,
}

private enum string allowedOutputFormat(OutputFormat Member) =
    Member.to!string.strip('_');
private enum string[] allowedOutputFormats = [
    staticMap!(allowedOutputFormat, EnumMembers!OutputFormat)
];

private OutputFormat parseOutputFormat(string input)
{
    switch (input)
    {
        case "debug":
            return OutputFormat.debug_;
        case "silent":
            return OutputFormat.silent;
        default:
            throw new Exception("");
    }
}

static struct Basic
{
    @(NamedArgument
            .AllowedValues!allowedOutputFormats
            .PreValidation!((string x) => true)
            .Parse!parseOutputFormat
            .Validation!((OutputFormat x) => true)
    )
    OutputFormat format;
}

mixin CLI!Basic.main!((args)
{
    return 0;
});

I don't mind writing some logic for such edge cases, but it would be nice if it were a bit easier. Defining PreValidation and Validation for this looks strange, but the parser doesn't work without it.

Inconsistent parsing of `bool`

(string value) // parse
{
switch(value)
{
case "": goto case;
case "yes": goto case;
case "y": return true;
case "no": goto case;
case "n": return false;
default: return value.to!T;
}
},

yes, y, no, and n are accepted in lower case only, but true and false are matched case-insensitively by std.conv.to. Also, when a parsing error occurs, the message only mentions true/false as allowed values.

Support ddoc as description for helptexts

I was playing with rust/clap and one feature I really liked there was, that you can use doc-comments as an alternative to annotations to document flags/commands...
Not sure that its possible to get to the doc-comments in dlang though (I found an old issue: dlang/dmd#6872 where one of the usecases was especially commandline help generation).

Wasted space in front of description with too long parameters

struct Parameters
{
    @(NamedArgument.Description("1 x"))
    string x;

    @(NamedArgument.Description("12 ys"))
    string yyyyyyyyyyyy;
}

gives a help like

Optional arguments:
  -x X                    1 x
  --yyyyyyyyyyyy YYYYYYYYYYYY
                          12 ys
  -h, --help              Show this help message and exit

I would have hoped that the too long parameter is just ignored and the description follows briefly after -h, --help.

( I had a parameter list with nice descriptions, but the introduction of one long parameter causes an ugly wrapping of the descriptions).

Add the ability to make a required MutuallyExclusive group

MutuallyExclusive groups should support having e.g. both arguments required so I can have at least --foo or --bar but not both.

For example, either:

    @MutuallyExclusive
    {
        @(
            NamedArgument
            .Required()
        )
        bool stdin;
        @(
            NamedArgument("files", "f")
            .Required()
            .Description("D source files to scan. You can provide a regex to match multiple files")
        )
        string[] files;
    }

or

    @(MutuallyExclusive.Required())
    {
        @NamedArgument
        bool stdin;
        @(
            NamedArgument("files", "f")
            .Description("D source files to scan. You can provide a regex to match multiple files")
        )
        string[] files;
    }

Lets say I want to pass files via arguments or pass a file via stdin, but I want at least one of the options?

If this is possible, please let me know :)

Support mixed case-sensitivity

I think, apart from being case-sensitive or case-insensitive, there is a third practically useful approach: ignore case in long options but be case-sensitive in short ones. The reason is that it’s fairly common to have one-letter options that differ only in their case. I believe they should not block the ability to match long options case-insensitively.

There is a tricky point, though: how should we process --g? There are three strategies that are all sane on the first glance:

  1. Be case-sensitive, despite the fact it has two dashes.
  2. Be case-insensitive; if there is an ambiguity (i.e., both -g and -G exist), emit an error.
  3. Be case-insensitive; if there is an ambiguity, prefer option that matches exactly.

To understand which one is better, let’s consider this scenario:

  1. There is a program that has a -g flag.
  2. A user writes a script that invokes the program with --G.
  3. A new version of the program is released. It now understands the -G flag.

With strategy 1, the user would face an error right on the second step, thus the problem is prevented before it even appeared.
With strategy 2, when the user upgrades their installation, their script will break. The program simply added a new option, and that turned out to be a backwards-incompatible change.
With strategy 3, when the user upgrades, their script will silently begin doing something different.

I hope the point is clear now.

Subcommands are not correctly parsed, if the subcommand struct does not have members

#!/usr/bin/env dub
/+ dub.sdl:
    name "argparse_subcommand_bug"
    dependency "argparse" version="0.10.0"
+/

import argparse;
import std.stdio: writeln;

struct sum
{
}

struct min
{
}

struct max
{
}

int main_(max cmd)
{
    import std.algorithm: maxElement;

    writeln("max = ");

    return 0;
}

int main_(min cmd)
{
    import std.algorithm: minElement;

    writeln("min = ");

    return 0;
}

int main_(sum cmd)
{
    import std.algorithm: sum;

    writeln("sum = ");

    return 0;
}

// This mixin defines standard main function that parses command line and calls the provided function:
mixin CLI!(sum, min, max).main!main_;

running this program with sum or min as arguments always prints sum.

Allow to customize the exit code

Currently, if a parsing error occurs, the program exits with code 1; this is hard-coded into the library. However, the program may want to use return code 1 itself and leave another code for the CLI framework. If I understood correctly, to do this, we have to invoke the lower-level parseArgs manually, check if it returned 1, and if so, distinguish it from the program’s 1 somehow (e.g., by checking a global variable). It feels really hacky for such simple task.

A blog post explaining why having different error codes is important.

Affect usage description for a main program?

For a subcommand, you can affect the description by attributing the command with information.

But I want to just add a description for the whole program. Is that possible to do?

Inconsistency in Styling API

auto myBold = bold;
assert(is(typeof(myBold("text")) == string));
assert(!is(typeof(bold("text")): string)); // Not even convertible.

(That’s because bold()("text") returns string while bold("text") returns StyledText.)

IMHO, it shouldn’t work like this. Unfortunately, any change in this would be an API change. Is it justified for 2.0?

In case we decide to unify them, we’ll have at least 3 ways:

  1. Make everything return string, remove StyledText altogether.
  2. Make everything return StyledText, which will be implicitly convertible to string.
  3. Make everything return StyledText, which will retain its explicit toString method.

Why do we consider options 2 and 3? Because StyledText can become a lazy range – and it’ll be possible to rule out allocations of temporary strings:

// Allocates a temporary just to concatenate it; then it immediately becomes garbage.
string msg0 = "argument " ~ bold("name") ~ " is bold";

// Proposed alternative:
auto app = appender!string();
app ~= "argument ";
app ~= bold("name"); // No temporaries; writes directly into the buffer.
app ~= " is bold";
string msg1 = app[];

// The same in one line:
string msg2 = chain("argument ".byCodeUnit, bold("name"), " is bold".byCodeUnit).array();

I think StyledText can retain its opBinary!"~" and opBinaryRight!"~" for convenience: they clearly look like something that allocates. So all the snippets above will be compilable.

The question is, whether or not StyledText should implicitly convert to string. To be honest, a lazy range that can implicitly lose its laziness scares me off. I’d prefer option 3.

Implications:

  1. string msg = bold("text").toString() will continue to work.
  2. app ~= config.programName("text") will continue to work.
  3. writeln(config.programName("text")) will continue to work: writeln invokes toString.
  4. string msg = config.programName("text") ~ " is bold" will continue to work.
  5. string msg = config.programName("text") will break with approach 3: an explicit toString is required, just like in example 1 above.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.