diff --git a/README.md b/README.md index 48cd2b0..96325fc 100644 --- a/README.md +++ b/README.md @@ -17,15 +17,17 @@ ### Breaking changes -* Custom error handler function (`Config.errorHandler`) now receives message text with ANSI styling if styling is enabled. One can use `argparse.ansi.getUnstyledText` function to remove any styling - this function returns a range of unstyled `string` objects which can be used as is or `join`'ed into a string if needed: `message.getUnstyledText.join`. +* Changes in `Config`: -* `Config.namedArgChar` is renamed to `Config.namedArgPrefix`. + * Custom error handler function (`Config.errorHandler`) now receives message text with ANSI styling if styling is enabled. One can use `argparse.ansi.getUnstyledText` function to remove any styling - this function returns a range of unstyled `string` objects which can be used as is or `join`'ed into a string if needed: `message.getUnstyledText.join`. -* `Config.endOfArgs` is renamed to `Config.endOfNamedArgs`. + * `Config.namedArgChar` is renamed to `Config.namedArgPrefix`. -* `Config.helpStyle` is renamed to `Config.styling`. + * `Config.endOfArgs` is renamed to `Config.endOfNamedArgs`. -* `Config.addHelp` is renamed to `Config.addHelpArgument`. + * `Config.helpStyle` is renamed to `Config.styling`. + + * `Config.addHelp` is renamed to `Config.addHelpArgument`. * `Style.namedArgumentName` is renamed to `Style.argumentName`. @@ -57,6 +59,20 @@ * `@TrailingArguments` UDA is removed: all command line parameters that appear after double-dash `--` are considered as positional arguments. So if those parameters are to be parsed, use `@PositionalArgument` instead of `@TrailingArguments`. +* Functions for parsing customization (`PreValidation`, `Parse`, `Validation` and `Action`) now accept functions as runtime parameters instead of template arguments + + For example, replace this + ```d + .Parse !((string s) { return cast(char) s[1]; }) + .Validation!((char v) { return v >= '0' && v <= '9'; }) + ``` + with + ```d + .Parse ((string s) { return cast(char) s[1]; }) + .Validation((char v) { return v >= '0' && v <= '9'; }) + ``` + +* Dropped support for DMD-2.099. ### Enhancements and bug fixes diff --git a/docs/code_snippets/parsing_customization.d b/docs/code_snippets/parsing_customization.d index 8d7d721..a35f759 100644 --- a/docs/code_snippets/parsing_customization.d +++ b/docs/code_snippets/parsing_customization.d @@ -3,10 +3,10 @@ import argparse; struct T { @(NamedArgument - .PreValidation!((string s) { return s.length > 1 && s[0] == '!'; }) - .Parse !((string s) { return s[1]; }) - .Validation !((char v) { return v >= '0' && v <= '9'; }) - .Action !((ref int a, char v) { a = v - '0'; }) + .PreValidation((string s) { return s.length > 1 && s[0] == '!'; }) + .Parse ((string s) { return cast(char) s[1]; }) + .Validation ((char v) { return v >= '0' && v <= '9'; }) + .Action ((ref int a, char v) { a = v - '0'; }) ) int a; } diff --git a/docs/code_snippets/types_custom.d b/docs/code_snippets/types_custom.d index 5b719f1..0e28393 100644 --- a/docs/code_snippets/types_custom.d +++ b/docs/code_snippets/types_custom.d @@ -6,7 +6,7 @@ struct Value } struct T { - @(NamedArgument.Parse!((string s) { return Value(s); })) + @(NamedArgument.Parse((string s) { return Value(s); })) Value s; } diff --git a/docs/topics/Parsing-customization.md b/docs/topics/Parsing-customization.md index c503c3c..55d17ea 100644 --- a/docs/topics/Parsing-customization.md +++ b/docs/topics/Parsing-customization.md @@ -55,16 +55,24 @@ Return value: the following signatures: - `ParseType parse(string value)` -- `ParseType parse(string[] value)` -- `ParseType parse(RawParam param)` -- `bool parse(ref ParseType receiver, RawParam param)` + + `ParseType parse(string[] value)` + + `ParseType parse(RawParam param)` + + > `ParseType` is a type that a string value is supposed to be parsed to and it is not required be the same as + a type of destination - `argparse` tries to detect this type from provided function. + > + > `ParseType` must not be immutable or const. + > + {title="ParseType"} + - `void parse(ref ParseType receiver, RawParam param)` -- `Result parse(ref ParseType receiver, RawParam param)` -> `ParseType` is a type that a string value is supposed to be parsed to and it is not required be the same as -a type of destination - `argparse` tries to detect this type from provided function. -> -{title="ParseType"} + `bool parse(ref ParseType receiver, RawParam param)` + + `Result parse(ref ParseType receiver, RawParam param)` + Parameters: @@ -84,14 +92,14 @@ signatures: `Result validate(ParseType value)` -- `bool validate(ParseType[] value)` - - `Result validate(ParseType[] value)` - - `bool validate(Param!ParseType param)` `Result validate(Param!ParseType param)` +> `ParseType` is a type that is used in `Parse` modifier or `string` if the latter is omitted. +> +{title="ParseType"} + Parameters: - `value`/`param` contains a value returned from `Parse` step. @@ -106,18 +114,22 @@ Return value: `Action` modifier allows customizing a logic of how "destination" should be changed when argument has a value in command line. It accepts a function with one of the following signatures: -- `bool action(ref T receiver, ParseType value)` - - `void action(ref T receiver, ParseType value)` +- `void action(ref T receiver, ParseType value)` -- `Result action(ref T receiver, ParseType value)` + `bool action(ref T receiver, ParseType value)` - `bool action(ref T receiver, Param!ParseType param)` + `Result action(ref T receiver, ParseType value)` - `void action(ref T receiver, Param!ParseType param)` + `bool action(ref T receiver, Param!ParseType param)` + `Result action(ref T receiver, Param!ParseType param)` +> `ParseType` is a type that is used in `Parse` modifier or `string` if the latter is omitted. +> +{title="ParseType"} + Parameters: - `receiver` is a receiver (destination) which is supposed to be changed based on a `value`/`param`. diff --git a/docs/topics/reference/PositionalNamedArgument.md b/docs/topics/reference/PositionalNamedArgument.md index e498c6b..edb81df 100644 --- a/docs/topics/reference/PositionalNamedArgument.md +++ b/docs/topics/reference/PositionalNamedArgument.md @@ -349,7 +349,7 @@ struct my_command **Signature** ```C++ -PreValidation(alias func)(auto ref ... argument) +PreValidation(auto ref ... argument, RETURN function(VALUE value) func) ``` **Parameters** @@ -363,7 +363,7 @@ PreValidation(alias func)(auto ref ... argument) ```C++ struct my_command { - @(NamedArgument.PreValidation!((string s) { return s.length > 0;})) + @(NamedArgument.PreValidation((string s) { return s.length > 0;})) int a; } ``` @@ -375,7 +375,8 @@ struct my_command **Signature** ```C++ -Parse(alias func)(auto ref ... argument) +Parse(auto ref ... argument, RECEIVER function(VALUE value) func) +Parse(auto ref ... argument, RETURN function(ref RECEIVER receiver, RawParam param) func) ``` **Parameters** @@ -389,7 +390,7 @@ Parse(alias func)(auto ref ... argument) ```C++ struct my_command { - @(NamedArgument.Parse!((string s) { return s[1]; })) + @(NamedArgument.Parse((string s) { return s[1]; })) char a; } ``` @@ -401,7 +402,7 @@ struct my_command **Signature** ```C++ -Validation(alias func)(auto ref ... argument) +Validation(auto ref ... argument, RETURN function(VALUE value) func) ``` **Parameters** @@ -415,7 +416,7 @@ Validation(alias func)(auto ref ... argument) ```C++ struct my_command { - @(NamedArgument.Validation!((int a) { return a >= 0 && a <= 9; })) + @(NamedArgument.Validation((int a) { return a >= 0 && a <= 9; })) int a; } ``` @@ -427,7 +428,7 @@ struct my_command **Signature** ```C++ -Action(alias func)(auto ref ... argument) +Action(auto ref ... argument, RETURN function(ref RECEIVER receiver, VALUE value) func) ``` **Parameters** @@ -441,7 +442,7 @@ Action(alias func)(auto ref ... argument) ```C++ struct my_command { - @(NamedArgument.Action!((ref int a, int v) { a += v; }) + @(NamedArgument.Action((ref int a, int v) { a += v; }) int a; } ``` diff --git a/source/argparse/api/ansi.d b/source/argparse/api/ansi.d index f812a4d..61e6073 100644 --- a/source/argparse/api/ansi.d +++ b/source/argparse/api/ansi.d @@ -3,7 +3,6 @@ module argparse.api.ansi; import argparse.config; import argparse.param; import argparse.api.argument: NamedArgument, Description, NumberOfValues, AllowedValues, Parse, Action, ActionNoValue; -import argparse.internal.parsehelpers: PassThrough; /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /// Public API for ANSI coloring @@ -13,9 +12,9 @@ import argparse.internal.parsehelpers: PassThrough; .Description("Colorize the output. If value is omitted then 'always' is used.") .AllowedValues!(["always","auto","never"]) .NumberOfValues(0, 1) -.Parse!(PassThrough) -.Action!(AnsiStylingArgument.action) -.ActionNoValue!(AnsiStylingArgument.action) +.Parse((string _) => _) +.Action(AnsiStylingArgument.action) +.ActionNoValue(AnsiStylingArgument.actionNoValue) ) private struct AnsiStylingArgument { @@ -26,39 +25,40 @@ private struct AnsiStylingArgument return isEnabled; } - private static void action(ref AnsiStylingArgument receiver, RawParam param) + private enum action = (ref AnsiStylingArgument receiver, Param!string param) { - switch(param.value[0]) + switch(param.value) { case "auto": isEnabled = param.config.stylingMode == Config.StylingMode.on; return; case "always": isEnabled = true; return; case "never": isEnabled = false; return; default: } - } - private static void action(ref AnsiStylingArgument receiver, Param!void param) + }; + + private enum actionNoValue = (ref AnsiStylingArgument receiver, Param!void param) { isEnabled = true; - } + }; } unittest { AnsiStylingArgument arg; - AnsiStylingArgument.action(arg, Param!void.init); + AnsiStylingArgument.actionNoValue(arg, Param!void.init); assert(arg); - AnsiStylingArgument.action(arg, RawParam(null, "", [""])); + AnsiStylingArgument.action(arg, Param!string(null, "", "")); } unittest { AnsiStylingArgument arg; - AnsiStylingArgument.action(arg, RawParam(null, "", ["always"])); + AnsiStylingArgument.action(arg, Param!string(null, "", "always")); assert(arg); - AnsiStylingArgument.action(arg, RawParam(null, "", ["never"])); + AnsiStylingArgument.action(arg, Param!string(null, "", "never")); assert(!arg); } @@ -68,11 +68,11 @@ unittest AnsiStylingArgument arg; config.stylingMode = Config.StylingMode.on; - AnsiStylingArgument.action(arg, RawParam(&config, "", ["auto"])); + AnsiStylingArgument.action(arg, Param!string(&config, "", "auto")); assert(arg); config.stylingMode = Config.StylingMode.off; - AnsiStylingArgument.action(arg, RawParam(&config, "", ["auto"])); + AnsiStylingArgument.action(arg, Param!string(&config, "", "auto")); assert(!arg); } diff --git a/source/argparse/api/argument.d b/source/argparse/api/argument.d index 2e84fe5..fe68220 100644 --- a/source/argparse/api/argument.d +++ b/source/argparse/api/argument.d @@ -4,9 +4,12 @@ import argparse.param; import argparse.result; import argparse.internal.arguments: ArgumentInfo; -import argparse.internal.argumentuda: ArgumentUDA; +import argparse.internal.argumentuda: ArgumentUDA, createArgumentUDA; import argparse.internal.valueparser: ValueParser; -import argparse.internal.parsehelpers: ValueInList; +import argparse.internal.actionfunc; +import argparse.internal.novalueactionfunc; +import argparse.internal.parsefunc; +import argparse.internal.validationfunc; import argparse.internal.utils: formatAllowedValues; /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -82,7 +85,7 @@ auto ref MaxNumberOfValues(T)(auto ref ArgumentUDA!T uda, size_t max) unittest { - ArgumentUDA!void arg; + ArgumentUDA!(ValueParser!(void, void)) arg; assert(!arg.info.hideFromHelp); assert(!arg.info.required); assert(arg.info.minValuesCount.isNull); @@ -109,22 +112,13 @@ unittest arg = arg.MinNumberOfValues(2).MaxNumberOfValues(3); assert(arg.info.minValuesCount.get == 2); assert(arg.info.maxValuesCount.get == 3); - - // values shouldn't be changed - arg.addDefaults(ArgumentUDA!void.init); - assert(arg.info.placeholder == "text"); - assert(arg.info.description.get == "qwer"); - assert(arg.info.hideFromHelp); - assert(!arg.info.required); - assert(arg.info.minValuesCount.get == 2); - assert(arg.info.maxValuesCount.get == 3); } /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// auto PositionalArgument(uint position) { - auto arg = ArgumentUDA!(ValueParser!(void, void, void, void, void, void))(ArgumentInfo.init).Required(); + auto arg = ArgumentUDA!(ValueParser!(void, void))(ArgumentInfo.init).Required(); arg.info.position = position; return arg; } @@ -136,7 +130,7 @@ auto PositionalArgument(uint position, string placeholder) auto NamedArgument(string[] names...) { - return ArgumentUDA!(ValueParser!(void, void, void, void, void, void))(ArgumentInfo(names.dup)).Optional(); + return ArgumentUDA!(ValueParser!(void, void))(ArgumentInfo(names.dup)).Optional(); } unittest @@ -176,7 +170,7 @@ unittest auto AllowNoValue(alias valueToUse, T)(ArgumentUDA!T uda) { - return uda.ActionNoValue!(() => valueToUse); + return ActionNoValue(uda, (ref typeof(valueToUse) _) { _ = valueToUse; }); } auto RequireNoValue(alias valueToUse, T)(ArgumentUDA!T uda) @@ -190,16 +184,14 @@ auto RequireNoValue(alias valueToUse, T)(ArgumentUDA!T uda) unittest { auto uda = NamedArgument.AllowNoValue!({}); - assert(is(typeof(uda) : ArgumentUDA!(ValueParser!(void, void, void, void, void, FUNC)), alias FUNC)); - assert(!is(FUNC == void)); + assert(is(typeof(uda) : ArgumentUDA!(ValueParser!(P, R)), P, R)); assert(uda.info.minValuesCount == 0); } unittest { auto uda = NamedArgument.RequireNoValue!"value"; - assert(is(typeof(uda) : ArgumentUDA!(ValueParser!(void, void, void, void, void, FUNC)), alias FUNC)); - assert(!is(FUNC == void)); + assert(is(typeof(uda) : ArgumentUDA!(ValueParser!(P, R)), P, R)); assert(uda.info.minValuesCount == 0); assert(uda.info.maxValuesCount == 0); } @@ -207,16 +199,22 @@ unittest /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /// Parsing customization -auto PreValidation(alias func, T)(ArgumentUDA!T uda) +auto PreValidation(T, RETURN, VALUE)(ArgumentUDA!T uda, RETURN function(VALUE value) func) +if((is(VALUE == string) || is(VALUE == string[]) || is(VALUE == RawParam)) && + (is(RETURN == bool) || is(RETURN == Result))) { - return ArgumentUDA!(uda.parsingFunc.changePreValidation!func)(uda.tupleof); + auto desc = createArgumentUDA(uda.info, uda.valueParser.changePreValidation(ValidationFunc!(string[])(func))); + + return desc; } -auto Parse(alias func, T)(ArgumentUDA!T uda) +/////////////////////////// + +private auto ParseImpl(T, RECEIVER)(ArgumentUDA!T uda, ParseFunc!RECEIVER func) { - auto desc = ArgumentUDA!(uda.parsingFunc.changeParse!func)(uda.tupleof); + auto desc = createArgumentUDA(uda.info, uda.valueParser.changeParse(func)); - static if(__traits(compiles, { func(string.init); })) + static if(is(RECEIVER == string)) desc.info.minValuesCount = desc.info.maxValuesCount = 1; else { @@ -227,68 +225,98 @@ auto Parse(alias func, T)(ArgumentUDA!T uda) return desc; } -auto Validation(alias func, T)(ArgumentUDA!T uda) +auto Parse(T, RECEIVER, VALUE)(ArgumentUDA!T uda, RECEIVER function(VALUE value) func) +if((is(VALUE == string) || is(VALUE == string[]) || is(VALUE == RawParam))) +{ + return ParseImpl(uda, ParseFunc!RECEIVER(func)); +} + +auto Parse(T, RETURN, RECEIVER)(ArgumentUDA!T uda, RETURN function(ref RECEIVER receiver, RawParam param) func) +if(is(RETURN == void) || is(RETURN == bool) || is(RETURN == Result)) +{ + return ParseImpl(uda, ParseFunc!RECEIVER(func)); +} + +/////////////////////////// + +auto Validation(T, RETURN, VALUE)(ArgumentUDA!T uda, RETURN function(VALUE value) func) +if(is(RETURN == bool) || is(RETURN == Result)) { - return ArgumentUDA!(uda.parsingFunc.changeValidation!func)(uda.tupleof); + static if(!is(VALUE == Param!TYPE, TYPE)) + alias TYPE = VALUE; + + auto desc = createArgumentUDA(uda.info, uda.valueParser.changeValidation(ValidationFunc!TYPE(func))); + + return desc; } -auto Action(alias func, T)(ArgumentUDA!T uda) +/////////////////////////// + +auto Action(T, RETURN, RECEIVER, VALUE)(ArgumentUDA!T uda, RETURN function(ref RECEIVER receiver, VALUE value) func) +if(is(RETURN == void) || is(RETURN == bool) || is(RETURN == Result)) { - return ArgumentUDA!(uda.parsingFunc.changeAction!func)(uda.tupleof); + static if(!is(VALUE == Param!TYPE, TYPE)) + alias TYPE = VALUE; + + auto desc = createArgumentUDA(uda.info, uda.valueParser.changeAction(ActionFunc!(RECEIVER, TYPE)(func))); + + return desc; } -package auto ActionNoValue(alias func, T)(ArgumentUDA!T uda) +/////////////////////////// +private auto ActionNoValueImpl(T, RECEIVER)(ArgumentUDA!T uda, NoValueActionFunc!RECEIVER func) { - auto desc = ArgumentUDA!(uda.parsingFunc.changeNoValueAction!func)(uda.tupleof); + auto desc = createArgumentUDA(uda.info, uda.valueParser.changeNoValueAction(func)); desc.info.minValuesCount = 0; return desc; } +package auto ActionNoValue(T, RETURN, RECEIVER)(ArgumentUDA!T uda, RETURN function(ref RECEIVER receiver) func) +if(is(RETURN == void) || is(RETURN == bool) || is(RETURN == Result)) +{ + return ActionNoValueImpl(uda, NoValueActionFunc!RECEIVER(func)); +} -unittest +package auto ActionNoValue(T, RETURN, RECEIVER)(ArgumentUDA!T uda, RETURN function(ref RECEIVER receiver, Param!void param) func) +if(is(RETURN == void) || is(RETURN == bool) || is(RETURN == Result)) { - auto uda = NamedArgument.PreValidation!({}); - assert(is(typeof(uda) : ArgumentUDA!(ValueParser!(void, FUNC, void, void, void, void)), alias FUNC)); - assert(!is(FUNC == void)); + return ActionNoValueImpl(uda, NoValueActionFunc!RECEIVER(func)); } +/////////////////////////// + unittest { - auto uda = NamedArgument.Parse!({}); - assert(is(typeof(uda) : ArgumentUDA!(ValueParser!(void, void, FUNC, void, void, void)), alias FUNC)); - assert(!is(FUNC == void)); + auto uda = NamedArgument.PreValidation((RawParam _) => true); + assert(is(typeof(uda) : ArgumentUDA!(ValueParser!(void, void)))); } unittest { - auto uda = NamedArgument.Parse!((string _) => _); - assert(is(typeof(uda) : ArgumentUDA!(ValueParser!(void, void, FUNC, void, void, void)), alias FUNC)); - assert(!is(FUNC == void)); + auto uda = NamedArgument.Parse((string _) => _); + assert(is(typeof(uda) : ArgumentUDA!(ValueParser!(P, void)), alias P)); assert(uda.info.minValuesCount == 1); assert(uda.info.maxValuesCount == 1); } unittest { - auto uda = NamedArgument.Parse!((string[] _) => _); - assert(is(typeof(uda) : ArgumentUDA!(ValueParser!(void, void, FUNC, void, void, void)), alias FUNC)); - assert(!is(FUNC == void)); + auto uda = NamedArgument.Parse((string[] _) => _); + assert(is(typeof(uda) : ArgumentUDA!(ValueParser!(P, void)), alias P)); assert(uda.info.minValuesCount == 0); assert(uda.info.maxValuesCount == size_t.max); } unittest { - auto uda = NamedArgument.Validation!({}); - assert(is(typeof(uda) : ArgumentUDA!(ValueParser!(void, void, void, FUNC, void, void)), alias FUNC)); - assert(!is(FUNC == void)); + auto uda = NamedArgument.Validation((RawParam _) => true); + assert(is(typeof(uda) : ArgumentUDA!(ValueParser!(P, void)), alias P)); } unittest { - auto uda = NamedArgument.Action!({}); - assert(is(typeof(uda) : ArgumentUDA!(ValueParser!(void, void, void, void, FUNC, void)), alias FUNC)); - assert(!is(FUNC == void)); + auto uda = NamedArgument.Action((ref string _1, RawParam _2) {}); + assert(is(typeof(uda) : ArgumentUDA!(ValueParser!(P, R)), alias P, alias R)); } /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -296,13 +324,7 @@ unittest auto AllowedValues(alias values, T)(ArgumentUDA!T uda) { - import std.array : assocArray; - import std.range : repeat; - import std.traits: KeyType; - - enum valuesAA = assocArray(values, false.repeat); - - auto desc = uda.Validation!(ValueInList!(values, KeyType!(typeof(valuesAA)))); + auto desc = uda.Validation((Param!(typeof(values[0])) _) => ValueInList(values)(_)); if(desc.info.placeholder.length == 0) desc.info.placeholder = formatAllowedValues(values); @@ -318,11 +340,11 @@ unittest private struct CounterParsingFunction { - static Result parse(T)(ref T receiver, const ref RawParam param) + static Result parseParameter(T)(T* receiver, RawParam param) { assert(param.value.length == 0); - ++receiver; + ++(*receiver); return Result.Success; } @@ -330,7 +352,7 @@ private struct CounterParsingFunction auto Counter(T)(ArgumentUDA!T uda) { - auto desc = ArgumentUDA!CounterParsingFunction(uda.tupleof); + auto desc = ArgumentUDA!CounterParsingFunction(uda.info); desc.info.minValuesCount = 0; desc.info.maxValuesCount = 0; return desc; diff --git a/source/argparse/internal/actionfunc.d b/source/argparse/internal/actionfunc.d new file mode 100644 index 0000000..b24d223 --- /dev/null +++ b/source/argparse/internal/actionfunc.d @@ -0,0 +1,220 @@ +module argparse.internal.actionfunc; + +import argparse.config; +import argparse.param; +import argparse.result; +import argparse.internal.errorhelpers; + +import std.traits; +import std.sumtype; + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +private struct Handler(RECEIVER, PARSE) +{ + static Result opCall(bool function(ref RECEIVER receiver, PARSE value) func, ref RECEIVER receiver, Param!PARSE param) + { + return func(receiver, param.value) ? Result.Success : processingError(param); + } + static Result opCall(void function(ref RECEIVER receiver, PARSE value) func, ref RECEIVER receiver, Param!PARSE param) + { + func(receiver, param.value); + return Result.Success; + } + static Result opCall(Result function(ref RECEIVER receiver, PARSE value) func, ref RECEIVER receiver, Param!PARSE param) + { + return func(receiver, param.value); + } + static Result opCall(bool function(ref RECEIVER receiver, Param!PARSE param) func, ref RECEIVER receiver, Param!PARSE param) + { + return func(receiver, param) ? Result.Success : processingError(param); + } + static Result opCall(void function(ref RECEIVER receiver, Param!PARSE param) func, ref RECEIVER receiver, Param!PARSE param) + { + func(receiver, param); + return Result.Success; + } + static Result opCall(Result function(ref RECEIVER receiver, Param!PARSE param) func, ref RECEIVER receiver, Param!PARSE param) + { + return func(receiver, param); + } +} + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +// bool action(ref T receiver, ParseType value) +// void action(ref T receiver, ParseType value) +// Result action(ref T receiver, ParseType value) +// bool action(ref T receiver, Param!ParseType param) +// void action(ref T receiver, Param!ParseType param) +// Result action(ref T receiver, Param!ParseType param) +package(argparse) struct ActionFunc(RECEIVER, PARSE) +{ + alias getFirstParameter(T) = Parameters!T[0]; + alias TYPES = staticMap!(getFirstParameter, typeof(__traits(getOverloads, Handler!(RECEIVER, PARSE), "opCall"))); + + SumType!TYPES F; + + static foreach(T; TYPES) + this(T func) + { + F = func; + } + + static foreach(T; TYPES) + auto opAssign(T func) + { + F = func; + } + + bool opCast(T : bool)() const + { + return F != typeof(F).init; + } + + Result opCall(ref RECEIVER receiver, Param!PARSE param) const + { + return F.match!(_ => Handler!(RECEIVER, PARSE)(_, receiver, param)); + } +} + +unittest +{ + auto test(T, F)(F func, T values) + { + T receiver; + Config config; + assert(ActionFunc!(T,T)(func)(receiver, Param!T(&config, "", values))); + return receiver; + } + auto testErr(T, F)(F func, T values) + { + T receiver; + Config config; + return ActionFunc!(T,T)(func)(receiver, Param!T(&config, "", values)); + } + + // Result action(ref T receiver, ParseType value) + assert(test((ref string[] p, string[] a) { p=a; return Result.Success; }, ["1","2","3"]) == ["1","2","3"]); + assert(testErr((ref string[] p, string[] a) => Result.Error("error text"), ["1","2","3"]).isError("error text")); + + // bool action(ref T receiver, ParseType value) + assert(test((ref string[] p, string[] a) { p=a; return true; }, ["1","2","3"]) == ["1","2","3"]); + assert(testErr((ref string[] p, string[] a) => false, ["1","2","3"]).isError("Can't process value")); + + // void action(ref T receiver, ParseType value) + assert(test((ref string[] p, string[] a) { p=a; }, ["1","2","3"]) == ["1","2","3"]); + + // Result action(ref T receiver, Param!ParseType param) + assert(test((ref string[] p, Param!(string[]) a) { p=a.value; return Result.Success; }, ["1","2","3"]) == ["1","2","3"]); + assert(testErr((ref string[] p, Param!(string[]) a) => Result.Error("error text"), ["1","2","3"]).isError("error text")); + + // bool action(ref T receiver, Param!ParseType param) + assert(test((ref string[] p, Param!(string[]) a) { p=a.value; return true; }, ["1","2","3"]) == ["1","2","3"]); + assert(testErr((ref string[] p, Param!(string[]) a) => false, ["1","2","3"]).isError("Can't process value")); + + // void action(ref T receiver, Param!ParseType param) + assert(test((ref string[] p, Param!(string[]) a) { p=a.value; }, ["1","2","3"]) == ["1","2","3"]); +} + +unittest +{ + alias test = (int[] v1, int[] v2) { + int[] res; + + Param!(int[]) param; + + enum append = ActionFunc!(int[],int[])((ref int[] _1, int[] _2) { _1 ~= _2; }); + + param.value = v1; append(res, param); + + param.value = v2; append(res, param); + + return res; + }; + assert(test([1,2,3],[7,8,9]) == [1,2,3,7,8,9]); +} + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +package enum Assign(TYPE) = ActionFunc!(TYPE, TYPE) + ((ref TYPE _1, TYPE _2) + { + _1 = _2; + }); + +unittest +{ + Config config; + int i; + Assign!int(i,Param!int(&config,"",7)); + assert(i == 7); +} + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +package enum Append(TYPE) = ActionFunc!(TYPE, TYPE) + ((ref TYPE _1, TYPE _2) + { + _1 ~= _2; + }); + +unittest +{ + Config config; + int[] i; + Append!(int[])(i,Param!(int[])(&config,"",[1,2,3])); + Append!(int[])(i,Param!(int[])(&config,"",[7,8,9])); + assert(i == [1, 2, 3, 7, 8, 9]); +} + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +package enum Extend(TYPE) = ActionFunc!(TYPE, ForeachType!TYPE) + ((ref TYPE _1, ForeachType!TYPE _2) + { + _1 ~= _2; + }); + +unittest +{ + Config config; + int[][] i; + Extend!(int[][])(i,Param!(int[])(&config,"",[1,2,3])); + Extend!(int[][])(i,Param!(int[])(&config,"",[7,8,9])); + assert(i == [[1,2,3],[7,8,9]]); +} + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +package enum CallFunction(FUNC) = ActionFunc!(FUNC, string[]) + ((ref FUNC func, RawParam param) + { + // ... func() + static if(__traits(compiles, { func(); })) + { + func(); + } + // ... func(RawParam param) + else static if(__traits(compiles, { func(param); })) + { + func(param); + } + // ... func(string[] value) + else static if(__traits(compiles, { func(param.value); })) + { + func(param.value); + } + // ... func(string value) + else static if(__traits(compiles, { func(param.value[0]); })) + { + foreach(value; param.value) + func(value); + } + else + static assert(false, "Unsupported callback: " ~ FUNC.stringof); + + return Result.Success; + }); + diff --git a/source/argparse/internal/argumentuda.d b/source/argparse/internal/argumentuda.d index 6fe0fb7..470e3aa 100644 --- a/source/argparse/internal/argumentuda.d +++ b/source/argparse/internal/argumentuda.d @@ -1,16 +1,40 @@ module argparse.internal.argumentuda; +import argparse.config; +import argparse.param; +import argparse.result; import argparse.internal.arguments: ArgumentInfo; /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +package(argparse) auto createArgumentUDA(ValueParser)(ArgumentInfo info, ValueParser valueParser) +{ + return ArgumentUDA!ValueParser(info, valueParser); +} + package(argparse) struct ArgumentUDA(ValueParser) { - ArgumentInfo info; + package(argparse) ArgumentInfo info; + + package(argparse) ValueParser valueParser; + + package Result parse(COMMAND_STACK, RECEIVER)(const Config config, const COMMAND_STACK cmdStack, ref RECEIVER receiver, string argName, string[] rawValues) + { + try + { + auto res = info.checkValuesCount(config, argName, rawValues.length); + if(!res) + return res; - alias parsingFunc = ValueParser; + return valueParser.parseParameter(receiver, RawParam(&config, argName, rawValues)); + } + catch(Exception e) + { + return Result.Error("Argument '", config.styling.argumentName(argName), ": ", e.msg); + } + } - auto addDefaults(T)(ArgumentUDA!T uda) + package auto addDefaults(T)(ArgumentUDA!T uda) { auto newInfo = info; @@ -22,9 +46,77 @@ package(argparse) struct ArgumentUDA(ValueParser) if(newInfo.minValuesCount.isNull()) newInfo.minValuesCount = uda.info.minValuesCount; if(newInfo.maxValuesCount.isNull()) newInfo.maxValuesCount = uda.info.maxValuesCount; - static if(is(ValueParser == void)) - return ArgumentUDA!ValueParser(newInfo); + auto newValueParser = valueParser.addDefaults(uda.valueParser); + + return createArgumentUDA(newInfo, newValueParser); + } + + auto addDefaultsForType(T)() + { + static if(is(typeof(valueParser.addTypeDefaults!T))) + { + auto newValueParser = valueParser.addTypeDefaults!T; + + return createArgumentUDA(info, newValueParser); + } else - return ArgumentUDA!(ValueParser.addDefaults!T)(newInfo); + return this; + } +} + +unittest +{ + struct S(string value) + { + string str = value; + auto addDefaults(T)(T s) { + static if(value.length == 0) + return s; + else + return this; + } + } + + ArgumentUDA!(S!"foo1") arg1; + arg1.info.shortNames = ["a1","b1"]; + arg1.info.longNames = ["aa1","bb1"]; + arg1.info.placeholder = "ph1"; + arg1.info.description = "des1"; + arg1.info.position = 1; + arg1.info.minValuesCount = 2; + arg1.info.maxValuesCount = 3; + + ArgumentUDA!(S!"foo2") arg2; + arg2.info.shortNames = ["a2","b2"]; + arg2.info.longNames = ["aa2","bb2"]; + arg2.info.placeholder = "ph2"; + arg2.info.description = "des2"; + arg2.info.position = 10; + arg2.info.minValuesCount = 20; + arg2.info.maxValuesCount = 30; + + { + // values shouldn't be changed + auto res = arg1.addDefaults(arg2); + assert(res.info.shortNames == ["a1", "b1"]); + assert(res.info.longNames == ["aa1", "bb1"]); + assert(res.info.placeholder == "ph1"); + assert(res.info.description.get == "des1"); + assert(res.info.position.get == 1); + assert(res.info.minValuesCount.get == 2); + assert(res.info.maxValuesCount.get == 3); + assert(res.valueParser.str == "foo1"); + } + + { // values should be changed + auto res = ArgumentUDA!(S!"").init.addDefaults(arg1); + assert(res.info.shortNames == ["a1", "b1"]); + assert(res.info.longNames == ["aa1", "bb1"]); + assert(res.info.placeholder == "ph1"); + assert(res.info.description.get == "des1"); + assert(res.info.position.get == 1); + assert(res.info.minValuesCount.get == 2); + assert(res.info.maxValuesCount.get == 3); + assert(res.valueParser.str == "foo1"); } } diff --git a/source/argparse/internal/argumentudahelpers.d b/source/argparse/internal/argumentudahelpers.d index b593a58..90e0286 100644 --- a/source/argparse/internal/argumentudahelpers.d +++ b/source/argparse/internal/argumentudahelpers.d @@ -92,7 +92,11 @@ package auto getMemberArgumentUDA(TYPE, string symbol)(const Config config) else enum initUDA = memberUDA; - auto result = initUDA; + static if(is(MemberType == function) || is(MemberType == delegate)) + auto result = initUDA.addDefaultsForType!(typeof(&__traits(getMember, TYPE.init, symbol))); + else + auto result = initUDA.addDefaultsForType!MemberType; + result.info = result.info.finalize!MemberType(config, symbol); static if(initUDA.info.minValuesCount.isNull) result.info.minValuesCount = defaultValuesCount!MemberType.min; diff --git a/source/argparse/internal/command.d b/source/argparse/internal/command.d index e4936a5..53e2b2f 100644 --- a/source/argparse/internal/command.d +++ b/source/argparse/internal/command.d @@ -46,29 +46,12 @@ private Result ArgumentParsingFunction(Config config, alias uda, RECEIVER)(const string argName, string[] rawValues) { - static if(is(typeof(uda.parse))) - return uda.parse!config(cmdStack, receiver, argName, rawValues); + static if(uda.info.memberSymbol) + auto target = &__traits(getMember, receiver, uda.info.memberSymbol); else - try - { - auto res = uda.info.checkValuesCount(config, argName, rawValues.length); - if(!res) - return res; - - const cfg = config; - auto param = RawParam(&cfg, argName, rawValues); - - auto target = &__traits(getMember, receiver, uda.info.memberSymbol); + auto target = null; - static if(is(typeof(target) == function) || is(typeof(target) == delegate)) - return uda.parsingFunc.parse(target, param); - else - return uda.parsingFunc.parse(*target, param); - } - catch(Exception e) - { - return Result.Error("Argument '", config.styling.argumentName(argName), ": ", e.msg); - } + return uda.parse(config, cmdStack, target, argName, rawValues); } private Result ArgumentCompleteFunction(Config config, alias uda, RECEIVER)(const Command[] cmdStack, diff --git a/source/argparse/internal/errorhelpers.d b/source/argparse/internal/errorhelpers.d new file mode 100644 index 0000000..4dd926d --- /dev/null +++ b/source/argparse/internal/errorhelpers.d @@ -0,0 +1,46 @@ +module argparse.internal.errorhelpers; + +import argparse.config; +import argparse.param; +import argparse.result; + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +package Result processingError(T)(Param!T param, string prefix = "Can't process value") +{ + import std.conv: to; + import std.array: appender; + + auto a = appender!string(prefix); + + static if(is(typeof(param.value))) + { + a ~= " '"; + a ~= param.config.styling.positionalArgumentValue(param.value.to!string); + a ~= "'"; + } + + if(param.name.length > 0 && param.name[0] == param.config.namedArgPrefix) + { + a ~= " for argument '"; + a ~= param.config.styling.argumentName(param.name); + a ~= "'"; + } + + return Result.Error(a[]); +} + +package Result invalidValueError(T)(Param!T param) +{ + return processingError(param, "Invalid value"); +} + + +unittest +{ + Config config; + assert(processingError(Param!void(&config, "")).isError("Can't process value")); + assert(processingError(Param!void(&config, "--abc")).isError("Can't process value for argument","--abc")); + assert(processingError(Param!(int[])(&config, "", [1,2])).isError("Can't process value '","[1, 2]")); + assert(processingError(Param!(int[])(&config, "--abc", [1,2])).isError("Can't process value '","[1, 2]","' for argument '","--abc")); +} diff --git a/source/argparse/internal/help.d b/source/argparse/internal/help.d index 511283b..37371ad 100644 --- a/source/argparse/internal/help.d +++ b/source/argparse/internal/help.d @@ -154,7 +154,7 @@ package struct HelpArgumentUDA return info; }(); - auto parse(COMMAND_STACK)(const Config config, const COMMAND_STACK cmdStack, string argName, string[] rawValues) + Result parse(COMMAND_STACK, RECEIVER)(const Config config, const COMMAND_STACK cmdStack, ref RECEIVER, string argName, string[] rawValues) { import std.stdio: stdout; import std.algorithm: map; @@ -169,11 +169,6 @@ package struct HelpArgumentUDA return Result.Error(0, ""); // hack to force-exit (to be removed) } - - auto parse(Config config, COMMAND_STACK, RECEIVER)(const COMMAND_STACK cmdStack, ref RECEIVER, string argName, string[] rawValues) - { - return parse(config, cmdStack, argName, rawValues); - } } unittest diff --git a/source/argparse/internal/novalueactionfunc.d b/source/argparse/internal/novalueactionfunc.d new file mode 100644 index 0000000..a709583 --- /dev/null +++ b/source/argparse/internal/novalueactionfunc.d @@ -0,0 +1,151 @@ +module argparse.internal.novalueactionfunc; + +import argparse.config; +import argparse.param; +import argparse.result; +import argparse.internal.errorhelpers; + +import std.traits; +import std.sumtype; + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +private struct Handler(RECEIVER) +{ + static Result opCall(bool function(ref RECEIVER receiver) func, ref RECEIVER receiver, Param!void param) + { + return func(receiver) ? Result.Success : processingError(param); + } + static Result opCall(void function(ref RECEIVER receiver) func, ref RECEIVER receiver, Param!void param) + { + func(receiver); + return Result.Success; + } + static Result opCall(Result function(ref RECEIVER receiver) func, ref RECEIVER receiver, Param!void param) + { + return func(receiver); + } + static Result opCall(bool function(ref RECEIVER receiver, Param!void param) func, ref RECEIVER receiver, Param!void param) + { + return func(receiver, param) ? Result.Success : processingError(param); + } + static Result opCall(void function(ref RECEIVER receiver, Param!void param) func, ref RECEIVER receiver, Param!void param) + { + func(receiver, param); + return Result.Success; + } + static Result opCall(Result function(ref RECEIVER receiver, Param!void param) func, ref RECEIVER receiver, Param!void param) + { + return func(receiver, param); + } +} + +// bool action(ref DEST receiver) +// void action(ref DEST receiver) +// Result action(ref DEST receiver) +// bool action(ref DEST receiver, Param!void param) +// void action(ref DEST receiver, Param!void param) +// Result action(ref DEST receiver, Param!void param) +package(argparse) struct NoValueActionFunc(RECEIVER) +{ + alias getFirstParameter(T) = Parameters!T[0]; + alias TYPES = staticMap!(getFirstParameter, typeof(__traits(getOverloads, Handler!RECEIVER, "opCall"))); + + SumType!TYPES F; + + static foreach(T; TYPES) + this(T func) + { + F = func; + } + + static foreach(T; TYPES) + auto opAssign(T func) + { + F = func; + } + + bool opCast(T : bool)() const + { + return F != typeof(F).init; + } + + Result opCall(ref RECEIVER receiver, Param!void param) const + { + return F.match!(_ => Handler!RECEIVER(_, receiver, param)); + } +} + +unittest +{ + auto test(T, F)(F func) + { + T receiver; + assert(NoValueActionFunc!T(func)(receiver, Param!void.init)); + return receiver; + } + auto testErr(T, F)(F func) + { + T receiver; + return NoValueActionFunc!T(func)(receiver, Param!void.init); + } + + // Result action(ref DEST receiver) + assert(test!int((ref int r) { r=7; return Result.Success; }) == 7); + assert(testErr!int((ref int r) => Result.Error("error text")).isError("error text")); + + // bool action(ref DEST receiver) + assert(test!int((ref int p) { p=7; return true; }) == 7); + assert(testErr!int((ref int p) => false).isError("Can't process value")); + + // void action(ref DEST receiver) + assert(test!int((ref int p) { p=7; }) == 7); + + // Result action(ref DEST receiver, Param!void param) + assert(test!int((ref int r, Param!void p) { r=7; return Result.Success; }) == 7); + assert(testErr!int((ref int r, Param!void p) => Result.Error("error text")).isError("error text")); + + // bool action(ref DEST receiver, Param!void param) + assert(test!int((ref int r, Param!void p) { r=7; return true; }) == 7); + assert(testErr!int((ref int r, Param!void p) => false).isError("Can't process value")); + + // void action(ref DEST receiver, Param!void param) + assert(test!int((ref int r, Param!void p) { r=7; }) == 7); +} + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +package enum CallFunctionNoParam(FUNC) = NoValueActionFunc!FUNC + ((ref FUNC func, Param!void param) + { + // ... func() + static if(__traits(compiles, { func(); })) + { + func(); + } + // ... func(string value) + else static if(__traits(compiles, { func(string.init); })) + { + func(string.init); + } + // ... func(string[] value) + else static if(__traits(compiles, { func([]); })) + { + func([]); + } + // ... func(Param!void param) + else static if(__traits(compiles, { func(param); })) + { + func(param); + } + // ... func(RawParam param) + else static if(__traits(compiles, { func(RawParam.init); })) + { + func(RawParam(param.config, param.name)); + } + else + static assert(false, "Unsupported callback: " ~ FUNC.stringof); + + return Result.Success; + }); diff --git a/source/argparse/internal/parsefunc.d b/source/argparse/internal/parsefunc.d new file mode 100644 index 0000000..406cfc2 --- /dev/null +++ b/source/argparse/internal/parsefunc.d @@ -0,0 +1,187 @@ +module argparse.internal.parsefunc; + +import argparse.config; +import argparse.param; +import argparse.result; +import argparse.internal.errorhelpers; + +import std.traits; +import std.sumtype; + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +private struct Handler(TYPE) +{ + static Result opCall(TYPE function(string[] value) func, ref TYPE receiver, RawParam param) + { + receiver = func(param.value); + return Result.Success; + } + static Result opCall(TYPE function(string value) func, ref TYPE receiver, RawParam param) + { + foreach (value; param.value) + receiver = func(value); + return Result.Success; + } + static Result opCall(TYPE function(RawParam param) func, ref TYPE receiver, RawParam param) + { + receiver = func(param); + return Result.Success; + } + static Result opCall(Result function(ref TYPE receiver, RawParam param) func, ref TYPE receiver, RawParam param) + { + return func(receiver, param); + } + static Result opCall(bool function(ref TYPE receiver, RawParam param) func, ref TYPE receiver, RawParam param) + { + return func(receiver, param) ? Result.Success : processingError(param); + } + static Result opCall(void function(ref TYPE receiver, RawParam param) func, ref TYPE receiver, RawParam param) + { + func(receiver, param); + return Result.Success; + } +} + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +// T parse(string[] value) +// T parse(string value) +// T parse(RawParam param) +// Result parse(ref T receiver, RawParam param) +// bool parse(ref T receiver, RawParam param) +// void parse(ref T receiver, RawParam param) +package(argparse) struct ParseFunc(RECEIVER) +{ + alias getFirstParameter(T) = Parameters!T[0]; + alias TYPES = staticMap!(getFirstParameter, typeof(__traits(getOverloads, Handler!RECEIVER, "opCall"))); + + SumType!TYPES F; + + static foreach(T; TYPES) + this(T func) + { + F = func; + } + + static foreach(T; TYPES) + auto opAssign(T func) + { + F = func; + } + + bool opCast(T : bool)() const + { + return F != typeof(F).init; + } + + Result opCall(ref RECEIVER receiver, RawParam param) const + { + return F.match!(_ => Handler!RECEIVER(_, receiver, param)); + } +} + +unittest +{ + auto test(T, F)(F func, string[] values) + { + T receiver; + Config config; + assert(ParseFunc!T(func)(receiver, RawParam(&config, "", values))); + return receiver; + } + auto testErr(T, F)(F func, string[] values) + { + T receiver; + Config config; + return ParseFunc!T(func)(receiver, RawParam(&config, "", values)); + } + + // T parse(string value) + assert(test!string((string a) => a, ["1","2","3"]) == "3"); + + // T parse(string[] value) + assert(test!(string[])((string[] a) => a, ["1","2","3"]) == ["1","2","3"]); + + // T parse(RawParam param) + assert(test!string((RawParam p) => p.value[0], ["1","2","3"]) == "1"); + + // Result parse(ref T receiver, RawParam param) + assert(test!(string[])((ref string[] r, RawParam p) { r = p.value; return Result.Success; }, ["1","2","3"]) == ["1","2","3"]); + assert(testErr!(string[])((ref string[] r, RawParam p) => Result.Error("error text"), ["1","2","3"]).isError("error text")); + + // bool parse(ref T receiver, RawParam param) + assert(test!(string[])((ref string[] r, RawParam p) { r = p.value; return true; }, ["1","2","3"]) == ["1","2","3"]); + assert(testErr!(string[])((ref string[] r, RawParam p) => false, ["1","2","3"]).isError("Can't process value")); + + // void parse(ref T receiver, RawParam param) + assert(test!(string[])((ref string[] r, RawParam p) { r = p.value; }, ["1","2","3"]) == ["1","2","3"]); +} + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +package enum Convert(TYPE) = ParseFunc!TYPE + ((ref TYPE receiver, RawParam param) + { + try + { + import std.conv: to; + + foreach (value; param.value) + receiver = value.length > 0 ? value.to!TYPE : TYPE.init; + + return Result.Success; + } + catch(Exception e) + { + return Result.Error(e.msg); + } + }); + +unittest +{ + alias test(T) = (string value) + { + T receiver; + assert(Convert!T(receiver, RawParam(null, "", [value]))); + return receiver; + }; + + assert(test!int("7") == 7); + assert(test!string("7") == "7"); + assert(test!char("7") == '7'); +} + +unittest +{ + alias testErr(T) = (string value) + { + T receiver; + return Convert!T(receiver, RawParam(null, "", [value])); + }; + + assert(testErr!int("unknown").isError()); + assert(testErr!bool("unknown").isError()); +} + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +// has to do this way otherwise DMD compiler chokes - linker reports unresolved symbol for lambda: +// Error: undefined reference to `pure nothrow @nogc @safe immutable(char)[][] argparse.internal.parsefunc.__lambda12(immutable(char)[][])` +// referenced from `pure nothrow @nogc @safe argparse.internal.valueparser.ValueParser!(immutable(char)[][], void delegate()).ValueParser argparse.internal.valueparser.ValueParser!(void, void).ValueParser.addTypeDefaults!(void delegate()).addTypeDefaults()` +private enum PassThroughImpl(TYPE) = ParseFunc!TYPE + ((TYPE value) + { + return value; + }); + +package enum PassThrough = PassThroughImpl!(string[]); + +unittest +{ + Config config; + string[] s; + PassThrough(s, Param!(string[])(&config,"",["7","8"])); + assert(s == ["7","8"]); +} diff --git a/source/argparse/internal/parsehelpers.d b/source/argparse/internal/parsehelpers.d deleted file mode 100644 index 2bda91f..0000000 --- a/source/argparse/internal/parsehelpers.d +++ /dev/null @@ -1,241 +0,0 @@ -module argparse.internal.parsehelpers; - -import argparse.param; -import argparse.result; - -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -package Result Convert(T)(ref T receiver, RawParam param) -{ - try - { - import std.conv: to; - - foreach (value; param.value) - receiver = value.length > 0 ? value.to!T : T.init; - - return Result.Success; - } - catch(Exception e) - { - return Result.Error(e.msg); - } -} - -unittest -{ - alias test(T) = (string value) - { - T receiver; - assert(Convert!T(receiver, RawParam(null, "", [value]))); - return receiver; - }; - - assert(test!int("7") == 7); - assert(test!string("7") == "7"); - assert(test!char("7") == '7'); -} - -unittest -{ - alias testErr(T) = (string value) - { - T receiver; - return Convert!T(receiver, RawParam(null, "", [value])); - }; - - assert(testErr!int("unknown").isError()); - assert(testErr!bool("unknown").isError()); -} - -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -package(argparse) string[] PassThrough(string[] values) -{ - return values; -} - -unittest -{ - assert(PassThrough(["7","8"]) == ["7","8"]); -} - -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -package struct Assign -{ - static void opCall(T)(ref T param, T[] value) - { - foreach(ref v; value) - param = v; - } - static void opCall(T)(ref T receiver, T value) - { - receiver = value; - } -} - -unittest -{ - int i; - Assign(i,7); - assert(i == 7); - - Assign(i,[1,2,3]); - assert(i == 3); -} -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -package struct Append -{ - static void opCall(T)(ref T param, T value) - { - param ~= value; - } -} - -unittest -{ - int[] i; - Append(i, [1, 2, 3]); - Append(i, [7, 8, 9]); - assert(i == [1, 2, 3, 7, 8, 9]); -} - -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -package struct Extend -{ - static void opCall(T)(ref T[] param, T value) - { - param ~= value; - } -} - -unittest -{ - int[][] i; - Extend(i,[1,2,3]); - Extend(i,[7,8,9]); - assert(i == [[1,2,3],[7,8,9]]); -} - -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -package auto CallFunction(F)(ref F func, RawParam param) -{ - // ... func() - static if(__traits(compiles, { func(); })) - { - func(); - } - // ... func(string value) - else static if(__traits(compiles, { func(param.value[0]); })) - { - foreach(value; param.value) - func(value); - } - // ... func(string[] value) - else static if(__traits(compiles, { func(param.value); })) - { - func(param.value); - } - // ... func(RawParam param) - else static if(__traits(compiles, { func(param); })) - { - func(param); - } - else - static assert(false, "Unsupported callback: " ~ F.stringof); -} - -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -package auto CallFunctionNoParam(F)(ref F func, Param!void param) -{ - // ... func() - static if(__traits(compiles, { func(); })) - { - func(); - } - // ... func(string value) - else static if(__traits(compiles, { func(string.init); })) - { - func(string.init); - } - // ... func(string[] value) - else static if(__traits(compiles, { func([]); })) - { - func([]); - } - // ... func(Param!void param) - else static if(__traits(compiles, { func(param); })) - { - func(param); - } - // ... func(RawParam param) - else static if(__traits(compiles, { func(RawParam.init); })) - { - func(RawParam(param.config, param.name)); - } - else - static assert(false, "Unsupported callback: " ~ F.stringof); -} - -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -package(argparse) template ValueInList(alias values, TYPE) -{ - static auto ValueInList(Param!TYPE param) - { - import std.algorithm: map; - import std.array : assocArray, join; - import std.range : repeat, front; - import std.conv: to; - - enum valuesAA = assocArray(values, false.repeat); - enum allowedValues = values.to!(string[]); - - auto valueStyle = (param.name.length == 0 || param.name[0] != param.config.namedArgPrefix) ? - param.config.styling.positionalArgumentValue : - param.config.styling.namedArgumentValue; - - static if(is(typeof(values.front) == TYPE)) - auto paramValues = [param.value]; - else - auto paramValues = param.value; - - foreach(value; paramValues) - if(!(value in valuesAA)) - return Result.Error("Invalid value '", valueStyle(value.to!string), "' for argument '", param.config.styling.argumentName(param.name), - "'.\nValid argument values are: ", allowedValues.map!(_ => valueStyle(_)).join(",")); - - return Result.Success; - } - static auto ValueInList(Param!(TYPE[]) param) - { - foreach(ref value; param.value) - { - auto res = ValueInList!(values, TYPE)(Param!TYPE(param.config, param.name, value)); - if(!res) - return res; - } - return Result.Success; - } -} - -unittest -{ - enum values = ["a","b","c"]; - - import argparse.config; - Config config; - - assert(ValueInList!(values, string)(Param!string(&config, "", "b"))); - assert(!ValueInList!(values, string)(Param!string(&config, "", "d"))); - - assert(ValueInList!(values, string)(RawParam(&config, "", ["b"]))); - assert(ValueInList!(values, string)(RawParam(&config, "", ["b","a"]))); - assert(!ValueInList!(values, string)(RawParam(&config, "", ["d"]))); - assert(!ValueInList!(values, string)(RawParam(&config, "", ["b","d"]))); -} diff --git a/source/argparse/internal/validationfunc.d b/source/argparse/internal/validationfunc.d new file mode 100644 index 0000000..b88eb2b --- /dev/null +++ b/source/argparse/internal/validationfunc.d @@ -0,0 +1,231 @@ +module argparse.internal.validationfunc; + +import argparse.config; +import argparse.param; +import argparse.result; +import argparse.internal.errorhelpers; + +import std.traits; +import std.sumtype; + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +private struct Handler(TYPE) +{ + static Result opCall(bool function(TYPE value) func, Param!TYPE param) + { + return func(param.value) ? Result.Success : invalidValueError(param); + } + static Result opCall(Result function(TYPE value) func, Param!TYPE param) + { + return func(param.value); + } + static Result opCall(bool function(Param!TYPE param) func, Param!TYPE param) + { + return func(param) ? Result.Success : invalidValueError(param); + } + static Result opCall(Result function(Param!TYPE param) func, Param!TYPE param) + { + return func(param); + } + + static if(isArray!TYPE) + { + static Result opCall(bool function(ForeachType!TYPE value) func, Param!TYPE param) + { + foreach(value; param.value) + if(!func(value)) + return invalidValueError(param); + return Result.Success; + } + static Result opCall(Result function(ForeachType!TYPE value) func, Param!TYPE param) + { + foreach (value; param.value) + { + Result res = func(value); + if (!res) + return res; + } + return Result.Success; + } + } +} + +// bool validate(T value) +// bool validate(T[] value) +// bool validate(Param!T param) +// Result validate(T value) +// Result validate(T[] value) +// Result validate(Param!T param) +package(argparse) struct ValidationFunc(TYPE) +{ + alias getFirstParameter(T) = Parameters!T[0]; + alias TYPES = staticMap!(getFirstParameter, typeof(__traits(getOverloads, Handler!TYPE, "opCall"))); + + SumType!TYPES F; + + static foreach(T; TYPES) + this(T func) + { + F = func; + } + + static foreach(T; TYPES) + auto opAssign(T func) + { + F = func; + } + + bool opCast(T : bool)() const + { + return F != typeof(F).init; + } + + Result opCall(Param!TYPE param) const + { + return F.match!(_ => Handler!TYPE(_, param)); + } +} + +unittest +{ + auto test(T, F)(F func, T[] values) + { + Config config; + return ValidationFunc!(T[])(func)(Param!(T[])(&config, "", values)); + } + + // Result validate(Param!T param) + assert(test((RawParam _) => Result.Success, ["1","2","3"])); + assert(test((RawParam _) => Result.Error("error text"), ["1","2","3"]).isError("error text")); + + // bool validate(Param!T param) + assert(test((RawParam _) => true, ["1","2","3"])); + assert(test((RawParam _) => false, ["1","2","3"]).isError("Invalid value")); + + // Result validate(T value) + assert(test((string _) => Result.Success, ["1","2","3"])); + assert(test((string _) => Result.Error("error text"), ["1","2","3"]).isError("error text")); + + // bool validate(T value) + assert(test((string _) => true, ["1","2","3"])); + assert(test((string _) => false, ["1","2","3"]).isError("Invalid value")); + + // Result validate(T[] value) + assert(test((string[] _) => Result.Success, ["1","2","3"])); + assert(test((string[] _) => Result.Error("error text"), ["1","2","3"]).isError("error text")); + + // bool validate(T[] value) + assert(test((string[] _) => true, ["1","2","3"])); + assert(test((string[] _) => false, ["1","2","3"]).isError("Invalid value")); +} + +unittest +{ + auto test(T, F)(F func, T[] values) + { + Config config; + return ValidationFunc!(T[])(func)(Param!(T[])(&config, "--argname", values)); + } + + // Result validate(Param!T param) + assert(test((RawParam _) => Result.Success, ["1","2","3"])); + assert(test((RawParam _) => Result.Error("error text"), ["1","2","3"]).isError("error text")); + + // bool validate(Param!T param) + assert(test((RawParam _) => true, ["1","2","3"])); + assert(test((RawParam _) => false, ["1","2","3"]).isError("Invalid value","for argument","--argname")); + + // Result validate(T value) + assert(test((string _) => Result.Success, ["1","2","3"])); + assert(test((string _) => Result.Error("error text"), ["1","2","3"]).isError("error text")); + + // bool validate(T value) + assert(test((string _) => true, ["1","2","3"])); + assert(test((string _) => false, ["1","2","3"]).isError("Invalid value","for argument","--argname")); + + // Result validate(T[] value) + assert(test((string[] _) => Result.Success, ["1","2","3"])); + assert(test((string[] _) => Result.Error("error text"), ["1","2","3"]).isError("error text")); + + // bool validate(T[] value) + assert(test((string[] _) => true, ["1","2","3"])); + assert(test((string[] _) => false, ["1","2","3"]).isError("Invalid value","for argument","--argname")); +} + +unittest +{ + auto test(T, F)(F func) + { + Config config; + return ValidationFunc!F(func)(RawParam(&config, "", ["1","2","3"])); + } + + static assert(!__traits(compiles, { test!(string[])(() {}); })); + static assert(!__traits(compiles, { test!(string[])((int,int) {}); })); +} + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +package(argparse) auto ValueInList(TYPE)(TYPE[] values...) +{ + struct Impl { + bool[TYPE] values; + + this(TYPE[] values) + { + import std.array : assocArray; + import std.range : repeat; + + this.values = assocArray(values, false.repeat); + } + + Result opCall(Param!TYPE param) const + { + if(!(param.value in values)) + { + import std.algorithm : map; + import std.array : join; + import std.conv: to; + + auto valueStyle = (param.name.length == 0 || param.name[0] != param.config.namedArgPrefix) ? + param.config.styling.positionalArgumentValue : + param.config.styling.namedArgumentValue; + + return Result.Error("Invalid value '", valueStyle(param.value.to!string), "' for argument '", param.config.styling.argumentName(param.name), + "'.\nValid argument values are: ", values.keys.map!(_ => valueStyle(_.to!string)).join(",")); + } + + return Result.Success; + } + + Result opCall(Param!(TYPE[]) param) const + { + foreach(ref value; param.value) + { + auto res = opCall(Param!TYPE(param.config, param.name, value)); + if(!res) + return res; + } + return Result.Success; + } + } + return Impl(values); +} + +unittest +{ + enum values = ["a","b","c"]; + + import argparse.config; + Config config; + + assert(ValueInList(values)(Param!string(&config, "", "b"))); + assert(!ValueInList(values)(Param!string(&config, "", "d"))); + + assert(ValueInList(values)(RawParam(&config, "", ["b"]))); + assert(ValueInList(values)(RawParam(&config, "", ["b","a"]))); + assert(!ValueInList(values)(RawParam(&config, "", ["d"]))); + assert(!ValueInList(values)(RawParam(&config, "", ["b","d"]))); +} diff --git a/source/argparse/internal/valueparser.d b/source/argparse/internal/valueparser.d index 8d8520a..a40d4b3 100644 --- a/source/argparse/internal/valueparser.d +++ b/source/argparse/internal/valueparser.d @@ -3,99 +3,147 @@ module argparse.internal.valueparser; import argparse.config; import argparse.param; import argparse.result; -import argparse.internal.parsehelpers; +import argparse.internal.errorhelpers; import argparse.internal.enumhelpers: getEnumValues, getEnumValue; +import argparse.internal.actionfunc; +import argparse.internal.novalueactionfunc; +import argparse.internal.parsefunc; +import argparse.internal.validationfunc; import std.traits; /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -private Result processingError(T)(Param!T param, string prefix = "Can't process value") +package(argparse) struct ValueParser(PARSE_TYPE, RECEIVER_TYPE) { - import std.conv: to; - import std.array: appender; + ////////////////////////// + /// pre process + void function(ref RawParam param) preProcess; - auto a = appender!string(prefix); + auto changePreProcess(void function(ref RawParam param) func) + { + preProcess = func; + return this; + } - static if(is(typeof(param.value))) + ////////////////////////// + /// pre validation + ValidationFunc!(string[]) preValidate; + + auto changePreValidation(ValidationFunc!(string[]) func) { - a ~= " '"; - a ~= param.config.styling.positionalArgumentValue(param.value.to!string); - a ~= "'"; + preValidate = func; + return this; } - if(param.name.length > 0 && param.name[0] == param.config.namedArgPrefix) + ////////////////////////// + /// parse + static if(!is(PARSE_TYPE == void)) + ParseFunc!PARSE_TYPE parse; + + auto changeParse(P)(ParseFunc!P func) const + if(is(PARSE_TYPE == void) || is(PARSE_TYPE == P)) { - a ~= " for argument '"; - a ~= param.config.styling.argumentName(param.name); - a ~= "'"; + return ValueParser!(P, RECEIVER_TYPE)( parse: func ).addDefaults(this); } - return Result.Error(a[]); -} + ////////////////////////// + /// validation + static if(!is(PARSE_TYPE == void)) + ValidationFunc!PARSE_TYPE validate; -private Result invalidValueError(T)(Param!T param) -{ - return processingError(param, "Invalid value"); -} + auto changeValidation(P)(ValidationFunc!P func) const + if(is(PARSE_TYPE == void) || is(PARSE_TYPE == P)) + { + return ValueParser!(P, RECEIVER_TYPE)( validate: func ).addDefaults(this); + } + ////////////////////////// + /// action + static if(!is(PARSE_TYPE == void) && !is(RECEIVER_TYPE == void)) + ActionFunc!(RECEIVER_TYPE, PARSE_TYPE) action; -unittest -{ - Config config; - assert(processingError(Param!void(&config, "")).isError("Can't process value")); - assert(processingError(Param!void(&config, "--abc")).isError("Can't process value for argument","--abc")); - assert(processingError(Param!(int[])(&config, "", [1,2])).isError("Can't process value '","[1, 2]")); - assert(processingError(Param!(int[])(&config, "--abc", [1,2])).isError("Can't process value '","[1, 2]","' for argument '","--abc")); -} + auto changeAction(P, R)(ActionFunc!(R, P) func) const + if((is(PARSE_TYPE == void) || is(PARSE_TYPE == P)) && + (is(RECEIVER_TYPE == void) || is(RECEIVER_TYPE == R))) + { + return ValueParser!(P, R)( action: func ).addDefaults(this); + } -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + ////////////////////////// + /// noValueAction + static if(!is(RECEIVER_TYPE == void)) + NoValueActionFunc!RECEIVER_TYPE noValueAction; -package(argparse) struct ValueParser(alias PreProcess, - alias PreValidation, - alias Parse, - alias Validation, - alias Action, - alias NoValueAction) -{ - alias PreProcessArg = PreProcess; - alias PreValidationArg = PreValidation; - alias ParseArg = Parse; - alias ValidationArg = Validation; - alias ActionArg = Action; - alias NoValueActionArg = NoValueAction; - - alias changePreProcess (alias func) = ValueParser!( func, PreValidation, Parse, Validation, Action, NoValueAction); - alias changePreValidation(alias func) = ValueParser!(PreProcess, func, Parse, Validation, Action, NoValueAction); - alias changeParse (alias func) = ValueParser!(PreProcess, PreValidation, func, Validation, Action, NoValueAction); - alias changeValidation (alias func) = ValueParser!(PreProcess, PreValidation, Parse, func, Action, NoValueAction); - alias changeAction (alias func) = ValueParser!(PreProcess, PreValidation, Parse, Validation, func, NoValueAction); - alias changeNoValueAction(alias func) = ValueParser!(PreProcess, PreValidation, Parse, Validation, Action, func); - - template addDefaults(DefaultParseFunctions) + auto changeNoValueAction(R)(NoValueActionFunc!R func) const + if(is(RECEIVER_TYPE == void) || is(RECEIVER_TYPE == R)) { - template Get(string symbol) + return ValueParser!(PARSE_TYPE, R)( noValueAction: func ).addDefaults(this); + } + + ////////////////////////// + auto addDefaults(P, R)(ValueParser!(P, R) other) const + { + static if(is(PARSE_TYPE == void)) + alias RES_P = P; + else + alias RES_P = PARSE_TYPE; + + static if(is(RECEIVER_TYPE == void)) + alias RES_R = R; + else + alias RES_R = RECEIVER_TYPE; + + ValueParser!(RES_P, RES_R) res; + res.preProcess = preProcess ? preProcess : other.preProcess; + res.preValidate = preValidate ? preValidate : other.preValidate; + + static if(!is(PARSE_TYPE == void)) { - alias M = mixin(symbol); - static if(is(M == void)) - alias Get = __traits(getMember, DefaultParseFunctions, symbol); - else - alias Get = M; + res.parse = parse; + res.validate = validate; } - alias addDefaults = ValueParser!( - Get!"PreProcessArg", - Get!"PreValidationArg", - Get!"ParseArg", - Get!"ValidationArg", - Get!"ActionArg", - Get!"NoValueActionArg", - ); + static if(!is(PARSE_TYPE == void) && !is(RECEIVER_TYPE == void)) + res.action = action; + + static if(!is(RECEIVER_TYPE == void)) + res.noValueAction = noValueAction; + + static if(!is(RES_P == void) && is(RES_P == P)) + { + if(!res.parse) + res.parse = other.parse; + if(!res.validate) + res.validate = other.validate; + } + + static if(!is(RES_P == void) && !is(RES_R == void)) + { + static if(is(RES_P == P) && is(RES_R == R)) + if(!res.action) + res.action = other.action; + } + + static if(!is(RES_R == void) && is(RES_R == R)) + { + if(!res.noValueAction) + res.noValueAction = other.noValueAction; + } + + return res; } + auto addTypeDefaults(TYPE)() + { + static if(!is(typeof(TypedValueParser!TYPE) == void)) + return addDefaults(TypedValueParser!TYPE); + else + return this; + } - // Procedure to process (parse) the values to an argument of type T + // Procedure to process (parse) the values to an argument of type RECEIVER // - if there is a value(s): // - pre validate raw strings // - parse raw strings @@ -104,46 +152,75 @@ package(argparse) struct ValueParser(alias PreProcess, // - if there is no value: // - action if no value // Requirement: rawValues.length must be correct - static Result parse(T)(ref T receiver, RawParam param) + Result parseParameter(RECEIVER)(ref RECEIVER receiver, RawParam param) { - return addDefaults!(TypedValueParser!T).addDefaults!DefaultValueParser.parseImpl(receiver, param); + static assert(!is(PARSE_TYPE == void) && !is(RECEIVER_TYPE == void)); + return addTypeDefaults!RECEIVER.addDefaults.parseImpl(receiver, param); } - static Result parseImpl(T)(ref T receiver, ref RawParam rawParam) + + static if(!is(PARSE_TYPE == void) && !is(RECEIVER_TYPE == void)) { - alias preValidation = ValidateFunc!(PreValidation, "Pre validation"); - alias parse = ParseFunc!Parse; - alias ParseType = parse.ReturnType; - alias validation = ValidateFunc!Validation; - alias action = ActionFunc!Action; - alias noValueAction = NoValueActionFunc!NoValueAction; - - if(rawParam.value.length == 0) + auto addDefaults() { - return noValueAction(receiver, Param!void(rawParam.config, rawParam.name)); + if(!preProcess) + preProcess = (ref _) {}; + if(!preValidate) + preValidate = (string[] _) => true; + if(!validate) + validate = (PARSE_TYPE _) => true; + static if(__traits(compiles, { RECEIVER_TYPE receiver; receiver = PARSE_TYPE.init; })) + { + if (!action) + action = (ref RECEIVER_TYPE receiver, Param!PARSE_TYPE param) { receiver = param.value; }; + } + if(!noValueAction) + noValueAction = (ref RECEIVER_TYPE _, param) => processingError(param); + + return this; } - else + + + Result parseImpl(RECEIVER_TYPE* receiver, ref RawParam rawParam) const { - PreProcess(rawParam); + return parseImpl(*receiver, rawParam); + } + Result parseImpl(ref RECEIVER_TYPE receiver, ref RawParam rawParam) const + { + assert(preProcess); + assert(preValidate); + assert(parse); + assert(validate); + assert(action); + assert(noValueAction); + + if (rawParam.value.length == 0) + { + return noValueAction(receiver, Param!void(rawParam.config, rawParam.name)); + } + else + { + preProcess(rawParam); - Result res = preValidation(rawParam); - if(!res) - return res; + Result res = preValidate(rawParam); + if (!res) + return res; - auto parsedParam = Param!ParseType(rawParam.config, rawParam.name); + auto parsedParam = Param!PARSE_TYPE(rawParam.config, rawParam.name); - res = parse(parsedParam.value, rawParam); - if(!res) - return res; + res = parse(parsedParam.value, rawParam); + if (!res) + return res; - res = validation(parsedParam); - if(!res) - return res; + res = validate(parsedParam); + if (!res) + return res; - res = action(receiver, parsedParam); - if(!res) - return res; + res = action(receiver, parsedParam); + if (!res) + return res; - return Result.Success; + return Result.Success; + } } } } @@ -152,22 +229,13 @@ package(argparse) struct ValueParser(alias PreProcess, unittest { int receiver; - assert(ValueParser!(void, void, (ref int i, RawParam p) => Result.Error("test error"), void, Assign, void).parse(receiver, RawParam(null,"",[""])).isError("test error")); + auto vp = ValueParser!(void, void)() + .changeParse(ParseFunc!int((ref int i, RawParam p) => Result.Error("test error"))); + assert(vp.parse(receiver, RawParam(null,"",[""])).isError("test error")); } /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -private alias DefaultValueParser = ValueParser!( - (_) {}, // pre process - _ => Result.Success, // pre validate - void, // parse - _ => Result.Success, // validate - (ref receiver, value) => Assign(receiver, value), // action - (ref receiver, Param!void param) => processingError(param) // no-value action -); - -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - private template TypedValueParser(T) if(!is(T == void)) { @@ -175,30 +243,19 @@ if(!is(T == void)) static if(is(T == enum)) { - alias TypedValueParser = ValueParser!( - void, // pre process - ValueInList!(getEnumValues!T, typeof(RawParam.value)), // pre validate - getEnumValue!T, // parse - void, // validate - void, // action - void // no-value action - ); + enum TypedValueParser = ValueParser!(T, T).init + .changePreValidation(ValidationFunc!(string[])((RawParam _) => ValueInList(getEnumValues!T)(_))) + .changeParse(ParseFunc!T((string _) => getEnumValue!T(_))); } else static if(isSomeString!T || isNumeric!T) { - alias TypedValueParser = ValueParser!( - void, // pre process - void, // pre validate - Convert!T, // parse - void, // validate - void, // action - void // no-value action - ); + enum TypedValueParser = ValueParser!(T, T).init + .changeParse(Convert!T); } else static if(isBoolean!T) { - alias TypedValueParser = ValueParser!( - (ref RawParam param) // pre process + enum TypedValueParser = ValueParser!(T, T).init + .changePreProcess((ref RawParam param) { import std.algorithm.iteration: map; import std.array: array; @@ -208,75 +265,70 @@ if(!is(T == void)) // convert values to lower case and replace "" with "y" foreach(ref value; param.value) value = value.length == 0 ? "y" : value.representation.map!(_ => immutable char(_.toLower)).array; - }, - ValueInList!(["true","yes","y","false","no","n"], typeof(RawParam.value)), // pre validate - (string value) // parse + }) + .changePreValidation(ValidationFunc!(string[])((RawParam _) => ValueInList("true","yes","y","false","no","n")(_))) + .changeParse(ParseFunc!T((string value) { switch(value) { case "true", "yes", "y": return true; default: return false; } - }, - void, // validate - void, // action - (ref T result) { result = true; } // no-value action - ); + })) + .changeNoValueAction(NoValueActionFunc!T((ref T receiver) { receiver = true; })); } else static if(isSomeChar!T) { - alias TypedValueParser = ValueParser!( - void, // pre process - void, // pre validate - (string value) // parse + enum TypedValueParser = ValueParser!(T, T).init + .changeParse(ParseFunc!T((string value) { + import std.conv: to; return value.length > 0 ? value[0].to!T : T.init; - }, - void, // validate - void, // action - void // no-value action - ); + })); } else static if(isArray!T) { + enum parseValue(TYPE) = ParseFunc!TYPE((ref TYPE receiver, RawParam param) + { + static if(!isStaticArray!TYPE) + { + if(receiver.length < param.value.length) + receiver.length = param.value.length; + } + + foreach(i, value; param.value) + { + Result res = TypedValueParser!(ForeachType!TYPE).parseParameter(receiver[i], RawParam(param.config, param.name, [value])); + if(!res) + return res; + } + + return Result.Success; + }); + + alias TElement = ForeachType!T; static if(!isArray!TElement || isSomeString!TElement) // 1D array { static if(!isStaticArray!T) - alias action = Append; + enum action = Append!T; else - alias action = Assign; + enum action = Assign!T; - alias TypedValueParser = - TypedValueParser!TElement - .changePreProcess!splitValues - .changeParse!((ref T receiver, RawParam param) - { - static if(!isStaticArray!T) - { - if(receiver.length < param.value.length) - receiver.length = param.value.length; - } - - foreach(i, value; param.value) - { - Result res = TypedValueParser!TElement.parse(receiver[i], RawParam(param.config, param.name, [value])); - if(!res) - return res; - } - - return Result.Success; - }) - .changeAction!(action) - .changeNoValueAction!((ref T param) {}); + enum TypedValueParser = ValueParser!(T, T).init + .changePreProcess((ref _) => splitValues(_)) + .changeParse(parseValue!T) + .changeAction(action) + .changeNoValueAction(NoValueActionFunc!T((ref T receiver) => Result.Success)); } else static if(!isArray!(ForeachType!TElement) || isSomeString!(ForeachType!TElement)) // 2D array { - alias TypedValueParser = - TypedValueParser!TElement - .changeAction!Extend - .changeNoValueAction!((ref T param) { param ~= TElement.init; }); + enum TypedValueParser = ValueParser!(TElement, T).init + .changePreProcess((ref _) => splitValues(_)) + .changeParse(parseValue!TElement) + .changeAction(Extend!T) + .changeNoValueAction(NoValueActionFunc!T((ref T receiver) { receiver ~= TElement.init; })); } else static assert(false, "Multi-dimentional arrays are not supported: " ~ T.stringof); @@ -284,12 +336,10 @@ if(!is(T == void)) else static if(isAssociativeArray!T) { import std.string : indexOf; - alias TypedValueParser = ValueParser!( - splitValues, // pre process - void, // pre validate - PassThrough, // parse - void, // validate - (ref T recepient, Param!(string[]) param) // action + enum TypedValueParser = ValueParser!(string[], T).init + .changePreProcess((ref _) => splitValues(_)) + .changeParse(PassThrough) + .changeAction(ActionFunc!(T,string[])((ref T receiver, RawParam param) { alias K = KeyType!T; alias V = ValueType!T; @@ -301,43 +351,32 @@ if(!is(T == void)) return invalidValueError(param); K key; - Result res = TypedValueParser!K.parse(key, RawParam(param.config, param.name, [input[0 .. j]])); + Result res = TypedValueParser!K.parseParameter(key, RawParam(param.config, param.name, [input[0 .. j]])); if(!res) return res; V value; - res = TypedValueParser!V.parse(value, RawParam(param.config, param.name, [input[j + 1 .. $]])); + res = TypedValueParser!V.parseParameter(value, RawParam(param.config, param.name, [input[j + 1 .. $]])); if(!res) return res; - recepient[key] = value; + receiver[key] = value; } return Result.Success; - }, - (ref T param) {} // no-value action - ); + })) + .changeNoValueAction(NoValueActionFunc!T((ref T receiver) => Result.Success)); } else static if(is(T == function) || is(T == delegate) || is(typeof(*T) == function) || is(typeof(*T) == delegate)) { - alias TypedValueParser = ValueParser!( - void, // pre process - void, // pre validate - PassThrough, // parse - void, // validate - CallFunction!T, // action - CallFunctionNoParam!T // no-value action - ); + enum TypedValueParser = ValueParser!(string[], T).init + .changeParse(PassThrough) + .changeAction(CallFunction!T) + .changeNoValueAction(CallFunctionNoParam!T); } else { - alias TypedValueParser = ValueParser!( - void, // pre process - void, // pre validate - void, // parse - void, // validate - void, // action - void // no-value action - ); + enum TypedValueParser = ValueParser!(T, T).init + .changeAction(Assign!T); } } @@ -352,7 +391,8 @@ unittest // ensure that this compiles R receiver; Config config; - TypedValueParser!R.parse(receiver, RawParam(&config, "", [""])); + auto rawParam = RawParam(&config, "", [""]); + TypedValueParser!R.parseParameter(receiver, rawParam); }} } @@ -364,21 +404,22 @@ unittest R receiver; foreach(value; values) { - assert(TypedValueParser!R.parse(receiver, RawParam(&config, "", value))); + auto rawParam = RawParam(&config, "", value); + assert(TypedValueParser!R.parseParameter(receiver, rawParam)); } return receiver; }; - static assert(test!(string[])([["1","2","3"], [], ["4"]]) == ["1","2","3","4"]); - static assert(test!(string[][])([["1","2","3"], [], ["4"]]) == [["1","2","3"],[],["4"]]); + assert(test!(string[])([["1","2","3"], [], ["4"]]) == ["1","2","3","4"]); + assert(test!(string[][])([["1","2","3"], [], ["4"]]) == [["1","2","3"],[],["4"]]); - static assert(test!(string[string])([["a=bar","b=foo"], [], ["b=baz","c=boo"]]) == ["a":"bar", "b":"baz", "c":"boo"]); + assert(test!(string[string])([["a=bar","b=foo"], [], ["b=baz","c=boo"]]) == ["a":"bar", "b":"baz", "c":"boo"]); - static assert(test!(string[])([["1,2,3"], [], ["4"]]) == ["1","2","3","4"]); - static assert(test!(string[string])([["a=bar,b=foo"], [], ["b=baz,c=boo"]]) == ["a":"bar", "b":"baz", "c":"boo"]); + assert(test!(string[])([["1,2,3"], [], ["4"]]) == ["1","2","3","4"]); + assert(test!(string[string])([["a=bar,b=foo"], [], ["b=baz,c=boo"]]) == ["a":"bar", "b":"baz", "c":"boo"]); - static assert(test!(int[])([["1","2","3"], [], ["4"]]) == [1,2,3,4]); - static assert(test!(int[][])([["1","2","3"], [], ["4"]]) == [[1,2,3],[],[4]]); + assert(test!(int[])([["1","2","3"], [], ["4"]]) == [1,2,3,4]); + assert(test!(int[][])([["1","2","3"], [], ["4"]]) == [[1,2,3],[],[4]]); } @@ -391,7 +432,7 @@ unittest { T receiver; Config config; - assert(TypedValueParser!T.parse(receiver, RawParam(&config, "", values))); + assert(TypedValueParser!T.parseParameter(receiver, RawParam(&config, "", values))); return receiver; }; @@ -434,7 +475,7 @@ unittest { T receiver; Config config; - return TypedValueParser!T.parse(receiver, RawParam(&config, "", values)); + return TypedValueParser!T.parseParameter(receiver, RawParam(&config, "", values)); }; assert(testErr!string([]).isError("Can't process value")); @@ -447,455 +488,6 @@ unittest /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// bool validate(T value) -// bool validate(T[] value) -// bool validate(Param!T param) -// Result validate(T value) -// Result validate(T[] value) -// Result validate(Param!T param) -private struct ValidateFunc(alias F, string funcName="Validation") -{ - static Result opCall(T)(Param!T param) - { - // Result validate(Param!T param) - static if(__traits(compiles, { Result res = F(param); })) - { - return F(param); - } - // bool validate(Param!T param) - else static if(__traits(compiles, { F(param); })) - { - return F(param) ? Result.Success : invalidValueError(param); - } - // Result validate(T value) - else static if(__traits(compiles, { Result res = F(param.value); })) - { - return F(param.value); - } - // bool validate(T value) - else static if(__traits(compiles, { F(param.value); })) - { - return F(param.value) ? Result.Success : invalidValueError(param); - } - // Result validate(T[] value) - else static if(__traits(compiles, { Result res = F(param.value[0]); })) - { - foreach(value; param.value) - { - Result res = F(value); - if(!res) - return res; - } - return Result.Success; - } - // bool validate(T[] value) - else static if(__traits(compiles, { F(param.value[0]); })) - { - foreach(value; param.value) - if(!F(value)) - return invalidValueError(param); - return Result.Success; - } - else - static assert(false, funcName~" function is not supported for type "~T.stringof~": "~typeof(F).stringof); - } -} - -unittest -{ - auto test(alias F, T)(T[] values) - { - Config config; - return ValidateFunc!F(Param!(T[])(&config, "", values)); - } - - // Result validate(Param!T param) - assert(test!((RawParam _) => Result.Success)(["1","2","3"])); - assert(test!((RawParam _) => Result.Error("error text"))(["1","2","3"]).isError("error text")); - - // bool validate(Param!T param) - assert(test!((RawParam _) => true)(["1","2","3"])); - assert(test!((RawParam _) => false)(["1","2","3"]).isError("Invalid value")); - - // Result validate(T value) - assert(test!((string _) => Result.Success)(["1","2","3"])); - assert(test!((string _) => Result.Error("error text"))(["1","2","3"]).isError("error text")); - - // bool validate(T value) - assert(test!((string _) => true)(["1","2","3"])); - assert(test!((string _) => false)(["1","2","3"]).isError("Invalid value")); - - // Result validate(T[] value) - assert(test!((string[] _) => Result.Success)(["1","2","3"])); - assert(test!((string[] _) => Result.Error("error text"))(["1","2","3"]).isError("error text")); - - // bool validate(T[] value) - assert(test!((string[] _) => true)(["1","2","3"])); - assert(test!((string[] _) => false)(["1","2","3"]).isError("Invalid value")); -} - -unittest -{ - auto test(alias F, T)(T[] values) - { - Config config; - return ValidateFunc!F(Param!(T[])(&config, "--argname", values)); - } - - // Result validate(Param!T param) - assert(test!((RawParam _) => Result.Success)(["1","2","3"])); - assert(test!((RawParam _) => Result.Error("error text"))(["1","2","3"]).isError("error text")); - - // bool validate(Param!T param) - assert(test!((RawParam _) => true)(["1","2","3"])); - assert(test!((RawParam _) => false)(["1","2","3"]).isError("Invalid value","for argument","--argname")); - - // Result validate(T value) - assert(test!((string _) => Result.Success)(["1","2","3"])); - assert(test!((string _) => Result.Error("error text"))(["1","2","3"]).isError("error text")); - - // bool validate(T value) - assert(test!((string _) => true)(["1","2","3"])); - assert(test!((string _) => false)(["1","2","3"]).isError("Invalid value","for argument","--argname")); - - // Result validate(T[] value) - assert(test!((string[] _) => Result.Success)(["1","2","3"])); - assert(test!((string[] _) => Result.Error("error text"))(["1","2","3"]).isError("error text")); - - // bool validate(T[] value) - assert(test!((string[] _) => true)(["1","2","3"])); - assert(test!((string[] _) => false)(["1","2","3"]).isError("Invalid value","for argument","--argname")); -} - -unittest -{ - auto test(alias F, T)() - { - Config config; - return ValidateFunc!F(RawParam(&config, "", ["1","2","3"])); - } - - static assert(!__traits(compiles, { test!(() {}, string[]); })); - static assert(!__traits(compiles, { test!((int,int) {}, string[]); })); -} - -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -// DEST action() -// bool action(ref DEST receiver) -// void action(ref DEST receiver) -// Result action(ref DEST receiver) -// bool action(ref DEST receiver, Param!void param) -// void action(ref DEST receiver, Param!void param) -// Result action(ref DEST receiver, Param!void param) -package struct NoValueActionFunc(alias F) -{ - static Result opCall(T)(ref T receiver, Param!void param) - { - // DEST action() - static if(__traits(compiles, { receiver = cast(T) F(); })) - { - receiver = cast(T) F(); - return Result.Success; - } - // Result action(ref DEST receiver) - else static if(__traits(compiles, { Result res = F(receiver); })) - { - return F(receiver); - } - // bool action(ref DEST receiver) - else static if(__traits(compiles, { auto res = cast(bool) F(receiver); })) - { - return cast(bool) F(receiver) ? Result.Success : processingError(param); - } - // void action(ref DEST receiver) - else static if(__traits(compiles, { F(receiver); })) - { - F(receiver); - return Result.Success; - } - // Result action(ref DEST receiver, Param!void param) - else static if(__traits(compiles, { Result res = F(receiver, param); })) - { - return F(receiver, param); - } - // bool action(ref DEST receiver, Param!void param) - else static if(__traits(compiles, { auto res = cast(bool) F(receiver, param); })) - { - return cast(bool) F(receiver, param) ? Result.Success : processingError(param); - } - // void action(ref DEST receiver, Param!void param) - else static if(__traits(compiles, { F(receiver, param); })) - { - F(receiver, param); - return Result.Success; - } - else - static assert(false, "No-value action function has too many parameters: "~Parameters!F.stringof); - } -} - -unittest -{ - auto test(alias F, T)() - { - T receiver; - assert(NoValueActionFunc!F(receiver, Param!void.init)); - return receiver; - } - auto testErr(alias F, T)() - { - T receiver; - return NoValueActionFunc!F(receiver, Param!void.init); - } - - // DEST action() - assert(test!(() => 7, int) == 7); - - // Result action(ref DEST receiver) - assert(test!((ref int r) { r=7; return Result.Success; }, int) == 7); - assert(testErr!((ref int r) => Result.Error("error text"), int).isError("error text")); - - // bool action(ref DEST receiver) - assert(test!((ref int p) { p=7; return true; }, int) == 7); - assert(testErr!((ref int p) => false, int).isError("Can't process value")); - - // void action(ref DEST receiver) - assert(test!((ref int p) { p=7; }, int) == 7); - - // Result action(ref DEST receiver, Param!void param) - assert(test!((ref int r, Param!void p) { r=7; return Result.Success; }, int) == 7); - assert(testErr!((ref int r, Param!void p) => Result.Error("error text"), int).isError("error text")); - - // bool action(ref DEST receiver, Param!void param) - assert(test!((ref int r, Param!void p) { r=7; return true; }, int) == 7); - assert(testErr!((ref int r, Param!void p) => false, int).isError("Can't process value")); - - // void action(ref DEST receiver, Param!void param) - assert(test!((ref int r, Param!void p) { r=7; }, int) == 7); -} - -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -// T parse(string[] value) -// T parse(string value) -// T parse(RawParam param) -// Result parse(ref T receiver, RawParam param) -// bool parse(ref T receiver, RawParam param) -// void parse(ref T receiver, RawParam param) -private struct ParseFunc(alias F) -{ - // T parse(string[] value) - static if(__traits(compiles, { F(RawParam.init.value); })) - { - alias ReturnType = Unqual!(typeof(F(RawParam.init.value))); - - static Result opCall(ref ReturnType receiver, RawParam param) - { - receiver = cast(ReturnType) F(param.value); - return Result.Success; - } - } - // T parse(string value) - else static if(__traits(compiles, { F(RawParam.init.value[0]); })) - { - alias ReturnType = Unqual!(typeof(F(RawParam.init.value[0]))); - - static Result opCall(ref ReturnType receiver, RawParam param) - { - foreach (value; param.value) - receiver = cast(ReturnType) F(value); - return Result.Success; - } - } - // T parse(RawParam param) - else static if(__traits(compiles, { F(RawParam.init); })) - { - alias ReturnType = Unqual!(typeof(F(RawParam.init))); - - static Result opCall(ref ReturnType receiver, RawParam param) - { - receiver = cast(ReturnType) F(param); - return Result.Success; - } - } - // ... parse(..., ...) - else static if(isCallable!F && Parameters!F.length == 2) - { - alias ReturnType = Parameters!F[0]; - - static Result opCall(ref ReturnType receiver, RawParam param) - { - // Result parse(ref T receiver, RawParam param) - static if(__traits(compiles, { Result res = F(receiver, param); })) - { - return F(receiver, param); - } - // bool parse(ref T receiver, RawParam param) - else static if(__traits(compiles, { auto res = cast(bool) F(receiver, param); })) - { - return (cast(bool) F(receiver, param)) ? Result.Success : processingError(param); - } - // void parse(ref T receiver, RawParam param) - else static if(__traits(compiles, { F(receiver, param); })) - { - F(receiver, param); - return Result.Success; - } - else - static assert(false, "Parse function is not supported"); - } - } - else - static assert(false, "Parse function is not supported"); -} - -unittest -{ - auto test(alias F, T)(string[] values) - { - T receiver; - Config config; - assert(ParseFunc!F(receiver, RawParam(&config, "", values))); - return receiver; - } - auto testErr(alias F, T)(string[] values) - { - T receiver; - Config config; - return ParseFunc!F(receiver, RawParam(&config, "", values)); - } - - // T parse(string value) - assert(test!((string a) => a, string)(["1","2","3"]) == "3"); - - // T parse(string[] value) - assert(test!((string[] a) => a, string[])(["1","2","3"]) == ["1","2","3"]); - - // T parse(RawParam param) - assert(test!((RawParam p) => p.value[0], string)(["1","2","3"]) == "1"); - - // Result parse(ref T receiver, RawParam param) - assert(test!((ref string[] r, RawParam p) { r = p.value; return Result.Success; }, string[])(["1","2","3"]) == ["1","2","3"]); - assert(testErr!((ref string[] r, RawParam p) => Result.Error("error text"), string[])(["1","2","3"]).isError("error text")); - - // bool parse(ref T receiver, RawParam param) - assert(test!((ref string[] r, RawParam p) { r = p.value; return true; }, string[])(["1","2","3"]) == ["1","2","3"]); - assert(testErr!((ref string[] r, RawParam p) => false, string[])(["1","2","3"]).isError("Can't process value")); - - // void parse(ref T receiver, RawParam param) - assert(test!((ref string[] r, RawParam p) { r = p.value; }, string[])(["1","2","3"]) == ["1","2","3"]); -} - -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -// bool action(ref T receiver, ParseType value) -// void action(ref T receiver, ParseType value) -// Result action(ref T receiver, ParseType value) -// bool action(ref T receiver, Param!ParseType param) -// void action(ref T receiver, Param!ParseType param) -// Result action(ref T receiver, Param!ParseType param) -private struct ActionFunc(alias F) -{ - static Result opCall(T, ParseType)(ref T receiver, Param!ParseType param) - { - // Result action(ref T receiver, Param!ParseType param) - static if(__traits(compiles, { Result res = F(receiver, param.value); })) - { - return F(receiver, param.value); - } - // bool action(ref T receiver, ParseType value) - else static if(__traits(compiles, { auto res = cast(bool) F(receiver, param.value); })) - { - return cast(bool) F(receiver, param.value) ? Result.Success : processingError(param); - } - // void action(ref T receiver, ParseType value) - else static if(__traits(compiles, { F(receiver, param.value); })) - { - F(receiver, param.value); - return Result.Success; - } - // Result action(ref T receiver, Param!ParseType param) - else static if(__traits(compiles, { Result res = F(receiver, param); })) - { - return F(receiver, param); - } - // bool action(ref T receiver, Param!ParseType param) - else static if(__traits(compiles, { auto res = cast(bool) F(receiver, param); })) - { - return cast(bool) F(receiver, param) ? Result.Success : processingError(param); - } - // void action(ref T receiver, Param!ParseType param) - else static if(__traits(compiles, { F(receiver, param); })) - { - F(receiver, param); - return Result.Success; - } - else - static assert(false, "Action function is not supported"); - } -} - -unittest -{ - auto test(alias F, T)(T values) - { - T receiver; - Config config; - assert(ActionFunc!F(receiver, Param!T(&config, "", values))); - return receiver; - } - auto testErr(alias F, T)(T values) - { - T receiver; - Config config; - return ActionFunc!F(receiver, Param!T(&config, "", values)); - } - - static assert(!__traits(compiles, { test!(() {})(["1","2","3"]); })); - static assert(!__traits(compiles, { test!((int,int) {})(["1","2","3"]); })); - - // Result action(ref T receiver, ParseType value) - assert(test!((ref string[] p, string[] a) { p=a; return Result.Success; })(["1","2","3"]) == ["1","2","3"]); - assert(testErr!((ref string[] p, string[] a) => Result.Error("error text"))(["1","2","3"]).isError("error text")); - - // bool action(ref T receiver, ParseType value) - assert(test!((ref string[] p, string[] a) { p=a; return true; })(["1","2","3"]) == ["1","2","3"]); - assert(testErr!((ref string[] p, string[] a) => false)(["1","2","3"]).isError("Can't process value")); - - // void action(ref T receiver, ParseType value) - assert(test!((ref string[] p, string[] a) { p=a; })(["1","2","3"]) == ["1","2","3"]); - - // Result action(ref T receiver, Param!ParseType param) - assert(test!((ref string[] p, Param!(string[]) a) { p=a.value; return Result.Success; }) (["1","2","3"]) == ["1","2","3"]); - assert(testErr!((ref string[] p, Param!(string[]) a) => Result.Error("error text"))(["1","2","3"]).isError("error text")); - - // bool action(ref T receiver, Param!ParseType param) - assert(test!((ref string[] p, Param!(string[]) a) { p=a.value; return true; }) (["1","2","3"]) == ["1","2","3"]); - assert(testErr!((ref string[] p, Param!(string[]) a) => false)(["1","2","3"]).isError("Can't process value")); - - // void action(ref T receiver, Param!ParseType param) - assert(test!((ref string[] p, Param!(string[]) a) { p=a.value; })(["1","2","3"]) == ["1","2","3"]); -} - -unittest -{ - alias test = (int[] v1, int[] v2) { - int[] res; - - Param!(int[]) param; - - param.value = v1; ActionFunc!Append(res, param); - - param.value = v2; ActionFunc!Append(res, param); - - return res; - }; - assert(test([1,2,3],[7,8,9]) == [1,2,3,7,8,9]); -} - -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - private void splitValues(ref RawParam param) { if(param.config.arraySep == char.init) diff --git a/source/argparse/package.d b/source/argparse/package.d index a9fc27a..5daa32c 100644 --- a/source/argparse/package.d +++ b/source/argparse/package.d @@ -137,9 +137,8 @@ unittest @(PositionalArgument(0, "a") .Description("Argument 'a'") - .Validation!((int a) { return a > 3;}) - .PreValidation!((string s) { return s.length > 0;}) - .Validation!((int a) { return a > 0;}) + .PreValidation((string s) { return s.length > 0;}) + .Validation((int a) { return a > 0;}) ) int a; @@ -710,10 +709,10 @@ unittest struct T { @(NamedArgument - .PreValidation!((string s) { return s.length > 1 && s[0] == '!'; }) - .Parse !((string s) { return s[1]; }) - .Validation !((char v) { return v >= '0' && v <= '9'; }) - .Action !((ref int a, char v) { a = v - '0'; }) + .PreValidation((string s) { return s.length > 1 && s[0] == '!'; }) + .Parse ((string s) { return cast(char) s[1]; }) + .Validation ((char v) { return v >= '0' && v <= '9'; }) + .Action ((ref int a, char v) { a = v - '0'; }) ) int a; } @@ -738,7 +737,7 @@ unittest struct Value { string a; } struct T { - @(NamedArgument.Parse!((string s) { return Value(s); })) + @(NamedArgument.Parse((string _) => Value(_))) Value s; }