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     /**
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        Add a -h/--help option to the parser.
53        Defaults to true.
54      */
55     bool addHelp = true;
56 
57     /**
58        Delegate that processes error messages if they happen during argument parsing.
59        By default all errors are printed to stderr.
60      */
61     private void delegate(string s) nothrow errorHandlerFunc;
62 
63     @property auto errorHandler(void function(string s) nothrow func)
64     {
65         return errorHandlerFunc = (string msg) { func(msg); };
66     }
67 
68     @property auto errorHandler(void delegate(string s) nothrow func)
69     {
70         return errorHandlerFunc = func;
71     }
72 
73 
74     private void onError(A...)(A args) const nothrow
75     {
76         import std.conv: text;
77         import std.stdio: stderr, writeln;
78 
79         try
80         {
81             if(errorHandlerFunc)
82                 errorHandlerFunc(text!A(args));
83             else
84                 stderr.writeln("Error: ", args);
85         }
86         catch(Exception e)
87         {
88             throw new Error(e.msg);
89         }
90     }
91 }
92 
93 unittest
94 {
95     Config.init.onError("--just testing error func--",1,2.3,false);
96     Config c;
97     c.errorHandler = (string s){};
98     c.onError("--just testing error func--",1,2.3,false);
99 }
100 
101 
102 struct Param(VALUE_TYPE)
103 {
104     const Config config;
105     string name;
106 
107     static if(!is(VALUE_TYPE == void))
108         VALUE_TYPE value;
109 }
110 
111 alias RawParam = Param!(string[]);
112 
113 
114 private enum ArgumentType { unknown, positional, shortName, longName }
115 
116 private auto splitArgumentName(string arg, const Config config)
117 {
118     import std.typecons : nullable;
119     import std.string : indexOf;
120 
121     struct Result
122     {
123         ArgumentType    type = ArgumentType.unknown;
124         string          name;
125         string          origName;
126         Nullable!string value;
127     }
128 
129     if(arg.length == 0)
130         return Result.init;
131 
132     if(arg[0] != config.namedArgChar)
133         return Result(ArgumentType.positional, string.init, string.init, nullable(arg));
134 
135     if(arg.length == 1)
136         return Result.init;
137 
138     Result result;
139 
140     auto idxAssignChar = config.assignChar == char.init ? -1 : arg.indexOf(config.assignChar);
141     if(idxAssignChar < 0)
142         result.origName = arg;
143     else
144     {
145         result.origName = arg[0 .. idxAssignChar];
146         result.value = nullable(arg[idxAssignChar + 1 .. $]);
147     }
148 
149     if(result.origName[1] == config.namedArgChar)
150     {
151         result.type = ArgumentType.longName;
152         result.name = result.origName[2..$];
153     }
154     else
155     {
156         result.type = ArgumentType.shortName;
157         result.name = result.origName[1..$];
158     }
159 
160     return result;
161 }
162 
163 unittest
164 {
165     import std.typecons : tuple, nullable;
166 
167     static assert(splitArgumentName("", Config.init).tupleof == tuple(ArgumentType.init, string.init, string.init, Nullable!string.init).tupleof);
168     static assert(splitArgumentName("-", Config.init).tupleof == tuple(ArgumentType.init, string.init, string.init, Nullable!string.init).tupleof);
169     static assert(splitArgumentName("abc=4", Config.init).tupleof == tuple(ArgumentType.positional, string.init, string.init, "abc=4").tupleof);
170     static assert(splitArgumentName("-abc", Config.init).tupleof == tuple(ArgumentType.shortName, "abc", "-abc", Nullable!string.init).tupleof);
171     static assert(splitArgumentName("--abc", Config.init).tupleof == tuple(ArgumentType.longName, "abc", "--abc", Nullable!string.init).tupleof);
172     static assert(splitArgumentName("-abc=fd", Config.init).tupleof == tuple(ArgumentType.shortName, "abc", "-abc", "fd").tupleof);
173     static assert(splitArgumentName("--abc=fd", Config.init).tupleof == tuple(ArgumentType.longName, "abc", "--abc", "fd").tupleof);
174     static assert(splitArgumentName("-abc=", Config.init).tupleof == tuple(ArgumentType.shortName, "abc", "-abc", nullable("")).tupleof);
175     static assert(splitArgumentName("--abc=", Config.init).tupleof == tuple(ArgumentType.longName, "abc", "--abc", nullable("")).tupleof);
176     static assert(splitArgumentName("-=abc", Config.init).tupleof == tuple(ArgumentType.shortName, string.init, "-", "abc").tupleof);
177     static assert(splitArgumentName("--=abc", Config.init).tupleof == tuple(ArgumentType.longName, string.init, "--", "abc").tupleof);
178 }
179 
180 
181 private template defaultValuesCount(T)
182 if(!is(T == void))
183 {
184     import std.traits;
185 
186     static if(isBoolean!T)
187     {
188         enum min = 0;
189         enum max = 1;
190     }
191     else static if(isSomeString!T || isScalarType!T)
192     {
193         enum min = 1;
194         enum max = 1;
195     }
196     else static if(isStaticArray!T)
197     {
198         enum min = 1;
199         enum max = T.length;
200     }
201     else static if(isArray!T || isAssociativeArray!T)
202     {
203         enum min = 1;
204         enum max = ulong.max;
205     }
206     else static if(is(T == function))
207     {
208         // ... function()
209         static if(__traits(compiles, { T(); }))
210         {
211             enum min = 0;
212             enum max = 0;
213         }
214         // ... function(string value)
215         else static if(__traits(compiles, { T(string.init); }))
216         {
217             enum min = 1;
218             enum max = 1;
219         }
220         // ... function(string[] value)
221         else static if(__traits(compiles, { T([string.init]); }))
222         {
223             enum min = 0;
224             enum max = ulong.max;
225         }
226         // ... function(RawParam param)
227         else static if(__traits(compiles, { T(RawParam.init); }))
228         {
229             enum min = 1;
230             enum max = ulong.max;
231         }
232         else
233             static assert(false, "Unsupported callback: " ~ T.stringof);
234     }
235     else
236         static assert(false, "Type is not supported: " ~ T.stringof);
237 }
238 
239 
240 private template EnumMembersAsStrings(E)
241 {
242     enum EnumMembersAsStrings = {
243         import std.traits: EnumMembers;
244         alias members = EnumMembers!E;
245 
246         typeof(__traits(identifier, members[0]))[] res;
247         static foreach (i, _; members)
248             res ~= __traits(identifier, members[i]);
249 
250         return res;
251     }();
252 }
253 
254 unittest
255 {
256     enum E { abc, def, ghi }
257     assert(EnumMembersAsStrings!E == ["abc", "def", "ghi"]);
258 }
259 
260 private auto setDefaults(TYPE, alias symbol)(ArgumentInfo info)
261 {
262     static if(!isBoolean!TYPE)
263         info.allowBooleanNegation = false;
264 
265     static if(is(TYPE == enum))
266         info.setAllowedValues!(EnumMembersAsStrings!TYPE);
267 
268     if(info.names.length == 0)
269         info.names = [ symbol ];
270 
271     if(info.minValuesCount.isNull) info.minValuesCount = defaultValuesCount!TYPE.min;
272     if(info.maxValuesCount.isNull) info.maxValuesCount = defaultValuesCount!TYPE.max;
273 
274     if(info.placeholder.length == 0)
275     {
276         import std.uni : toUpper;
277         info.placeholder = info.positional ? symbol : symbol.toUpper;
278     }
279 
280     return info;
281 }
282 
283 unittest
284 {
285     ArgumentInfo info;
286     info.allowBooleanNegation = true;
287     info.position = 0;
288 
289     auto res = info.setDefaults!(int, "default-name");
290     assert(!res.allowBooleanNegation);
291     assert(res.names == [ "default-name" ]);
292     assert(res.minValuesCount == defaultValuesCount!int.min);
293     assert(res.maxValuesCount == defaultValuesCount!int.max);
294     assert(res.placeholder == "default-name");
295 
296     info.placeholder = "myvalue";
297     res = info.setDefaults!(int, "default-name");
298     assert(res.placeholder == "myvalue");
299 }
300 
301 unittest
302 {
303     ArgumentInfo info;
304     info.allowBooleanNegation = true;
305 
306     auto res = info.setDefaults!(bool, "default_name");
307     assert(res.allowBooleanNegation);
308     assert(res.names == ["default_name"]);
309     assert(res.minValuesCount == defaultValuesCount!bool.min);
310     assert(res.maxValuesCount == defaultValuesCount!bool.max);
311     assert(res.placeholder == "DEFAULT_NAME");
312 
313     info.placeholder = "myvalue";
314     res = info.setDefaults!(bool, "default_name");
315     assert(res.placeholder == "myvalue");
316 }
317 
318 unittest
319 {
320     enum E { a=1, b=1, c }
321     static assert(EnumMembersAsStrings!E == ["a","b","c"]);
322 
323     ArgumentInfo info;
324     auto res = info.setDefaults!(E, "default-name");
325     assert(res.placeholder == "{a,b,c}");
326 
327     info.placeholder = "myvalue";
328     res = info.setDefaults!(E, "default-name");
329     assert(res.placeholder == "myvalue");
330 }
331 
332 
333 private auto checkDuplicates(alias sortedRange, string errorMsg)() {
334     static if(sortedRange.length >= 2)
335     {
336         enum value = {
337             import std.conv : to;
338 
339             foreach(i; 1..sortedRange.length-1)
340                 if(sortedRange[i-1] == sortedRange[i])
341                     return sortedRange[i].to!string;
342 
343             return "";
344         }();
345         static assert(value.length == 0, errorMsg ~ value);
346     }
347 
348     return true;
349 }
350 
351 private bool checkArgumentNames(T)()
352 {
353     enum names = () {
354         import std.algorithm : sort;
355 
356         string[] names;
357         static foreach (sym; getSymbolsByUDA!(T, ArgumentUDA))
358         {{
359             enum argUDA = getUDAs!(__traits(getMember, T, __traits(identifier, sym)), ArgumentUDA)[0];
360 
361             static assert(!argUDA.info.positional || argUDA.info.names.length <= 1,
362                  "Positional argument should have exactly one name: "~T.stringof~"."~sym.stringof);
363 
364             static foreach (name; argUDA.info.names)
365             {
366                 static assert(name.length > 0, "Argument name can't be empty: "~T.stringof~"."~sym.stringof);
367 
368                 names ~= name;
369             }
370         }}
371 
372         return names.sort;
373     }();
374 
375     return checkDuplicates!(names, "Argument name appears more than once: ");
376 }
377 
378 private bool checkPositionalIndexes(T)()
379 {
380     import std.conv  : to;
381     import std.range : lockstep, iota;
382 
383 
384     enum positions = () {
385         import std.algorithm : sort;
386 
387         uint[] positions;
388         static foreach (sym; getSymbolsByUDA!(T, ArgumentUDA))
389         {{
390             enum argUDA = getUDAs!(__traits(getMember, T, __traits(identifier, sym)), ArgumentUDA)[0];
391 
392             static if (argUDA.info.positional)
393                 positions ~= argUDA.info.position.get;
394         }}
395 
396         return positions.sort;
397     }();
398 
399     if(!checkDuplicates!(positions, "Positional arguments have duplicated position: "))
400         return false;
401 
402     static foreach (i, pos; lockstep(iota(0, positions.length), positions))
403         static assert(i == pos, "Positional arguments have missed position: " ~ i.to!string);
404 
405     return true;
406 }
407 
408 private struct Group
409 {
410     string name;
411     string description;
412 
413     private size_t[] arguments;
414 
415     auto ref Description(string text)
416     {
417         description = text;
418         return this;
419     }
420 
421 }
422 
423 auto ArgumentGroup(string name)
424 {
425     return Group(name);
426 }
427 
428 unittest
429 {
430     auto g = ArgumentGroup("name").Description("description");
431     assert(g.name == "name");
432     assert(g.description == "description");
433 }
434 
435 
436 private struct RestrictionGroup
437 {
438     string location;
439 
440     enum Type { together, exclusive }
441     Type type;
442 
443     private size_t[] arguments;
444 }
445 
446 auto RequiredTogether(string file=__FILE__, uint line = __LINE__)()
447 {
448     import std.conv: to;
449     return RestrictionGroup(file~":"~line.to!string, RestrictionGroup.Type.together);
450 }
451 
452 auto MutuallyExclusive(string file=__FILE__, uint line = __LINE__)()
453 {
454     import std.conv: to;
455     return RestrictionGroup(file~":"~line.to!string, RestrictionGroup.Type.exclusive);
456 }
457 
458 unittest
459 {
460     auto t = RequiredTogether();
461     assert(t.location.length > 0);
462     assert(t.type == RestrictionGroup.Type.together);
463 
464     auto e = MutuallyExclusive();
465     assert(e.location.length > 0);
466     assert(e.type == RestrictionGroup.Type.exclusive);
467 }
468 
469 
470 private alias ParseFunction(RECEIVER) = bool delegate(in Config config, string argName, ref RECEIVER receiver, string[] rawValues);
471 private alias Restriction = bool delegate(in Config config, in bool[size_t] cliArgs);
472 
473 // Have to do this magic because closures are not supported in CFTE
474 // DMD v2.098.0 prints "Error: closures are not yet supported in CTFE"
475 auto partiallyApply(alias fun,C...)(C context)
476 {
477     import std.traits: ParameterTypeTuple;
478     import core.lifetime: move, forward;
479 
480     return &new class(move(context))
481     {
482         C context;
483 
484         this(C ctx)
485         {
486             foreach(i, ref c; context)
487                 c = move(ctx[i]);
488         }
489 
490         auto opCall(ParameterTypeTuple!fun[context.length..$] args) const
491         {
492             return fun(context, forward!args);
493         }
494     }.opCall;
495 }
496 
497 private struct Restrictions
498 {
499     static Restriction RequiredArg(ArgumentInfo info)(size_t index)
500     {
501         return partiallyApply!((size_t index, in Config config, in bool[size_t] cliArgs)
502         {
503             if(index in cliArgs)
504                 return true;
505 
506             config.onError("The following argument is required: ", info.names[0].getArgumentName(config));
507             return false;
508         })(index);
509     }
510 
511     static bool RequiredTogether(in Config config,
512                                  in bool[size_t] cliArgs,
513                                  in size_t[] restrictionArgs,
514                                  in ArgumentInfo[] allArgs)
515     {
516         size_t foundIndex = size_t.max;
517         size_t missedIndex = size_t.max;
518 
519         foreach(index; restrictionArgs)
520         {
521             if(index in cliArgs)
522             {
523                 if(foundIndex == size_t.max)
524                     foundIndex = index;
525             }
526             else if(missedIndex == size_t.max)
527                 missedIndex = index;
528 
529             if(foundIndex != size_t.max && missedIndex != size_t.max)
530             {
531                 config.onError("Missed argument '", allArgs[missedIndex].names[0].getArgumentName(config),
532                     "' - it is required by argument '", allArgs[foundIndex].names[0].getArgumentName(config),"'");
533                 return false;
534             }
535         }
536 
537         return true;
538     }
539 
540     static bool MutuallyExclusive(in Config config,
541                                   in bool[size_t] cliArgs,
542                                   in size_t[] restrictionArgs,
543                                   in ArgumentInfo[] allArgs)
544     {
545         size_t foundIndex = size_t.max;
546 
547         foreach(index; restrictionArgs)
548             if(index in cliArgs)
549             {
550                 if(foundIndex == size_t.max)
551                     foundIndex = index;
552                 else
553                 {
554                     config.onError("Argument '", allArgs[foundIndex].names[0].getArgumentName(config),
555                                    "' is not allowed with argument '", allArgs[index].names[0].getArgumentName(config),"'");
556                     return false;
557                 }
558 
559             }
560 
561         return true;
562     }
563 }
564 
565 private struct Arguments(RECEIVER)
566 {
567     static assert(getSymbolsByUDA!(RECEIVER, TrailingArguments).length <= 1,
568                   "Type "~RECEIVER.stringof~" must have at most one 'TrailingArguments' UDA");
569 
570     private enum _validate = checkArgumentNames!RECEIVER &&
571                              checkPositionalIndexes!RECEIVER;
572 
573 
574 
575     immutable string function(string str) convertCase;
576 
577     private ArgumentInfo[] arguments;
578     private ParseFunction!RECEIVER[] parseFunctions;
579 
580     // named arguments
581     private size_t[string] argsNamed;
582 
583     // positional arguments
584     private size_t[] argsPositional;
585 
586 
587     Group[] groups;
588     enum requiredGroupIndex = 0;
589     enum optionalGroupIndex = 1;
590 
591     size_t[string] groupsByName;
592 
593     Restriction[] restrictions;
594     RestrictionGroup[] restrictionGroups;
595 
596     @property ref Group requiredGroup() { return groups[requiredGroupIndex]; }
597     @property ref const(Group) requiredGroup() const { return groups[requiredGroupIndex]; }
598     @property ref Group optionalGroup() { return groups[optionalGroupIndex]; }
599     @property ref const(Group) optionalGroup() const { return groups[optionalGroupIndex]; }
600 
601     @property auto positionalArguments() const { return argsPositional; }
602 
603 
604     this(bool caseSensitive)
605     {
606         if(caseSensitive)
607             convertCase = s => s;
608         else
609             convertCase = (string str)
610             {
611                 import std.uni : toUpper;
612                 return str.toUpper;
613             };
614 
615         groups = [ Group("Required arguments"), Group("Optional arguments") ];
616     }
617 
618     private void addArgument(ArgumentInfo info, RestrictionGroup[] restrictions, Group group)(ParseFunction!RECEIVER parse)
619     {
620         auto index = (group.name in groupsByName);
621         if(index !is null)
622             addArgument!(info, restrictions)(parse, groups[*index]);
623         else
624         {
625             groupsByName[group.name] = groups.length;
626             groups ~= group;
627             addArgument!(info, restrictions)(parse, groups[$-1]);
628         }
629     }
630 
631     private void addArgument(ArgumentInfo info, RestrictionGroup[] restrictions = [])(ParseFunction!RECEIVER parse)
632     {
633         static if(info.required)
634             addArgument!(info, restrictions)(parse, requiredGroup);
635         else
636             addArgument!(info, restrictions)(parse, optionalGroup);
637     }
638 
639     private void addArgument(ArgumentInfo info, RestrictionGroup[] argRestrictions = [])(ParseFunction!RECEIVER parse, ref Group group)
640     {
641         static assert(info.names.length > 0);
642 
643         immutable index = arguments.length;
644 
645         static if(info.positional)
646         {
647             if(argsPositional.length <= info.position.get)
648                 argsPositional.length = info.position.get + 1;
649 
650             argsPositional[info.position.get] = index;
651         }
652         else
653             static foreach(name; info.names)
654             {
655                 assert(!(name in argsNamed), "Duplicated argument name: "~name);
656                 argsNamed[convertCase(name)] = index;
657             }
658 
659         arguments ~= info;
660         parseFunctions ~= parse;
661         group.arguments ~= index;
662 
663         static if(info.required)
664             restrictions ~= Restrictions.RequiredArg!info(index);
665 
666         static foreach(restriction; argRestrictions)
667             addRestriction!(info, restriction)(index);
668     }
669 
670     private void addRestriction(ArgumentInfo info, RestrictionGroup restriction)(size_t argIndex)
671     {
672         auto groupIndex = (restriction.location in groupsByName);
673         auto index = groupIndex !is null
674             ? *groupIndex
675             : {
676                 auto index = groupsByName[restriction.location] = restrictionGroups.length;
677                 restrictionGroups ~= restriction;
678                 return index;
679             }();
680 
681         restrictionGroups[index].arguments ~= argIndex;
682     }
683 
684 
685     private bool checkRestrictions(in bool[size_t] cliArgs, in Config config) const
686     {
687         foreach(restriction; restrictions)
688             if(!restriction(config, cliArgs))
689                 return false;
690 
691         foreach(restriction; restrictionGroups)
692             final switch(restriction.type)
693             {
694                 case RestrictionGroup.Type.together:
695                     if(!Restrictions.RequiredTogether(config, cliArgs, restriction.arguments, arguments))
696                         return false;
697                     break;
698                 case RestrictionGroup.Type.exclusive:
699                     if(!Restrictions.MutuallyExclusive(config, cliArgs, restriction.arguments, arguments))
700                         return false;
701                     break;
702             }
703 
704         return true;
705     }
706 
707 
708     private auto findArgumentImpl(const size_t* pIndex) const
709     {
710         import std.typecons : Tuple;
711 
712         alias Result = Tuple!(size_t, "index", const(ArgumentInfo)*, "arg", ParseFunction!RECEIVER, "parse");
713 
714         return pIndex ? Result(*pIndex, &arguments[*pIndex], parseFunctions[*pIndex]) : Result(size_t.max, null, null);
715     }
716 
717     auto findPositionalArgument(size_t position) const
718     {
719         return findArgumentImpl(position < argsPositional.length ? &argsPositional[position] : null);
720     }
721 
722     auto findNamedArgument(string name) const
723     {
724         return findArgumentImpl(convertCase(name) in argsNamed);
725     }
726 
727     static if(getSymbolsByUDA!(RECEIVER, TrailingArguments).length == 1)
728     {
729         private void setTrailingArgs(ref RECEIVER receiver, string[] rawValues) const
730         {
731             enum symbol = __traits(identifier, getSymbolsByUDA!(RECEIVER, TrailingArguments)[0]);
732             auto target = &__traits(getMember, receiver, symbol);
733 
734             static if(__traits(compiles, { *target = rawValues; }))
735                 *target = rawValues;
736             else
737                 static assert(false, "Type '"~typeof(*target).stringof~"' of `"~
738                     RECEIVER.stringof~"."~symbol~"` is not supported for 'TrailingArguments' UDA");
739         }
740     }
741 }
742 
743 private alias ParsingFunction(alias symbol, alias uda, ArgumentInfo info, RECEIVER) =
744     delegate(in Config config, string argName, ref RECEIVER receiver, string[] rawValues)
745     {
746         try
747         {
748             if(!info.checkValuesCount(config, argName, rawValues.length))
749                 return false;
750 
751             auto param = RawParam(config, argName, rawValues);
752 
753             auto target = &__traits(getMember, receiver, symbol);
754 
755             static if(is(typeof(target) == function) || is(typeof(target) == delegate))
756                 return uda.parsingFunc.parse(target, param);
757             else
758                 return uda.parsingFunc.parse(*target, param);
759         }
760         catch(Exception e)
761         {
762             config.onError(argName, ": ", e.msg);
763             return false;
764         }
765     };
766 
767 
768 private void addArgument(alias symbol, RECEIVER)(ref Arguments!RECEIVER args)
769 {
770     alias member = __traits(getMember, RECEIVER, symbol);
771 
772     static assert(getUDAs!(member, ArgumentUDA).length <= 1,
773         "Member "~RECEIVER.stringof~"."~symbol~" has multiple '*Argument' UDAs");
774 
775     static assert(getUDAs!(member, Group).length <= 1,
776         "Member "~RECEIVER.stringof~"."~symbol~" has multiple 'Group' UDAs");
777 
778     static if(getUDAs!(member, ArgumentUDA).length > 0)
779         enum uda = getUDAs!(member, ArgumentUDA)[0];
780     else
781         enum uda = NamedArgument();
782 
783     enum info = uda.info.setDefaults!(typeof(member), symbol);
784 
785     enum restrictions = {
786         RestrictionGroup[] restrictions;
787         static foreach(gr; getUDAs!(member, RestrictionGroup))
788             restrictions ~= gr;
789         return restrictions;
790     }();
791 
792     static if(getUDAs!(member, Group).length > 0)
793         args.addArgument!(info, restrictions, getUDAs!(member, Group)[0])(ParsingFunction!(symbol, uda, info, RECEIVER));
794     else
795         args.addArgument!(info, restrictions)(ParsingFunction!(symbol, uda, info, RECEIVER));
796 }
797 
798 private auto createArguments(RECEIVER)(bool caseSensitive)
799 {
800     auto args = Arguments!RECEIVER(caseSensitive);
801 
802     enum hasNoUDAs = getSymbolsByUDA!(RECEIVER, ArgumentUDA  ).length == 0 &&
803                      getSymbolsByUDA!(RECEIVER, NamedArgument).length == 0;
804 
805     static foreach(sym; __traits(allMembers, RECEIVER))
806     {{
807         alias mem = __traits(getMember,RECEIVER,sym);
808 
809         static if(!is(mem)) // skip types
810             static if(hasNoUDAs || hasUDA!(mem, ArgumentUDA) || hasUDA!(mem, NamedArgument))
811                 addArgument!(sym)(args);
812     }}
813 
814     return args;
815 }
816 
817 unittest
818 {
819     struct T
820     {
821         @(NamedArgument)
822         int a;
823         @(NamedArgument.Optional())
824         int b;
825         @(NamedArgument.Required())
826         int c;
827         @(NamedArgument)
828         int d;
829         @(NamedArgument.Required())
830         int e;
831         @(NamedArgument)
832         int f;
833     }
834     static assert(createArguments!T(true).arguments.length == 6);
835 
836     auto a = createArguments!T(true);
837     assert(a.requiredGroup.arguments == [2,4]);
838     assert(a.argsNamed == ["a":0LU, "b":1LU, "c":2LU, "d":3LU, "e":4LU, "f":5LU]);
839     assert(a.argsPositional == []);
840 }
841 
842 unittest
843 {
844     struct T
845     {
846         int a,b,c,d,e,f;
847     }
848     static assert(createArguments!T(true).arguments.length == 6);
849 
850     auto a = createArguments!T(true);
851     assert(a.requiredGroup.arguments == []);
852     assert(a.argsNamed == ["a":0LU, "b":1LU, "c":2LU, "d":3LU, "e":4LU, "f":5LU]);
853     assert(a.argsPositional == []);
854 }
855 
856 unittest
857 {
858     struct T1
859     {
860         @(NamedArgument("1"))
861         @(NamedArgument("2"))
862         int a;
863     }
864     static assert(!__traits(compiles, { createArguments!T1(true); }));
865 
866     struct T2
867     {
868         @(NamedArgument("1"))
869         int a;
870         @(NamedArgument("1"))
871         int b;
872     }
873     static assert(!__traits(compiles, { createArguments!T1(true); }));
874 
875     struct T3
876     {
877         @(PositionalArgument(0)) int a;
878         @(PositionalArgument(0)) int b;
879     }
880     static assert(!__traits(compiles, { createArguments!T3(true); }));
881 
882     struct T4
883     {
884         @(PositionalArgument(0)) int a;
885         @(PositionalArgument(2)) int b;
886     }
887     static assert(!__traits(compiles, { createArguments!T4(true); }));
888 }
889 
890 private void checkArgumentName(T)(char namedArgChar)
891 {
892     import std.exception: enforce;
893 
894     static foreach(sym; getSymbolsByUDA!(T, ArgumentUDA))
895         static foreach(name; getUDAs!(__traits(getMember, T, __traits(identifier, sym)), ArgumentUDA)[0].info.names)
896             enforce(name[0] != namedArgChar, "Name of argument should not begin with '"~namedArgChar~"': "~name);
897 }
898 
899 private auto consumeValuesFromCLI(ref string[] args, in ArgumentInfo argumentInfo, in Config config)
900 {
901     import std.range: empty, front, popFront;
902 
903     immutable minValuesCount = argumentInfo.minValuesCount.get;
904     immutable maxValuesCount = argumentInfo.maxValuesCount.get;
905 
906     string[] values;
907 
908     if(minValuesCount > 0)
909     {
910         if(minValuesCount < args.length)
911         {
912             values = args[0..minValuesCount];
913             args = args[minValuesCount..$];
914         }
915         else
916         {
917             values = args;
918             args = [];
919         }
920     }
921 
922     while(!args.empty &&
923         values.length < maxValuesCount &&
924         (config.endOfArgs.length == 0 || args.front != config.endOfArgs) &&
925         (args.front.length == 0 || args.front[0] != config.namedArgChar))
926     {
927         values ~= args.front;
928         args.popFront();
929     }
930 
931     return values;
932 }
933 
934 
935 private enum helpArgument = {
936     ArgumentInfo arg;
937     arg.names = ["h","help"];
938     arg.description = "Show this help message and exit";
939     arg.minValuesCount = 0;
940     arg.maxValuesCount = 0;
941     arg.allowBooleanNegation = false;
942     arg.parsingTerminateCode = 0;
943     return arg;
944 }();
945 
946 struct ParseCLIResult
947 {
948     int  resultCode;
949 
950     private bool done;
951 
952     bool opCast(type)() if (is(type == bool))
953     {
954         return done;
955     }
956 
957     private static enum failure = ParseCLIResult(1);
958     private static enum success = ParseCLIResult(0, true);
959 }
960 
961 private ParseCLIResult parseCLIKnownArgs(T)(ref T receiver,
962                                             string[] args,
963                                             out string[] unrecognizedArgs,
964                                             const ref CommandArguments!T command,
965                                             in Config config)
966 {
967     import std.algorithm: map;
968     import std.array: assocArray;
969     import std.range: repeat, empty, front, popFront, join;
970     import std.typecons : tuple;
971 
972     checkArgumentName!T(config.namedArgChar);
973 
974     bool[size_t] cliArgs;
975 
976     alias parseNamedArg = (arg, res) {
977         args.popFront();
978 
979         auto values = arg.value.isNull ? consumeValuesFromCLI(args, *res.arg, config) : [ arg.value.get ];
980 
981         if(!res.parse(config, arg.origName, receiver, values))
982             return false;
983 
984         cliArgs[res.index] = true;
985 
986         return true;
987     };
988 
989     size_t positionalArgIdx = 0;
990 
991     while(!args.empty)
992     {
993         if(config.endOfArgs.length > 0 && args.front == config.endOfArgs)
994         {
995             // End of arguments
996             static if(is(typeof(command.arguments.setTrailingArgs)))
997                 command.arguments.setTrailingArgs(receiver, args[1..$]);
998             else
999                 unrecognizedArgs ~= args[1..$];
1000             break;
1001         }
1002 
1003         auto arg = splitArgumentName(args.front, config);
1004 
1005         final switch(arg.type)
1006         {
1007             case ArgumentType.positional:
1008             {
1009                 auto res = command.arguments.findPositionalArgument(positionalArgIdx);
1010                 if(res.arg is null)
1011                     goto case ArgumentType.unknown;
1012 
1013                 auto values = consumeValuesFromCLI(args, *res.arg, config);
1014 
1015                 if(!res.parse(config, res.arg.names[0], receiver, values))
1016                     return ParseCLIResult.failure;
1017 
1018                 positionalArgIdx++;
1019 
1020                 cliArgs[res.index] = true;
1021 
1022                 break;
1023             }
1024 
1025             case ArgumentType.longName:
1026             {
1027                 if(arg.name.length == 0)
1028                 {
1029                     config.onError("Empty argument name: ", args.front);
1030                     return ParseCLIResult.failure;
1031                 }
1032 
1033                 auto res = command.arguments.findNamedArgument(arg.name);
1034                 if(res.arg !is null)
1035                 {
1036                     if(!parseNamedArg(arg, res))
1037                         return ParseCLIResult.failure;
1038 
1039                     if(!res.arg.parsingTerminateCode.isNull)
1040                         return ParseCLIResult(res.arg.parsingTerminateCode.get);
1041 
1042                     break;
1043                 }
1044 
1045                 import std.algorithm : startsWith;
1046 
1047                 if(arg.name.startsWith("no-"))
1048                 {
1049                     res = command.arguments.findNamedArgument(arg.name[3..$]);
1050                     if(res.arg !is null && res.arg.allowBooleanNegation)
1051                     {
1052                         args.popFront();
1053 
1054                         if(!res.parse(config, arg.origName, receiver, ["false"]))
1055                             return ParseCLIResult.failure;
1056 
1057                         cliArgs[res.index] = true;
1058 
1059                         break;
1060                     }
1061                 }
1062 
1063                 goto case ArgumentType.unknown;
1064             }
1065 
1066             case ArgumentType.shortName:
1067             {
1068                 if(arg.name.length == 0)
1069                 {
1070                     config.onError("Empty argument name: ", args.front);
1071                     return ParseCLIResult.failure;
1072                 }
1073 
1074                 auto res = command.arguments.findNamedArgument(arg.name);
1075                 if(res.arg !is null)
1076                 {
1077                     if(!parseNamedArg(arg, res))
1078                         return ParseCLIResult.failure;
1079 
1080                     if(!res.arg.parsingTerminateCode.isNull)
1081                         return ParseCLIResult(res.arg.parsingTerminateCode.get);
1082 
1083                     break;
1084                 }
1085 
1086                 if(arg.name.length == 1)
1087                     goto case ArgumentType.unknown;
1088 
1089                 if(!config.bundling)
1090                 {
1091                     auto name = [arg.name[0]];
1092                     res = command.arguments.findNamedArgument(name);
1093                     if(res.arg is null || res.arg.minValuesCount != 1)
1094                         goto case ArgumentType.unknown;
1095 
1096                     if(!res.parse(config, "-"~name, receiver, [arg.name[1..$]]))
1097                         return ParseCLIResult.failure;
1098 
1099                     cliArgs[res.index] = true;
1100 
1101                     if(!res.arg.parsingTerminateCode.isNull)
1102                         return ParseCLIResult(res.arg.parsingTerminateCode.get);
1103 
1104                     args.popFront();
1105                     break;
1106                 }
1107                 else
1108                 {
1109                     while(arg.name.length > 0)
1110                     {
1111                         auto name = [arg.name[0]];
1112                         res = command.arguments.findNamedArgument(name);
1113                         if(res.arg is null)
1114                             goto case ArgumentType.unknown;
1115 
1116                         if(res.arg.minValuesCount == 0)
1117                         {
1118                             if(!res.parse(config, "-"~name, receiver, []))
1119                                 return ParseCLIResult.failure;
1120 
1121                             cliArgs[res.index] = true;
1122 
1123                             arg.name = arg.name[1..$];
1124                         }
1125                         else if(res.arg.minValuesCount == 1)
1126                         {
1127                             if(!res.parse(config, "-"~name, receiver, [arg.name[1..$]]))
1128                                 return ParseCLIResult.failure;
1129 
1130                             cliArgs[res.index] = true;
1131 
1132                             arg.name = [];
1133                         }
1134                         else
1135                         {
1136                             // trigger an error
1137                             res.arg.checkValuesCount(config, name, 1);
1138                             return ParseCLIResult.failure;
1139                         }
1140 
1141                         if(!res.arg.parsingTerminateCode.isNull)
1142                             return ParseCLIResult(res.arg.parsingTerminateCode.get);
1143                     }
1144 
1145                     if(arg.name.length == 0)
1146                     {
1147                         args.popFront();
1148                         break;
1149                     }
1150                 }
1151 
1152                 goto case ArgumentType.unknown;
1153             }
1154 
1155             case ArgumentType.unknown:
1156                 unrecognizedArgs ~= args.front;
1157                 args.popFront();
1158         }
1159     }
1160 
1161     if(!command.arguments.checkRestrictions(cliArgs, config))
1162         return ParseCLIResult.failure;
1163 
1164     return ParseCLIResult.success;
1165 }
1166 
1167 ParseCLIResult parseCLIKnownArgs(T)(ref T receiver,
1168                                     string[] args,
1169                                     out string[] unrecognizedArgs,
1170                                     in Config config = Config.init)
1171 {
1172     auto command = CommandArguments!T(config);
1173     return parseCLIKnownArgs(receiver, args, unrecognizedArgs, command, config);
1174 }
1175 
1176 auto parseCLIKnownArgs(T)(ref T receiver, ref string[] args, in Config config = Config.init)
1177 {
1178     string[] unrecognizedArgs;
1179 
1180     auto res = parseCLIKnownArgs(receiver, args, unrecognizedArgs, config);
1181     if(res)
1182         args = unrecognizedArgs;
1183 
1184     return res;
1185 }
1186 
1187 Nullable!T parseCLIKnownArgs(T)(ref string[] args, in Config config = Config.init)
1188 {
1189     import std.typecons : nullable;
1190 
1191     T receiver;
1192 
1193     return parseCLIKnownArgs(receiver, args, config) ? receiver.nullable : Nullable!T.init;
1194 }
1195 
1196 int parseCLIKnownArgs(T, FUNC)(string[] args, FUNC func, in Config config = Config.init, T initialValue = T.init)
1197 if(__traits(compiles, { func(T.init, args); }))
1198 {
1199     alias value = initialValue;
1200 
1201     auto res = parseCLIKnownArgs(value, args, config);
1202     if(!res)
1203         return res.resultCode;
1204 
1205     static if(__traits(compiles, { int a = cast(int) func(value, args); }))
1206         return cast(int) func(value, args);
1207     else
1208     {
1209         func(value, args);
1210         return 0;
1211     }
1212 }
1213 
1214 
1215 auto parseCLIArgs(T)(ref T receiver, string[] args, in Config config = Config.init)
1216 {
1217     string[] unrecognizedArgs;
1218 
1219     auto res = parseCLIKnownArgs(receiver, args, unrecognizedArgs, config);
1220 
1221     if(res && unrecognizedArgs.length > 0)
1222     {
1223         config.onError("Unrecognized arguments: ", unrecognizedArgs);
1224         return ParseCLIResult.failure;
1225     }
1226 
1227     return res;
1228 }
1229 
1230 Nullable!T parseCLIArgs(T)(string[] args, in Config config = Config.init)
1231 {
1232     import std.typecons : nullable;
1233 
1234     T receiver;
1235 
1236     return parseCLIArgs(receiver, args, config) ? receiver.nullable : Nullable!T.init;
1237 }
1238 
1239 int parseCLIArgs(T, FUNC)(string[] args, FUNC func, in Config config = Config.init, T initialValue = T.init)
1240 if(__traits(compiles, { func(T.init); }))
1241 {
1242     alias value = initialValue;
1243 
1244     auto res = parseCLIArgs(value, args, config);
1245     if(!res)
1246         return res.resultCode;
1247 
1248     static if(__traits(compiles, { int a = cast(int) func(value); }))
1249         return cast(int) func(value);
1250     else
1251     {
1252         func(value);
1253         return 0;
1254     }
1255 }
1256 
1257 unittest
1258 {
1259     import std.exception;
1260 
1261     struct T
1262     {
1263         @(NamedArgument("--"))
1264         int a;
1265     }
1266     static assert(!__traits(compiles, { enum p = parseCLIArgs!T([]); }));
1267     assertThrown(parseCLIArgs!T([]));
1268 }
1269 
1270 unittest
1271 {
1272 
1273     import std.conv;
1274     import std.traits;
1275 
1276     struct params
1277     {
1278         int no_a;
1279 
1280         @(PositionalArgument(0, "a")
1281         .Description("Argument 'a'")
1282         .Validation!((int a) { return a > 3;})
1283         .PreValidation!((string s) { return s.length > 0;})
1284         .Validation!((int a) { return a > 0;})
1285         )
1286         int a;
1287 
1288         int no_b;
1289 
1290         @(NamedArgument(["b", "boo"]).Description("Flag boo")
1291         .AllowNoValue!55
1292         )
1293         int b;
1294 
1295         int no_c;
1296     }
1297 
1298     enum p = CommandArguments!params(Config.init);
1299     static assert(p.arguments.findNamedArgument("a").arg is null);
1300     static assert(p.arguments.findNamedArgument("b").arg !is null);
1301     static assert(p.arguments.findNamedArgument("boo").arg !is null);
1302     static assert(p.arguments.findPositionalArgument(0).arg !is null);
1303     static assert(p.arguments.findPositionalArgument(1).arg is null);
1304 }
1305 
1306 unittest
1307 {
1308     import std.typecons : tuple;
1309 
1310     struct T
1311     {
1312         string a;
1313         string b;
1314     }
1315 
1316     auto test(string[] args)
1317     {
1318         return tuple(args.parseCLIKnownArgs!T.get, args);
1319     }
1320 
1321     assert(test(["-a","A","--"]) == tuple(T("A"), []));
1322     static assert(test(["-a","A","--","-b","B"]) == tuple(T("A"), ["-b","B"]));
1323 
1324     {
1325         T args;
1326 
1327         args.parseCLIArgs([ "-a", "A"]);
1328         args.parseCLIArgs([ "-b", "B"]);
1329 
1330         assert(args == T("A","B"));
1331     }
1332 }
1333 
1334 unittest
1335 {
1336     struct T
1337     {
1338         string a;
1339     }
1340 
1341     {
1342         auto test_called(string[] args)
1343         {
1344             bool called;
1345             auto dg = (T t) {
1346                 called = true;
1347             };
1348             assert(args.parseCLIArgs!T(dg) == 0 || !called);
1349             return called;
1350         }
1351 
1352         static assert(test_called([]));
1353         assert(test_called([]));
1354         assert(!test_called(["-g"]));
1355     }
1356     {
1357         auto test_called(string[] args)
1358         {
1359             bool called;
1360             auto dg = (T t, string[] args) {
1361                 assert(args.length == 0 || args == ["-g"]);
1362                 called = true;
1363             };
1364             assert(args.parseCLIKnownArgs!T(dg) == 0);
1365             return called;
1366         }
1367 
1368         assert(test_called([]));
1369         static assert(test_called(["-g"]));
1370     }
1371 }
1372 
1373 unittest
1374 {
1375     struct T
1376     {
1377         string a;
1378     }
1379 
1380     int my_main(T command)
1381     {
1382         // do something
1383         return 0;
1384     }
1385 
1386     static assert(["-a","aa"].parseCLIArgs!T(&my_main) == 0);
1387     assert(["-a","aa"].parseCLIArgs!T(&my_main) == 0);
1388 }
1389 
1390 unittest
1391 {
1392     struct T
1393     {
1394         string a;
1395     }
1396 
1397     auto args = [ "-a", "A", "-c", "C" ];
1398 
1399     assert(parseCLIKnownArgs!T(args).get == T("A"));
1400     assert(args == ["-c", "C"]);
1401 }
1402 
1403 unittest
1404 {
1405 
1406     struct T
1407     {
1408         @NamedArgument                           string x;
1409         @NamedArgument                           string foo;
1410         @(PositionalArgument(0, "a").Optional()) string a;
1411         @(PositionalArgument(1, "b").Optional()) string[] b;
1412     }
1413     static assert(["--foo","FOO","-x","X"].parseCLIArgs!T.get == T("X", "FOO"));
1414     static assert(["--foo=FOO","-x=X"].parseCLIArgs!T.get == T("X", "FOO"));
1415     static assert(["--foo=FOO","1","-x=X"].parseCLIArgs!T.get == T("X", "FOO", "1"));
1416     static assert(["--foo=FOO","1","2","3","4"].parseCLIArgs!T.get == T(string.init, "FOO", "1",["2","3","4"]));
1417     static assert(["-xX"].parseCLIArgs!T.get == T("X"));
1418     assert(["--foo","FOO","-x","X"].parseCLIArgs!T.get == T("X", "FOO"));
1419     assert(["--foo=FOO","-x=X"].parseCLIArgs!T.get == T("X", "FOO"));
1420     assert(["--foo=FOO","1","-x=X"].parseCLIArgs!T.get == T("X", "FOO", "1"));
1421     assert(["--foo=FOO","1","2","3","4"].parseCLIArgs!T.get == T(string.init, "FOO", "1",["2","3","4"]));
1422     assert(["-xX"].parseCLIArgs!T.get == T("X"));
1423 
1424     struct T1
1425     {
1426         @(PositionalArgument(0, "a")) string[3] a;
1427         @(PositionalArgument(1, "b")) string[] b;
1428     }
1429     static assert(["1","2","3","4","5","6"].parseCLIArgs!T1.get == T1(["1","2","3"],["4","5","6"]));
1430     assert(["1","2","3","4","5","6"].parseCLIArgs!T1.get == T1(["1","2","3"],["4","5","6"]));
1431 
1432     struct T2
1433     {
1434         bool foo = true;
1435     }
1436     static assert(["--no-foo"].parseCLIArgs!T2.get == T2(false));
1437     assert(["--no-foo"].parseCLIArgs!T2.get == T2(false));
1438 }
1439 
1440 unittest
1441 {
1442     struct T
1443     {
1444         @(PositionalArgument(0, "a").Optional())
1445         string a = "not set";
1446 
1447         @(NamedArgument.Required())
1448         int b;
1449     }
1450 
1451     static assert(["-b", "4"].parseCLIArgs!T.get == T("not set", 4));
1452     assert(["-b", "4"].parseCLIArgs!T.get == T("not set", 4));
1453 }
1454 
1455 unittest
1456 {
1457     struct T
1458     {
1459         string x;
1460         string foo;
1461     }
1462 
1463     auto test(T)(string[] args)
1464     {
1465         Config config;
1466         config.caseSensitive = false;
1467 
1468         return args.parseCLIArgs!T(config).get;
1469     }
1470 
1471     static assert(test!T(["--Foo","FOO","-X","X"]) == T("X", "FOO"));
1472     static assert(test!T(["--FOo=FOO","-X=X"]) == T("X", "FOO"));
1473     assert(test!T(["--Foo","FOO","-X","X"]) == T("X", "FOO"));
1474     assert(test!T(["--FOo=FOO","-X=X"]) == T("X", "FOO"));
1475 }
1476 
1477 unittest
1478 {
1479     auto test(T)(string[] args)
1480     {
1481         Config config;
1482         config.bundling = true;
1483 
1484         return args.parseCLIArgs!T(config).get;
1485     }
1486 
1487     struct T
1488     {
1489         bool a;
1490         bool b;
1491     }
1492     static assert(test!T(["-a","-b"]) == T(true, true));
1493     static assert(test!T(["-ab"]) == T(true, true));
1494     assert(test!T(["-a","-b"]) == T(true, true));
1495     assert(test!T(["-ab"]) == T(true, true));
1496 }
1497 
1498 unittest
1499 {
1500     struct T
1501     {
1502         bool b;
1503     }
1504 
1505     static assert(["-b"]        .parseCLIArgs!T.get == T(true));
1506     static assert(["-b","true"] .parseCLIArgs!T.get == T(true));
1507     static assert(["-b","false"].parseCLIArgs!T.get == T(false));
1508     static assert(["-b=true"]   .parseCLIArgs!T.get == T(true));
1509     static assert(["-b=false"]  .parseCLIArgs!T.get == T(false));
1510     assert(["-b"]        .parseCLIArgs!T.get == T(true));
1511     assert(["-b","true"] .parseCLIArgs!T.get == T(true));
1512     assert(["-b","false"].parseCLIArgs!T.get == T(false));
1513     assert(["-b=true"]   .parseCLIArgs!T.get == T(true));
1514     assert(["-b=false"]  .parseCLIArgs!T.get == T(false));
1515 }
1516 
1517 struct Main
1518 {
1519     mixin template parseCLIKnownArgs(TYPE, alias newMain, Config config = Config.init)
1520     {
1521         int main(string[] argv)
1522         {
1523             return parseCLIKnownArgs!TYPE(argv[1..$], (TYPE values, string[] args) => newMain(values, args), config);
1524         }
1525     }
1526 
1527     mixin template parseCLIArgs(TYPE, alias newMain, Config config = Config.init)
1528     {
1529         int main(string[] argv)
1530         {
1531             return parseCLIArgs!TYPE(argv[1..$], (TYPE values) => newMain(values), config);
1532         }
1533     }
1534 }
1535 
1536 unittest
1537 {
1538     struct T
1539     {
1540         int a;
1541     }
1542 
1543     static assert(__traits(compiles, { mixin Main.parseCLIArgs!(T, (params) => 0); }));
1544     static assert(__traits(compiles, { mixin Main.parseCLIKnownArgs!(T, (params, args) => 0); }));
1545 }
1546 
1547 
1548 private struct Parsers
1549 {
1550     static auto Convert(T)(string value)
1551     {
1552         import std.conv: to;
1553         return value.length > 0 ? value.to!T : T.init;
1554     }
1555 
1556     static auto PassThrough(string[] values)
1557     {
1558         return values;
1559     }
1560 }
1561 
1562 unittest
1563 {
1564     static assert(Parsers.Convert!int("7") == 7);
1565     static assert(Parsers.Convert!string("7") == "7");
1566     static assert(Parsers.Convert!char("7") == '7');
1567 
1568     static assert(Parsers.PassThrough(["7","8"]) == ["7","8"]);
1569 }
1570 
1571 
1572 private struct Actions
1573 {
1574     static auto Assign(DEST, SRC=DEST)(ref DEST param, SRC value)
1575     {
1576         param  = value;
1577     }
1578 
1579     static auto Append(T)(ref T param, T value)
1580     {
1581         param ~= value;
1582     }
1583 
1584     static auto Extend(T)(ref T[] param, T value)
1585     {
1586         param ~= value;
1587     }
1588 
1589     static auto CallFunction(F)(ref F func, RawParam param)
1590     {
1591         // ... func()
1592         static if(__traits(compiles, { func(); }))
1593         {
1594             func();
1595         }
1596         // ... func(string value)
1597         else static if(__traits(compiles, { func(param.value[0]); }))
1598         {
1599             foreach(value; param.value)
1600                 func(value);
1601         }
1602         // ... func(string[] value)
1603         else static if(__traits(compiles, { func(param.value); }))
1604         {
1605             func(param.value);
1606         }
1607         // ... func(RawParam param)
1608         else static if(__traits(compiles, { func(param); }))
1609         {
1610             func(param);
1611         }
1612         else
1613             static assert(false, "Unsupported callback: " ~ F.stringof);
1614     }
1615 
1616     static auto CallFunctionNoParam(F)(ref F func, Param!void param)
1617     {
1618         // ... func()
1619         static if(__traits(compiles, { func(); }))
1620         {
1621             func();
1622         }
1623         // ... func(string value)
1624         else static if(__traits(compiles, { func(string.init); }))
1625         {
1626             func(string.init);
1627         }
1628         // ... func(string[] value)
1629         else static if(__traits(compiles, { func([]); }))
1630         {
1631             func([]);
1632         }
1633         // ... func(Param!void param)
1634         else static if(__traits(compiles, { func(param); }))
1635         {
1636             func(param);
1637         }
1638         else
1639             static assert(false, "Unsupported callback: " ~ F.stringof);
1640     }
1641 }
1642 
1643 unittest
1644 {
1645     int i;
1646     Actions.Assign!(int)(i,7);
1647     assert(i == 7);
1648 }
1649 
1650 unittest
1651 {
1652     int[] i;
1653     Actions.Append!(int[])(i,[1,2,3]);
1654     Actions.Append!(int[])(i,[7,8,9]);
1655     assert(i == [1,2,3,7,8,9]);
1656 
1657     alias test = (int[] v1, int[] v2) {
1658         int[] res;
1659 
1660         Param!(int[]) param;
1661 
1662         alias F = Actions.Append!(int[]);
1663         param.value = v1;   ActionFunc!(F, int[], int[])(res, param);
1664 
1665         param.value = v2;   ActionFunc!(F, int[], int[])(res, param);
1666 
1667         return res;
1668     };
1669     static assert(test([1,2,3],[7,8,9]) == [1,2,3,7,8,9]);
1670 }
1671 
1672 unittest
1673 {
1674     int[][] i;
1675     Actions.Extend!(int[])(i,[1,2,3]);
1676     Actions.Extend!(int[])(i,[7,8,9]);
1677     assert(i == [[1,2,3],[7,8,9]]);
1678 }
1679 
1680 
1681 private struct Validators
1682 {
1683     static auto ValueInList(alias values, TYPE)(in Param!TYPE param)
1684     {
1685         import std.array : assocArray, join;
1686         import std.range : repeat, front;
1687         import std.conv: to;
1688 
1689         enum valuesAA = assocArray(values, false.repeat);
1690         enum allowedValues = values.to!(string[]).join(',');
1691 
1692         static if(is(typeof(values.front) == TYPE))
1693             auto paramValues = [param.value];
1694         else
1695             auto paramValues = param.value;
1696 
1697         foreach(value; paramValues)
1698             if(!(value in valuesAA))
1699             {
1700                 param.config.onError("Invalid value '", value, "' for argument '", param.name, "'.\nValid argument values are: ", allowedValues);
1701                 return false;
1702             }
1703 
1704         return true;
1705     }
1706 }
1707 
1708 
1709 // values => bool
1710 // bool validate(T value)
1711 // bool validate(T[i] value)
1712 // bool validate(Param!T param)
1713 private struct ValidateFunc(alias F, T, string funcName="Validation")
1714 {
1715     static bool opCall(Param!T param)
1716     {
1717         static if(is(F == void))
1718         {
1719             return true;
1720         }
1721         else static if(__traits(compiles, { F(param); }))
1722         {
1723             // bool validate(Param!T param)
1724             return cast(bool) F(param);
1725         }
1726         else static if(__traits(compiles, { F(param.value); }))
1727         {
1728             // bool validate(T values)
1729             return cast(bool) F(param.value);
1730         }
1731         else static if(/*isArray!T &&*/ __traits(compiles, { F(param.value[0]); }))
1732         {
1733             // bool validate(T[i] value)
1734             foreach(value; param.value)
1735                 if(!F(value))
1736                     return false;
1737             return true;
1738         }
1739         else
1740             static assert(false, funcName~" function is not supported for type "~T.stringof~": "~typeof(F).stringof);
1741     }
1742 }
1743 
1744 unittest
1745 {
1746     auto test(alias F, T)(T[] values)
1747     {
1748         Param!(T[]) param;
1749         param.value = values;
1750         return ValidateFunc!(F, T[])(param);
1751     }
1752 
1753     // bool validate(T[] values)
1754     static assert(test!((string[] a) => true, string)(["1","2","3"]));
1755     static assert(test!((int[] a) => true, int)([1,2,3]));
1756 
1757     // bool validate(T value)
1758     static assert(test!((string a) => true, string)(["1","2","3"]));
1759     static assert(test!((int a) => true, int)([1,2,3]));
1760 
1761     // bool validate(Param!T param)
1762     static assert(test!((RawParam p) => true, string)(["1","2","3"]));
1763     static assert(test!((Param!(int[]) p) => true, int)([1,2,3]));
1764 }
1765 
1766 unittest
1767 {
1768     static assert(ValidateFunc!(void, string[])(RawParam(Config.init, "", ["1","2","3"])));
1769 
1770     static assert(!__traits(compiles, { ValidateFunc!(() {}, string[])(config, "", ["1","2","3"]); }));
1771     static assert(!__traits(compiles, { ValidateFunc!((int,int) {}, string[])(config, "", ["1","2","3"]); }));
1772 }
1773 
1774 
1775 private template ParseType(alias F, T)
1776 {
1777     import std.traits : Unqual;
1778 
1779     static if(is(F == void))
1780         alias ParseType = Unqual!T;
1781     else static if(Parameters!F.length == 0)
1782         static assert(false, "Parse function should take at least one parameter");
1783     else static if(Parameters!F.length == 1)
1784     {
1785         // T action(arg)
1786         alias ParseType = Unqual!(ReturnType!F);
1787         static assert(!is(ParseType == void), "Parse function should return value");
1788     }
1789     else static if(Parameters!F.length == 2 && is(Parameters!F[0] == Config))
1790     {
1791         // T action(Config config, arg)
1792         alias ParseType = Unqual!(ReturnType!F);
1793         static assert(!is(ParseType == void), "Parse function should return value");
1794     }
1795     else static if(Parameters!F.length == 2)
1796     {
1797         // ... action(ref T param, arg)
1798         alias ParseType = Parameters!F[0];
1799     }
1800     else static if(Parameters!F.length == 3)
1801     {
1802         // ... action(Config config, ref T param, arg)
1803         alias ParseType = Parameters!F[1];
1804     }
1805     else static if(Parameters!F.length == 4)
1806     {
1807         // ... action(Config config, string argName, ref T param, arg)
1808         alias ParseType = Parameters!F[2];
1809     }
1810     else
1811         static assert(false, "Parse function has too many parameters: "~Parameters!F.stringof);
1812 }
1813 
1814 unittest
1815 {
1816     static assert(is(ParseType!(void, double) == double));
1817     static assert(!__traits(compiles, { ParseType!((){}, double) p; }));
1818     static assert(!__traits(compiles, { ParseType!((int,int,int,int,int){}, double) p; }));
1819 
1820     // T action(arg)
1821     static assert(is(ParseType!((int)=>3, double) == int));
1822     static assert(!__traits(compiles, { ParseType!((int){}, double) p; }));
1823     // T action(Config config, arg)
1824     static assert(is(ParseType!((Config config, int)=>3, double) == int));
1825     static assert(!__traits(compiles, { ParseType!((Config config, int){}, double) p; }));
1826     // ... action(ref T param, arg)
1827     static assert(is(ParseType!((ref int, string v) {}, double) == int));
1828     // ... action(Config config, ref T param, arg)
1829     static assert(is(ParseType!((Config config, ref int, string v) {}, double) == int));
1830     // ... action(Config config, string argName, ref T param, arg)
1831     //static assert(is(ParseType!((Config config, string argName, ref int, string v) {}, double) == int));
1832 }
1833 
1834 
1835 // T parse(string[] values)
1836 // T parse(string value)
1837 // T parse(RawParam param)
1838 // bool parse(ref T receiver, RawParam param)
1839 // void parse(ref T receiver, RawParam param)
1840 private struct ParseFunc(alias F, T)
1841 {
1842     alias ParseType = .ParseType!(F, T);
1843 
1844     static bool opCall(ref ParseType receiver, RawParam param)
1845     {
1846         static if(is(F == void))
1847         {
1848             foreach(value; param.value)
1849                 receiver = Parsers.Convert!T(value);
1850             return true;
1851         }
1852         // T parse(string[] values)
1853         else static if(__traits(compiles, { receiver = cast(ParseType) F(param.value); }))
1854         {
1855             receiver = cast(ParseType) F(param.value);
1856             return true;
1857         }
1858         // T parse(string value)
1859         else static if(__traits(compiles, { receiver = cast(ParseType) F(param.value[0]); }))
1860         {
1861             foreach(value; param.value)
1862                 receiver = cast(ParseType) F(value);
1863             return true;
1864         }
1865         // T parse(RawParam param)
1866         else static if(__traits(compiles, { receiver = cast(ParseType) F(param); }))
1867         {
1868             receiver = cast(ParseType) F(param);
1869             return true;
1870         }
1871         // bool parse(ref T receiver, RawParam param)
1872         // void parse(ref T receiver, RawParam param)
1873         else static if(__traits(compiles, { F(receiver, param); }))
1874         {
1875             static if(__traits(compiles, { auto res = cast(bool) F(receiver, param); }))
1876             {
1877                 // bool parse(ref T receiver, RawParam param)
1878                 return cast(bool) F(receiver, param);
1879             }
1880             else
1881             {
1882                 // void parse(ref T receiver, RawParam param)
1883                 F(receiver, param);
1884                 return true;
1885             }
1886         }
1887         else
1888             static assert(false, "Parse function is not supported");
1889     }
1890 }
1891 
1892 unittest
1893 {
1894     int i;
1895     RawParam param;
1896     param.value = ["1","2","3"];
1897     assert(ParseFunc!(void, int)(i, param));
1898     assert(i == 3);
1899 }
1900 
1901 unittest
1902 {
1903     auto test(alias F, T)(string[] values)
1904     {
1905         T value;
1906         RawParam param;
1907         param.value = values;
1908         assert(ParseFunc!(F, T)(value, param));
1909         return value;
1910     }
1911 
1912     // T parse(string value)
1913     static assert(test!((string a) => a, string)(["1","2","3"]) == "3");
1914 
1915     // T parse(string[] values)
1916     static assert(test!((string[] a) => a, string[])(["1","2","3"]) == ["1","2","3"]);
1917 
1918     // T parse(RawParam param)
1919     static assert(test!((RawParam p) => p.value[0], string)(["1","2","3"]) == "1");
1920 
1921     // bool parse(ref T receiver, RawParam param)
1922     static assert(test!((ref string[] r, RawParam p) { r = p.value; return true; }, string[])(["1","2","3"]) == ["1","2","3"]);
1923 
1924     // void parse(ref T receiver, RawParam param)
1925     static assert(test!((ref string[] r, RawParam p) { r = p.value; }, string[])(["1","2","3"]) == ["1","2","3"]);
1926 }
1927 
1928 
1929 // bool action(ref T receiver, ParseType value)
1930 // void action(ref T receiver, ParseType value)
1931 // bool action(ref T receiver, Param!ParseType param)
1932 // void action(ref T receiver, Param!ParseType param)
1933 private struct ActionFunc(alias F, T, ParseType)
1934 {
1935     static bool opCall(ref T receiver, Param!ParseType param)
1936     {
1937         static if(is(F == void))
1938         {
1939             Actions.Assign!(T, ParseType)(receiver, param.value);
1940             return true;
1941         }
1942         // bool action(ref T receiver, ParseType value)
1943         // void action(ref T receiver, ParseType value)
1944         else static if(__traits(compiles, { F(receiver, param.value); }))
1945         {
1946             static if(__traits(compiles, { auto res = cast(bool) F(receiver, param.value); }))
1947             {
1948                 // bool action(ref T receiver, ParseType value)
1949                 return cast(bool) F(receiver, param.value);
1950             }
1951             else
1952             {
1953                 // void action(ref T receiver, ParseType value)
1954                 F(receiver, param.value);
1955                 return true;
1956             }
1957         }
1958         // bool action(ref T receiver, Param!ParseType param)
1959         // void action(ref T receiver, Param!ParseType param)
1960         else static if(__traits(compiles, { F(receiver, param); }))
1961         {
1962             static if(__traits(compiles, { auto res = cast(bool) F(receiver, param); }))
1963             {
1964                 // bool action(ref T receiver, Param!ParseType param)
1965                 return cast(bool) F(receiver, param);
1966             }
1967             else
1968             {
1969                 // void action(ref T receiver, Param!ParseType param)
1970                 F(receiver, param);
1971                 return true;
1972             }
1973         }
1974         else
1975             static assert(false, "Action function is not supported");
1976     }
1977 }
1978 
1979 unittest
1980 {
1981     auto param(T)(T values)
1982     {
1983         Param!T param;
1984         param.value = values;
1985         return param;
1986     }
1987     auto test(alias F, T)(T values)
1988     {
1989         T receiver;
1990         assert(ActionFunc!(F, T, T)(receiver, param(values)));
1991         return receiver;
1992     }
1993 
1994     static assert(test!(void, string[])(["1","2","3"]) == ["1","2","3"]);
1995 
1996     static assert(!__traits(compiles, { test!(() {}, string[])(["1","2","3"]); }));
1997     static assert(!__traits(compiles, { test!((int,int) {}, string[])(["1","2","3"]); }));
1998 
1999     // bool action(ref T receiver, ParseType value)
2000     static assert(test!((ref string[] p, string[] a) { p=a; return true; }, string[])(["1","2","3"]) == ["1","2","3"]);
2001 
2002     // void action(ref T receiver, ParseType value)
2003     static assert(test!((ref string[] p, string[] a) { p=a; }, string[])(["1","2","3"]) == ["1","2","3"]);
2004 
2005     // bool action(ref T receiver, Param!ParseType param)
2006     static assert(test!((ref string[] p, Param!(string[]) a) { p=a.value; return true; }, string[]) (["1","2","3"]) == ["1","2","3"]);
2007 
2008     // void action(ref T receiver, Param!ParseType param)
2009     static assert(test!((ref string[] p, Param!(string[]) a) { p=a.value; }, string[])(["1","2","3"]) == ["1","2","3"]);
2010 }
2011 
2012 
2013 // => receiver + bool
2014 // DEST action()
2015 // bool action(ref DEST receiver)
2016 // void action(ref DEST receiver)
2017 // bool action(ref DEST receiver, Param!void param)
2018 // void action(ref DEST receiver, Param!void param)
2019 private struct NoValueActionFunc(alias F, T)
2020 {
2021     static bool opCall(ref T receiver, Param!void param)
2022     {
2023         static if(is(F == void))
2024         {
2025             assert(false, "No-value action function is not provided");
2026         }
2027         else static if(__traits(compiles, { receiver = cast(T) F(); }))
2028         {
2029             // DEST action()
2030             receiver = cast(T) F();
2031             return true;
2032         }
2033         else static if(__traits(compiles, { F(receiver); }))
2034         {
2035             static if(__traits(compiles, { auto res = cast(bool) F(receiver); }))
2036             {
2037                 // bool action(ref DEST receiver)
2038                 return cast(bool) F(receiver);
2039             }
2040             else
2041             {
2042                 // void action(ref DEST receiver)
2043                 F(receiver);
2044                 return true;
2045             }
2046         }
2047         else static if(__traits(compiles, { F(receiver, param); }))
2048         {
2049             static if(__traits(compiles, { auto res = cast(bool) F(receiver, param); }))
2050             {
2051                 // bool action(ref DEST receiver, Param!void param)
2052                 return cast(bool) F(receiver, param);
2053             }
2054             else
2055             {
2056                 // void action(ref DEST receiver, Param!void param)
2057                 F(receiver, param);
2058                 return true;
2059             }
2060         }
2061         else
2062             static assert(false, "No-value action function has too many parameters: "~Parameters!F.stringof);
2063     }
2064 }
2065 
2066 unittest
2067 {
2068     auto test(alias F, T)()
2069     {
2070         T receiver;
2071         assert(NoValueActionFunc!(F, T)(receiver, Param!void.init));
2072         return receiver;
2073     }
2074 
2075     static assert(!__traits(compiles, { NoValueActionFunc!(() {}, int); }));
2076     static assert(!__traits(compiles, { NoValueActionFunc!((int) {}, int); }));
2077     static assert(!__traits(compiles, { NoValueActionFunc!((int,int) {}, int); }));
2078     static assert(!__traits(compiles, { NoValueActionFunc!((int,int,int) {}, int); }));
2079 
2080     // DEST action()
2081     static assert(test!(() => 7, int) == 7);
2082 
2083     // bool action(ref DEST param)
2084     static assert(test!((ref int p) { p=7; return true; }, int) == 7);
2085 
2086     // void action(ref DEST param)
2087     static assert(test!((ref int p) { p=7; }, int) == 7);
2088 
2089     // bool action(ref DEST receiver, Param!void param)
2090     static assert(test!((ref int r, Param!void p) { r=7; return true; }, int) == 7);
2091 
2092     // void action(ref DEST receiver, Param!void param)
2093     static assert(test!((ref int r, Param!void p) { r=7; }, int) == 7);
2094 }
2095 
2096 
2097 private void splitValues(ref RawParam param)
2098 {
2099     if(param.config.arraySep == char.init)
2100         return;
2101 
2102     import std.array : array, split;
2103     import std.algorithm : map, joiner;
2104 
2105     param.value = param.value.map!((string s) => s.split(param.config.arraySep)).joiner.array;
2106 }
2107 
2108 unittest
2109 {
2110     alias test = (char arraySep, string[] values)
2111     {
2112         Config config;
2113         config.arraySep = arraySep;
2114 
2115         auto param = RawParam(config, "", values);
2116 
2117         splitValues(param);
2118 
2119         return param.value;
2120     };
2121 
2122     static assert(test(',', []) == []);
2123     static assert(test(',', ["a","b","c"]) == ["a","b","c"]);
2124     static assert(test(',', ["a,b","c","d,e,f"]) == ["a","b","c","d","e","f"]);
2125     static assert(test(' ', ["a,b","c","d,e,f"]) == ["a,b","c","d,e,f"]);
2126 }
2127 
2128 
2129 private struct ValueParseFunctions(alias PreProcess,
2130                                    alias PreValidation,
2131                                    alias Parse,
2132                                    alias Validation,
2133                                    alias Action,
2134                                    alias NoValueAction)
2135 {
2136     alias changePreProcess   (alias func) = ValueParseFunctions!(      func, PreValidation, Parse, Validation, Action, NoValueAction);
2137     alias changePreValidation(alias func) = ValueParseFunctions!(PreProcess,          func, Parse, Validation, Action, NoValueAction);
2138     alias changeParse        (alias func) = ValueParseFunctions!(PreProcess, PreValidation,  func, Validation, Action, NoValueAction);
2139     alias changeValidation   (alias func) = ValueParseFunctions!(PreProcess, PreValidation, Parse,       func, Action, NoValueAction);
2140     alias changeAction       (alias func) = ValueParseFunctions!(PreProcess, PreValidation, Parse, Validation,   func, NoValueAction);
2141     alias changeNoValueAction(alias func) = ValueParseFunctions!(PreProcess, PreValidation, Parse, Validation, Action,          func);
2142 
2143     template addDefaults(T)
2144     {
2145         static if(is(PreProcess == void))
2146             alias preProc = DefaultValueParseFunctions!T;
2147         else
2148             alias preProc = DefaultValueParseFunctions!T.changePreProcess!PreProcess;
2149 
2150         static if(is(PreValidation == void))
2151             alias preVal = preProc;
2152         else
2153             alias preVal = preProc.changePreValidation!PreValidation;
2154 
2155         static if(is(Parse == void))
2156             alias parse = preVal;
2157         else
2158             alias parse = preVal.changeParse!Parse;
2159 
2160         static if(is(Validation == void))
2161             alias val = parse;
2162         else
2163             alias val = parse.changeValidation!Validation;
2164 
2165         static if(is(Action == void))
2166             alias action = val;
2167         else
2168             alias action = val.changeAction!Action;
2169 
2170         static if(is(NoValueAction == void))
2171             alias addDefaults = action;
2172         else
2173             alias addDefaults = action.changeNoValueAction!NoValueAction;
2174     }
2175 
2176 
2177     // Procedure to process (parse) the values to an argument of type T
2178     //  - if there is a value(s):
2179     //      - pre validate raw strings
2180     //      - parse raw strings
2181     //      - validate parsed values
2182     //      - action with values
2183     //  - if there is no value:
2184     //      - action if no value
2185     // Requirement: rawValues.length must be correct
2186     static bool parse(T)(ref T receiver, RawParam param)
2187     {
2188         return addDefaults!T.parseImpl(receiver, param);
2189     }
2190     static bool parseImpl(T)(ref T receiver, ref RawParam rawParam)
2191     {
2192         alias ParseType(T)     = .ParseType!(Parse, T);
2193 
2194         alias preValidation    = ValidateFunc!(PreValidation, string[], "Pre validation");
2195         alias parse(T)         = ParseFunc!(Parse, T);
2196         alias validation(T)    = ValidateFunc!(Validation, ParseType!T);
2197         alias action(T)        = ActionFunc!(Action, T, ParseType!T);
2198         alias noValueAction(T) = NoValueActionFunc!(NoValueAction, T);
2199 
2200         if(rawParam.value.length == 0)
2201         {
2202             return noValueAction!T(receiver, Param!void(rawParam.config, rawParam.name));
2203         }
2204         else
2205         {
2206             static if(!is(PreProcess == void))
2207                 PreProcess(rawParam);
2208 
2209             if(!preValidation(rawParam))
2210                 return false;
2211 
2212             auto parsedParam = Param!(ParseType!T)(rawParam.config, rawParam.name);
2213 
2214             if(!parse!T(parsedParam.value, rawParam))
2215                 return false;
2216 
2217             if(!validation!T(parsedParam))
2218                 return false;
2219 
2220             if(!action!T(receiver, parsedParam))
2221                 return false;
2222 
2223             return true;
2224         }
2225     }
2226 }
2227 
2228 
2229 private template DefaultValueParseFunctions(T)
2230 if(!is(T == void))
2231 {
2232     import std.traits;
2233     import std.conv: to;
2234 
2235     static if(is(T == enum))
2236     {
2237         alias DefaultValueParseFunctions = ValueParseFunctions!(
2238         void,   // pre process
2239         Validators.ValueInList!(EnumMembersAsStrings!T, typeof(RawParam.value)),   // pre validate
2240         void,   // parse
2241         void,   // validate
2242         void,   // action
2243         void    // no-value action
2244         );
2245     }
2246     else static if(isSomeString!T || isNumeric!T)
2247     {
2248         alias DefaultValueParseFunctions = ValueParseFunctions!(
2249         void,   // pre process
2250         void,   // pre validate
2251         void,   // parse
2252         void,   // validate
2253         void,   // action
2254         void    // no-value action
2255         );
2256     }
2257     else static if(isBoolean!T)
2258     {
2259         alias DefaultValueParseFunctions = ValueParseFunctions!(
2260         void,                               // pre process
2261         void,                               // pre validate
2262         (string value)                      // parse
2263         {
2264             switch(value)
2265             {
2266                 case "":    goto case;
2267                 case "yes": goto case;
2268                 case "y":   return true;
2269                 case "no":  goto case;
2270                 case "n":   return false;
2271                 default:    return value.to!T;
2272             }
2273         },
2274         void,                               // validate
2275         void,                               // action
2276         (ref T result) { result = true; }   // no-value action
2277         );
2278     }
2279     else static if(isSomeChar!T)
2280     {
2281         alias DefaultValueParseFunctions = ValueParseFunctions!(
2282         void,                         // pre process
2283         void,                         // pre validate
2284         (string value)                // parse
2285         {
2286             return value.length > 0 ? value[0].to!T : T.init;
2287         },
2288         void,                         // validate
2289         void,                         // action
2290         void                          // no-value action
2291         );
2292     }
2293     else static if(isArray!T)
2294     {
2295         import std.traits: ForeachType;
2296 
2297         alias TElement = ForeachType!T;
2298 
2299         static if(!isArray!TElement || isSomeString!TElement)  // 1D array
2300         {
2301             static if(!isStaticArray!T)
2302                 alias action = Actions.Append!T;
2303             else
2304                 alias action = Actions.Assign!T;
2305 
2306             alias DefaultValueParseFunctions = DefaultValueParseFunctions!TElement
2307             .changePreProcess!splitValues
2308             .changeParse!((ref T receiver, RawParam param)
2309             {
2310                 static if(!isStaticArray!T)
2311                 {
2312                     if(receiver.length < param.value.length)
2313                         receiver.length = param.value.length;
2314                 }
2315 
2316                 foreach(i, value; param.value)
2317                 {
2318                     if(!DefaultValueParseFunctions!TElement.parse(receiver[i],
2319                         RawParam(param.config, param.name, [value])))
2320                         return false;
2321                 }
2322 
2323                 return true;
2324             })
2325             .changeAction!(action)
2326             .changeNoValueAction!((ref T param) {});
2327         }
2328         else static if(!isArray!(ForeachType!TElement) || isSomeString!(ForeachType!TElement))  // 2D array
2329         {
2330             alias DefaultValueParseFunctions = DefaultValueParseFunctions!TElement
2331             .changeAction!(Actions.Extend!TElement)
2332             .changeNoValueAction!((ref T param) { param ~= TElement.init; });
2333         }
2334         else
2335         {
2336             static assert(false, "Multi-dimentional arrays are not supported: " ~ T.stringof);
2337         }
2338     }
2339     else static if(isAssociativeArray!T)
2340     {
2341         import std.string : indexOf;
2342         alias DefaultValueParseFunctions = ValueParseFunctions!(
2343         splitValues,                                                // pre process
2344         void,                                                       // pre validate
2345         Parsers.PassThrough,                                        // parse
2346         void,                                                       // validate
2347         (ref T recepient, Param!(string[]) param)                   // action
2348         {
2349             alias K = KeyType!T;
2350             alias V = ValueType!T;
2351 
2352             foreach(input; param.value)
2353             {
2354                 auto j = indexOf(input, param.config.assignChar);
2355                 if(j < 0)
2356                     return false;
2357 
2358                 K key;
2359                 if(!DefaultValueParseFunctions!K.parse(key, RawParam(param.config, param.name, [input[0 .. j]])))
2360                     return false;
2361 
2362                 V value;
2363                 if(!DefaultValueParseFunctions!V.parse(value, RawParam(param.config, param.name, [input[j + 1 .. $]])))
2364                     return false;
2365 
2366                 recepient[key] = value;
2367             }
2368             return true;
2369         },
2370         (ref T param) {}    // no-value action
2371         );
2372     }
2373     else static if(is(T == delegate))
2374     {
2375         alias DefaultValueParseFunctions = ValueParseFunctions!(
2376             void,                           // pre process
2377             void,                           // pre validate
2378             Parsers.PassThrough,            // parse
2379             void,                           // validate
2380             Actions.CallFunction!T,         // action
2381             Actions.CallFunctionNoParam!T   // no-value action
2382         );
2383     }
2384      else
2385         static assert(false, "Type is not supported: " ~ T.stringof);
2386 }
2387 
2388 unittest
2389 {
2390     enum MyEnum { foo, bar, }
2391 
2392     import std.meta: AliasSeq;
2393     static foreach(T; AliasSeq!(string, bool, int, double, char, MyEnum))
2394         static foreach(R; AliasSeq!(T, T[], T[][]))
2395         {{
2396             // ensure that this compiles
2397             R receiver;
2398             RawParam param;
2399             param.value = [""];
2400             DefaultValueParseFunctions!R.parse(receiver, param);
2401         }}
2402 }
2403 
2404 unittest
2405 {
2406     alias test(R) = (string[][] values)
2407     {
2408         auto config = Config('=', ',');
2409         R receiver;
2410         foreach(value; values)
2411         {
2412             assert(DefaultValueParseFunctions!R.parse(receiver, RawParam(config, "", value)));
2413         }
2414         return receiver;
2415     };
2416 
2417     static assert(test!(string[])([["1","2","3"], [], ["4"]]) == ["1","2","3","4"]);
2418     static assert(test!(string[][])([["1","2","3"], [], ["4"]]) == [["1","2","3"],[],["4"]]);
2419 
2420     static assert(test!(string[string])([["a=bar","b=foo"], [], ["b=baz","c=boo"]]) == ["a":"bar", "b":"baz", "c":"boo"]);
2421 
2422     static assert(test!(string[])([["1,2,3"], [], ["4"]]) == ["1","2","3","4"]);
2423     static assert(test!(string[string])([["a=bar,b=foo"], [], ["b=baz,c=boo"]]) == ["a":"bar", "b":"baz", "c":"boo"]);
2424 
2425     static assert(test!(int[])([["1","2","3"], [], ["4"]]) == [1,2,3,4]);
2426     static assert(test!(int[][])([["1","2","3"], [], ["4"]]) == [[1,2,3],[],[4]]);
2427 
2428 }
2429 
2430 unittest
2431 {
2432     import std.math: isNaN;
2433     enum MyEnum { foo, bar, }
2434 
2435     alias test(T) = (string[] values)
2436     {
2437         T receiver;
2438         RawParam param;
2439         param.value = values;
2440         assert(DefaultValueParseFunctions!T.parse(receiver, param));
2441         return receiver;
2442     };
2443 
2444     static assert(test!string([""]) == "");
2445     static assert(test!string(["foo"]) == "foo");
2446     static assert(isNaN(test!double([""])));
2447     static assert(test!double(["-12.34"]) == -12.34);
2448     static assert(test!double(["12.34"]) == 12.34);
2449     static assert(test!uint(["1234"]) == 1234);
2450     static assert(test!int([""]) == int.init);
2451     static assert(test!int(["-1234"]) == -1234);
2452     static assert(test!char([""]) == char.init);
2453     static assert(test!char(["f"]) == 'f');
2454     static assert(test!bool([]) == true);
2455     static assert(test!bool([""]) == true);
2456     static assert(test!bool(["yes"]) == true);
2457     static assert(test!bool(["y"]) == true);
2458     static assert(test!bool(["true"]) == true);
2459     static assert(test!bool(["no"]) == false);
2460     static assert(test!bool(["n"]) == false);
2461     static assert(test!bool(["false"]) == false);
2462     static assert(test!MyEnum(["foo"]) == MyEnum.foo);
2463     static assert(test!MyEnum(["bar"]) == MyEnum.bar);
2464     static assert(test!(MyEnum[])(["bar","foo"]) == [MyEnum.bar, MyEnum.foo]);
2465     static assert(test!(string[string])(["a=bar","b=foo"]) == ["a":"bar", "b":"foo"]);
2466     static assert(test!(MyEnum[string])(["a=bar","b=foo"]) == ["a":MyEnum.bar, "b":MyEnum.foo]);
2467     static assert(test!(int[MyEnum])(["bar=3","foo=5"]) == [MyEnum.bar:3, MyEnum.foo:5]);
2468 }
2469 
2470 
2471 private struct ArgumentInfo
2472 {
2473     string[] names;
2474 
2475     string description;
2476     string placeholder;
2477 
2478     private void setAllowedValues(alias names)()
2479     {
2480         if(placeholder.length == 0)
2481         {
2482             import std.conv: to;
2483             import std.array: join;
2484             import std.format: format;
2485             placeholder = "{%s}".format(names.to!(string[]).join(','));
2486         }
2487     }
2488 
2489     bool hideFromHelp = false;      // if true then this argument is not printed on help page
2490 
2491     bool required;
2492 
2493     Nullable!uint position;
2494 
2495     @property bool positional() const { return !position.isNull; }
2496 
2497     Nullable!ulong minValuesCount;
2498     Nullable!ulong maxValuesCount;
2499 
2500     private bool checkValuesCount(in Config config, string argName, ulong count) const
2501     {
2502         immutable min = minValuesCount.get;
2503         immutable max = maxValuesCount.get;
2504 
2505         // override for boolean flags
2506         if(allowBooleanNegation && count == 1)
2507             return true;
2508 
2509         if(min == max && count != min)
2510         {
2511             config.onError("argument ",argName,": expected ",min,min == 1 ? " value" : " values");
2512             return false;
2513         }
2514         if(count < min)
2515         {
2516             config.onError("argument ",argName,": expected at least ",min,min == 1 ? " value" : " values");
2517             return false;
2518         }
2519         if(count > max)
2520         {
2521             config.onError("argument ",argName,": expected at most ",max,max == 1 ? " value" : " values");
2522             return false;
2523         }
2524 
2525         return true;
2526     }
2527 
2528     private bool allowBooleanNegation = true;
2529 
2530     Nullable!int parsingTerminateCode;
2531 }
2532 
2533 
2534 
2535 ////////////////////////////////////////////////////////////////////////////////////////////////////
2536 // User defined attributes
2537 ////////////////////////////////////////////////////////////////////////////////////////////////////
2538 private struct ArgumentUDA(alias ValueParseFunctions)
2539 {
2540     ArgumentInfo info;
2541 
2542     alias parsingFunc = ValueParseFunctions;
2543 
2544 
2545 
2546     auto ref Description(string text)
2547     {
2548         info.description = text;
2549         return this;
2550     }
2551 
2552     auto ref HideFromHelp(bool hide = true)
2553     {
2554         info.hideFromHelp = hide;
2555         return this;
2556     }
2557 
2558     auto ref Placeholder(string value)
2559     {
2560         info.placeholder = value;
2561         return this;
2562     }
2563 
2564     auto ref Required()
2565     {
2566         info.required = true;
2567         return this;
2568     }
2569 
2570     auto ref Optional()
2571     {
2572         info.required = false;
2573         return this;
2574     }
2575 
2576     auto ref NumberOfValues(ulong num)
2577     {
2578         info.minValuesCount = num;
2579         info.maxValuesCount = num;
2580         return this;
2581     }
2582 
2583     auto ref NumberOfValues(ulong min, ulong max)
2584     {
2585         info.minValuesCount = min;
2586         info.maxValuesCount = max;
2587         return this;
2588     }
2589 
2590     auto ref MinNumberOfValues(ulong min)
2591     {
2592         assert(min <= info.maxValuesCount.get(ulong.max));
2593 
2594         info.minValuesCount = min;
2595         return this;
2596     }
2597 
2598     auto ref MaxNumberOfValues(ulong max)
2599     {
2600         assert(max >= info.minValuesCount.get(0));
2601 
2602         info.maxValuesCount = max;
2603         return this;
2604     }
2605 
2606     // ReverseSwitch
2607 }
2608 
2609 unittest
2610 {
2611     auto arg = NamedArgument.Description("desc").Placeholder("text");
2612     assert(arg.info.description == "desc");
2613     assert(arg.info.placeholder == "text");
2614     assert(!arg.info.hideFromHelp);
2615     assert(!arg.info.required);
2616     assert(arg.info.minValuesCount.isNull);
2617     assert(arg.info.maxValuesCount.isNull);
2618 
2619     arg = arg.HideFromHelp().Required().NumberOfValues(10);
2620     assert(arg.info.hideFromHelp);
2621     assert(arg.info.required);
2622     assert(arg.info.minValuesCount.get == 10);
2623     assert(arg.info.maxValuesCount.get == 10);
2624 
2625     arg = arg.Optional().NumberOfValues(20,30);
2626     assert(!arg.info.required);
2627     assert(arg.info.minValuesCount.get == 20);
2628     assert(arg.info.maxValuesCount.get == 30);
2629 
2630     arg = arg.MinNumberOfValues(2).MaxNumberOfValues(3);
2631     assert(arg.info.minValuesCount.get == 2);
2632     assert(arg.info.maxValuesCount.get == 3);
2633 }
2634 
2635 unittest
2636 {
2637     struct T
2638     {
2639         @(NamedArgument.NumberOfValues(1,3))
2640         int[] a;
2641         @(NamedArgument.NumberOfValues(2))
2642         int[] b;
2643     }
2644 
2645     assert(["-a","1","2","3","-b","4","5"].parseCLIArgs!T.get == T([1,2,3],[4,5]));
2646     assert(["-a","1","-b","4","5"].parseCLIArgs!T.get == T([1],[4,5]));
2647 }
2648 
2649 private enum bool isArgumentUDA(T) = (is(typeof(T.info) == ArgumentInfo) && is(T.parsingFunc));
2650 
2651 
2652 auto PreValidation(alias func, ARG)(ARG arg)
2653 if(isArgumentUDA!ARG)
2654 {
2655     return ArgumentUDA!(arg.parsingFunc.changePreValidation!func)(arg.tupleof);
2656 }
2657 
2658 auto Parse(alias func, ARG)(ARG arg)
2659 if(isArgumentUDA!ARG)
2660 {
2661     return ArgumentUDA!(arg.parsingFunc.changeParse!func)(arg.tupleof);
2662 }
2663 
2664 auto Validation(alias func, ARG)(ARG arg)
2665 if(isArgumentUDA!ARG)
2666 {
2667     return ArgumentUDA!(arg.parsingFunc.changeValidation!func)(arg.tupleof);
2668 }
2669 
2670 auto Action(alias func, ARG)(ARG arg)
2671 if(isArgumentUDA!ARG)
2672 {
2673     return ArgumentUDA!(arg.parsingFunc.changeAction!func)(arg.tupleof);
2674 }
2675 
2676 auto AllowNoValue(alias valueToUse, ARG)(ARG arg)
2677 if(isArgumentUDA!ARG)
2678 {
2679     auto desc = ArgumentUDA!(arg.parsingFunc.changeNoValueAction!(() { return valueToUse; }))(arg.tupleof);
2680     desc.info.minValuesCount = 0;
2681     return desc;
2682 }
2683 
2684 auto RequireNoValue(alias valueToUse, ARG)(ARG arg)
2685 if(isArgumentUDA!ARG)
2686 {
2687     auto desc = arg.AllowNoValue!valueToUse;
2688     desc.info.minValuesCount = 0;
2689     desc.info.maxValuesCount = 0;
2690     return desc;
2691 }
2692 
2693 auto Counter(ARG)(ARG arg)
2694 if(isArgumentUDA!ARG)
2695 {
2696     struct CounterParsingFunction
2697     {
2698         static bool parse(T)(ref T receiver, const ref RawParam param)
2699         {
2700             assert(param.value.length == 0);
2701 
2702             ++receiver;
2703 
2704             return true;
2705         }
2706     }
2707 
2708     auto desc = ArgumentUDA!(CounterParsingFunction)(arg.tupleof);
2709     desc.info.minValuesCount = 0;
2710     desc.info.maxValuesCount = 0;
2711     return desc;
2712 }
2713 
2714 
2715 unittest
2716 {
2717     struct T
2718     {
2719         @(NamedArgument.Counter()) int a;
2720     }
2721 
2722     static assert(["-a","-a","-a"].parseCLIArgs!T.get == T(3));
2723     assert(["-a","-a","-a"].parseCLIArgs!T.get == T(3));
2724 }
2725 
2726 
2727 auto AllowedValues(alias values, ARG)(ARG arg)
2728 {
2729     import std.array : assocArray;
2730     import std.range : repeat;
2731 
2732     enum valuesAA = assocArray(values, false.repeat);
2733 
2734     auto desc = arg.Validation!(Validators.ValueInList!(values, KeyType!(typeof(valuesAA))));
2735     desc.info.setAllowedValues!values;
2736     return desc;
2737 }
2738 
2739 
2740 unittest
2741 {
2742     struct T
2743     {
2744         @(NamedArgument.AllowedValues!([1,3,5])) int a;
2745     }
2746 
2747     static assert(["-a","3"].parseCLIArgs!T.get == T(3));
2748     assert(["-a","2"].parseCLIArgs!T.isNull);
2749     assert(["-a","3"].parseCLIArgs!T.get == T(3));
2750 }
2751 
2752 unittest
2753 {
2754     struct T
2755     {
2756         @(NamedArgument.AllowedValues!(["apple","pear","banana"]))
2757         string fruit;
2758     }
2759 
2760     static assert(["--fruit", "apple"].parseCLIArgs!T.get == T("apple"));
2761     assert(["--fruit", "kiwi"].parseCLIArgs!T.isNull);
2762 }
2763 
2764 unittest
2765 {
2766     enum Fruit { apple, pear, banana }
2767     struct T
2768     {
2769         @NamedArgument
2770         Fruit fruit;
2771     }
2772 
2773     static assert(["--fruit", "apple"].parseCLIArgs!T.get == T(Fruit.apple));
2774     assert(["--fruit", "kiwi"].parseCLIArgs!T.isNull);
2775 }
2776 
2777 
2778 auto PositionalArgument(uint pos)
2779 {
2780     auto arg = ArgumentUDA!(ValueParseFunctions!(void, void, void, void, void, void))(ArgumentInfo()).Required();
2781     arg.info.position = pos;
2782     return arg;
2783 }
2784 
2785 auto PositionalArgument(uint pos, string name)
2786 {
2787     auto arg = ArgumentUDA!(ValueParseFunctions!(void, void, void, void, void, void))(ArgumentInfo([name])).Required();
2788     arg.info.position = pos;
2789     return arg;
2790 }
2791 
2792 auto NamedArgument(string[] name...)
2793 {
2794     return ArgumentUDA!(ValueParseFunctions!(void, void, void, void, void, void))(ArgumentInfo(name)).Optional();
2795 }
2796 
2797 auto NamedArgument(string name)
2798 {
2799     return ArgumentUDA!(ValueParseFunctions!(void, void, void, void, void, void))(ArgumentInfo([name])).Optional();
2800 }
2801 
2802 struct TrailingArguments {}
2803 
2804 
2805 unittest
2806 {
2807     struct T
2808     {
2809         string a;
2810         string b;
2811 
2812         @TrailingArguments string[] args;
2813     }
2814 
2815     static assert(["-a","A","--","-b","B"].parseCLIArgs!T.get == T("A","",["-b","B"]));
2816     assert(["-a","A","--","-b","B"].parseCLIArgs!T.get == T("A","",["-b","B"]));
2817 }
2818 
2819 unittest
2820 {
2821     struct T
2822     {
2823         @NamedArgument int i;
2824         @NamedArgument(["u","u1"])  uint u;
2825         @NamedArgument("d","d1")  double d;
2826     }
2827 
2828     static assert(["-i","-5","-u","8","-d","12.345"].parseCLIArgs!T.get == T(-5,8,12.345));
2829     static assert(["-i","-5","-u1","8","-d1","12.345"].parseCLIArgs!T.get == T(-5,8,12.345));
2830     assert(["-i","-5","-u","8","-d","12.345"].parseCLIArgs!T.get == T(-5,8,12.345));
2831     assert(["-i","-5","-u1","8","-d1","12.345"].parseCLIArgs!T.get == T(-5,8,12.345));
2832 }
2833 
2834 unittest
2835 {
2836     struct T
2837     {
2838         @(NamedArgument) int[]   a;
2839         @(NamedArgument) int[][] b;
2840     }
2841 
2842     static assert(["-a","1","2","3","-a","4","5"].parseCLIArgs!T.get.a == [1,2,3,4,5]);
2843     static assert(["-b","1","2","3","-b","4","5"].parseCLIArgs!T.get.b == [[1,2,3],[4,5]]);
2844     assert(["-a","1","2","3","-a","4","5"].parseCLIArgs!T.get.a == [1,2,3,4,5]);
2845     assert(["-b","1","2","3","-b","4","5"].parseCLIArgs!T.get.b == [[1,2,3],[4,5]]);
2846 }
2847 
2848 unittest
2849 {
2850     struct T
2851     {
2852         @NamedArgument int[] a;
2853     }
2854 
2855     Config cfg;
2856     cfg.arraySep = ',';
2857 
2858     assert(["-a","1,2,3","-a","4","5"].parseCLIArgs!T(cfg).get == T([1,2,3,4,5]));
2859 }
2860 
2861 unittest
2862 {
2863     struct T
2864     {
2865         @NamedArgument int[string] a;
2866     }
2867 
2868     static assert(["-a=foo=3","-a","boo=7"].parseCLIArgs!T.get.a == ["foo":3,"boo":7]);
2869     assert(["-a=foo=3","-a","boo=7"].parseCLIArgs!T.get.a == ["foo":3,"boo":7]);
2870 }
2871 
2872 unittest
2873 {
2874     struct T
2875     {
2876         @NamedArgument int[string] a;
2877     }
2878 
2879     Config cfg;
2880     cfg.arraySep = ',';
2881 
2882     assert(["-a=foo=3,boo=7"].parseCLIArgs!T(cfg).get.a == ["foo":3,"boo":7]);
2883     assert(["-a","foo=3,boo=7"].parseCLIArgs!T(cfg).get.a == ["foo":3,"boo":7]);
2884 }
2885 
2886 unittest
2887 {
2888     struct T
2889     {
2890         enum Fruit { apple, pear };
2891 
2892         @NamedArgument Fruit a;
2893     }
2894 
2895     static assert(["-a","apple"].parseCLIArgs!T.get == T(T.Fruit.apple));
2896     static assert(["-a=pear"].parseCLIArgs!T.get == T(T.Fruit.pear));
2897     assert(["-a","apple"].parseCLIArgs!T.get == T(T.Fruit.apple));
2898     assert(["-a=pear"].parseCLIArgs!T.get == T(T.Fruit.pear));
2899 }
2900 
2901 unittest
2902 {
2903     struct T
2904     {
2905         @NamedArgument string[] a;
2906     }
2907 
2908     assert(["-a","1,2,3","-a","4","5"].parseCLIArgs!T.get == T(["1,2,3","4","5"]));
2909 
2910     Config cfg;
2911     cfg.arraySep = ',';
2912 
2913     assert(["-a","1,2,3","-a","4","5"].parseCLIArgs!T(cfg).get == T(["1","2","3","4","5"]));
2914 }
2915 
2916 unittest
2917 {
2918     struct T
2919     {
2920         @(NamedArgument.AllowNoValue  !10) int a;
2921         @(NamedArgument.RequireNoValue!20) int b;
2922     }
2923 
2924     static assert(["-a"].parseCLIArgs!T.get.a == 10);
2925     static assert(["-b"].parseCLIArgs!T.get.b == 20);
2926     static assert(["-a", "30"].parseCLIArgs!T.get.a == 30);
2927     assert(["-a"].parseCLIArgs!T.get.a == 10);
2928     assert(["-b"].parseCLIArgs!T.get.b == 20);
2929     assert(["-a", "30"].parseCLIArgs!T.get.a == 30);
2930     assert(["-b", "30"].parseCLIArgs!T.isNull);
2931 }
2932 
2933 unittest
2934 {
2935     struct T
2936     {
2937         @(NamedArgument
2938          .PreValidation!((string s) { return s.length > 1 && s[0] == '!'; })
2939          .Parse        !((string s) { return s[1]; })
2940          .Validation   !((char v) { return v >= '0' && v <= '9'; })
2941          .Action       !((ref int a, char v) { a = v - '0'; })
2942         )
2943         int a;
2944     }
2945 
2946     static assert(["-a","!4"].parseCLIArgs!T.get.a == 4);
2947     assert(["-a","!4"].parseCLIArgs!T.get.a == 4);
2948 }
2949 
2950 unittest
2951 {
2952     static struct T
2953     {
2954         int a;
2955 
2956         @(NamedArgument("a")) void foo() { a++; }
2957     }
2958 
2959     static assert(["-a","-a","-a","-a"].parseCLIArgs!T.get.a == 4);
2960     assert(["-a","-a","-a","-a"].parseCLIArgs!T.get.a == 4);
2961 }
2962 
2963 
2964 //private
2965 string getProgramName()
2966 {
2967     import core.runtime: Runtime;
2968     import std.path: baseName;
2969     return Runtime.args[0].baseName;
2970 }
2971 
2972 unittest
2973 {
2974     assert(getProgramName().length > 0);
2975 }
2976 
2977 
2978 private struct CommandInfo
2979 {
2980     private string name;
2981     private string usage;
2982     private string description;
2983     private string epilog;
2984 
2985     auto ref Usage(string text)
2986     {
2987         usage = text;
2988         return this;
2989     }
2990 
2991     auto ref Description(string text)
2992     {
2993         description = text;
2994         return this;
2995     }
2996 
2997     auto ref Epilog(string text)
2998     {
2999         epilog = text;
3000         return this;
3001     }
3002 }
3003 
3004 auto Command(string name = "")
3005 {
3006     return CommandInfo(name);
3007 }
3008 
3009 unittest
3010 {
3011     assert(Command("MYPROG").name == "MYPROG");
3012 }
3013 
3014 
3015 private struct CommandArguments(RECEIVER)
3016 {
3017     static assert(getUDAs!(RECEIVER, CommandInfo).length <= 1);
3018 
3019     static if(getUDAs!(RECEIVER, CommandInfo).length == 0)
3020         CommandInfo info;
3021     else
3022         CommandInfo info = getUDAs!(RECEIVER, CommandInfo)[0];
3023 
3024     Arguments!RECEIVER arguments;
3025 
3026 
3027     private this(in Config config)
3028     {
3029         arguments = createArguments!RECEIVER(config.caseSensitive);
3030 
3031         if(config.addHelp)
3032         {
3033             arguments.addArgument!helpArgument(delegate (in Config config, string argName, ref RECEIVER receiver, string[] rawValues)
3034             {
3035                 import std.stdio: stdout;
3036 
3037                 printHelp(stdout.lockingTextWriter(), this, config);
3038 
3039                 return true;
3040             });
3041         }
3042     }
3043 }
3044 
3045 ////////////////////////////////////////////////////////////////////////////////////////////////////
3046 // Help-printing functions
3047 ////////////////////////////////////////////////////////////////////////////////////////////////////
3048 private void printValue(Output)(auto ref Output output, in ArgumentInfo info)
3049 {
3050     if(info.maxValuesCount.get == 0)
3051         return;
3052 
3053     if(info.minValuesCount.get == 0)
3054         output.put('[');
3055 
3056     output.put(info.placeholder);
3057     if(info.maxValuesCount.get > 1)
3058         output.put(" ...");
3059 
3060     if(info.minValuesCount.get == 0)
3061         output.put(']');
3062 }
3063 
3064 unittest
3065 {
3066     auto test(int min, int max)
3067     {
3068         ArgumentInfo info;
3069         info.placeholder = "v";
3070         info.minValuesCount = min;
3071         info.maxValuesCount = max;
3072 
3073         import std.array: appender;
3074         auto a = appender!string;
3075         a.printValue(info);
3076         return a[];
3077     }
3078 
3079     assert(test(0,0) == "");
3080     assert(test(0,1) == "[v]");
3081     assert(test(0,5) == "[v ...]");
3082     assert(test(1,1) == "v");
3083     assert(test(1,5) == "v ...");
3084     assert(test(3,3) == "v ...");
3085     assert(test(3,5) == "v ...");
3086 }
3087 
3088 
3089 private string getArgumentName(string name, in Config config)
3090 {
3091     name = config.namedArgChar ~ name;
3092     return name.length > 2 ? config.namedArgChar ~ name : name;
3093 }
3094 
3095 unittest
3096 {
3097     assert(getArgumentName("f", Config.init) == "-f");
3098     assert(getArgumentName("foo", Config.init) == "--foo");
3099 }
3100 
3101 
3102 private void printInvocation(Output)(auto ref Output output, in ArgumentInfo info, in string[] names, in Config config)
3103 {
3104     if(info.positional)
3105         output.printValue(info);
3106     else
3107     {
3108         import std.algorithm: each;
3109 
3110         names.each!((i, name)
3111         {
3112             if(i > 0)
3113                 output.put(", ");
3114 
3115             output.put(getArgumentName(name, config));
3116 
3117             if(info.maxValuesCount.get > 0)
3118             {
3119                 output.put(' ');
3120                 output.printValue(info);
3121             }
3122         });
3123     }
3124 }
3125 
3126 unittest
3127 {
3128     auto test(bool positional)
3129     {
3130         ArgumentInfo info;
3131         info.placeholder = "v";
3132         if(positional)
3133             info.position = 0;
3134 
3135         import std.array: appender;
3136         auto a = appender!string;
3137         a.printInvocation(info.setDefaults!(int, "foo"), ["f","foo"], Config.init);
3138         return a[];
3139     }
3140 
3141     assert(test(false) == "-f v, --foo v");
3142     assert(test(true) == "v");
3143 }
3144 
3145 
3146 private void printUsage(Output)(auto ref Output output, in ArgumentInfo info, in Config config)
3147 {
3148     if(!info.required)
3149         output.put('[');
3150 
3151     output.printInvocation(info, [info.names[0]], config);
3152 
3153     if(!info.required)
3154         output.put(']');
3155 }
3156 
3157 unittest
3158 {
3159     auto test(bool required, bool positional)
3160     {
3161         ArgumentInfo info;
3162         info.names ~= "foo";
3163         info.placeholder = "v";
3164         info.required = required;
3165         if(positional)
3166             info.position = 0;
3167 
3168         import std.array: appender;
3169         auto a = appender!string;
3170         a.printUsage(info.setDefaults!(int, "foo"), Config.init);
3171         return a[];
3172     }
3173 
3174     assert(test(false, false) == "[--foo v]");
3175     assert(test(false, true) == "[v]");
3176     assert(test(true, false) == "--foo v");
3177     assert(test(true, true) == "v");
3178 }
3179 
3180 
3181 private void substituteProg(Output)(auto ref Output output, string text, string prog)
3182 {
3183     import std.array: replaceInto;
3184     output.replaceInto(text, "%(PROG)", prog);
3185 }
3186 
3187 unittest
3188 {
3189     import std.array: appender;
3190     auto a = appender!string;
3191     a.substituteProg("this is some text where %(PROG) is substituted but PROG and prog are not", "-myprog-");
3192     assert(a[] == "this is some text where -myprog- is substituted but PROG and prog are not");
3193 }
3194 
3195 
3196 private string spaces(ulong num)
3197 {
3198     import std.range: repeat;
3199     import std.array: array;
3200     return ' '.repeat(num).array;
3201 }
3202 
3203 unittest
3204 {
3205     assert(spaces(0) == "");
3206     assert(spaces(1) == " ");
3207     assert(spaces(5) == "     ");
3208 }
3209 
3210 private void printUsage(T, Output)(auto ref Output output, in CommandArguments!T cmd, in Config config)
3211 {
3212     output.put("usage: ");
3213 
3214     if(cmd.info.usage.length > 0)
3215         substituteProg(output, cmd.info.usage, cmd.info.name);
3216     else
3217     {
3218         import std.algorithm: filter, each, map;
3219 
3220         alias print = (r) => r
3221             .filter!((ref _) => !_.hideFromHelp)
3222             .each!((ref _)
3223             {
3224                 output.put(' ');
3225                 output.printUsage(_, config);
3226             });
3227 
3228         output.put(cmd.info.name.length > 0 ? cmd.info.name : getProgramName());
3229 
3230         // named args
3231         print(cmd.arguments.arguments.filter!((ref _) => !_.positional));
3232         // positional args
3233         print(cmd.arguments.positionalArguments.map!(ref (_) => cmd.arguments.arguments[_]));
3234 
3235         output.put('\n');
3236     }
3237 }
3238 
3239 void printUsage(T, Output)(auto ref Output output, in Config config)
3240 {
3241     printUsage(output, CommandArguments!T(config), config);
3242 }
3243 
3244 unittest
3245 {
3246     @(Command("MYPROG").Usage("custom usage of %(PROG)"))
3247     struct T
3248     {
3249         string s;
3250     }
3251 
3252     auto test(string usage)
3253     {
3254         import std.array: appender;
3255 
3256         auto a = appender!string;
3257         a.printUsage!T(Config.init);
3258         return a[];
3259     }
3260 
3261     enum expected = "usage: custom usage of MYPROG";
3262     static assert(test("custom usage of %(PROG)") == expected);
3263     assert(test("custom usage of %(PROG)") == expected);
3264 }
3265 
3266 
3267 private void printHelp(Output, ARGS)(auto ref Output output, in Group group, ARGS args, int helpPosition)
3268 {
3269     import std.string: wrap, leftJustify;
3270 
3271     if(group.arguments.length == 0 || group.name.length == 0)
3272         return;
3273 
3274     output.put(group.name);
3275     output.put(":\n");
3276 
3277     if(group.description.length > 0)
3278     {
3279         output.put("  ");
3280         output.put(group.description);
3281         output.put("\n\n");
3282     }
3283 
3284     immutable ident = spaces(helpPosition + 2);
3285 
3286     foreach(idx; group.arguments)
3287     {
3288         auto arg = &args[idx];
3289 
3290         if(arg.invocation.length == 0)
3291             continue;
3292         else if(arg.invocation.length <= helpPosition - 4) // 2=indent, 2=two spaces between invocation and help text
3293         {
3294             import std.array: appender;
3295 
3296             auto invocation = appender!string;
3297             invocation ~= "  ";
3298             invocation ~= arg.invocation.leftJustify(helpPosition);
3299             output.put(arg.help.wrap(80-2, invocation[], ident));
3300         }
3301         else
3302         {
3303             // long action name; start on the next line
3304             output.put("  ");
3305             output.put(arg.invocation);
3306             output.put("\n");
3307             output.put(arg.help.wrap(80-2, ident, ident));
3308         }
3309     }
3310 
3311     output.put('\n');
3312 }
3313 
3314 
3315 private void printHelp(T, Output)(auto ref Output output, in CommandArguments!T cmd, in Config config)
3316 {
3317     import std.algorithm: map, maxElement, min;
3318     import std.array: appender, array;
3319 
3320     printUsage(output, cmd, config);
3321     output.put('\n');
3322 
3323     if(cmd.info.description.length > 0)
3324     {
3325         output.put(cmd.info.description);
3326         output.put("\n\n");
3327     }
3328 
3329     // pre-compute the output
3330     auto args =
3331         cmd.arguments.arguments
3332         .map!((ref _)
3333         {
3334             import std.typecons : tuple;
3335 
3336             if(_.hideFromHelp)
3337                 return tuple!("invocation","help")("", "");
3338 
3339             auto invocation = appender!string;
3340             invocation.printInvocation(_, _.names, config);
3341 
3342             return tuple!("invocation","help")(invocation[], _.description);
3343         }).array;
3344 
3345     immutable maxInvocationWidth = args.map!(_ => _.invocation.length).maxElement;
3346     immutable helpPosition = min(maxInvocationWidth + 4, 24);
3347 
3348     //user-defined groups
3349     foreach(ref group; cmd.arguments.groups[2..$])
3350         output.printHelp(group, args, helpPosition);
3351 
3352     //required args
3353     output.printHelp(cmd.arguments.requiredGroup, args, helpPosition);
3354 
3355     //optionals args
3356     output.printHelp(cmd.arguments.optionalGroup, args, helpPosition);
3357 
3358     if(cmd.info.epilog.length > 0)
3359     {
3360         output.put(cmd.info.epilog);
3361         output.put('\n');
3362     }
3363 }
3364 
3365 void printHelp(T, Output)(auto ref Output output, in Config config)
3366 {
3367     printHelp(output, CommandArguments!T(config), config);
3368 }
3369 
3370 unittest
3371 {
3372     @(Command("MYPROG")
3373      .Description("custom description")
3374      .Epilog("custom epilog")
3375     )
3376     struct T
3377     {
3378         @NamedArgument  string s;
3379         @(NamedArgument.Placeholder("VALUE"))  string p;
3380 
3381         @(NamedArgument.HideFromHelp())  string hidden;
3382 
3383         enum Fruit { apple, pear };
3384         @(NamedArgument(["f","fruit"]).Required().Description("This is a help text for fruit. Very very very very very very very very very very very very very very very very very very very long text")) Fruit f;
3385 
3386         @(NamedArgument.AllowedValues!([1,4,16,8])) int i;
3387 
3388         @(PositionalArgument(0).Description("This is a help text for param0. Very very very very very very very very very very very very very very very very very very very long text")) string param0;
3389         @(PositionalArgument(1).AllowedValues!(["q","a"])) string param1;
3390 
3391         @TrailingArguments string[] args;
3392     }
3393 
3394     auto test(alias func)()
3395     {
3396         import std.array: appender;
3397 
3398         auto a = appender!string;
3399         func!T(a, Config.init);
3400         return a[];
3401     }
3402     static assert(test!printUsage.length > 0);  // ensure that it works at compile time
3403     static assert(test!printHelp .length > 0);  // ensure that it works at compile time
3404 
3405     assert(test!printUsage == "usage: MYPROG [-s S] [-p VALUE] -f {apple,pear} [-i {1,4,16,8}] [-h] param0 {q,a}\n");
3406     assert(test!printHelp  == "usage: MYPROG [-s S] [-p VALUE] -f {apple,pear} [-i {1,4,16,8}] [-h] param0 {q,a}\n\n"~
3407         "custom description\n\n"~
3408         "Required arguments:\n"~
3409         "  -f {apple,pear}, --fruit {apple,pear}\n"~
3410         "                          This is a help text for fruit. Very very very very\n"~
3411         "                          very very very very very very very very very very\n"~
3412         "                          very very very very very long text\n"~
3413         "  param0                  This is a help text for param0. Very very very very\n"~
3414         "                          very very very very very very very very very very\n"~
3415         "                          very very very very very long text\n"~
3416         "  {q,a}                   \n\n"~
3417         "Optional arguments:\n"~
3418         "  -s S                    \n"~
3419         "  -p VALUE                \n"~
3420         "  -i {1,4,16,8}           \n"~
3421         "  -h, --help              Show this help message and exit\n\n"~
3422         "custom epilog\n");
3423 }
3424 
3425 unittest
3426 {
3427     @Command("MYPROG")
3428     struct T
3429     {
3430         @(ArgumentGroup("group1").Description("group1 description"))
3431         {
3432             @NamedArgument
3433             {
3434                 string a;
3435                 string b;
3436             }
3437             @PositionalArgument(0) string p;
3438         }
3439 
3440         @(ArgumentGroup("group2").Description("group2 description"))
3441         @NamedArgument
3442         {
3443             string c;
3444             string d;
3445         }
3446         @PositionalArgument(1) string q;
3447     }
3448 
3449     auto test(alias func)()
3450     {
3451         import std.array: appender;
3452 
3453         auto a = appender!string;
3454         func!T(a, Config.init);
3455         return a[];
3456     }
3457 
3458     assert(test!printHelp  == "usage: MYPROG [-a A] [-b B] [-c C] [-d D] [-h] p q\n\n"~
3459         "group1:\n"~
3460         "  group1 description\n\n"~
3461         "  -a A          \n"~
3462         "  -b B          \n"~
3463         "  p             \n\n"~
3464         "group2:\n"~
3465         "  group2 description\n\n"~
3466         "  -c C          \n"~
3467         "  -d D          \n\n"~
3468         "Required arguments:\n"~
3469         "  q             \n\n"~
3470         "Optional arguments:\n"~
3471         "  -h, --help    Show this help message and exit\n\n");
3472 }
3473 
3474 unittest
3475 {
3476     @Command("MYPROG")
3477     struct T
3478     {
3479         @(NamedArgument.HideFromHelp())  string s;
3480     }
3481 
3482     assert(parseCLIArgs!T(["-h","-s","asd"]).isNull());
3483     assert(parseCLIArgs!T(["-h"], (T t) { assert(false); }) == 0);
3484 
3485     auto args = ["-h","-s","asd"];
3486     assert(parseCLIKnownArgs!T(args).isNull());
3487     assert(args.length == 3);
3488     assert(parseCLIKnownArgs!T(["-h"], (T t, string[] args) { assert(false); }) == 0);
3489 }
3490 
3491 unittest
3492 {
3493     @Command("MYPROG")
3494     struct T
3495     {
3496         @(NamedArgument.Required())  string s;
3497     }
3498 
3499     assert(parseCLIArgs!T([], (T t) { assert(false); }) != 0);
3500 }
3501 
3502 unittest
3503 {
3504     @Command("MYPROG")
3505     struct T
3506     {
3507         @MutuallyExclusive()
3508         {
3509             string a;
3510             string b;
3511         }
3512     }
3513     assert(parseCLIArgs!T(["-a","a","-b","b"], (T t) { assert(false); }) != 0);
3514     assert(parseCLIArgs!T(["-a","a"], (T t) {}) == 0);
3515     assert(parseCLIArgs!T(["-b","b"], (T t) {}) == 0);
3516     assert(parseCLIArgs!T([], (T t) {}) == 0);
3517 }
3518 
3519 unittest
3520 {
3521     @Command("MYPROG")
3522     struct T
3523     {
3524         @RequiredTogether()
3525         {
3526             string a;
3527             string b;
3528         }
3529     }
3530     assert(parseCLIArgs!T(["-a","a","-b","b"], (T t) {}) == 0);
3531     assert(parseCLIArgs!T(["-a","a"], (T t) { assert(false); }) != 0);
3532     assert(parseCLIArgs!T(["-b","b"], (T t) { assert(false); }) != 0);
3533     assert(parseCLIArgs!T([], (T t) {}) == 0);
3534 }