1 module argparse;
2 
3 
4 import std.typecons: Nullable;
5 import std.traits;
6 
7 struct Config
8 {
9     /**
10        The assignment character used in options with parameters.
11        Defaults to '='.
12      */
13     char assignChar = '=';
14 
15     /**
16        When set to char.init, parameters to array and associative array receivers are
17        treated as an individual argument. That is, only one argument is appended or
18        inserted per appearance of the option switch. If `arraySep` is set to
19        something else, then each parameter is first split by the separator, and the
20        individual pieces are treated as arguments to the same option.
21 
22        Defaults to char.init
23      */
24     char arraySep = char.init;
25 
26     /**
27        The option character.
28        Defaults to '-'.
29      */
30     char namedArgChar = '-';
31 
32     /**
33        The string that conventionally marks the end of all options.
34        Assigning an empty string to `endOfArgs` effectively disables it.
35        Defaults to "--".
36      */
37     string endOfArgs = "--";
38 
39     /**caseSensitive
40        If set then argument names are case-sensitive.
41        Defaults to true.
42      */
43     bool caseSensitive = true;
44 
45     /**
46         Single-letter arguments can be bundled together, i.e. "-abc" is the same as "-a -b -c".
47         Disabled by default.
48      */
49     bool bundling = false;
50 
51     /**
52        Delegate that processes error messages if they happen during argument parsing.
53        By default all errors are printed to stderr.
54      */
55     private void delegate(string s) nothrow errorHandlerFunc;
56 
57     @property auto errorHandler(void function(string s) nothrow func)
58     {
59         return errorHandlerFunc = (string msg) { func(msg); };
60     }
61 
62     @property auto errorHandler(void delegate(string s) nothrow func)
63     {
64         return errorHandlerFunc = func;
65     }
66 
67 
68     private bool onError(A...)(A args) const nothrow
69     {
70         import std.conv: text;
71         import std.stdio: stderr, writeln;
72 
73         try
74         {
75             if(errorHandlerFunc)
76                 errorHandlerFunc(text!A(args));
77             else
78                 stderr.writeln("Error: ", args);
79         }
80         catch(Exception e)
81         {
82             throw new Error(e.msg);
83         }
84 
85         return false;
86     }
87 }
88 
89 unittest
90 {
91     Config.init.onError("--just testing error func--",1,2.3,false);
92     Config c;
93     c.errorHandler = (string s){};
94     c.onError("--just testing error func--",1,2.3,false);
95 }
96 
97 
98 struct Param(VALUE_TYPE)
99 {
100     const Config config;
101     string name;
102 
103     static if(!is(VALUE_TYPE == void))
104         VALUE_TYPE value;
105 }
106 
107 alias RawParam = Param!(string[]);
108 
109 
110 private enum ArgumentType { unknown, positional, shortName, longName }
111 
112 private auto splitArgumentName(string arg, const Config config)
113 {
114     import std.typecons : nullable;
115     import std.string : indexOf;
116 
117     struct Result
118     {
119         ArgumentType    type = ArgumentType.unknown;
120         string          name;
121         string          origName;
122         Nullable!string value;
123     }
124 
125     if(arg.length == 0)
126         return Result.init;
127 
128     if(arg[0] != config.namedArgChar)
129         return Result(ArgumentType.positional, string.init, string.init, nullable(arg));
130 
131     if(arg.length == 1)
132         return Result.init;
133 
134     Result result;
135 
136     auto idxAssignChar = config.assignChar == char.init ? -1 : arg.indexOf(config.assignChar);
137     if(idxAssignChar < 0)
138         result.origName = arg;
139     else
140     {
141         result.origName = arg[0 .. idxAssignChar];
142         result.value = nullable(arg[idxAssignChar + 1 .. $]);
143     }
144 
145     if(result.origName[1] == config.namedArgChar)
146     {
147         result.type = ArgumentType.longName;
148         result.name = result.origName[2..$];
149     }
150     else
151     {
152         result.type = ArgumentType.shortName;
153         result.name = result.origName[1..$];
154     }
155 
156     return result;
157 }
158 
159 unittest
160 {
161     import std.typecons : tuple, nullable;
162 
163     static assert(splitArgumentName("", Config.init).tupleof == tuple(ArgumentType.init, string.init, string.init, Nullable!string.init).tupleof);
164     static assert(splitArgumentName("-", Config.init).tupleof == tuple(ArgumentType.init, string.init, string.init, Nullable!string.init).tupleof);
165     static assert(splitArgumentName("abc=4", Config.init).tupleof == tuple(ArgumentType.positional, string.init, string.init, "abc=4").tupleof);
166     static assert(splitArgumentName("-abc", Config.init).tupleof == tuple(ArgumentType.shortName, "abc", "-abc", Nullable!string.init).tupleof);
167     static assert(splitArgumentName("--abc", Config.init).tupleof == tuple(ArgumentType.longName, "abc", "--abc", Nullable!string.init).tupleof);
168     static assert(splitArgumentName("-abc=fd", Config.init).tupleof == tuple(ArgumentType.shortName, "abc", "-abc", "fd").tupleof);
169     static assert(splitArgumentName("--abc=fd", Config.init).tupleof == tuple(ArgumentType.longName, "abc", "--abc", "fd").tupleof);
170     static assert(splitArgumentName("-abc=", Config.init).tupleof == tuple(ArgumentType.shortName, "abc", "-abc", nullable("")).tupleof);
171     static assert(splitArgumentName("--abc=", Config.init).tupleof == tuple(ArgumentType.longName, "abc", "--abc", nullable("")).tupleof);
172     static assert(splitArgumentName("-=abc", Config.init).tupleof == tuple(ArgumentType.shortName, string.init, "-", "abc").tupleof);
173     static assert(splitArgumentName("--=abc", Config.init).tupleof == tuple(ArgumentType.longName, string.init, "--", "abc").tupleof);
174 }
175 
176 
177 private template defaultValuesCount(T)
178 if(!is(T == void))
179 {
180     import std.traits;
181 
182     static if(isBoolean!T)
183     {
184         enum min = 0;
185         enum max = 1;
186     }
187     else static if(isSomeString!T || isScalarType!T)
188     {
189         enum min = 1;
190         enum max = 1;
191     }
192     else static if(isStaticArray!T)
193     {
194         enum min = 1;
195         enum max = T.length;
196     }
197     else static if(isArray!T || isAssociativeArray!T)
198     {
199         enum min = 1;
200         enum max = ulong.max;
201     }
202     else static if(is(T == function))
203     {
204         // ... function()
205         static if(__traits(compiles, { T(); }))
206         {
207             enum min = 0;
208             enum max = 0;
209         }
210         // ... function(string value)
211         else static if(__traits(compiles, { T(string.init); }))
212         {
213             enum min = 1;
214             enum max = 1;
215         }
216         // ... function(string[] value)
217         else static if(__traits(compiles, { T([string.init]); }))
218         {
219             enum min = 0;
220             enum max = ulong.max;
221         }
222         // ... function(RawParam param)
223         else static if(__traits(compiles, { T(RawParam.init); }))
224         {
225             enum min = 1;
226             enum max = ulong.max;
227         }
228         else
229             static assert(false, "Unsupported callback: " ~ T.stringof);
230     }
231     else
232         static assert(false, "Type is not supported: " ~ T.stringof);
233 }
234 
235 private auto addDefaultValuesCount(T)(ArgumentInfo info)
236 {
237     if(info.minValuesCount.isNull) info.minValuesCount = defaultValuesCount!T.min;
238     if(info.maxValuesCount.isNull) info.maxValuesCount = defaultValuesCount!T.max;
239 
240     return info;
241 }
242 
243 
244 
245 private bool checkMemberWithMultiArgs(T)()
246 {
247     static foreach (sym; getSymbolsByUDA!(T, ArgumentUDA))
248         static assert(getUDAs!(__traits(getMember, T, __traits(identifier, sym)), ArgumentUDA).length == 1,
249                       "Member "~T.stringof~"."~sym.stringof~" has multiple '*Argument' UDAs");
250 
251     return true;
252 }
253 
254 private auto checkDuplicates(alias sortedRange, string errorMsg)() {
255     import std.range : lockstep;
256     import std.conv  : to;
257 
258     static if(sortedRange.length >= 2)
259         static foreach(value1, value2; lockstep(sortedRange[0..$-1], sortedRange[1..$]))
260             static assert(value1 != value2, errorMsg ~ value1.to!string);
261 
262     return true;
263 }
264 
265 private bool checkArgumentNames(T)()
266 {
267     enum names = () {
268         import std.algorithm : sort;
269 
270         string[] names;
271         static foreach (sym; getSymbolsByUDA!(T, ArgumentUDA))
272         {{
273             enum argUDA = getUDAs!(__traits(getMember, T, __traits(identifier, sym)), ArgumentUDA)[0];
274 
275             static assert(!argUDA.info.positional || argUDA.info.names.length <= 1,
276                  "Positional argument should have exactly one name: "~T.stringof~"."~sym.stringof);
277 
278             static foreach (name; argUDA.info.names)
279             {
280                 static assert(name.length > 0, "Argument name can't be empty: "~T.stringof~"."~sym.stringof);
281 
282                 names ~= name;
283             }
284         }}
285 
286         return names.sort;
287     }();
288 
289     return checkDuplicates!(names, "Argument name appears more than once: ");
290 }
291 
292 private bool checkPositionalIndexes(T)()
293 {
294     import std.conv  : to;
295     import std.range : lockstep, iota;
296 
297 
298     enum positions = () {
299         import std.algorithm : sort;
300 
301         uint[] positions;
302         static foreach (sym; getSymbolsByUDA!(T, ArgumentUDA))
303         {{
304             enum argUDA = getUDAs!(__traits(getMember, T, __traits(identifier, sym)), ArgumentUDA)[0];
305 
306             static if (argUDA.info.positional)
307                 positions ~= argUDA.info.position.get;
308         }}
309 
310         return positions.sort;
311     }();
312 
313     if(!checkDuplicates!(positions, "Positional arguments have duplicated position: "))
314         return false;
315 
316     static foreach (i, pos; lockstep(iota(0, positions.length), positions))
317         static assert(i == pos, "Positional arguments have missed position: " ~ i.to!string);
318 
319     return true;
320 }
321 
322 private ulong countArguments(T)()
323 {
324     return getSymbolsByUDA!(T, ArgumentUDA).length;
325 }
326 
327 private ulong countPositionalArguments(T)()
328 {
329     ulong count;
330     static foreach (sym; getSymbolsByUDA!(T, ArgumentUDA))
331         static if (getUDAs!(__traits(getMember, T, __traits(identifier, sym)), ArgumentUDA)[0].info.positional)
332             count++;
333 
334     return count;
335 }
336 
337 
338 private struct Arguments(T)
339 {
340     static assert(getSymbolsByUDA!(T, ArgumentUDA).length > 0, "Type "~T.stringof~" has no members with '*Argument' UDA");
341     static assert(getSymbolsByUDA!(T, TrailingArgumentUDA).length <= 1, "Type "~T.stringof~" must have at most one 'TrailingArguments' UDA");
342 
343     private enum _validate = checkMemberWithMultiArgs!T && checkArgumentNames!T && checkPositionalIndexes!T;
344 
345 
346     struct Argument
347     {
348         ArgumentInfo info;
349 
350         private bool function(in Config config, string argName, ref T receiver, string[] rawValues) parsingFunc;
351 
352 
353         bool parse(in Config config, string argName, ref T receiver, string[] rawValues) const
354         {
355             try
356             {
357                 return info.checkValuesCount(config, argName, rawValues.length) &&
358                        parsingFunc(config, argName, receiver, rawValues);
359             }
360             catch(Exception e)
361             {
362                 return config.onError(argName, ": ", e.msg);
363             }
364         }
365     }
366 
367     immutable string function(string str) convertCase;
368 
369     static if(getSymbolsByUDA!(T, TrailingArgumentUDA).length == 1)
370     {
371         private void setTrailingArgs(ref T receiver, string[] rawValues)
372         {
373             alias symbol = getSymbolsByUDA!(T, TrailingArgumentUDA)[0];
374 
375             static if(__traits(compiles, { __traits(getMember, receiver, symbol.stringof) = rawValues; }))
376                 __traits(getMember, receiver, symbol.stringof) = rawValues;
377             else
378                 static assert(false, "Type '"~typeof(__traits(getMember, receiver, symbol.stringof)).stringof~"' of `"~
379                                      T.stringof~"."~symbol.stringof~"` is not supported for 'TrailingArguments' UDA");
380         }
381     }
382 
383     private Argument[countArguments!T] arguments;
384 
385     // named arguments
386     private ulong[string] argsNamed;
387 
388     // positional arguments
389     private ulong[countPositionalArguments!T] argsPositional;
390 
391     // required arguments
392     private bool[ulong] argsRequired;
393 
394 
395     @property auto requiredArguments() const { return argsRequired; }
396 
397 
398     this(bool caseSensitive)
399     {
400         if(caseSensitive)
401             convertCase = s => s;
402         else
403             convertCase = (string str)
404             {
405                 import std.uni : toUpper;
406                 return str.toUpper;
407             };
408 
409         ulong idx = 0;
410 
411         static foreach(sym; getSymbolsByUDA!(T, ArgumentUDA))
412         {{
413             alias member = __traits(getMember, T, __traits(identifier, sym));
414 
415             enum argUDA = getUDAs!(member, ArgumentUDA)[0];
416 
417             static if(argUDA.info.positional)
418                 argsPositional[argUDA.info.position.get] = idx;
419             else
420                 static foreach (name; argUDA.info.names)
421                     argsNamed[convertCase(name)] = idx;
422 
423             static if(argUDA.info.required)
424                 argsRequired[idx] = true;
425 
426             enum arg = Argument(
427                     () {
428                         auto info = argUDA.info;
429 
430                         static if(!isBoolean!(typeof(member)))
431                             info.allowBooleanNegation = false;
432 
433                         static if(argUDA.info.positional && argUDA.info.names.length == 0)
434                             info.names ~= sym.stringof;
435 
436                         return info.addDefaultValuesCount!(typeof(member));
437                     }(),
438                     function (in Config config, string argName, ref T receiver, string[] rawValues)
439                     {
440                         auto param = RawParam(config, argName, rawValues);
441 
442                         static if(is(typeof(__traits(getMember, receiver, __traits(identifier, sym))) == function))
443                         {
444                             auto target = &__traits(getMember, receiver, __traits(identifier, sym));
445                             return argUDA.parsingFunc.parse(target, param);
446                         }
447                         else
448                             return argUDA.parsingFunc.parse(__traits(getMember, receiver, __traits(identifier, sym)), param);
449                     }
450                 );
451 
452             arguments[idx++] = arg;
453         }}
454     }
455 
456 
457     private auto findArgumentImpl(const ulong* pIndex) const
458     {
459         import std.typecons : Tuple;
460 
461         alias Result = Tuple!(ulong, "index", typeof(&arguments[0]), "arg");
462 
463         return pIndex ? Result(*pIndex, &arguments[*pIndex]) : Result(ulong.max, null);
464     }
465 
466     auto findPositionalArgument(ulong position) const
467     {
468         return findArgumentImpl(position < argsPositional.length ? &argsPositional[position] : null);
469     }
470 
471     auto findNamedArgument(string name) const
472     {
473         return findArgumentImpl(convertCase(name) in argsNamed);
474     }
475 
476 }
477 
478 unittest
479 {
480     struct T
481     {
482         @(NamedArgument("a"))
483         int a;
484         @(NamedArgument("b").Optional())
485         int b;
486         @(NamedArgument("c").Required())
487         int c;
488         @(NamedArgument("d"))
489         int d;
490         @(NamedArgument("e").Required())
491         int e;
492         @(NamedArgument("f"))
493         int f;
494     }
495     static assert(Arguments!T(true).requiredArguments.keys == [2,4]);
496 }
497 
498 unittest
499 {
500     struct T0
501     {
502         int a;
503     }
504     static assert(!__traits(compiles, { Arguments!T0(true); }));
505 
506     struct T1
507     {
508         @(NamedArgument("1"))
509         @(NamedArgument("2"))
510         int a;
511     }
512     static assert(!__traits(compiles, { Arguments!T1(true); }));
513 
514     struct T2
515     {
516         @(NamedArgument("1"))
517         int a;
518         @(NamedArgument("1"))
519         int b;
520     }
521     static assert(!__traits(compiles, { Arguments!T1(true); }));
522 
523     struct T3
524     {
525         @(PositionalArgument(0)) int a;
526         @(PositionalArgument(0)) int b;
527     }
528     static assert(!__traits(compiles, { Arguments!T3(true); }));
529 
530     struct T4
531     {
532         @(PositionalArgument(0)) int a;
533         @(PositionalArgument(2)) int b;
534     }
535     static assert(!__traits(compiles, { Arguments!T4(true); }));
536 }
537 
538 private void checkArgumentName(T)(char namedArgChar)
539 {
540     import std.exception: enforce;
541 
542     static foreach(sym; getSymbolsByUDA!(T, ArgumentUDA))
543         static foreach(name; getUDAs!(__traits(getMember, T, __traits(identifier, sym)), ArgumentUDA)[0].info.names)
544             enforce(name[0] != namedArgChar, "Name of argument should not begin with '"~namedArgChar~"': "~name);
545 }
546 
547 struct CommandLineParser(T)
548 {
549     import std.range;
550     import std.typecons : tuple;
551 
552     private immutable Config config;
553 
554     private Arguments!T arguments;
555 
556 
557     this(in Config config)
558     {
559         checkArgumentName!T(config.namedArgChar);
560 
561         this.config = config;
562 
563         arguments = Arguments!T(config.caseSensitive);
564     }
565 
566     private auto consumeValues(ref string[] args, in ArgumentInfo argumentInfo) const
567     {
568         immutable minValuesCount = argumentInfo.minValuesCount.get;
569         immutable maxValuesCount = argumentInfo.maxValuesCount.get;
570 
571         string[] values;
572 
573         if(minValuesCount > 0)
574         {
575             if(minValuesCount < args.length)
576             {
577                 values = args[0..minValuesCount];
578                 args = args[minValuesCount..$];
579             }
580             else
581             {
582                 values = args;
583                 args = [];
584             }
585         }
586 
587         while(!args.empty &&
588             values.length < maxValuesCount &&
589             (config.endOfArgs.length == 0 || args.front != config.endOfArgs) &&
590             (args.front.length == 0 || args.front[0] != config.namedArgChar))
591         {
592             values ~= args.front;
593             args.popFront();
594         }
595 
596         return values;
597     }
598 
599     bool parseArgs(ref T receiver, string[] args)
600     {
601         string[] unrecognizedArgs;
602         immutable res = parseKnownArgs(receiver, args, unrecognizedArgs);
603         if(!res)
604             return false;
605 
606         if(unrecognizedArgs.length > 0)
607             return config.onError("Unrecognized arguments: ", unrecognizedArgs);
608 
609         return true;
610     }
611 
612     bool parseKnownArgs(ref T receiver, string[] args, out string[] unrecognizedArgs)
613     {
614         auto requiredArgs = arguments.requiredArguments.dup;
615 
616         alias parseNamedArg = (arg, res) {
617             args.popFront();
618 
619             auto values = arg.value.isNull ? consumeValues(args, res.arg.info) : [ arg.value.get ];
620 
621             if(!res.arg.parse(config, arg.origName, receiver, values))
622                 return false;
623 
624             requiredArgs.remove(res.index);
625 
626             return true;
627         };
628 
629         ulong positionalArgIdx = 0;
630 
631         while(!args.empty)
632         {
633             if(config.endOfArgs.length > 0 && args.front == config.endOfArgs)
634             {
635                 // End of arguments
636                 static if(is(typeof(arguments.setTrailingArgs)))
637                     arguments.setTrailingArgs(receiver, args[1..$]);
638                 else
639                     unrecognizedArgs ~= args[1..$];
640                 break;
641             }
642 
643             auto arg = splitArgumentName(args.front, config);
644 
645             final switch(arg.type)
646             {
647                 case ArgumentType.positional:
648                 {
649                     auto res = arguments.findPositionalArgument(positionalArgIdx);
650                     if(res.arg is null)
651                         goto case ArgumentType.unknown;
652 
653                     auto values = consumeValues(args, res.arg.info);
654 
655                     if(!res.arg.parse(config, res.arg.info.names[0], receiver, values))
656                         return false;
657 
658                     positionalArgIdx++;
659 
660                     requiredArgs.remove(res.index);
661 
662                     break;
663                 }
664 
665                 case ArgumentType.longName:
666                 {
667                     if(arg.name.length == 0)
668                         return config.onError("Empty argument name: ", args.front);
669 
670                     auto res = arguments.findNamedArgument(arg.name);
671                     if(res.arg !is null)
672                     {
673                         if(!parseNamedArg(arg, res))
674                             return false;
675 
676                         break;
677                     }
678 
679                     import std.algorithm : startsWith;
680 
681                     if(arg.name.startsWith("no-"))
682                     {
683                         res = arguments.findNamedArgument(arg.name[3..$]);
684                         if(res.arg !is null && res.arg.info.allowBooleanNegation)
685                         {
686                             args.popFront();
687 
688                             if(!res.arg.parse(config, arg.origName, receiver, ["false"]))
689                                 return false;
690 
691                             requiredArgs.remove(res.index);
692 
693                             break;
694                         }
695                     }
696 
697                     goto case ArgumentType.unknown;
698                 }
699 
700                 case ArgumentType.shortName:
701                 {
702                     if(arg.name.length == 0)
703                         return config.onError("Empty argument name: ", args.front);
704 
705                     auto res = arguments.findNamedArgument(arg.name);
706                     if(res.arg !is null)
707                     {
708                         if(!parseNamedArg(arg, res))
709                             return false;
710 
711                         break;
712                     }
713 
714                     if(arg.name.length == 1)
715                         goto case ArgumentType.unknown;
716 
717                     if(!config.bundling)
718                     {
719                         auto name = [arg.name[0]];
720                         res = arguments.findNamedArgument(name);
721                         if(res.arg is null || res.arg.info.minValuesCount != 1)
722                             goto case ArgumentType.unknown;
723 
724                         if(!res.arg.parse(config, "-"~name, receiver, [arg.name[1..$]]))
725                             return false;
726 
727                         requiredArgs.remove(res.index);
728 
729                         args.popFront();
730                         break;
731                     }
732                     else
733                     {
734                         while(arg.name.length > 0)
735                         {
736                             auto name = [arg.name[0]];
737                             res = arguments.findNamedArgument(name);
738                             if(res.arg is null)
739                                 goto case ArgumentType.unknown;
740 
741                             if(res.arg.info.minValuesCount == 0)
742                             {
743                                 if(!res.arg.parse(config, "-"~name, receiver, []))
744                                     return false;
745 
746                                 requiredArgs.remove(res.index);
747 
748                                 arg.name = arg.name[1..$];
749                             }
750                             else if(res.arg.info.minValuesCount == 1)
751                             {
752                                 if(!res.arg.parse(config, "-"~name, receiver, [arg.name[1..$]]))
753                                     return false;
754 
755                                 requiredArgs.remove(res.index);
756 
757                                 arg.name = [];
758                             }
759                             else // trigger an error
760                                 return res.arg.info.checkValuesCount(config, name, 1);
761                         }
762 
763                         if(arg.name.length == 0)
764                         {
765                             args.popFront();
766                             break;
767                         }
768                     }
769 
770                     goto case ArgumentType.unknown;
771                 }
772 
773                 case ArgumentType.unknown:
774                     unrecognizedArgs ~= args.front;
775                     args.popFront();
776             }
777         }
778 
779         if(requiredArgs.length > 0)
780         {
781             import std.algorithm : map;
782             return config.onError("The following arguments are required: ",
783                 requiredArgs.keys.map!(idx => arguments.arguments[idx].info.names[0]).join(", "));
784         }
785 
786         return true;
787     }
788 }
789 
790 auto parseCLIArgs(T)(string[] args, Config config = Config.init)
791 {
792     import std.typecons : nullable;
793 
794     T receiver;
795 
796     return CommandLineParser!T(config).parseArgs(receiver, args) ? receiver.nullable : Nullable!T.init;
797 }
798 
799 auto parseCLIKnownArgs(T)(ref string[] args, Config config = Config.init)
800 {
801     import std.typecons : nullable;
802 
803     T receiver;
804     string[] unrecognizedArgs;
805 
806     if(!CommandLineParser!T(config).parseKnownArgs(receiver, args, unrecognizedArgs))
807         return Nullable!T.init;
808     else
809     {
810         args = unrecognizedArgs;
811         return receiver.nullable;
812     }
813 }
814 
815 
816 unittest
817 {
818     import std.exception;
819 
820     struct T
821     {
822         @(NamedArgument("--"))
823         int a;
824     }
825     static assert(!__traits(compiles, { enum p = CommandLineParser!T(Config.init); }));
826     assertThrown(CommandLineParser!T(Config.init));
827 }
828 
829 unittest
830 {
831 
832     import std.conv;
833     import std.traits;
834 
835     struct params
836     {
837         int no_a;
838 
839         @(PositionalArgument(0, "a")
840         .HelpText("Argument 'a'")
841         .Validation!((int a) { return a > 3;})
842         .PreValidation!((string s) { return s.length > 0;})
843         .Validation!((int a) { return a > 0;})
844         )
845         int a;
846 
847         int no_b;
848 
849         @(NamedArgument(["b", "boo"]).HelpText("Flag boo")
850         .AllowNoValue!55
851         )
852         int b;
853 
854         int no_c;
855     }
856 
857     enum p = Arguments!params(true);
858     static assert(p.findNamedArgument("a").arg is null);
859     static assert(p.findNamedArgument("b").arg !is null);
860     static assert(p.findNamedArgument("boo").arg !is null);
861     static assert(p.findPositionalArgument(0).arg !is null);
862     static assert(p.findPositionalArgument(1).arg is null);
863 
864     params args;
865     p.findPositionalArgument(0).arg.parse(Config.init, "", args, ["123"]);
866     p.findNamedArgument("b").arg.parse(Config.init, "", args, ["456"]);
867     p.findNamedArgument("boo").arg.parse(Config.init, "", args, ["789"]);
868     assert(args.a == 123);
869     assert(args.b == 789);
870 }
871 
872 unittest
873 {
874     import std.typecons : tuple;
875 
876     struct T
877     {
878         @NamedArgument("a") string a;
879         @NamedArgument("b") string b;
880     }
881 
882     auto test(string[] args)
883     {
884         return tuple(args.parseCLIKnownArgs!T.get, args);
885     }
886 
887     static assert(test(["-a","A","--","-b","B"]) == tuple(T("A"), ["-b","B"]));
888 
889     {
890         enum parser = CommandLineParser!T(Config.init);
891 
892         T args;
893         parser.parseArgs(args, [ "-a", "A"]);
894         parser.parseArgs(args, [ "-b", "B"]);
895 
896         assert(args == T("A","B"));
897     }
898 }
899 
900 unittest
901 {
902 
903     struct T
904     {
905         @NamedArgument("x")                      string x;
906         @NamedArgument("foo")                    string foo;
907         @(PositionalArgument(0, "a").Optional()) string a;
908         @(PositionalArgument(1, "b").Optional()) string[] b;
909     }
910     static assert(["--foo","FOO","-x","X"].parseCLIArgs!T.get == T("X", "FOO"));
911     static assert(["--foo=FOO","-x=X"].parseCLIArgs!T.get == T("X", "FOO"));
912     static assert(["--foo=FOO","1","-x=X"].parseCLIArgs!T.get == T("X", "FOO", "1"));
913     static assert(["--foo=FOO","1","2","3","4"].parseCLIArgs!T.get == T(string.init, "FOO", "1",["2","3","4"]));
914     static assert(["-xX"].parseCLIArgs!T.get == T("X"));
915 
916     struct T1
917     {
918         @(PositionalArgument(0, "a")) string[3] a;
919         @(PositionalArgument(1, "b")) string[] b;
920     }
921     static assert(["1","2","3","4","5","6"].parseCLIArgs!T1.get == T1(["1","2","3"],["4","5","6"]));
922 
923     struct T2
924     {
925         @NamedArgument("foo") bool foo = true;
926     }
927     static assert(["--no-foo"].parseCLIArgs!T2.get == T2(false));
928 }
929 
930 unittest
931 {
932     struct T
933     {
934         @(PositionalArgument(0, "a").Optional())
935         string a = "not set";
936 
937         @(NamedArgument("b").Required())
938         int b;
939     }
940 
941     static assert(["-b", "4"].parseCLIArgs!T.get == T("not set", 4));
942 }
943 
944 unittest
945 {
946     struct T
947     {
948         @NamedArgument("x")   string x;
949         @NamedArgument("foo") string foo;
950     }
951 
952     auto test(T)(string[] args)
953     {
954         Config config;
955         config.caseSensitive = false;
956 
957         return args.parseCLIArgs!T(config).get;
958     }
959 
960     static assert(test!T(["--Foo","FOO","-X","X"]) == T("X", "FOO"));
961     static assert(test!T(["--FOo=FOO","-X=X"]) == T("X", "FOO"));
962 }
963 
964 unittest
965 {
966     auto test(T)(string[] args)
967     {
968         Config config;
969         config.bundling = true;
970 
971         return args.parseCLIArgs!T(config).get;
972     }
973 
974     struct T
975     {
976         @NamedArgument("a") bool a;
977         @NamedArgument("b") bool b;
978     }
979     static assert(test!T(["-a","-b"]) == T(true, true));
980     static assert(test!T(["-ab"]) == T(true, true));
981 }
982 
983 unittest
984 {
985     struct T
986     {
987         @NamedArgument("b") bool b;
988     }
989 
990     static assert(["-b"]        .parseCLIArgs!T.get == T(true));
991     static assert(["-b","true"] .parseCLIArgs!T.get == T(true));
992     static assert(["-b","false"].parseCLIArgs!T.get == T(false));
993     static assert(["-b=true"]   .parseCLIArgs!T.get == T(true));
994     static assert(["-b=false"]  .parseCLIArgs!T.get == T(false));
995 }
996 
997 
998 private struct Parsers
999 {
1000     static auto Convert(T)(string value)
1001     {
1002         import std.conv: to;
1003         return value.length > 0 ? value.to!T : T.init;
1004     }
1005 
1006     static auto PassThrough(string[] values)
1007     {
1008         return values;
1009     }
1010 }
1011 
1012 unittest
1013 {
1014     static assert(Parsers.Convert!int("7") == 7);
1015     static assert(Parsers.Convert!string("7") == "7");
1016     static assert(Parsers.Convert!char("7") == '7');
1017 
1018     static assert(Parsers.PassThrough(["7","8"]) == ["7","8"]);
1019 }
1020 
1021 
1022 private struct Actions
1023 {
1024     static auto Assign(DEST, SRC=DEST)(ref DEST param, SRC value)
1025     {
1026         param  = value;
1027     }
1028 
1029     static auto Append(T)(ref T param, T value)
1030     {
1031         param ~= value;
1032     }
1033 
1034     static auto Extend(T)(ref T[] param, T value)
1035     {
1036         param ~= value;
1037     }
1038 
1039     static auto CallFunction(F)(ref F func, RawParam param)
1040     {
1041         // ... func()
1042         static if(__traits(compiles, { func(); }))
1043         {
1044             func();
1045         }
1046         // ... func(string value)
1047         else static if(__traits(compiles, { func(param.value[0]); }))
1048         {
1049             foreach(value; param.value)
1050                 func(value);
1051         }
1052         // ... func(string[] value)
1053         else static if(__traits(compiles, { func(param.value); }))
1054         {
1055             func(param.value);
1056         }
1057         // ... func(RawParam param)
1058         else static if(__traits(compiles, { func(param); }))
1059         {
1060             func(param);
1061         }
1062         else
1063             static assert(false, "Unsupported callback: " ~ F.stringof);
1064     }
1065 
1066     static auto CallFunctionNoParam(F)(ref F func, Param!void param)
1067     {
1068         // ... func()
1069         static if(__traits(compiles, { func(); }))
1070         {
1071             func();
1072         }
1073         // ... func(string value)
1074         else static if(__traits(compiles, { func(string.init); }))
1075         {
1076             func(string.init);
1077         }
1078         // ... func(string[] value)
1079         else static if(__traits(compiles, { func([]); }))
1080         {
1081             func([]);
1082         }
1083         // ... func(Param!void param)
1084         else static if(__traits(compiles, { func(param); }))
1085         {
1086             func(param);
1087         }
1088         else
1089             static assert(false, "Unsupported callback: " ~ F.stringof);
1090     }
1091 }
1092 
1093 unittest
1094 {
1095     int i;
1096     Actions.Assign!(int)(i,7);
1097     assert(i == 7);
1098 }
1099 
1100 unittest
1101 {
1102     int[] i;
1103     Actions.Append!(int[])(i,[1,2,3]);
1104     Actions.Append!(int[])(i,[7,8,9]);
1105     assert(i == [1,2,3,7,8,9]);
1106 
1107     alias test = (int[] v1, int[] v2) {
1108         int[] res;
1109 
1110         Param!(int[]) param;
1111 
1112         alias F = Actions.Append!(int[]);
1113         param.value = v1;   ActionFunc!(F, int[], int[])(res, param);
1114 
1115         param.value = v2;   ActionFunc!(F, int[], int[])(res, param);
1116 
1117         return res;
1118     };
1119     static assert(test([1,2,3],[7,8,9]) == [1,2,3,7,8,9]);
1120 }
1121 
1122 unittest
1123 {
1124     int[][] i;
1125     Actions.Extend!(int[])(i,[1,2,3]);
1126     Actions.Extend!(int[])(i,[7,8,9]);
1127     assert(i == [[1,2,3],[7,8,9]]);
1128 }
1129 
1130 
1131 // values => bool
1132 // bool validate(T value)
1133 // bool validate(T[i] value)
1134 // bool validate(Param!T param)
1135 private struct ValidateFunc(alias F, T, string funcName="Validation")
1136 {
1137     static bool opCall(Param!T param)
1138     {
1139         static if(is(F == void))
1140         {
1141             return true;
1142         }
1143         else static if(__traits(compiles, { F(param); }))
1144         {
1145             // bool validate(Param!T param)
1146             return cast(bool) F(param);
1147         }
1148         else static if(__traits(compiles, { F(param.value); }))
1149         {
1150             // bool validate(T values)
1151             return cast(bool) F(param.value);
1152         }
1153         else static if(/*isArray!T &&*/ __traits(compiles, { F(param.value[0]); }))
1154         {
1155             // bool validate(T[i] value)
1156             foreach(value; param.value)
1157                 if(!F(value))
1158                     return false;
1159             return true;
1160         }
1161         else
1162             static assert(false, funcName~" function is not supported");
1163     }
1164 }
1165 
1166 unittest
1167 {
1168     auto test(alias F, T)(T[] values)
1169     {
1170         Param!(T[]) param;
1171         param.value = values;
1172         return ValidateFunc!(F, T[])(param);
1173     }
1174 
1175     // bool validate(T[] values)
1176     static assert(test!((string[] a) => true, string)(["1","2","3"]));
1177     static assert(test!((int[] a) => true, int)([1,2,3]));
1178 
1179     // bool validate(T value)
1180     static assert(test!((string a) => true, string)(["1","2","3"]));
1181     static assert(test!((int a) => true, int)([1,2,3]));
1182 
1183     // bool validate(Param!T param)
1184     static assert(test!((RawParam p) => true, string)(["1","2","3"]));
1185     static assert(test!((Param!(int[]) p) => true, int)([1,2,3]));
1186 }
1187 
1188 unittest
1189 {
1190     static assert(ValidateFunc!(void, string[])(RawParam(Config.init, "", ["1","2","3"])));
1191 
1192     static assert(!__traits(compiles, { ValidateFunc!(() {}, string[])(config, "", ["1","2","3"]); }));
1193     static assert(!__traits(compiles, { ValidateFunc!((int,int) {}, string[])(config, "", ["1","2","3"]); }));
1194 }
1195 
1196 
1197 private template ParseType(alias F, T)
1198 {
1199     import std.traits : Unqual;
1200 
1201     static if(is(F == void))
1202         alias ParseType = Unqual!T;
1203     else static if(Parameters!F.length == 0)
1204         static assert(false, "Parse function should take at least one parameter");
1205     else static if(Parameters!F.length == 1)
1206     {
1207         // T action(arg)
1208         alias ParseType = Unqual!(ReturnType!F);
1209         static assert(!is(ParseType == void), "Parse function should return value");
1210     }
1211     else static if(Parameters!F.length == 2 && is(Parameters!F[0] == Config))
1212     {
1213         // T action(Config config, arg)
1214         alias ParseType = Unqual!(ReturnType!F);
1215         static assert(!is(ParseType == void), "Parse function should return value");
1216     }
1217     else static if(Parameters!F.length == 2)
1218     {
1219         // ... action(ref T param, arg)
1220         alias ParseType = Parameters!F[0];
1221     }
1222     else static if(Parameters!F.length == 3)
1223     {
1224         // ... action(Config config, ref T param, arg)
1225         alias ParseType = Parameters!F[1];
1226     }
1227     else static if(Parameters!F.length == 4)
1228     {
1229         // ... action(Config config, string argName, ref T param, arg)
1230         alias ParseType = Parameters!F[2];
1231     }
1232     else
1233         static assert(false, "Parse function has too many parameters: "~Parameters!F.stringof);
1234 }
1235 
1236 unittest
1237 {
1238     static assert(is(ParseType!(void, double) == double));
1239     static assert(!__traits(compiles, { ParseType!((){}, double) p; }));
1240     static assert(!__traits(compiles, { ParseType!((int,int,int,int,int){}, double) p; }));
1241 
1242     // T action(arg)
1243     static assert(is(ParseType!((int)=>3, double) == int));
1244     static assert(!__traits(compiles, { ParseType!((int){}, double) p; }));
1245     // T action(Config config, arg)
1246     static assert(is(ParseType!((Config config, int)=>3, double) == int));
1247     static assert(!__traits(compiles, { ParseType!((Config config, int){}, double) p; }));
1248     // ... action(ref T param, arg)
1249     static assert(is(ParseType!((ref int, string v) {}, double) == int));
1250     // ... action(Config config, ref T param, arg)
1251     static assert(is(ParseType!((Config config, ref int, string v) {}, double) == int));
1252     // ... action(Config config, string argName, ref T param, arg)
1253     //static assert(is(ParseType!((Config config, string argName, ref int, string v) {}, double) == int));
1254 }
1255 
1256 
1257 // T parse(string[] values)
1258 // T parse(string value)
1259 // T parse(RawParam param)
1260 // bool parse(ref T receiver, RawParam param)
1261 // void parse(ref T receiver, RawParam param)
1262 private struct ParseFunc(alias F, T)
1263 {
1264     alias ParseType = .ParseType!(F, T);
1265 
1266     static bool opCall(ref ParseType receiver, RawParam param)
1267     {
1268         static if(is(F == void))
1269         {
1270             foreach(value; param.value)
1271                 receiver = Parsers.Convert!T(value);
1272             return true;
1273         }
1274         // T parse(string[] values)
1275         else static if(__traits(compiles, { receiver = cast(ParseType) F(param.value); }))
1276         {
1277             receiver = cast(ParseType) F(param.value);
1278             return true;
1279         }
1280         // T parse(string value)
1281         else static if(__traits(compiles, { receiver = cast(ParseType) F(param.value[0]); }))
1282         {
1283             foreach(value; param.value)
1284                 receiver = cast(ParseType) F(value);
1285             return true;
1286         }
1287         // T parse(RawParam param)
1288         else static if(__traits(compiles, { receiver = cast(ParseType) F(param); }))
1289         {
1290             receiver = cast(ParseType) F(param);
1291             return true;
1292         }
1293         // bool parse(ref T receiver, RawParam param)
1294         // void parse(ref T receiver, RawParam param)
1295         else static if(__traits(compiles, { F(receiver, param); }))
1296         {
1297             static if(__traits(compiles, { auto res = cast(bool) F(receiver, param); }))
1298             {
1299                 // bool parse(ref T receiver, RawParam param)
1300                 return cast(bool) F(receiver, param);
1301             }
1302             else
1303             {
1304                 // void parse(ref T receiver, RawParam param)
1305                 F(receiver, param);
1306                 return true;
1307             }
1308         }
1309         else
1310             static assert(false, "Parse function is not supported");
1311     }
1312 }
1313 
1314 unittest
1315 {
1316     int i;
1317     RawParam param;
1318     param.value = ["1","2","3"];
1319     assert(ParseFunc!(void, int)(i, param));
1320     assert(i == 3);
1321 }
1322 
1323 unittest
1324 {
1325     auto test(alias F, T)(string[] values)
1326     {
1327         T value;
1328         RawParam param;
1329         param.value = values;
1330         assert(ParseFunc!(F, T)(value, param));
1331         return value;
1332     }
1333 
1334     // T parse(string value)
1335     static assert(test!((string a) => a, string)(["1","2","3"]) == "3");
1336 
1337     // T parse(string[] values)
1338     static assert(test!((string[] a) => a, string[])(["1","2","3"]) == ["1","2","3"]);
1339 
1340     // T parse(RawParam param)
1341     static assert(test!((RawParam p) => p.value[0], string)(["1","2","3"]) == "1");
1342 
1343     // bool parse(ref T receiver, RawParam param)
1344     static assert(test!((ref string[] r, RawParam p) { r = p.value; return true; }, string[])(["1","2","3"]) == ["1","2","3"]);
1345 
1346     // void parse(ref T receiver, RawParam param)
1347     static assert(test!((ref string[] r, RawParam p) { r = p.value; }, string[])(["1","2","3"]) == ["1","2","3"]);
1348 }
1349 
1350 
1351 // bool action(ref T receiver, ParseType value)
1352 // void action(ref T receiver, ParseType value)
1353 // bool action(ref T receiver, Param!ParseType param)
1354 // void action(ref T receiver, Param!ParseType param)
1355 private struct ActionFunc(alias F, T, ParseType)
1356 {
1357     static bool opCall(ref T receiver, Param!ParseType param)
1358     {
1359         static if(is(F == void))
1360         {
1361             Actions.Assign!(T, ParseType)(receiver, param.value);
1362             return true;
1363         }
1364         // bool action(ref T receiver, ParseType value)
1365         // void action(ref T receiver, ParseType value)
1366         else static if(__traits(compiles, { F(receiver, param.value); }))
1367         {
1368             static if(__traits(compiles, { auto res = cast(bool) F(receiver, param.value); }))
1369             {
1370                 // bool action(ref T receiver, ParseType value)
1371                 return cast(bool) F(receiver, param.value);
1372             }
1373             else
1374             {
1375                 // void action(ref T receiver, ParseType value)
1376                 F(receiver, param.value);
1377                 return true;
1378             }
1379         }
1380         // bool action(ref T receiver, Param!ParseType param)
1381         // void action(ref T receiver, Param!ParseType param)
1382         else static if(__traits(compiles, { F(receiver, param); }))
1383         {
1384             static if(__traits(compiles, { auto res = cast(bool) F(receiver, param); }))
1385             {
1386                 // bool action(ref T receiver, Param!ParseType param)
1387                 return cast(bool) F(receiver, param);
1388             }
1389             else
1390             {
1391                 // void action(ref T receiver, Param!ParseType param)
1392                 F(receiver, param);
1393                 return true;
1394             }
1395         }
1396         else
1397             static assert(false, "Action function is not supported");
1398     }
1399 }
1400 
1401 unittest
1402 {
1403     auto param(T)(T values)
1404     {
1405         Param!T param;
1406         param.value = values;
1407         return param;
1408     }
1409     auto test(alias F, T)(T values)
1410     {
1411         T receiver;
1412         assert(ActionFunc!(F, T, T)(receiver, param(values)));
1413         return receiver;
1414     }
1415 
1416     static assert(test!(void, string[])(["1","2","3"]) == ["1","2","3"]);
1417 
1418     static assert(!__traits(compiles, { test!(() {}, string[])(["1","2","3"]); }));
1419     static assert(!__traits(compiles, { test!((int,int) {}, string[])(["1","2","3"]); }));
1420 
1421     // bool action(ref T receiver, ParseType value)
1422     static assert(test!((ref string[] p, string[] a) { p=a; return true; }, string[])(["1","2","3"]) == ["1","2","3"]);
1423 
1424     // void action(ref T receiver, ParseType value)
1425     static assert(test!((ref string[] p, string[] a) { p=a; }, string[])(["1","2","3"]) == ["1","2","3"]);
1426 
1427     // bool action(ref T receiver, Param!ParseType param)
1428     static assert(test!((ref string[] p, Param!(string[]) a) { p=a.value; return true; }, string[]) (["1","2","3"]) == ["1","2","3"]);
1429 
1430     // void action(ref T receiver, Param!ParseType param)
1431     static assert(test!((ref string[] p, Param!(string[]) a) { p=a.value; }, string[])(["1","2","3"]) == ["1","2","3"]);
1432 }
1433 
1434 
1435 // => receiver + bool
1436 // DEST action()
1437 // bool action(ref DEST receiver)
1438 // void action(ref DEST receiver)
1439 // bool action(ref DEST receiver, Param!void param)
1440 // void action(ref DEST receiver, Param!void param)
1441 private struct NoValueActionFunc(alias F, T)
1442 {
1443     static bool opCall(ref T receiver, Param!void param)
1444     {
1445         static if(is(F == void))
1446         {
1447             assert(false, "No-value action function is not provided");
1448         }
1449         else static if(__traits(compiles, { receiver = cast(T) F(); }))
1450         {
1451             // DEST action()
1452             receiver = cast(T) F();
1453             return true;
1454         }
1455         else static if(__traits(compiles, { F(receiver); }))
1456         {
1457             static if(__traits(compiles, { auto res = cast(bool) F(receiver); }))
1458             {
1459                 // bool action(ref DEST receiver)
1460                 return cast(bool) F(receiver);
1461             }
1462             else
1463             {
1464                 // void action(ref DEST receiver)
1465                 F(receiver);
1466                 return true;
1467             }
1468         }
1469         else static if(__traits(compiles, { F(receiver, param); }))
1470         {
1471             static if(__traits(compiles, { auto res = cast(bool) F(receiver, param); }))
1472             {
1473                 // bool action(ref DEST receiver, Param!void param)
1474                 return cast(bool) F(receiver, param);
1475             }
1476             else
1477             {
1478                 // void action(ref DEST receiver, Param!void param)
1479                 F(receiver, param);
1480                 return true;
1481             }
1482         }
1483         else
1484             static assert(false, "No-value action function has too many parameters: "~Parameters!F.stringof);
1485     }
1486 }
1487 
1488 unittest
1489 {
1490     auto test(alias F, T)()
1491     {
1492         T receiver;
1493         assert(NoValueActionFunc!(F, T)(receiver, Param!void.init));
1494         return receiver;
1495     }
1496 
1497     static assert(!__traits(compiles, { NoValueActionFunc!(() {}, int); }));
1498     static assert(!__traits(compiles, { NoValueActionFunc!((int) {}, int); }));
1499     static assert(!__traits(compiles, { NoValueActionFunc!((int,int) {}, int); }));
1500     static assert(!__traits(compiles, { NoValueActionFunc!((int,int,int) {}, int); }));
1501 
1502     // DEST action()
1503     static assert(test!(() => 7, int) == 7);
1504 
1505     // bool action(ref DEST param)
1506     static assert(test!((ref int p) { p=7; return true; }, int) == 7);
1507 
1508     // void action(ref DEST param)
1509     static assert(test!((ref int p) { p=7; }, int) == 7);
1510 
1511     // bool action(ref DEST receiver, Param!void param)
1512     static assert(test!((ref int r, Param!void p) { r=7; return true; }, int) == 7);
1513 
1514     // void action(ref DEST receiver, Param!void param)
1515     static assert(test!((ref int r, Param!void p) { r=7; }, int) == 7);
1516 }
1517 
1518 
1519 private void splitValues(ref RawParam param)
1520 {
1521     if(param.config.arraySep == char.init)
1522         return;
1523 
1524     import std.array : array, split;
1525     import std.algorithm : map, joiner;
1526 
1527     param.value = param.value.map!((string s) => s.split(param.config.arraySep)).joiner.array;
1528 }
1529 
1530 unittest
1531 {
1532     alias test = (char arraySep, string[] values)
1533     {
1534         Config config;
1535         config.arraySep = arraySep;
1536 
1537         auto param = RawParam(config, "", values);
1538 
1539         splitValues(param);
1540 
1541         return param.value;
1542     };
1543 
1544     static assert(test(',', []) == []);
1545     static assert(test(',', ["a","b","c"]) == ["a","b","c"]);
1546     static assert(test(',', ["a,b","c","d,e,f"]) == ["a","b","c","d","e","f"]);
1547     static assert(test(' ', ["a,b","c","d,e,f"]) == ["a,b","c","d,e,f"]);
1548 }
1549 
1550 
1551 private struct ValueParseFunctions(alias PreProcess,
1552                                    alias PreValidation,
1553                                    alias Parse,
1554                                    alias Validation,
1555                                    alias Action,
1556                                    alias NoValueAction)
1557 {
1558     alias changePreProcess   (alias func) = ValueParseFunctions!(      func, PreValidation, Parse, Validation, Action, NoValueAction);
1559     alias changePreValidation(alias func) = ValueParseFunctions!(PreProcess,          func, Parse, Validation, Action, NoValueAction);
1560     alias changeParse        (alias func) = ValueParseFunctions!(PreProcess, PreValidation,  func, Validation, Action, NoValueAction);
1561     alias changeValidation   (alias func) = ValueParseFunctions!(PreProcess, PreValidation, Parse,       func, Action, NoValueAction);
1562     alias changeAction       (alias func) = ValueParseFunctions!(PreProcess, PreValidation, Parse, Validation,   func, NoValueAction);
1563     alias changeNoValueAction(alias func) = ValueParseFunctions!(PreProcess, PreValidation, Parse, Validation, Action,          func);
1564 
1565     template addDefaults(T)
1566     {
1567         static if(is(PreProcess == void))
1568             alias preProc = DefaultValueParseFunctions!T;
1569         else
1570             alias preProc = DefaultValueParseFunctions!T.changePreProcess!PreProcess;
1571 
1572         static if(is(PreValidation == void))
1573             alias preVal = preProc;
1574         else
1575             alias preVal = preProc.changePreValidation!PreValidation;
1576 
1577         static if(is(Parse == void))
1578             alias parse = preVal;
1579         else
1580             alias parse = preVal.changeParse!Parse;
1581 
1582         static if(is(Validation == void))
1583             alias val = parse;
1584         else
1585             alias val = parse.changeValidation!Validation;
1586 
1587         static if(is(Action == void))
1588             alias action = val;
1589         else
1590             alias action = val.changeAction!Action;
1591 
1592         static if(is(NoValueAction == void))
1593             alias addDefaults = action;
1594         else
1595             alias addDefaults = action.changeNoValueAction!NoValueAction;
1596     }
1597 
1598 
1599     // Procedure to process (parse) the values to an argument of type T
1600     //  - if there is a value(s):
1601     //      - pre validate raw strings
1602     //      - parse raw strings
1603     //      - validate parsed values
1604     //      - action with values
1605     //  - if there is no value:
1606     //      - action if no value
1607     // Requirement: rawValues.length must be correct
1608     static bool parse(T)(ref T receiver, RawParam param)
1609     {
1610         return addDefaults!T.parseImpl(receiver, param);
1611     }
1612     static bool parseImpl(T)(ref T receiver, ref RawParam rawParam)
1613     {
1614         alias ParseType(T)     = .ParseType!(Parse, T);
1615 
1616         alias preValidation    = ValidateFunc!(PreValidation, string[], "Pre validation");
1617         alias parse(T)         = ParseFunc!(Parse, T);
1618         alias validation(T)    = ValidateFunc!(Validation, ParseType!T);
1619         alias action(T)        = ActionFunc!(Action, T, ParseType!T);
1620         alias noValueAction(T) = NoValueActionFunc!(NoValueAction, T);
1621 
1622         if(rawParam.value.length == 0)
1623         {
1624             return noValueAction!T(receiver, Param!void(rawParam.config, rawParam.name));
1625         }
1626         else
1627         {
1628             static if(!is(PreProcess == void))
1629                 PreProcess(rawParam);
1630 
1631             if(!preValidation(rawParam))
1632                 return false;
1633 
1634             auto parsedParam = Param!(ParseType!T)(rawParam.config, rawParam.name);
1635 
1636             if(!parse!T(parsedParam.value, rawParam))
1637                 return false;
1638 
1639             if(!validation!T(parsedParam))
1640                 return false;
1641 
1642             if(!action!T(receiver, parsedParam))
1643                 return false;
1644 
1645             return true;
1646         }
1647     }
1648 }
1649 
1650 
1651 private template DefaultValueParseFunctions(T)
1652 if(!is(T == void))
1653 {
1654     import std.traits;
1655     import std.conv: to;
1656 
1657     static if(isSomeString!T || isNumeric!T || is(T == enum))
1658     {
1659         alias DefaultValueParseFunctions = ValueParseFunctions!(
1660         void,   // pre process
1661         void,   // pre validate
1662         void,   // parse
1663         void,   // validate
1664         void,   // action
1665         void    // no-value action
1666         );
1667     }
1668     else static if(isBoolean!T)
1669     {
1670         alias DefaultValueParseFunctions = ValueParseFunctions!(
1671         void,                               // pre process
1672         void,                               // pre validate
1673         (string value)                      // parse
1674         {
1675             switch(value)
1676             {
1677                 case "":    goto case;
1678                 case "yes": goto case;
1679                 case "y":   return true;
1680                 case "no":  goto case;
1681                 case "n":   return false;
1682                 default:    return value.to!T;
1683             }
1684         },
1685         void,                               // validate
1686         void,                               // action
1687         (ref T result) { result = true; }   // no-value action
1688         );
1689     }
1690     else static if(isSomeChar!T)
1691     {
1692         alias DefaultValueParseFunctions = ValueParseFunctions!(
1693         void,                         // pre process
1694         void,                         // pre validate
1695         (string value)                // parse
1696         {
1697             return value.length > 0 ? value[0].to!T : T.init;
1698         },
1699         void,                         // validate
1700         void,                         // action
1701         void                          // no-value action
1702         );
1703     }
1704     else static if(isArray!T)
1705     {
1706         import std.traits: ForeachType;
1707 
1708         alias TElement = ForeachType!T;
1709 
1710         static if(!isArray!TElement || isSomeString!TElement)  // 1D array
1711         {
1712             static if(!isStaticArray!T)
1713                 alias action = Actions.Append!T;
1714             else
1715                 alias action = Actions.Assign!T;
1716 
1717             alias DefaultValueParseFunctions = DefaultValueParseFunctions!TElement
1718             .changePreProcess!splitValues
1719             .changeParse!((ref T receiver, RawParam param)
1720             {
1721                 static if(!isStaticArray!T)
1722                 {
1723                     if(receiver.length < param.value.length)
1724                         receiver.length = param.value.length;
1725                 }
1726 
1727                 foreach(i, value; param.value)
1728                 {
1729                     if(!DefaultValueParseFunctions!TElement.parse(receiver[i],
1730                         RawParam(param.config, param.name, [value])))
1731                         return false;
1732                 }
1733 
1734                 return true;
1735             })
1736             .changeAction!(action)
1737             .changeNoValueAction!((ref T param) {});
1738         }
1739         else static if(!isArray!(ForeachType!TElement) || isSomeString!(ForeachType!TElement))  // 2D array
1740         {
1741             alias DefaultValueParseFunctions = DefaultValueParseFunctions!TElement
1742             .changeAction!(Actions.Extend!TElement)
1743             .changeNoValueAction!((ref T param) { param ~= TElement.init; });
1744         }
1745         else
1746         {
1747             static assert(false, "Multi-dimentional arrays are not supported: " ~ T.stringof);
1748         }
1749     }
1750     else static if(isAssociativeArray!T)
1751     {
1752         import std.string : indexOf;
1753         alias DefaultValueParseFunctions = ValueParseFunctions!(
1754         splitValues,                                                // pre process
1755         void,                                                       // pre validate
1756         Parsers.PassThrough,                                        // parse
1757         void,                                                       // validate
1758         (ref T recepient, Param!(string[]) param)                   // action
1759         {
1760             alias K = KeyType!T;
1761             alias V = ValueType!T;
1762 
1763             foreach(input; param.value)
1764             {
1765                 auto j = indexOf(input, param.config.assignChar);
1766                 if(j < 0)
1767                     return false;
1768 
1769                 K key;
1770                 if(!DefaultValueParseFunctions!K.parse(key, RawParam(param.config, param.name, [input[0 .. j]])))
1771                     return false;
1772 
1773                 V value;
1774                 if(!DefaultValueParseFunctions!V.parse(value, RawParam(param.config, param.name, [input[j + 1 .. $]])))
1775                     return false;
1776 
1777                 recepient[key] = value;
1778             }
1779             return true;
1780         },
1781         (ref T param) {}    // no-value action
1782         );
1783     }
1784     else static if(is(T == delegate))
1785     {
1786         alias DefaultValueParseFunctions = ValueParseFunctions!(
1787             void,                           // pre process
1788             void,                           // pre validate
1789             Parsers.PassThrough,            // parse
1790             void,                           // validate
1791             Actions.CallFunction!T,         // action
1792             Actions.CallFunctionNoParam!T   // no-value action
1793         );
1794     }
1795      else
1796         static assert(false, "Type is not supported: " ~ T.stringof);
1797 }
1798 
1799 unittest
1800 {
1801     enum MyEnum { foo, bar, }
1802 
1803     import std.meta: AliasSeq;
1804     static foreach(T; AliasSeq!(string, bool, int, double, char, MyEnum))
1805         static foreach(R; AliasSeq!(T, T[], T[][]))
1806         {{
1807             // ensure that this compiles
1808             R receiver;
1809             RawParam param;
1810             param.value = [""];
1811             DefaultValueParseFunctions!R.parse(receiver, param);
1812         }}
1813 }
1814 
1815 unittest
1816 {
1817     alias test(R) = (string[][] values)
1818     {
1819         auto config = Config('=', ',');
1820         R receiver;
1821         foreach(value; values)
1822         {
1823             assert(DefaultValueParseFunctions!R.parse(receiver, RawParam(config, "", value)));
1824         }
1825         return receiver;
1826     };
1827 
1828     static assert(test!(string[])([["1","2","3"], [], ["4"]]) == ["1","2","3","4"]);
1829     static assert(test!(string[][])([["1","2","3"], [], ["4"]]) == [["1","2","3"],[],["4"]]);
1830 
1831     static assert(test!(string[string])([["a=bar","b=foo"], [], ["b=baz","c=boo"]]) == ["a":"bar", "b":"baz", "c":"boo"]);
1832 
1833     static assert(test!(string[])([["1,2,3"], [], ["4"]]) == ["1","2","3","4"]);
1834     static assert(test!(string[string])([["a=bar,b=foo"], [], ["b=baz,c=boo"]]) == ["a":"bar", "b":"baz", "c":"boo"]);
1835 
1836     static assert(test!(int[])([["1","2","3"], [], ["4"]]) == [1,2,3,4]);
1837     static assert(test!(int[][])([["1","2","3"], [], ["4"]]) == [[1,2,3],[],[4]]);
1838 
1839 }
1840 
1841 unittest
1842 {
1843     import std.math: isNaN;
1844     enum MyEnum { foo, bar, }
1845 
1846     alias test(T) = (string[] values)
1847     {
1848         T receiver;
1849         RawParam param;
1850         param.value = values;
1851         assert(DefaultValueParseFunctions!T.parse(receiver, param));
1852         return receiver;
1853     };
1854 
1855     static assert(test!string([""]) == "");
1856     static assert(test!string(["foo"]) == "foo");
1857     static assert(isNaN(test!double([""])));
1858     static assert(test!double(["-12.34"]) == -12.34);
1859     static assert(test!double(["12.34"]) == 12.34);
1860     static assert(test!uint(["1234"]) == 1234);
1861     static assert(test!int([""]) == int.init);
1862     static assert(test!int(["-1234"]) == -1234);
1863     static assert(test!char([""]) == char.init);
1864     static assert(test!char(["f"]) == 'f');
1865     static assert(test!bool([]) == true);
1866     static assert(test!bool([""]) == true);
1867     static assert(test!bool(["yes"]) == true);
1868     static assert(test!bool(["y"]) == true);
1869     static assert(test!bool(["true"]) == true);
1870     static assert(test!bool(["no"]) == false);
1871     static assert(test!bool(["n"]) == false);
1872     static assert(test!bool(["false"]) == false);
1873     static assert(test!MyEnum(["foo"]) == MyEnum.foo);
1874     static assert(test!MyEnum(["bar"]) == MyEnum.bar);
1875     static assert(test!(MyEnum[])(["bar","foo"]) == [MyEnum.bar, MyEnum.foo]);
1876     static assert(test!(string[string])(["a=bar","b=foo"]) == ["a":"bar", "b":"foo"]);
1877     static assert(test!(MyEnum[string])(["a=bar","b=foo"]) == ["a":MyEnum.bar, "b":MyEnum.foo]);
1878     static assert(test!(int[MyEnum])(["bar=3","foo=5"]) == [MyEnum.bar:3, MyEnum.foo:5]);
1879 }
1880 
1881 
1882 private struct ArgumentInfo
1883 {
1884     string[] names;
1885 
1886     string helpText;
1887 
1888     bool hideFromHelp = false;      // if true then this argument is not printed on help page
1889 
1890     bool required;
1891 
1892     Nullable!uint position;
1893 
1894     @property bool positional() const { return !position.isNull; }
1895 
1896     Nullable!ulong minValuesCount;
1897     Nullable!ulong maxValuesCount;
1898 
1899     private bool checkValuesCount(in Config config, string argName, ulong count) const
1900     {
1901         immutable min = minValuesCount.get;
1902         immutable max = maxValuesCount.get;
1903 
1904         // override for boolean flags
1905         if(allowBooleanNegation && count == 1)
1906             return true;
1907 
1908         if(min == max && count != min)
1909             return config.onError("argument ",argName,": expected ",min,min == 1 ? " value" : " values");
1910         if(count < min)
1911             return config.onError("argument ",argName,": expected at least ",min,min == 1 ? " value" : " values");
1912         if(count > max)
1913             return config.onError("argument ",argName,": expected at most ",max,max == 1 ? " value" : " values");
1914 
1915         return true;
1916     }
1917 
1918     private bool allowBooleanNegation = true;
1919 }
1920 
1921 
1922 
1923 private struct ArgumentUDA(alias ValueParseFunctions)
1924 {
1925     ArgumentInfo info;
1926 
1927     alias parsingFunc = ValueParseFunctions;
1928 
1929 
1930 
1931     auto ref HelpText(string text)
1932     {
1933         info.helpText = text;
1934         return this;
1935     }
1936 
1937     auto ref HideFromHelp(bool hide = true)
1938     {
1939         info.hideFromHelp = hide;
1940         return this;
1941     }
1942 
1943     auto ref Required()
1944     {
1945         info.required = true;
1946         return this;
1947     }
1948 
1949     auto ref Optional()
1950     {
1951         info.required = false;
1952         return this;
1953     }
1954 
1955     auto ref NumberOfValues(ulong num)()
1956     if(num > 0)
1957     {
1958         info.minValuesCount = num;
1959         info.maxValuesCount = num;
1960         return this;
1961     }
1962 
1963     auto ref NumberOfValues(ulong min, ulong max)()
1964     if(0 < min && min <= max)
1965     {
1966         info.minValuesCount = min;
1967         info.maxValuesCount = max;
1968         return this;
1969     }
1970 
1971     auto ref MinNumberOfValues(ulong min)()
1972     if(0 < min)
1973     {
1974         assert(min <= info.maxValuesCount.get(ulong.max));
1975 
1976         info.minValuesCount = min;
1977         return this;
1978     }
1979 
1980     auto ref MaxNumberOfValues(ulong max)()
1981     if(0 < max)
1982     {
1983         assert(max >= info.minValuesCount.get(0));
1984 
1985         info.maxValuesCount = max;
1986         return this;
1987     }
1988 
1989     // ReverseSwitch
1990 }
1991 
1992 private enum bool isArgumentUDA(T) = (is(typeof(T.info) == ArgumentInfo) && is(T.parsingFunc));
1993 
1994 
1995 auto PreValidation(alias func, ARG)(ARG arg)
1996 if(isArgumentUDA!ARG)
1997 {
1998     return ArgumentUDA!(arg.parsingFunc.changePreValidation!func)(arg.tupleof);
1999 }
2000 
2001 auto Parse(alias func, ARG)(ARG arg)
2002 if(isArgumentUDA!ARG)
2003 {
2004     return ArgumentUDA!(arg.parsingFunc.changeParse!func)(arg.tupleof);
2005 }
2006 
2007 auto Validation(alias func, ARG)(ARG arg)
2008 if(isArgumentUDA!ARG)
2009 {
2010     return ArgumentUDA!(arg.parsingFunc.changeValidation!func)(arg.tupleof);
2011 }
2012 
2013 auto Action(alias func, ARG)(ARG arg)
2014 if(isArgumentUDA!ARG)
2015 {
2016     return ArgumentUDA!(arg.parsingFunc.changeAction!func)(arg.tupleof);
2017 }
2018 
2019 auto AllowNoValue(alias valueToUse, ARG)(ARG arg)
2020 if(isArgumentUDA!ARG)
2021 {
2022     auto desc = ArgumentUDA!(arg.parsingFunc.changeNoValueAction!(() { return valueToUse; }))(arg.tupleof);
2023     desc.info.minValuesCount = 0;
2024     return desc;
2025 }
2026 
2027 auto RequireNoValue(alias valueToUse, ARG)(ARG arg)
2028 if(isArgumentUDA!ARG)
2029 {
2030     auto desc = arg.AllowNoValue!valueToUse;
2031     desc.info.minValuesCount = 0;
2032     desc.info.maxValuesCount = 0;
2033     return desc;
2034 }
2035 
2036 auto Counter(ARG)(ARG arg)
2037 if(isArgumentUDA!ARG)
2038 {
2039     struct CounterParsingFunction
2040     {
2041         static bool parse(T)(ref T receiver, const ref RawParam param)
2042         {
2043             assert(param.value.length == 0);
2044 
2045             ++receiver;
2046 
2047             return true;
2048         }
2049     }
2050 
2051     auto desc = ArgumentUDA!(CounterParsingFunction)(arg.tupleof);
2052     desc.info.minValuesCount = 0;
2053     desc.info.maxValuesCount = 0;
2054     return desc;
2055 }
2056 
2057 
2058 unittest
2059 {
2060     struct T
2061     {
2062         @(NamedArgument("a").Counter()) int a;
2063     }
2064 
2065     static assert(["-a","-a","-a"].parseCLIArgs!T.get == T(3));
2066 }
2067 
2068 
2069 auto AllowedValues(alias values, ARG)(ARG arg)
2070 {
2071     import std.array : assocArray;
2072     import std.range : cycle;
2073 
2074     enum valuesAA = assocArray(values, cycle([false]));
2075 
2076     return arg.Validation!((KeyType!(typeof(valuesAA)) value) => value in valuesAA);
2077 }
2078 
2079 
2080 unittest
2081 {
2082     struct T
2083     {
2084         @(NamedArgument("a").AllowedValues!([1,3,5])) int a;
2085     }
2086 
2087     static assert(["-a","2"].parseCLIArgs!T.isNull);
2088     static assert(["-a","3"].parseCLIArgs!T.get == T(3));
2089 }
2090 
2091 
2092 auto PositionalArgument(uint pos)
2093 {
2094     auto arg = ArgumentUDA!(ValueParseFunctions!(void, void, void, void, void, void))(ArgumentInfo()).Required();
2095     arg.info.position = pos;
2096     return arg;
2097 }
2098 
2099 auto PositionalArgument(uint pos, string name)
2100 {
2101     auto arg = ArgumentUDA!(ValueParseFunctions!(void, void, void, void, void, void))(ArgumentInfo([name])).Required();
2102     arg.info.position = pos;
2103     return arg;
2104 }
2105 
2106 auto NamedArgument(string[] name)
2107 {
2108     return ArgumentUDA!(ValueParseFunctions!(void, void, void, void, void, void))(ArgumentInfo(name)).Optional();
2109 }
2110 
2111 auto NamedArgument(string name)
2112 {
2113     return ArgumentUDA!(ValueParseFunctions!(void, void, void, void, void, void))(ArgumentInfo([name])).Optional();
2114 }
2115 
2116 private struct TrailingArgumentUDA
2117 {
2118 }
2119 
2120 auto TrailingArguments()
2121 {
2122     return TrailingArgumentUDA();
2123 }
2124 
2125 unittest
2126 {
2127     struct T
2128     {
2129         @NamedArgument("a")  string a;
2130         @NamedArgument("b")  string b;
2131 
2132         @TrailingArguments() string[] args;
2133     }
2134 
2135     static assert(["-a","A","--","-b","B"].parseCLIArgs!T.get == T("A","",["-b","B"]));
2136 }
2137 
2138 unittest
2139 {
2140     struct T
2141     {
2142         @NamedArgument("i")  int i;
2143         @NamedArgument("u")  uint u;
2144         @NamedArgument("d")  double d;
2145     }
2146 
2147     static assert(["-i","-5","-u","8","-d","12.345"].parseCLIArgs!T.get == T(-5,8,12.345));
2148 }
2149 
2150 unittest
2151 {
2152     struct T
2153     {
2154         @(NamedArgument("a")) int[]   a;
2155         @(NamedArgument("b")) int[][] b;
2156     }
2157 
2158     static assert(["-a","1","2","3","-a","4","5"].parseCLIArgs!T.get.a == [1,2,3,4,5]);
2159     static assert(["-b","1","2","3","-b","4","5"].parseCLIArgs!T.get.b == [[1,2,3],[4,5]]);
2160 }
2161 
2162 unittest
2163 {
2164     struct T
2165     {
2166         @(NamedArgument("a")) int[] a;
2167     }
2168 
2169     Config cfg;
2170     cfg.arraySep = ',';
2171 
2172     assert(["-a","1,2,3","-a","4","5"].parseCLIArgs!T(cfg).get == T([1,2,3,4,5]));
2173 }
2174 
2175 unittest
2176 {
2177     struct T
2178     {
2179         @(NamedArgument("a")) int[string] a;
2180     }
2181 
2182     static assert(["-a=foo=3","-a","boo=7"].parseCLIArgs!T.get.a == ["foo":3,"boo":7]);
2183 }
2184 
2185 unittest
2186 {
2187     struct T
2188     {
2189         @(NamedArgument("a")) int[string] a;
2190     }
2191 
2192     Config cfg;
2193     cfg.arraySep = ',';
2194 
2195     assert(["-a=foo=3,boo=7"].parseCLIArgs!T(cfg).get.a == ["foo":3,"boo":7]);
2196     assert(["-a","foo=3,boo=7"].parseCLIArgs!T(cfg).get.a == ["foo":3,"boo":7]);
2197 }
2198 
2199 unittest
2200 {
2201     struct T
2202     {
2203         enum Fruit { apple, pear };
2204 
2205         @(NamedArgument("a")) Fruit a;
2206     }
2207 
2208     static assert(["-a","apple"].parseCLIArgs!T.get == T(T.Fruit.apple));
2209     static assert(["-a=pear"].parseCLIArgs!T.get == T(T.Fruit.pear));
2210 }
2211 
2212 unittest
2213 {
2214     struct T
2215     {
2216         @(NamedArgument("a")) string[] a;
2217     }
2218 
2219     assert(["-a","1,2,3","-a","4","5"].parseCLIArgs!T.get == T(["1,2,3","4","5"]));
2220 
2221     Config cfg;
2222     cfg.arraySep = ',';
2223 
2224     assert(["-a","1,2,3","-a","4","5"].parseCLIArgs!T(cfg).get == T(["1","2","3","4","5"]));
2225 }
2226 
2227 unittest
2228 {
2229     struct T
2230     {
2231         @(NamedArgument("a").AllowNoValue  !10) int a;
2232         @(NamedArgument("b").RequireNoValue!20) int b;
2233     }
2234 
2235     static assert(["-a"].parseCLIArgs!T.get.a == 10);
2236     static assert(["-b"].parseCLIArgs!T.get.b == 20);
2237     static assert(["-a", "30"].parseCLIArgs!T.get.a == 30);
2238     assert(["-b", "30"].parseCLIArgs!T.isNull);
2239 }
2240 
2241 unittest
2242 {
2243     struct T
2244     {
2245         @(NamedArgument("a")
2246          .PreValidation!((string s) { return s.length > 1 && s[0] == '!'; })
2247          .Parse        !((string s) { return s[1]; })
2248          .Validation   !((char v) { return v >= '0' && v <= '9'; })
2249          .Action       !((ref int a, char v) { a = v - '0'; })
2250         )
2251         int a;
2252     }
2253 
2254     static assert(["-a","!4"].parseCLIArgs!T.get.a == 4);
2255 }
2256 
2257 unittest
2258 {
2259     static struct T
2260     {
2261         int a;
2262 
2263         @(NamedArgument("a")) void foo() { a++; }
2264     }
2265 
2266     static assert(["-a","-a","-a","-a"].parseCLIArgs!T.get.a == 4);
2267 }