Skip to content

Commit

Permalink
Support main callback with ref parameter
Browse files Browse the repository at this point in the history
  • Loading branch information
andrey-zherikov committed Oct 23, 2024
1 parent ee69ce3 commit da03df0
Show file tree
Hide file tree
Showing 2 changed files with 66 additions and 13 deletions.
64 changes: 54 additions & 10 deletions source/argparse/api/cli.d
Original file line number Diff line number Diff line change
Expand Up @@ -121,8 +121,8 @@ template CLI(Config config, COMMAND)
return res;
}

static int parseArgs(alias newMain)(string[] args, COMMAND initialValue = COMMAND.init)
if(__traits(compiles, { newMain(COMMAND.init); }))
static int parseArgs(alias newMain)(string[] args, auto ref COMMAND initialValue = COMMAND.init)
if(__traits(compiles, { newMain(initialValue); }))
{
alias value = initialValue;

Expand All @@ -139,8 +139,8 @@ template CLI(Config config, COMMAND)
}
}

static int parseArgs(alias newMain)(string[] args, COMMAND initialValue = COMMAND.init)
if(__traits(compiles, { newMain(COMMAND.init, string[].init); }))
static int parseArgs(alias newMain)(string[] args, auto ref COMMAND initialValue = COMMAND.init)
if(__traits(compiles, { newMain(initialValue, string[].init); }))
{
alias value = initialValue;

Expand Down Expand Up @@ -205,11 +205,55 @@ alias CLI(COMMANDS...) = CLI!(Config.init, COMMANDS);

unittest
{
struct Args {}
static struct Args {}

mixin CLI!({
Config cfg;
cfg.errorHandler = (string s) {};
return cfg;
}(), Args).main!((_){});
Args initValue;

enum Config cfg = { errorHandler: (string s) {} };

assert(CLI!(cfg, Args).parseArgs!((_ ){})([]) == 0);
assert(CLI!(cfg, Args).parseArgs!((_, string[] unknown){})([]) == 0);
assert(CLI!(cfg, Args).parseArgs!((_ ){ return 123; })([]) == 123);
assert(CLI!(cfg, Args).parseArgs!((_, string[] unknown){ return 123; })([]) == 123);

assert(CLI!(cfg, Args).parseArgs!((_ ){})([], initValue) == 0);
assert(CLI!(cfg, Args).parseArgs!((_, string[] unknown){})([], initValue) == 0);
assert(CLI!(cfg, Args).parseArgs!((_ ){ return 123; })([], initValue) == 123);
assert(CLI!(cfg, Args).parseArgs!((_, string[] unknown){ return 123; })([], initValue) == 123);

// Ensure that CLI.main is compilable
{ mixin CLI!(cfg, Args).main!((_ ){}); }
{ mixin CLI!(cfg, Args).main!((_, string[] unknown){}); }
{ mixin CLI!(cfg, Args).main!((_ ){ return 123; }); }
{ mixin CLI!(cfg, Args).main!((_, string[] unknown){ return 123; }); }
}

// Ensure that CLI works with non-copyable structs
unittest
{
static struct Args {
@disable this(ref Args);
this(int) {}
}

//Args initValue;
auto initValue = Args(0);

enum Config cfg = { errorHandler: (string s) {} };

assert(CLI!(cfg, Args).parseArgs!((ref _ ){})([]) == 0);
assert(CLI!(cfg, Args).parseArgs!((ref _, string[] unknown){})([]) == 0);
assert(CLI!(cfg, Args).parseArgs!((ref _ ){ return 123; })([]) == 123);
assert(CLI!(cfg, Args).parseArgs!((ref _, string[] unknown){ return 123; })([]) == 123);

assert(CLI!(cfg, Args).parseArgs!((ref _ ){})([], initValue) == 0);
assert(CLI!(cfg, Args).parseArgs!((ref _, string[] unknown){})([], initValue) == 0);
assert(CLI!(cfg, Args).parseArgs!((ref _ ){ return 123; })([], initValue) == 123);
assert(CLI!(cfg, Args).parseArgs!((ref _, string[] unknown){ return 123; })([], initValue) == 123);

// Ensure that CLI.main is compilable
{ mixin CLI!(cfg, Args).main!((ref _ ){}); }
{ mixin CLI!(cfg, Args).main!((ref _, string[] unknown){}); }
{ mixin CLI!(cfg, Args).main!((ref _ ){ return 123; }); }
{ mixin CLI!(cfg, Args).main!((ref _, string[] unknown){ return 123; }); }
}
15 changes: 12 additions & 3 deletions source/argparse/internal/command.d
Original file line number Diff line number Diff line change
Expand Up @@ -206,20 +206,29 @@ private enum hasNoMembersWithUDA(COMMAND) = getSymbolsByUDA!(COMMAND, ArgumentUD
getSymbolsByUDA!(COMMAND, NamedArgument ).length == 0;

private enum isOpFunction(alias mem) = is(typeof(mem) == function) && __traits(identifier, mem).length > 2 && __traits(identifier, mem)[0..2] == "op";
private enum isConstructor(alias mem) = is(typeof(mem) == function) && __traits(identifier, mem) == "__ctor";

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

private template iterateArguments(TYPE)
{
import std.meta: Filter, anySatisfy;

// In case if TYPE is no members with ArgumentUDA, we filter out "op..." functions and ctors
// Otherwise, we pick members with ArgumentUDA
static if(hasNoMembersWithUDA!TYPE)
enum isValidArgumentMember(alias mem) = !isOpFunction!mem && !isConstructor!mem;
else
enum isValidArgumentMember(alias mem) = anySatisfy!(isArgumentUDA, __traits(getAttributes, mem));

template filter(string sym)
{
alias mem = __traits(getMember, TYPE, sym);

enum filter = !is(mem) && !isSubCommand!(typeof(mem)) && (
anySatisfy!(isArgumentUDA, __traits(getAttributes, mem)) ||
hasNoMembersWithUDA!TYPE && !isOpFunction!mem);
enum filter =
!is(mem) && // not a type -- and --
!isSubCommand!(typeof(mem)) && // not subcommand -- and --
isValidArgumentMember!mem; // is valid argument member
}

alias iterateArguments = Filter!(filter, __traits(allMembers, TYPE));
Expand Down

0 comments on commit da03df0

Please sign in to comment.