Skip to content

Commit

Permalink
Default subcommand (#33)
Browse files Browse the repository at this point in the history
* Default subcommand

* Update readme

* Unit test
  • Loading branch information
andrey-zherikov authored Mar 17, 2022
1 parent bcfc961 commit 8b60bf9
Show file tree
Hide file tree
Showing 5 changed files with 120 additions and 7 deletions.
12 changes: 11 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ to [releases](https://github.com/andrey-zherikov/argparse/releases) for breaking
- Parsing of known arguments only (returning not recognized ones).
- Options terminator (e.g. parsing up to `--` leaving any argument specified after it).
- Arguments groups.
- Subcommands.
- Fully customizable parsing:
- Raw (`string`) data validation (i.e. before parsing).
- Custom conversion of argument value (`string` -> any `destination type`).
Expand Down Expand Up @@ -693,7 +694,7 @@ mixin CLI!Program.main!((prog)
});
```

### Command name and aliases
### Subcommand name and aliases

To define a command name that is not the same as the type that represents this command, one should use `Command` UDA -
it accepts a name and list of name aliases. All these names are recognized by the parser and are displayed in the help
Expand All @@ -715,6 +716,15 @@ Would result in this help fragment:
maximum,max Print the maximum
```

### Default subcommand

The default command is a command that is ran when user doesn't specify any command in the command line.
To mark a command as default, one should use `Default` template:

```d
SumType!(sum, min, Default!max) cmd;
```

## Help generation

### Command
Expand Down
6 changes: 4 additions & 2 deletions examples/dub.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,16 @@
"all:getting_started-basic":"*",
"all:sub_commands-advanced":"*",
"all:sub_commands-basic":"*",
"all:sub_commands-common_args":"*"
"all:sub_commands-common_args":"*",
"all:sub_commands-default":"*"
},
"subPackages": [
"..",
"getting_started/advanced",
"getting_started/basic",
"sub_commands/advanced",
"sub_commands/basic",
"sub_commands/common_args"
"sub_commands/common_args",
"sub_commands/default"
]
}
46 changes: 46 additions & 0 deletions examples/sub_commands/default/app.d
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import argparse;
import std.stdio: writeln;
import std.sumtype: SumType, match;


struct sum {}
struct min {}
struct max
{
string foo; // --foo argument
}

struct Program
{
int[] numbers; // --numbers argument

// SumType indicates sub-command
// Default!T marks T as default command
SumType!(sum, min, Default!max) cmd;
}

// This mixin defines standard main function that parses command line and calls the provided function:
mixin CLI!Program.main!((prog)
{
static assert(is(typeof(prog) == Program));

prog.cmd.match!(
(.max m)
{
import std.algorithm: maxElement;
writeln("max = ", prog.numbers.maxElement, " foo = ", m.foo);
},
(.min)
{
import std.algorithm: minElement;
writeln("min = ", prog.numbers.minElement);
},
(.sum)
{
import std.algorithm: sum;
writeln("sum = ", prog.numbers.sum);
}
);

return 0;
});
7 changes: 7 additions & 0 deletions examples/sub_commands/default/dub.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"license": "BSL-1.0",
"name": "sub_commands-default",
"targetType":"executable",
"sourcePaths":["."],
"dependencies":{ "all:argparse":"*" }
}
56 changes: 52 additions & 4 deletions source/argparse.d
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ module argparse;
import std.typecons: Nullable;
import std.traits;

private enum DEFAULT_COMMAND = "";

struct Config
{
/**
Expand Down Expand Up @@ -710,7 +712,10 @@ private auto ParsingSubCommand(COMMAND_TYPE, CommandInfo info, RECEIVER, alias s

alias parse = (ref COMMAND_TYPE cmdTarget)
{
auto command = CommandArguments!COMMAND_TYPE(parser.config, info, parentArguments);
static if(!is(COMMAND_TYPE == Default!TYPE, TYPE))
alias TYPE = COMMAND_TYPE;

auto command = CommandArguments!TYPE(parser.config, info, parentArguments);

return arg.match!(_ => parser.parse(command, cmdTarget, _));
};
Expand All @@ -736,6 +741,13 @@ private auto ParsingSubCommand(COMMAND_TYPE, CommandInfo info, RECEIVER, alias s

struct SubCommands {}

// Default subcommand
struct Default(COMMAND)
{
COMMAND command;
alias command this;
}

unittest
{
struct T
Expand Down Expand Up @@ -1132,6 +1144,10 @@ private struct Parser
return arg.match!(_ => parse(cmd, receiver, _));
};

auto found = cmd.findSubCommand(DEFAULT_COMMAND);
if(found.parse !is null)
cmdStack ~= (const ref arg) => found.parse(this, arg, receiver);

while(!args.empty)
{
immutable res = parse(splitArgumentNameValue(args.front));
Expand Down Expand Up @@ -1544,6 +1560,24 @@ unittest
assert(["-c","C","cmd2","-b","B"].parseCLIArgs!T.get == T("C",null,typeof(T.cmd)(T.cmd2("B"))));
}

unittest
{
import std.sumtype: SumType, match;

struct T
{
struct cmd1 { string a; }
struct cmd2 { string b; }

string c;
string d;

SumType!(cmd1, Default!cmd2) cmd;
}

assert(["-c","C","-b","B"].parseCLIArgs!T.get == T("C",null,typeof(T.cmd)(Default!(T.cmd2)(T.cmd2("B")))));
}

struct Main
{
mixin template parseCLIKnownArgs(TYPE, alias newMain, Config config = Config.init)
Expand Down Expand Up @@ -3239,13 +3273,21 @@ private struct CommandArguments(RECEIVER)

private void addSubCommands(alias symbol)()
{
import std.sumtype: isSumType;

alias member = __traits(getMember, RECEIVER, symbol);

static assert(isSumType!(typeof(member)), RECEIVER.stringof~"."~symbol~" must have 'SumType' type");

static assert(getUDAs!(member, SubCommands).length <= 1,
"Member "~RECEIVER.stringof~"."~symbol~" has multiple 'SubCommands' UDAs");

static foreach(COMMAND_TYPE; typeof(member).Types)
static foreach(TYPE; typeof(member).Types)
{{
enum defaultCommand = is(TYPE == Default!COMMAND_TYPE, COMMAND_TYPE);
static if(!defaultCommand)
alias COMMAND_TYPE = TYPE;

static assert(getUDAs!(COMMAND_TYPE, CommandInfo).length <= 1);

//static assert(getUDAs!(member, Group).length <= 1,
Expand All @@ -3267,13 +3309,19 @@ private struct CommandArguments(RECEIVER)

static foreach(name; info.names)
{
assert(!(name in subCommandsByName), "Duplicated name of sub command: "~name);
assert(!(name in subCommandsByName), "Duplicated name of subcommand: "~name);
subCommandsByName[arguments.convertCase(name)] = index;
}

static if(defaultCommand)
{
assert(!(DEFAULT_COMMAND in subCommandsByName), "Multiple default subcommands: "~RECEIVER.stringof~"."~symbol);
subCommandsByName[DEFAULT_COMMAND] = index;
}

subCommands ~= info;
//group.arguments ~= index;
parseSubCommands ~= ParsingSubCommand!(COMMAND_TYPE, info, RECEIVER, symbol)(&this);
parseSubCommands ~= ParsingSubCommand!(TYPE, info, RECEIVER, symbol)(&this);
}}
}

Expand Down

0 comments on commit 8b60bf9

Please sign in to comment.