Skip to content

Commit

Permalink
Fix for subcommands with @command UDA without a name (#150)
Browse files Browse the repository at this point in the history
* Fix for subcommands with @command UDA without a name

* Add unit test

* Add getSubCommandInfo and getTopLevelCommandInfo

* Make CommandInfo.names empty by default

* Remove debug print
  • Loading branch information
andrey-zherikov authored Jan 2, 2024
1 parent a2092ca commit e4b5456
Show file tree
Hide file tree
Showing 7 changed files with 59 additions and 35 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -850,8 +850,8 @@ SumType!(sum, min, Default!max) cmd;
`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, then the name of
the type that represents the command is used.
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.
Expand Down
8 changes: 4 additions & 4 deletions examples/sub_commands/advanced/app.d
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,11 @@ auto filter(R)(R numbers, Filter filt)
return numbers.filter!func;
}

@(Command("sum")
@(Command // use "sum" (type name) as a command name
.Usage("%(PROG) [<number>...]")
.Description(() => "Print sum of the numbers")
)
struct SumCmd
struct sum
{
@PositionalArgument(0)
int[] numbers;
Expand Down Expand Up @@ -68,7 +68,7 @@ struct Program
// Sub-command
// name of the command is the same as a name of the type
@SubCommands
SumType!(SumCmd, MinCmd, MaxCmd) cmd;
SumType!(sum, MinCmd, MaxCmd) cmd;
}


Expand All @@ -88,7 +88,7 @@ mixin CLI!Program.main!((prog)
import std.algorithm: minElement;
return cmd.numbers.filter(prog.filter).minElement(0);
},
(SumCmd cmd)
(sum cmd)
{
import std.algorithm: sum;
return cmd.numbers.filter(prog.filter).sum;
Expand Down
4 changes: 2 additions & 2 deletions source/argparse/api/command.d
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ unittest
{
CommandInfo c;
c = c.Usage("usg").Description("desc").ShortDescription("sum").Epilog("epi");
assert(c.names == [""]);
assert(c.names == []);
assert(c.usage.get == "usg");
assert(c.description.get == "desc");
assert(c.shortDescription.get == "sum");
Expand All @@ -81,7 +81,7 @@ unittest
{
CommandInfo c;
c = c.Usage(() => "usg").Description(() => "desc").ShortDescription(() => "sum").Epilog(() => "epi");
assert(c.names == [""]);
assert(c.names == []);
assert(c.usage.get == "usg");
assert(c.description.get == "desc");
assert(c.shortDescription.get == "sum");
Expand Down
4 changes: 2 additions & 2 deletions source/argparse/internal/command.d
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ package struct Command

CommandInfo info;

string displayName() const { return info.displayNames[0]; }
string displayName() const { return info.displayNames.length > 0 ? info.displayNames[0] : ""; }

SubCommands subCommands;
Command delegate() [] subCommandCreate;
Expand Down Expand Up @@ -294,7 +294,7 @@ private template TypeTraits(Config config, TYPE)
/////////////////////////////////////////////////////////////////////
/// Subcommands

private enum getCommandInfo(CMD) = .getCommandInfo!(RemoveDefaultAttribute!CMD)(config, RemoveDefaultAttribute!CMD.stringof);
private enum getCommandInfo(CMD) = .getSubCommandInfo!(RemoveDefaultAttribute!CMD)(config);
private enum getSubcommand(CMD) = SubCommand!CMD(getCommandInfo!CMD);

static if(.subCommandSymbol!TYPE.length == 0)
Expand Down
40 changes: 32 additions & 8 deletions source/argparse/internal/commandinfo.d
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import argparse.internal.lazystring;

package(argparse) struct CommandInfo
{
string[] names = [""];
string[] names;
string[] displayNames;
LazyString usage;
LazyString description;
Expand Down Expand Up @@ -50,18 +50,42 @@ unittest
assert(uda.names == ["CMD-NAME"]);
}

package(argparse) CommandInfo getCommandInfo(COMMAND)(const Config config, string name = "")
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

private template getCommandInfo(TYPE)
{
import std.traits: getUDAs;

enum udas = getUDAs!(COMMAND, CommandInfo);
static assert(udas.length <= 1, COMMAND.stringof~" has multiple @Command UDA");
enum udas = getUDAs!(TYPE, CommandInfo);
static assert(udas.length <= 1, TYPE.stringof~" has multiple @Command UDA");

static if(udas.length > 0)
CommandInfo info = finalize(config, udas[0]);
enum getCommandInfo = udas[0];
else
CommandInfo info = finalize(config, CommandInfo([name]));
enum getCommandInfo = CommandInfo.init;
}

package(argparse) CommandInfo getSubCommandInfo(COMMAND)(const Config config)
{
CommandInfo info = getCommandInfo!COMMAND;

if(info.names.length == 0)
info.names = [COMMAND.stringof];

return finalize(config, info);
}

package(argparse) CommandInfo getTopLevelCommandInfo(COMMAND)(const Config config)
{
return finalize(config, getCommandInfo!COMMAND);
}

unittest
{
@CommandInfo()
struct T {}

assert(name == "" || info.names.length > 0 && info.names[0].length > 0, "Command "~COMMAND.stringof~" must have name");
return info;
auto info = getSubCommandInfo!T(Config.init);
assert(info.displayNames == ["T"]);
assert(info.names == ["T"]);
}
4 changes: 2 additions & 2 deletions source/argparse/internal/parser.d
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import argparse.api.ansi: ansiStylingArgument;
import argparse.api.command: Default, SubCommands;
import argparse.internal.arguments: ArgumentInfo;
import argparse.internal.command: Command, createCommand;
import argparse.internal.commandinfo: getCommandInfo;
import argparse.internal.commandinfo: getTopLevelCommandInfo;

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

Expand Down Expand Up @@ -592,7 +592,7 @@ package(argparse) Result callParser(Config config, bool completionMode, COMMAND)
if(config.stylingMode != Config.StylingMode.autodetect)
{
return callParser!(completionMode, config.bundling)(
config, createCommand!config(receiver, getCommandInfo!COMMAND(config)), args, unrecognizedArgs,
config, createCommand!config(receiver, getTopLevelCommandInfo!COMMAND(config)), args, unrecognizedArgs,
);
}

Expand Down
30 changes: 15 additions & 15 deletions source/argparse/package.d
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public import argparse.result;
version(unittest)
{
import argparse.internal.command : createCommand;
import argparse.internal.commandinfo : getCommandInfo;
import argparse.internal.commandinfo : getTopLevelCommandInfo;
import argparse.internal.help : printHelp;
import argparse.ansi : cleanStyleEnv, restoreStyleEnv;
}
Expand Down Expand Up @@ -51,7 +51,7 @@ unittest
}();

T receiver;
auto a = createCommand!config(receiver, getCommandInfo!T(config));
auto a = createCommand!config(receiver, getTopLevelCommandInfo!T(config));
assert(a.arguments.requiredGroup.argIndex == [2,4]);
assert(a.arguments.argsNamed == ["a":0LU, "b":1LU, "c":2LU, "d":3LU, "e":4LU, "f":5LU]);
assert(a.arguments.argsPositional == []);
Expand All @@ -71,7 +71,7 @@ unittest
}();

T receiver;
auto a = createCommand!config(receiver, getCommandInfo!T(config));
auto a = createCommand!config(receiver, getTopLevelCommandInfo!T(config));
assert(a.arguments.requiredGroup.argIndex == []);
assert(a.arguments.argsNamed == ["a":0LU, "b":1LU, "c":2LU, "d":3LU, "e":4LU, "f":5LU]);
assert(a.arguments.argsPositional == []);
Expand All @@ -85,7 +85,7 @@ unittest
@(NamedArgument("2"))
int a;
}
static assert(!__traits(compiles, { T1 t; enum c = createCommand!(Config.init)(t, getCommandInfo!T1(Config.init)); }));
static assert(!__traits(compiles, { T1 t; enum c = createCommand!(Config.init)(t, getTopLevelCommandInfo!T1(Config.init)); }));

struct T2
{
Expand All @@ -94,28 +94,28 @@ unittest
@(NamedArgument("1"))
int b;
}
static assert(!__traits(compiles, { T2 t; enum c = createCommand!(Config.init)(t, getCommandInfo!T2(Config.init)); }));
static assert(!__traits(compiles, { T2 t; enum c = createCommand!(Config.init)(t, getTopLevelCommandInfo!T2(Config.init)); }));

struct T3
{
@(PositionalArgument(0)) int a;
@(PositionalArgument(0)) int b;
}
static assert(!__traits(compiles, { T3 t; enum c = createCommand!(Config.init)(t, getCommandInfo!T3(Config.init)); }));
static assert(!__traits(compiles, { T3 t; enum c = createCommand!(Config.init)(t, getTopLevelCommandInfo!T3(Config.init)); }));

struct T4
{
@(PositionalArgument(0)) int a;
@(PositionalArgument(2)) int b;
}
static assert(!__traits(compiles, { T4 t; enum c = createCommand!(Config.init)(t, getCommandInfo!T4(Config.init)); }));
static assert(!__traits(compiles, { T4 t; enum c = createCommand!(Config.init)(t, getTopLevelCommandInfo!T4(Config.init)); }));

struct T5
{
@(PositionalArgument(0)) int[] a;
@(PositionalArgument(1)) int b;
}
static assert(!__traits(compiles, { T5 t; enum c = createCommand!(Config.init)(t, getCommandInfo!T5(Config.init)); }));
static assert(!__traits(compiles, { T5 t; enum c = createCommand!(Config.init)(t, getTopLevelCommandInfo!T5(Config.init)); }));
}

unittest
Expand Down Expand Up @@ -157,7 +157,7 @@ unittest
}

params receiver;
auto a = createCommand!(Config.init)(receiver, getCommandInfo!params(Config.init));
auto a = createCommand!(Config.init)(receiver, getTopLevelCommandInfo!params(Config.init));
}

unittest
Expand Down Expand Up @@ -895,7 +895,7 @@ unittest
auto a = appender!string;

T receiver;
auto cmd = createCommand!(Config.init)(receiver, getCommandInfo!T(Config.init));
auto cmd = createCommand!(Config.init)(receiver, getTopLevelCommandInfo!T(Config.init));

auto isEnabled = ansiStylingArgument.isEnabled;
scope(exit) ansiStylingArgument.isEnabled = isEnabled;
Expand Down Expand Up @@ -951,7 +951,7 @@ unittest
auto a = appender!string;

T receiver;
auto cmd = createCommand!(Config.init)(receiver, getCommandInfo!T(Config.init));
auto cmd = createCommand!(Config.init)(receiver, getTopLevelCommandInfo!T(Config.init));

auto isEnabled = ansiStylingArgument.isEnabled;
scope(exit) ansiStylingArgument.isEnabled = isEnabled;
Expand Down Expand Up @@ -983,8 +983,8 @@ unittest
@(Command("MYPROG"))
struct T
{
@(Command("cmd1").ShortDescription("Perform cmd 1"))
struct CMD1
@(Command.ShortDescription("Perform cmd 1"))
struct cmd1
{
string a;
}
Expand All @@ -997,15 +997,15 @@ unittest
string c;
string d;

SumType!(CMD1, CMD2) cmd;
SumType!(cmd1, CMD2) cmd;
}

import std.array: appender;

auto a = appender!string;

T receiver;
auto cmd = createCommand!(Config.init)(receiver, getCommandInfo!T(Config.init));
auto cmd = createCommand!(Config.init)(receiver, getTopLevelCommandInfo!T(Config.init));

auto isEnabled = ansiStylingArgument.isEnabled;
scope(exit) ansiStylingArgument.isEnabled = isEnabled;
Expand Down

0 comments on commit e4b5456

Please sign in to comment.