From da03df06a145e00304367bff9815caf5fa0ae004 Mon Sep 17 00:00:00 2001 From: Andrey Zherikov Date: Wed, 23 Oct 2024 13:40:17 -0400 Subject: [PATCH] Support main callback with ref parameter --- source/argparse/api/cli.d | 64 +++++++++++++++++++++++++----- source/argparse/internal/command.d | 15 +++++-- 2 files changed, 66 insertions(+), 13 deletions(-) diff --git a/source/argparse/api/cli.d b/source/argparse/api/cli.d index 1e836a4..25be7e2 100644 --- a/source/argparse/api/cli.d +++ b/source/argparse/api/cli.d @@ -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; @@ -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; @@ -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; }); } } diff --git a/source/argparse/internal/command.d b/source/argparse/internal/command.d index 8759b05..e4936a5 100644 --- a/source/argparse/internal/command.d +++ b/source/argparse/internal/command.d @@ -206,6 +206,7 @@ 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"; /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -213,13 +214,21 @@ 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));