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:
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)));
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:
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)));
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:
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)));
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:
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)));
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:
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;
});
Last modified: 09 November 2024