1 module argparse;
2 
3 
4 import std.typecons: Nullable;
5 import std.sumtype: SumType, match;
6 import std.traits;
7 
8 private enum DEFAULT_COMMAND = "";
9 
10 struct Config
11 {
12     /**
13        The assignment character used in options with parameters.
14        Defaults to '='.
15      */
16     char assignChar = '=';
17 
18     /**
19        When set to char.init, parameters to array and associative array receivers are
20        treated as an individual argument. That is, only one argument is appended or
21        inserted per appearance of the option switch. If `arraySep` is set to
22        something else, then each parameter is first split by the separator, and the
23        individual pieces are treated as arguments to the same option.
24 
25        Defaults to char.init
26      */
27     char arraySep = char.init;
28 
29     /**
30        The option character.
31        Defaults to '-'.
32      */
33     char namedArgChar = '-';
34 
35     /**
36        The string that conventionally marks the end of all options.
37        Assigning an empty string to `endOfArgs` effectively disables it.
38        Defaults to "--".
39      */
40     string endOfArgs = "--";
41 
42     /**
43        If set then argument names are case-sensitive.
44        Defaults to true.
45      */
46     bool caseSensitive = true;
47 
48     /**
49         Single-letter arguments can be bundled together, i.e. "-abc" is the same as "-a -b -c".
50         Disabled by default.
51      */
52     bool bundling = false;
53 
54     /**
55        Add a -h/--help option to the parser.
56        Defaults to true.
57      */
58     bool addHelp = true;
59 
60     /**
61        Delegate that processes error messages if they happen during argument parsing.
62        By default all errors are printed to stderr.
63      */
64     private void delegate(string s) nothrow errorHandlerFunc;
65 
66     @property auto errorHandler(void function(string s) nothrow func)
67     {
68         return errorHandlerFunc = (string msg) { func(msg); };
69     }
70 
71     @property auto errorHandler(void delegate(string s) nothrow func)
72     {
73         return errorHandlerFunc = func;
74     }
75 
76 
77     private void onError(A...)(A args) const nothrow
78     {
79         import std.conv: text;
80         import std.stdio: stderr, writeln;
81 
82         try
83         {
84             if(errorHandlerFunc)
85                 errorHandlerFunc(text!A(args));
86             else
87                 stderr.writeln("Error: ", args);
88         }
89         catch(Exception e)
90         {
91             throw new Error(e.msg);
92         }
93     }
94 }
95 
96 unittest
97 {
98     Config.init.onError("--just testing error func--",1,2.3,false);
99     Config c;
100     c.errorHandler = (string s){};
101     c.onError("--just testing error func--",1,2.3,false);
102 }
103 
104 
105 struct Param(VALUE_TYPE)
106 {
107     const Config config;
108     string name;
109 
110     static if(!is(VALUE_TYPE == void))
111         VALUE_TYPE value;
112 }
113 
114 alias RawParam = Param!(string[]);
115 
116 
117 private template defaultValuesCount(T)
118 if(!is(T == void))
119 {
120     import std.traits;
121 
122     static if(isBoolean!T)
123     {
124         enum min = 0;
125         enum max = 0;
126     }
127     else static if(isSomeString!T || isScalarType!T)
128     {
129         enum min = 1;
130         enum max = 1;
131     }
132     else static if(isStaticArray!T)
133     {
134         enum min = 1;
135         enum max = T.length;
136     }
137     else static if(isArray!T || isAssociativeArray!T)
138     {
139         enum min = 1;
140         enum max = ulong.max;
141     }
142     else static if(is(T == function))
143     {
144         // ... function()
145         static if(__traits(compiles, { T(); }))
146         {
147             enum min = 0;
148             enum max = 0;
149         }
150         // ... function(string value)
151         else static if(__traits(compiles, { T(string.init); }))
152         {
153             enum min = 1;
154             enum max = 1;
155         }
156         // ... function(string[] value)
157         else static if(__traits(compiles, { T([string.init]); }))
158         {
159             enum min = 0;
160             enum max = ulong.max;
161         }
162         // ... function(RawParam param)
163         else static if(__traits(compiles, { T(RawParam.init); }))
164         {
165             enum min = 1;
166             enum max = ulong.max;
167         }
168         else
169             static assert(false, "Unsupported callback: " ~ T.stringof);
170     }
171     else
172         static assert(false, "Type is not supported: " ~ T.stringof);
173 }
174 
175 
176 private template EnumMembersAsStrings(E)
177 {
178     enum EnumMembersAsStrings = {
179         import std.traits: EnumMembers;
180         alias members = EnumMembers!E;
181 
182         typeof(__traits(identifier, members[0]))[] res;
183         static foreach (i, _; members)
184             res ~= __traits(identifier, members[i]);
185 
186         return res;
187     }();
188 }
189 
190 unittest
191 {
192     enum E { abc, def, ghi }
193     assert(EnumMembersAsStrings!E == ["abc", "def", "ghi"]);
194 }
195 
196 private auto setDefaults(ArgumentInfo uda, TYPE, alias symbol)()
197 {
198     ArgumentInfo info = uda;
199 
200     static if(!isBoolean!TYPE)
201         info.allowBooleanNegation = false;
202 
203     static if(is(TYPE == enum))
204         info.setAllowedValues!(EnumMembersAsStrings!TYPE);
205 
206     static if(uda.names.length == 0)
207         info.names = [ symbol ];
208 
209     static if(uda.minValuesCount.isNull) info.minValuesCount = defaultValuesCount!TYPE.min;
210     static if(uda.maxValuesCount.isNull) info.maxValuesCount = defaultValuesCount!TYPE.max;
211 
212     static if(uda.placeholder.length == 0)
213         if(info.placeholder.length == 0)
214         {
215             import std.uni : toUpper;
216             info.placeholder = info.positional ? symbol : symbol.toUpper;
217         }
218 
219     return info;
220 }
221 
222 unittest
223 {
224     auto createInfo(string placeholder = "")()
225     {
226         ArgumentInfo info;
227         info.allowBooleanNegation = true;
228         info.position = 0;
229         info.placeholder = placeholder;
230         return info;
231     }
232     assert(createInfo().allowBooleanNegation); // make codecov happy
233 
234     auto res = setDefaults!(createInfo(), int, "default-name");
235     assert(!res.allowBooleanNegation);
236     assert(res.names == [ "default-name" ]);
237     assert(res.minValuesCount == defaultValuesCount!int.min);
238     assert(res.maxValuesCount == defaultValuesCount!int.max);
239     assert(res.placeholder == "default-name");
240 
241     res = setDefaults!(createInfo!"myvalue", int, "default-name");
242     assert(res.placeholder == "myvalue");
243 }
244 
245 unittest
246 {
247     auto createInfo(string placeholder = "")()
248     {
249         ArgumentInfo info;
250         info.allowBooleanNegation = true;
251         info.placeholder = placeholder;
252         return info;
253     }
254     assert(createInfo().allowBooleanNegation); // make codecov happy
255 
256     auto res = setDefaults!(createInfo(), bool, "default_name");
257     assert(res.allowBooleanNegation);
258     assert(res.names == ["default_name"]);
259     assert(res.minValuesCount == defaultValuesCount!bool.min);
260     assert(res.maxValuesCount == defaultValuesCount!bool.max);
261     assert(res.placeholder == "DEFAULT_NAME");
262 
263     res = setDefaults!(createInfo!"myvalue", bool, "default_name");
264     assert(res.placeholder == "myvalue");
265 }
266 
267 unittest
268 {
269     enum E { a=1, b=1, c }
270     static assert(EnumMembersAsStrings!E == ["a","b","c"]);
271 
272     auto createInfo(string placeholder = "")()
273     {
274         ArgumentInfo info;
275         info.placeholder = placeholder;
276         return info;
277     }
278     assert(createInfo().allowBooleanNegation); // make codecov happy
279 
280     auto res = setDefaults!(createInfo(), E, "default-name");
281     assert(res.placeholder == "{a,b,c}");
282 
283     res = setDefaults!(createInfo!"myvalue", E, "default-name");
284     assert(res.placeholder == "myvalue");
285 }
286 
287 
288 private auto checkDuplicates(alias sortedRange, string errorMsg)() {
289     static if(sortedRange.length >= 2)
290     {
291         enum value = {
292             import std.conv : to;
293 
294             foreach(i; 1..sortedRange.length-1)
295                 if(sortedRange[i-1] == sortedRange[i])
296                     return sortedRange[i].to!string;
297 
298             return "";
299         }();
300         static assert(value.length == 0, errorMsg ~ value);
301     }
302 
303     return true;
304 }
305 
306 private bool checkArgumentNames(T)()
307 {
308     enum names = () {
309         import std.algorithm : sort;
310 
311         string[] names;
312         static foreach (sym; getSymbolsByUDA!(T, ArgumentUDA))
313         {{
314             enum argUDA = getUDAs!(__traits(getMember, T, __traits(identifier, sym)), ArgumentUDA)[0];
315 
316             static assert(!argUDA.info.positional || argUDA.info.names.length <= 1,
317                  "Positional argument should have exactly one name: "~T.stringof~"."~sym.stringof);
318 
319             static foreach (name; argUDA.info.names)
320             {
321                 static assert(name.length > 0, "Argument name can't be empty: "~T.stringof~"."~sym.stringof);
322 
323                 names ~= name;
324             }
325         }}
326 
327         return names.sort;
328     }();
329 
330     return checkDuplicates!(names, "Argument name appears more than once: ");
331 }
332 
333 private bool checkPositionalIndexes(T)()
334 {
335     import std.conv  : to;
336     import std.range : lockstep, iota;
337 
338 
339     enum positions = () {
340         import std.algorithm : sort;
341 
342         uint[] positions;
343         static foreach (sym; getSymbolsByUDA!(T, ArgumentUDA))
344         {{
345             enum argUDA = getUDAs!(__traits(getMember, T, __traits(identifier, sym)), ArgumentUDA)[0];
346 
347             static if (argUDA.info.positional)
348                 positions ~= argUDA.info.position.get;
349         }}
350 
351         return positions.sort;
352     }();
353 
354     if(!checkDuplicates!(positions, "Positional arguments have duplicated position: "))
355         return false;
356 
357     static foreach (i, pos; lockstep(iota(0, positions.length), positions))
358         static assert(i == pos, "Positional arguments have missed position: " ~ i.to!string);
359 
360     return true;
361 }
362 
363 private struct Group
364 {
365     string name;
366     string description;
367 
368     private size_t[] arguments;
369 
370     auto ref Description(string text)
371     {
372         description = text;
373         return this;
374     }
375 
376 }
377 
378 auto ArgumentGroup(string name)
379 {
380     return Group(name);
381 }
382 
383 unittest
384 {
385     auto g = ArgumentGroup("name").Description("description");
386     assert(g.name == "name");
387     assert(g.description == "description");
388 }
389 
390 
391 private struct RestrictionGroup
392 {
393     string location;
394 
395     enum Type { together, exclusive }
396     Type type;
397 
398     private size_t[] arguments;
399 
400     private bool required;
401 
402     auto ref Required()
403     {
404         required = true;
405         return this;
406     }
407 }
408 
409 auto RequiredTogether(string file=__FILE__, uint line = __LINE__)()
410 {
411     import std.conv: to;
412     return RestrictionGroup(file~":"~line.to!string, RestrictionGroup.Type.together);
413 }
414 
415 auto MutuallyExclusive(string file=__FILE__, uint line = __LINE__)()
416 {
417     import std.conv: to;
418     return RestrictionGroup(file~":"~line.to!string, RestrictionGroup.Type.exclusive);
419 }
420 
421 unittest
422 {
423     auto t = RequiredTogether();
424     assert(t.location.length > 0);
425     assert(t.type == RestrictionGroup.Type.together);
426 
427     auto e = MutuallyExclusive();
428     assert(e.location.length > 0);
429     assert(e.type == RestrictionGroup.Type.exclusive);
430 }
431 
432 
433 private alias ParseFunction(RECEIVER) = Result delegate(in Config config, string argName, ref RECEIVER receiver, string rawValue, ref string[] rawArgs);
434 private alias ParseSubCommandFunction(RECEIVER) = Result delegate(ref Parser parser, const ref Parser.Argument arg, bool isDefaultCmd, ref RECEIVER receiver);
435 private alias InitSubCommandFunction(RECEIVER) = Result delegate(ref RECEIVER receiver);
436 private alias Restriction = bool delegate(in Config config, in bool[size_t] cliArgs);
437 
438 // Have to do this magic because closures are not supported in CFTE
439 // DMD v2.098.0 prints "Error: closures are not yet supported in CTFE"
440 private auto partiallyApply(alias fun,C...)(C context)
441 {
442     import std.traits: ParameterTypeTuple;
443     import core.lifetime: move, forward;
444 
445     return &new class(move(context))
446     {
447         C context;
448 
449         this(C ctx)
450         {
451             foreach(i, ref c; context)
452                 c = move(ctx[i]);
453         }
454 
455         auto opCall(ParameterTypeTuple!fun[context.length..$] args) const
456         {
457             return fun(context, forward!args);
458         }
459     }.opCall;
460 }
461 
462 private struct Restrictions
463 {
464     static Restriction RequiredArg(ArgumentInfo info)(size_t index)
465     {
466         return partiallyApply!((size_t index, in Config config, in bool[size_t] cliArgs)
467         {
468             if(index in cliArgs)
469                 return true;
470 
471             config.onError("The following argument is required: ", info.names[0].getArgumentName(config));
472             return false;
473         })(index);
474     }
475 
476     static bool RequiredTogether(in Config config,
477                                  in bool[size_t] cliArgs,
478                                  in size_t[] restrictionArgs,
479                                  in ArgumentInfo[] allArgs)
480     {
481         size_t foundIndex = size_t.max;
482         size_t missedIndex = size_t.max;
483 
484         foreach(index; restrictionArgs)
485         {
486             if(index in cliArgs)
487             {
488                 if(foundIndex == size_t.max)
489                     foundIndex = index;
490             }
491             else if(missedIndex == size_t.max)
492                 missedIndex = index;
493 
494             if(foundIndex != size_t.max && missedIndex != size_t.max)
495             {
496                 config.onError("Missed argument '", allArgs[missedIndex].names[0].getArgumentName(config),
497                     "' - it is required by argument '", allArgs[foundIndex].names[0].getArgumentName(config),"'");
498                 return false;
499             }
500         }
501 
502         return true;
503     }
504 
505     static bool MutuallyExclusive(in Config config,
506                                   in bool[size_t] cliArgs,
507                                   in size_t[] restrictionArgs,
508                                   in ArgumentInfo[] allArgs)
509     {
510         size_t foundIndex = size_t.max;
511 
512         foreach(index; restrictionArgs)
513             if(index in cliArgs)
514             {
515                 if(foundIndex == size_t.max)
516                     foundIndex = index;
517                 else
518                 {
519                     config.onError("Argument '", allArgs[foundIndex].names[0].getArgumentName(config),
520                                    "' is not allowed with argument '", allArgs[index].names[0].getArgumentName(config),"'");
521                     return false;
522                 }
523 
524             }
525 
526         return true;
527     }
528 
529     static bool RequiredAnyOf(in Config config,
530                               in bool[size_t] cliArgs,
531                               in size_t[] restrictionArgs,
532                               in ArgumentInfo[] allArgs)
533     {
534         import std.algorithm: map;
535         import std.array: join;
536 
537         foreach(index; restrictionArgs)
538             if(index in cliArgs)
539                 return true;
540 
541         config.onError("One of the following arguments is required: '", restrictionArgs.map!(_ => allArgs[_].names[0].getArgumentName(config)).join("', '"), "'");
542 
543         return false;
544     }
545 }
546 
547 private struct Arguments
548 {
549 
550     immutable string function(string str) convertCase;
551 
552     private ArgumentInfo[] arguments;
553 
554     // named arguments
555     private size_t[string] argsNamed;
556 
557     // positional arguments
558     private size_t[] argsPositional;
559 
560     private const Arguments* parentArguments;
561 
562     Group[] groups;
563     enum requiredGroupIndex = 0;
564     enum optionalGroupIndex = 1;
565 
566     size_t[string] groupsByName;
567 
568     Restriction[] restrictions;
569     RestrictionGroup[] restrictionGroups;
570 
571     @property ref Group requiredGroup() { return groups[requiredGroupIndex]; }
572     @property ref const(Group) requiredGroup() const { return groups[requiredGroupIndex]; }
573     @property ref Group optionalGroup() { return groups[optionalGroupIndex]; }
574     @property ref const(Group) optionalGroup() const { return groups[optionalGroupIndex]; }
575 
576     @property auto positionalArguments() const { return argsPositional; }
577 
578 
579     this(bool caseSensitive, const Arguments* parentArguments = null)
580     {
581         if(caseSensitive)
582             convertCase = s => s;
583         else
584             convertCase = (string str)
585             {
586                 import std.uni : toUpper;
587                 return str.toUpper;
588             };
589 
590         this.parentArguments = parentArguments;
591 
592         groups = [ Group("Required arguments"), Group("Optional arguments") ];
593     }
594 
595     private void addArgument(ArgumentInfo info, RestrictionGroup[] restrictions, Group group)()
596     {
597         auto index = (group.name in groupsByName);
598         if(index !is null)
599             addArgument!(info, restrictions)(groups[*index]);
600         else
601         {
602             groupsByName[group.name] = groups.length;
603             groups ~= group;
604             addArgument!(info, restrictions)(groups[$-1]);
605         }
606     }
607 
608     private void addArgument(ArgumentInfo info, RestrictionGroup[] restrictions = [])()
609     {
610         static if(info.required)
611             addArgument!(info, restrictions)(requiredGroup);
612         else
613             addArgument!(info, restrictions)(optionalGroup);
614     }
615 
616     private void addArgument(ArgumentInfo info, RestrictionGroup[] argRestrictions = [])( ref Group group)
617     {
618         static assert(info.names.length > 0);
619 
620         immutable index = arguments.length;
621 
622         static if(info.positional)
623         {
624             if(argsPositional.length <= info.position.get)
625                 argsPositional.length = info.position.get + 1;
626 
627             argsPositional[info.position.get] = index;
628         }
629         else
630             static foreach(name; info.names)
631             {
632                 assert(!(name in argsNamed), "Duplicated argument name: "~name);
633                 argsNamed[convertCase(name)] = index;
634             }
635 
636         arguments ~= info;
637         group.arguments ~= index;
638 
639         static if(info.required)
640             restrictions ~= Restrictions.RequiredArg!info(index);
641 
642         static foreach(restriction; argRestrictions)
643             addRestriction!(info, restriction)(index);
644     }
645 
646     private void addRestriction(ArgumentInfo info, RestrictionGroup restriction)(size_t argIndex)
647     {
648         auto groupIndex = (restriction.location in groupsByName);
649         auto index = groupIndex !is null
650             ? *groupIndex
651             : {
652                 auto index = groupsByName[restriction.location] = restrictionGroups.length;
653                 restrictionGroups ~= restriction;
654                 return index;
655             }();
656 
657         restrictionGroups[index].arguments ~= argIndex;
658     }
659 
660 
661     private bool checkRestrictions(in bool[size_t] cliArgs, in Config config) const
662     {
663         foreach(restriction; restrictions)
664             if(!restriction(config, cliArgs))
665                 return false;
666 
667         foreach(restriction; restrictionGroups)
668         {
669             if(restriction.required && !Restrictions.RequiredAnyOf(config, cliArgs, restriction.arguments, arguments))
670                 return false;
671 
672             final switch(restriction.type)
673             {
674                 case RestrictionGroup.Type.together:
675                     if(!Restrictions.RequiredTogether(config, cliArgs, restriction.arguments, arguments))
676                         return false;
677                     break;
678                 case RestrictionGroup.Type.exclusive:
679                     if(!Restrictions.MutuallyExclusive(config, cliArgs, restriction.arguments, arguments))
680                         return false;
681                     break;
682             }
683         }
684 
685         return true;
686     }
687 
688 
689     private auto findArgumentImpl(const size_t* pIndex) const
690     {
691         struct Result
692         {
693             size_t index = size_t.max;
694             const(ArgumentInfo)* arg;
695         }
696 
697         return pIndex ? Result(*pIndex, &arguments[*pIndex]) : Result.init;
698     }
699 
700     auto findPositionalArgument(size_t position) const
701     {
702         return findArgumentImpl(position < argsPositional.length ? &argsPositional[position] : null);
703     }
704 
705     auto findNamedArgument(string name) const
706     {
707         return findArgumentImpl(convertCase(name) in argsNamed);
708     }
709 }
710 
711 private alias ParsingArgument(alias symbol, alias uda, ArgumentInfo info, RECEIVER, bool completionMode) =
712     delegate(in Config config, string argName, ref RECEIVER receiver, string rawValue, ref string[] rawArgs)
713     {
714         static if(completionMode)
715         {
716             if(rawValue is null)
717                 consumeValuesFromCLI(rawArgs, info, config);
718 
719             return Result.Success;
720         }
721         else
722         {
723             try
724             {
725                 auto rawValues = rawValue !is null ? [ rawValue ] : consumeValuesFromCLI(rawArgs, info, config);
726 
727                 auto res = info.checkValuesCount(argName, rawValues.length);
728                 if(!res)
729                 {
730                     config.onError(res.errorMsg);
731                     return res;
732                 }
733 
734                 auto param = RawParam(config, argName, rawValues);
735 
736                 auto target = &__traits(getMember, receiver, symbol);
737 
738                 static if(is(typeof(target) == function) || is(typeof(target) == delegate))
739                     return uda.parsingFunc.parse(target, param) ? Result.Success : Result.Failure;
740                 else
741                     return uda.parsingFunc.parse(*target, param) ? Result.Success : Result.Failure;
742             }
743             catch(Exception e)
744             {
745                 config.onError(argName, ": ", e.msg);
746                 return Result.Failure;
747             }
748         }
749     };
750 
751 private auto ParsingSubCommandArgument(COMMAND_TYPE, CommandInfo info, RECEIVER, alias symbol, bool completionMode)(const CommandArguments!RECEIVER* parentArguments)
752 {
753     return delegate(ref Parser parser, const ref Parser.Argument arg, bool isDefaultCmd, ref RECEIVER receiver)
754     {
755         auto target = &__traits(getMember, receiver, symbol);
756 
757         alias parse = (ref COMMAND_TYPE cmdTarget)
758         {
759             static if(!is(COMMAND_TYPE == Default!TYPE, TYPE))
760                 alias TYPE = COMMAND_TYPE;
761 
762             auto command = CommandArguments!TYPE(parser.config, info, parentArguments);
763 
764             return parser.parse!completionMode(command, isDefaultCmd, cmdTarget, arg);
765         };
766 
767 
768         static if(typeof(*target).Types.length == 1)
769             return (*target).match!parse;
770         else
771         {
772             return (*target).match!(parse,
773                 (_)
774                 {
775                     assert(false, "This should never happen");
776                     return Result.Failure;
777                 }
778             );
779         }
780     };
781 }
782 
783 private alias ParsingSubCommandInit(COMMAND_TYPE, RECEIVER, alias symbol) =
784     delegate(ref RECEIVER receiver)
785     {
786         auto target = &__traits(getMember, receiver, symbol);
787 
788         static if(typeof(*target).Types.length > 1)
789             if((*target).match!((COMMAND_TYPE t) => false, _ => true))
790                 *target = COMMAND_TYPE.init;
791 
792         return Result.Success;
793     };
794 
795 struct SubCommands {}
796 
797 // Default subcommand
798 struct Default(COMMAND)
799 {
800     COMMAND command;
801     alias command this;
802 }
803 
804 unittest
805 {
806     struct T
807     {
808         @NamedArgument
809         int a;
810         @(NamedArgument.Optional())
811         int b;
812         @(NamedArgument.Required())
813         int c;
814         @NamedArgument
815         int d;
816         @(NamedArgument.Required())
817         int e;
818         @NamedArgument
819         int f;
820     }
821 
822     enum config = {
823         Config config;
824         config.addHelp = false;
825         return config;
826     }();
827 
828     static assert(CommandArguments!T(config).arguments.arguments.length == 6);
829 
830     auto a = CommandArguments!T(config);
831     assert(a.arguments.requiredGroup.arguments == [2,4]);
832     assert(a.arguments.argsNamed == ["a":0LU, "b":1LU, "c":2LU, "d":3LU, "e":4LU, "f":5LU]);
833     assert(a.arguments.argsPositional == []);
834 }
835 
836 unittest
837 {
838     struct T
839     {
840         int a,b,c,d,e,f;
841     }
842 
843     enum config = {
844         Config config;
845         config.addHelp = false;
846         return config;
847     }();
848 
849     static assert(CommandArguments!T(config).arguments.arguments.length == 6);
850 
851     auto a = CommandArguments!T(config);
852     assert(a.arguments.requiredGroup.arguments == []);
853     assert(a.arguments.argsNamed == ["a":0LU, "b":1LU, "c":2LU, "d":3LU, "e":4LU, "f":5LU]);
854     assert(a.arguments.argsPositional == []);
855 }
856 
857 unittest
858 {
859     struct T1
860     {
861         @(NamedArgument("1"))
862         @(NamedArgument("2"))
863         int a;
864     }
865     static assert(!__traits(compiles, { CommandArguments!T1(Config.init); }));
866 
867     struct T2
868     {
869         @(NamedArgument("1"))
870         int a;
871         @(NamedArgument("1"))
872         int b;
873     }
874     static assert(!__traits(compiles, { CommandArguments!T1(Config.init); }));
875 
876     struct T3
877     {
878         @(PositionalArgument(0)) int a;
879         @(PositionalArgument(0)) int b;
880     }
881     static assert(!__traits(compiles, { CommandArguments!T3(Config.init); }));
882 
883     struct T4
884     {
885         @(PositionalArgument(0)) int a;
886         @(PositionalArgument(2)) int b;
887     }
888     static assert(!__traits(compiles, { CommandArguments!T4(Config.init); }));
889 }
890 
891 private void checkArgumentName(T)(char namedArgChar)
892 {
893     import std.exception: enforce;
894 
895     static foreach(sym; getSymbolsByUDA!(T, ArgumentUDA))
896         static foreach(name; getUDAs!(__traits(getMember, T, __traits(identifier, sym)), ArgumentUDA)[0].info.names)
897             enforce(name[0] != namedArgChar, "Name of argument should not begin with '"~namedArgChar~"': "~name);
898 }
899 
900 private auto consumeValuesFromCLI(ref string[] args, in ArgumentInfo argumentInfo, in Config config)
901 {
902     import std.range: empty, front, popFront;
903 
904     immutable minValuesCount = argumentInfo.minValuesCount.get;
905     immutable maxValuesCount = argumentInfo.maxValuesCount.get;
906 
907     string[] values;
908 
909     if(minValuesCount > 0)
910     {
911         if(minValuesCount < args.length)
912         {
913             values = args[0..minValuesCount];
914             args = args[minValuesCount..$];
915         }
916         else
917         {
918             values = args;
919             args = [];
920         }
921     }
922 
923     while(!args.empty &&
924         values.length < maxValuesCount &&
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.ignoreInDefaultCommand = true;
943     return arg;
944 }();
945 
946 private bool isHelpArgument(string name)
947 {
948     static foreach(n; helpArgument.names)
949         if(n == name)
950             return true;
951 
952     return false;
953 }
954 
955 unittest
956 {
957     assert(isHelpArgument("h"));
958     assert(isHelpArgument("help"));
959     assert(!isHelpArgument("a"));
960     assert(!isHelpArgument("help1"));
961 }
962 
963 struct Result
964 {
965     int  resultCode;
966 
967     private enum Status { failure, success, unknownArgument };
968     private Status status;
969 
970     private string errorMsg;
971 
972     private const(string)[] suggestions;
973 
974     bool opCast(type)() const if (is(type == bool))
975     {
976         return status == Status.success;
977     }
978 
979     private static enum Failure = Result(1, Status.failure);
980     private static enum Success = Result(0, Status.success);
981     private static enum UnknownArgument = Result(0, Status.unknownArgument);
982 
983     private static auto Error(A...)(A args) nothrow
984     {
985         import std.conv: text;
986         import std.stdio: stderr, writeln;
987 
988         return Result(1, Status.failure, text!A(args));
989     }
990 }
991 
992 private struct Parser
993 {
994     struct Unknown {}
995     struct EndOfArgs {}
996     struct Positional {}
997     struct NamedShort {
998         string name;
999         string nameWithDash;
1000         string value = null;  // null when there is no value
1001     }
1002     struct NamedLong {
1003         string name;
1004         string nameWithDash;
1005         string value = null;  // null when there is no value
1006     }
1007 
1008     alias Argument = SumType!(Unknown, EndOfArgs, Positional, NamedShort, NamedLong);
1009 
1010     immutable Config config;
1011 
1012     string[] args;
1013     string[] unrecognizedArgs;
1014 
1015     bool[size_t] idxParsedArgs;
1016     size_t idxNextPositional = 0;
1017 
1018     struct CmdParser
1019     {
1020         Result delegate(const ref Argument) parse;
1021         Result delegate(const ref Argument) complete;
1022         const(string)[] completeSuggestion;
1023         bool isDefault;
1024     }
1025     CmdParser[] cmdStack;
1026 
1027     Argument splitArgumentNameValue(string arg)
1028     {
1029         import std.string : indexOf;
1030 
1031         if(arg.length == 0)
1032             return Argument.init;
1033 
1034         if(arg == config.endOfArgs)
1035             return Argument(EndOfArgs.init);
1036 
1037         if(arg[0] != config.namedArgChar)
1038             return Argument(Positional.init);
1039 
1040         if(arg.length == 1 || arg.length == 2 && arg[1] == config.namedArgChar)
1041             return Argument.init;
1042 
1043         auto idxAssignChar = config.assignChar == char.init ? -1 : arg.indexOf(config.assignChar);
1044 
1045         immutable string nameWithDash = idxAssignChar < 0 ? arg  : arg[0 .. idxAssignChar];
1046         immutable string value        = idxAssignChar < 0 ? null : arg[idxAssignChar + 1 .. $];
1047 
1048         return arg[1] == config.namedArgChar
1049         ? Argument(NamedLong (nameWithDash[2..$], nameWithDash, value))
1050         : Argument(NamedShort(nameWithDash[1..$], nameWithDash, value));
1051     }
1052 
1053     auto parseArgument(T, PARSE)(PARSE parse, ref T receiver, string value, string nameWithDash, size_t argIndex)
1054     {
1055         auto res = parse(config, nameWithDash, receiver, value, args);
1056         if(!res)
1057             return res;
1058 
1059         idxParsedArgs[argIndex] = true;
1060 
1061         return Result.Success;
1062     }
1063 
1064     auto parseSubCommand(T)(const ref CommandArguments!T cmd, ref T receiver)
1065     {
1066         import std.range: front, popFront;
1067 
1068         auto found = cmd.findSubCommand(args.front);
1069         if(found.parse is null)
1070             return Result.UnknownArgument;
1071 
1072         if(found.level < cmdStack.length)
1073             cmdStack.length = found.level;
1074 
1075         cmdStack ~= CmdParser((const ref arg) => found.parse(this, arg, false, receiver), (const ref arg) => found.complete(this, arg, false, receiver));
1076 
1077         found.initialize(receiver);
1078         args.popFront();
1079 
1080         return Result.Success;
1081     }
1082 
1083     auto parse(bool completionMode, T)(const ref CommandArguments!T cmd, bool isDefaultCmd, ref T receiver, Unknown)
1084     {
1085         static if(completionMode)
1086         {
1087             import std.range: front, popFront;
1088             import std.algorithm: filter;
1089             import std.string:startsWith;
1090             import std.array:array;
1091             if(args.length == 1)
1092             {
1093                 // last arg means we need to provide args and subcommands
1094                 auto A = args[0] == "" ? cmd.completeSuggestion : cmd.completeSuggestion.filter!(_ => _.startsWith(args[0])).array;
1095                 return Result(0, Result.Status.success, "", A);
1096             }
1097         }
1098 
1099         return Result.UnknownArgument;
1100     }
1101 
1102     auto parse(bool completionMode, T)(const ref CommandArguments!T cmd, bool isDefaultCmd, ref T receiver, EndOfArgs)
1103     {
1104         static if(!completionMode)
1105         {
1106             static if (is(typeof(cmd.setTrailingArgs)))
1107                 cmd.setTrailingArgs(receiver, args[1..$]);
1108             else
1109                 unrecognizedArgs ~= args[1..$];
1110         }
1111 
1112         args = [];
1113 
1114         return Result.Success;
1115     }
1116 
1117     auto parse(bool completionMode, T)(const ref CommandArguments!T cmd, bool isDefaultCmd, ref T receiver, Positional)
1118     {
1119         auto foundArg = cmd.findPositionalArgument(idxNextPositional);
1120         if(foundArg.arg is null)
1121             return parseSubCommand(cmd, receiver);
1122 
1123         auto res = parseArgument(cmd.getParseFunction!completionMode(foundArg.index), receiver, null, foundArg.arg.names[0], foundArg.index);
1124         if(!res)
1125             return res;
1126 
1127         idxNextPositional++;
1128 
1129         return Result.Success;
1130     }
1131 
1132     auto parse(bool completionMode, T)(const ref CommandArguments!T cmd, bool isDefaultCmd, ref T receiver, NamedLong arg)
1133     {
1134         import std.algorithm : startsWith;
1135         import std.range: popFront;
1136 
1137         auto foundArg = cmd.findNamedArgument(arg.name);
1138 
1139         if(foundArg.arg is null && arg.name.startsWith("no-"))
1140         {
1141             foundArg = cmd.findNamedArgument(arg.name[3..$]);
1142             if(foundArg.arg is null || !foundArg.arg.allowBooleanNegation)
1143                 return Result.UnknownArgument;
1144 
1145             arg.value = "false";
1146         }
1147 
1148         if(foundArg.arg is null)
1149             return Result.UnknownArgument;
1150 
1151         if(isDefaultCmd && foundArg.arg.ignoreInDefaultCommand)
1152             return Result.UnknownArgument;
1153 
1154         args.popFront();
1155         return parseArgument(cmd.getParseFunction!completionMode(foundArg.index), receiver, arg.value, arg.nameWithDash, foundArg.index);
1156     }
1157 
1158     auto parse(bool completionMode, T)(const ref CommandArguments!T cmd, bool isDefaultCmd, ref T receiver, NamedShort arg)
1159     {
1160         import std.range: popFront;
1161 
1162         auto foundArg = cmd.findNamedArgument(arg.name);
1163         if(foundArg.arg !is null)
1164         {
1165             if(isDefaultCmd && foundArg.arg.ignoreInDefaultCommand)
1166                 return Result.UnknownArgument;
1167 
1168             args.popFront();
1169             return parseArgument(cmd.getParseFunction!completionMode(foundArg.index), receiver, arg.value, arg.nameWithDash, foundArg.index);
1170         }
1171 
1172         // Try to parse "-ABC..." where "A","B","C" are different single-letter arguments
1173         do
1174         {
1175             auto name = [arg.name[0]];
1176             foundArg = cmd.findNamedArgument(name);
1177             if(foundArg.arg is null)
1178                 return Result.UnknownArgument;
1179 
1180             // In case of bundling there can be no or one argument value
1181             if(config.bundling && foundArg.arg.minValuesCount.get > 1)
1182                 return Result.UnknownArgument;
1183 
1184             // In case of NO bundling there MUST be one argument value
1185             if(!config.bundling && foundArg.arg.minValuesCount.get != 1)
1186                 return Result.UnknownArgument;
1187 
1188             string value;
1189             if(foundArg.arg.minValuesCount == 0)
1190                 arg.name = arg.name[1..$];
1191             else
1192             {
1193                 // Bundling case: try to parse "-ABvalue" where "A","B" are different single-letter arguments and "value" is a value for "B"
1194                 // No bundling case: try to parse "-Avalue" where "A" is a single-letter argument and "value" is its value
1195                 value = arg.name[1..$];
1196                 arg.name = "";
1197             }
1198 
1199             auto res = parseArgument(cmd.getParseFunction!completionMode(foundArg.index), receiver, value, "-"~name, foundArg.index);
1200             if(!res)
1201                 return res;
1202         }
1203         while(arg.name.length > 0);
1204 
1205         args.popFront();
1206         return Result.Success;
1207     }
1208 
1209     auto parse(bool completionMode, T)(const ref CommandArguments!T cmd, bool isDefaultCmd, ref T receiver, Argument arg)
1210     {
1211         return arg.match!(_ => parse!completionMode(cmd, isDefaultCmd, receiver, _));
1212     }
1213 
1214     auto parse(bool completionMode)(Argument arg)
1215     {
1216         import std.range: front, popFront;
1217 
1218         auto result = Result.Success;
1219 
1220         const argsCount = args.length;
1221 
1222         foreach_reverse(cmdParser; cmdStack)
1223         {
1224             static if(completionMode)
1225             {
1226                 auto res = cmdParser.complete(arg);
1227                 if(res)
1228                     result.suggestions ~= res.suggestions;
1229             }
1230             else
1231             {
1232                 auto res = cmdParser.parse(arg);
1233 
1234                 if(res.status != Result.Status.unknownArgument)
1235                     return res;
1236             }
1237         }
1238 
1239         if(args.length > 0 && argsCount == args.length)
1240         {
1241             unrecognizedArgs ~= args.front;
1242             args.popFront();
1243         }
1244 
1245         return result;
1246     }
1247 
1248     auto parseAll(bool completionMode, T)(const ref CommandArguments!T cmd, ref T receiver)
1249     {
1250         import std.range: empty, front;
1251 
1252         cmdStack ~= CmdParser(
1253         (const ref arg)
1254         {
1255             return parse!completionMode(cmd, false, receiver, arg);
1256         },
1257         (const ref arg)
1258         {
1259             return parse!completionMode(cmd, false, receiver, arg);
1260         },
1261         cmd.completeSuggestion);
1262 
1263         auto found = cmd.findSubCommand(DEFAULT_COMMAND);
1264         if(found.parse !is null)
1265         {
1266             auto p = CmdParser((const ref arg) => found.parse(this, arg, true, receiver));
1267             p.isDefault = true;
1268             cmdStack ~= p;
1269             found.initialize(receiver);
1270         }
1271 
1272         while(!args.empty)
1273         {
1274             static if(completionMode)
1275                 auto res = parse!completionMode(args.length > 1 ? splitArgumentNameValue(args.front) : Argument.init);
1276             else
1277                 auto res = parse!completionMode(splitArgumentNameValue(args.front));
1278             if(!res)
1279                 return res;
1280 
1281             static if(completionMode)
1282                 if(args.empty)
1283                     return res;
1284         }
1285 
1286         if(!cmd.checkRestrictions(idxParsedArgs, config))
1287             return Result.Failure;
1288 
1289         return Result.Success;
1290     }
1291 }
1292 
1293 unittest
1294 {
1295     assert(Parser.init.splitArgumentNameValue("") == Parser.Argument(Parser.Unknown.init));
1296     assert(Parser.init.splitArgumentNameValue("-") == Parser.Argument(Parser.Unknown.init));
1297     assert(Parser.init.splitArgumentNameValue("--") == Parser.Argument(Parser.EndOfArgs.init));
1298     assert(Parser.init.splitArgumentNameValue("abc=4") == Parser.Argument(Parser.Positional.init));
1299     assert(Parser.init.splitArgumentNameValue("-abc") == Parser.Argument(Parser.NamedShort("abc", "-abc", null)));
1300     assert(Parser.init.splitArgumentNameValue("--abc") == Parser.Argument(Parser.NamedLong("abc", "--abc", null)));
1301     assert(Parser.init.splitArgumentNameValue("-abc=fd") == Parser.Argument(Parser.NamedShort("abc", "-abc", "fd")));
1302     assert(Parser.init.splitArgumentNameValue("--abc=fd") == Parser.Argument(Parser.NamedLong("abc", "--abc", "fd")));
1303     assert(Parser.init.splitArgumentNameValue("-abc=") == Parser.Argument(Parser.NamedShort("abc", "-abc", "")));
1304     assert(Parser.init.splitArgumentNameValue("--abc=") == Parser.Argument(Parser.NamedLong("abc", "--abc", "")));
1305     assert(Parser.init.splitArgumentNameValue("-=abc") == Parser.Argument(Parser.NamedShort("", "-", "abc")));
1306     assert(Parser.init.splitArgumentNameValue("--=abc") == Parser.Argument(Parser.NamedLong("", "--", "abc")));
1307 }
1308 
1309 
1310 private Result parseCLIKnownArgs(T)(ref T receiver,
1311                                     string[] args,
1312                                     out string[] unrecognizedArgs,
1313                                     const ref CommandArguments!T cmd,
1314                                     in Config config)
1315 {
1316     auto parser = Parser(config, args);
1317 
1318     auto res = parser.parseAll!false(cmd, receiver);
1319     if(!res)
1320         return res;
1321 
1322     unrecognizedArgs = parser.unrecognizedArgs;
1323 
1324     return Result.Success;
1325 }
1326 
1327 deprecated("Use CLI!(config, COMMAND).parseKnownArgs")
1328 Result parseCLIKnownArgs(T)(ref T receiver,
1329                             string[] args,
1330                             out string[] unrecognizedArgs,
1331                             in Config config)
1332 {
1333     auto command = CommandArguments!T(config);
1334     return parseCLIKnownArgs(receiver, args, unrecognizedArgs, command, config);
1335 }
1336 
1337 deprecated("Use CLI!COMMAND.parseKnownArgs")
1338 Result parseCLIKnownArgs(T)(ref T receiver,
1339                             string[] args,
1340                             out string[] unrecognizedArgs)
1341 {
1342     return CLI!T.parseKnownArgs(receiver, args, unrecognizedArgs);
1343 }
1344 
1345 deprecated("Use CLI!(config, COMMAND).parseKnownArgs")
1346 auto parseCLIKnownArgs(T)(ref T receiver, ref string[] args, in Config config)
1347 {
1348     string[] unrecognizedArgs;
1349 
1350     auto res = parseCLIKnownArgs(receiver, args, unrecognizedArgs, config);
1351     if(res)
1352         args = unrecognizedArgs;
1353 
1354     return res;
1355 }
1356 
1357 deprecated("Use CLI!COMMAND.parseKnownArgs")
1358 auto parseCLIKnownArgs(T)(ref T receiver, ref string[] args)
1359 {
1360     return CLI!T.parseKnownArgs(receiver, args);
1361 }
1362 
1363 deprecated("Use CLI!(config, COMMAND).parseKnownArgs")
1364 Nullable!T parseCLIKnownArgs(T)(ref string[] args, in Config config)
1365 {
1366     import std.typecons : nullable;
1367 
1368     T receiver;
1369 
1370     return parseCLIKnownArgs(receiver, args, config) ? receiver.nullable : Nullable!T.init;
1371 }
1372 
1373 deprecated("Use CLI!COMMAND.parseKnownArgs")
1374 Nullable!T parseCLIKnownArgs(T)(ref string[] args)
1375 {
1376     import std.typecons : nullable;
1377 
1378     T receiver;
1379 
1380     return CLI!T.parseKnownArgs(receiver, args) ? receiver.nullable : Nullable!T.init;
1381 }
1382 
1383 deprecated("Use CLI!(config, COMMAND).parseArgs")
1384 int parseCLIKnownArgs(T, FUNC)(string[] args, FUNC func, in Config config, T initialValue = T.init)
1385 if(__traits(compiles, { func(T.init, args); }))
1386 {
1387     alias value = initialValue;
1388 
1389     auto res = parseCLIKnownArgs(value, args, config);
1390     if(!res)
1391         return res.resultCode;
1392 
1393     static if(__traits(compiles, { int a = cast(int) func(value, args); }))
1394         return cast(int) func(value, args);
1395     else
1396     {
1397         func(value, args);
1398         return 0;
1399     }
1400 }
1401 
1402 deprecated("Use CLI!COMMAND.parseArgs")
1403 int parseCLIKnownArgs(T, FUNC)(string[] args, FUNC func)
1404 if(__traits(compiles, { func(T.init, args); }))
1405 {
1406     T value;
1407 
1408     auto res = CLI!T.parseKnownArgs(value, args);
1409     if(!res)
1410         return res.resultCode;
1411 
1412     static if(__traits(compiles, { int a = cast(int) func(value, args); }))
1413         return cast(int) func(value, args);
1414     else
1415     {
1416         func(value, args);
1417         return 0;
1418     }
1419 }
1420 
1421 
1422 deprecated("Use CLI!(config, COMMAND).parseArgs")
1423 auto parseCLIArgs(T)(ref T receiver, string[] args, in Config config)
1424 {
1425     string[] unrecognizedArgs;
1426 
1427     auto res = parseCLIKnownArgs(receiver, args, unrecognizedArgs, config);
1428 
1429     if(res && unrecognizedArgs.length > 0)
1430     {
1431         config.onError("Unrecognized arguments: ", unrecognizedArgs);
1432         return Result.Failure;
1433     }
1434 
1435     return res;
1436 }
1437 
1438 deprecated("Use CLI!COMMAND.parseArgs")
1439 auto parseCLIArgs(T)(ref T receiver, string[] args)
1440 {
1441     return CLI!T.parseArgs(receiver, args);
1442 }
1443 
1444 deprecated("Use CLI!(config, COMMAND).parseArgs")
1445 Nullable!T parseCLIArgs(T)(string[] args, in Config config)
1446 {
1447     import std.typecons : nullable;
1448 
1449     T receiver;
1450 
1451     return parseCLIArgs(receiver, args, config) ? receiver.nullable : Nullable!T.init;
1452 }
1453 
1454 deprecated("Use CLI!COMMAND.parseArgs")
1455 Nullable!T parseCLIArgs(T)(string[] args)
1456 {
1457     import std.typecons : nullable;
1458 
1459     T receiver;
1460 
1461     return CLI!T.parseArgs(receiver, args) ? receiver.nullable : Nullable!T.init;
1462 }
1463 
1464 deprecated("Use CLI!(config, COMMAND).parseArgs")
1465 int parseCLIArgs(T, FUNC)(string[] args, FUNC func, in Config config, T initialValue = T.init)
1466 if(__traits(compiles, { func(T.init); }))
1467 {
1468     alias value = initialValue;
1469 
1470     auto res = parseCLIArgs(value, args, config);
1471     if(!res)
1472         return res.resultCode;
1473 
1474     static if(__traits(compiles, { int a = cast(int) func(value); }))
1475         return cast(int) func(value);
1476     else
1477     {
1478         func(value);
1479         return 0;
1480     }
1481 }
1482 
1483 deprecated("Use CLI!COMMAND.parseArgs")
1484 int parseCLIArgs(T, FUNC)(string[] args, FUNC func)
1485 if(__traits(compiles, { func(T.init); }))
1486 {
1487     T value;
1488 
1489     auto res = CLI!T.parseArgs(value, args);
1490     if(!res)
1491         return res.resultCode;
1492 
1493     static if(__traits(compiles, { int a = cast(int) func(value); }))
1494         return cast(int) func(value);
1495     else
1496     {
1497         func(value);
1498         return 0;
1499     }
1500 }
1501 
1502 unittest
1503 {
1504     import std.exception;
1505 
1506     struct T
1507     {
1508         @(NamedArgument("--"))
1509         int a;
1510     }
1511     static assert(!__traits(compiles, { enum p = parseCLIArgs!T([]); }));
1512     assertThrown(parseCLIArgs!T([]));
1513 }
1514 
1515 unittest
1516 {
1517 
1518     import std.conv;
1519     import std.traits;
1520 
1521     struct params
1522     {
1523         int no_a;
1524 
1525         @(PositionalArgument(0, "a")
1526         .Description("Argument 'a'")
1527         .Validation!((int a) { return a > 3;})
1528         .PreValidation!((string s) { return s.length > 0;})
1529         .Validation!((int a) { return a > 0;})
1530         )
1531         int a;
1532 
1533         int no_b;
1534 
1535         @(NamedArgument(["b", "boo"]).Description("Flag boo")
1536         .AllowNoValue!55
1537         )
1538         int b;
1539 
1540         int no_c;
1541     }
1542 
1543     enum p = CommandArguments!params(Config.init);
1544     static assert(p.findNamedArgument("a").arg is null);
1545     static assert(p.findNamedArgument("b").arg !is null);
1546     static assert(p.findNamedArgument("boo").arg !is null);
1547     static assert(p.findPositionalArgument(0).arg !is null);
1548     static assert(p.findPositionalArgument(1).arg is null);
1549     static assert(p.getParseFunction!false(p.findNamedArgument("b").index) !is null);
1550     static assert(p.getParseFunction!true(p.findNamedArgument("b").index) !is null);
1551 }
1552 
1553 unittest
1554 {
1555     import std.typecons : tuple;
1556 
1557     struct T
1558     {
1559         string a;
1560         string b;
1561     }
1562 
1563     auto test(string[] args)
1564     {
1565         return tuple(args.parseCLIKnownArgs!T.get, args);
1566     }
1567 
1568     assert(test(["-a","A","--"]) == tuple(T("A"), []));
1569     static assert(test(["-a","A","--","-b","B"]) == tuple(T("A"), ["-b","B"]));
1570 
1571     {
1572         T args;
1573 
1574         args.parseCLIArgs([ "-a", "A"]);
1575         args.parseCLIArgs([ "-b", "B"]);
1576 
1577         assert(args == T("A","B"));
1578     }
1579 }
1580 
1581 unittest
1582 {
1583     struct T
1584     {
1585         string a;
1586     }
1587 
1588     {
1589         auto test_called(string[] args)
1590         {
1591             bool called;
1592             auto dg = (T t) {
1593                 called = true;
1594             };
1595             assert(args.parseCLIArgs!T(dg) == 0 || !called);
1596             return called;
1597         }
1598 
1599         static assert(test_called([]));
1600         assert(test_called([]));
1601         assert(!test_called(["-g"]));
1602     }
1603     {
1604         auto test_called(string[] args)
1605         {
1606             bool called;
1607             auto dg = (T t, string[] args) {
1608                 assert(args.length == 0 || args == ["-g"]);
1609                 called = true;
1610             };
1611             assert(args.parseCLIKnownArgs!T(dg) == 0);
1612             return called;
1613         }
1614 
1615         assert(test_called([]));
1616         static assert(test_called(["-g"]));
1617     }
1618 }
1619 
1620 unittest
1621 {
1622     struct T
1623     {
1624         string a;
1625     }
1626 
1627     int my_main(T command)
1628     {
1629         // do something
1630         return 0;
1631     }
1632 
1633     static assert(["-a","aa"].parseCLIArgs!T(&my_main) == 0);
1634     assert(["-a","aa"].parseCLIArgs!T(&my_main) == 0);
1635 }
1636 
1637 unittest
1638 {
1639     struct T
1640     {
1641         string a;
1642     }
1643 
1644     auto args = [ "-a", "A", "-c", "C" ];
1645 
1646     assert(parseCLIKnownArgs!T(args).get == T("A"));
1647     assert(args == ["-c", "C"]);
1648 }
1649 
1650 unittest
1651 {
1652 
1653     struct T
1654     {
1655         @NamedArgument                           string x;
1656         @NamedArgument                           string foo;
1657         @(PositionalArgument(0, "a").Optional()) string a;
1658         @(PositionalArgument(1, "b").Optional()) string[] b;
1659     }
1660     static assert(["--foo","FOO","-x","X"].parseCLIArgs!T.get == T("X", "FOO"));
1661     static assert(["--foo=FOO","-x=X"].parseCLIArgs!T.get == T("X", "FOO"));
1662     static assert(["--foo=FOO","1","-x=X"].parseCLIArgs!T.get == T("X", "FOO", "1"));
1663     static assert(["--foo=FOO","1","2","3","4"].parseCLIArgs!T.get == T(string.init, "FOO", "1",["2","3","4"]));
1664     static assert(["-xX"].parseCLIArgs!T.get == T("X"));
1665     assert(["--foo","FOO","-x","X"].parseCLIArgs!T.get == T("X", "FOO"));
1666     assert(["--foo=FOO","-x=X"].parseCLIArgs!T.get == T("X", "FOO"));
1667     assert(["--foo=FOO","1","-x=X"].parseCLIArgs!T.get == T("X", "FOO", "1"));
1668     assert(["--foo=FOO","1","2","3","4"].parseCLIArgs!T.get == T(string.init, "FOO", "1",["2","3","4"]));
1669     assert(["-xX"].parseCLIArgs!T.get == T("X"));
1670 
1671     struct T1
1672     {
1673         @(PositionalArgument(0, "a")) string[3] a;
1674         @(PositionalArgument(1, "b")) string[] b;
1675     }
1676     static assert(["1","2","3","4","5","6"].parseCLIArgs!T1.get == T1(["1","2","3"],["4","5","6"]));
1677     assert(["1","2","3","4","5","6"].parseCLIArgs!T1.get == T1(["1","2","3"],["4","5","6"]));
1678 
1679     struct T2
1680     {
1681         bool foo = true;
1682     }
1683     static assert(["--no-foo"].parseCLIArgs!T2.get == T2(false));
1684     assert(["--no-foo"].parseCLIArgs!T2.get == T2(false));
1685 }
1686 
1687 unittest
1688 {
1689     struct T
1690     {
1691         @(PositionalArgument(0, "a").Optional())
1692         string a = "not set";
1693 
1694         @(NamedArgument.Required())
1695         int b;
1696     }
1697 
1698     assert(CLI!T.parseArgs!((T t) { assert(t == T("not set", 4)); })(["-b", "4"]) == 0);
1699     static assert(["-b", "4"].parseCLIArgs!T.get == T("not set", 4));
1700     assert(["-b", "4"].parseCLIArgs!T.get == T("not set", 4));
1701 }
1702 
1703 unittest
1704 {
1705     struct T
1706     {
1707         string x;
1708         string foo;
1709     }
1710 
1711     auto test(T)(string[] args)
1712     {
1713         Config config;
1714         config.caseSensitive = false;
1715 
1716         return args.parseCLIArgs!T(config).get;
1717     }
1718 
1719     static assert(test!T(["--Foo","FOO","-X","X"]) == T("X", "FOO"));
1720     static assert(test!T(["--FOo=FOO","-X=X"]) == T("X", "FOO"));
1721     assert(test!T(["--Foo","FOO","-X","X"]) == T("X", "FOO"));
1722     assert(test!T(["--FOo=FOO","-X=X"]) == T("X", "FOO"));
1723 }
1724 
1725 unittest
1726 {
1727     auto test(T)(string[] args)
1728     {
1729         Config config;
1730         config.bundling = true;
1731 
1732         return args.parseCLIArgs!T(config).get;
1733     }
1734 
1735     struct T
1736     {
1737         bool a;
1738         bool b;
1739     }
1740     static assert(test!T(["-a","-b"]) == T(true, true));
1741     static assert(test!T(["-ab"]) == T(true, true));
1742     assert(test!T(["-a","-b"]) == T(true, true));
1743     assert(test!T(["-ab"]) == T(true, true));
1744 }
1745 
1746 unittest
1747 {
1748     struct T
1749     {
1750         bool b;
1751     }
1752 
1753     assert(CLI!T.parseArgs!((T t) { assert(t == T(true)); })(["-b"]) == 0);
1754     assert(CLI!T.parseArgs!((T t) { assert(t == T(true)); })(["-b=true"]) == 0);
1755     assert(CLI!T.parseArgs!((T t) { assert(t == T(false)); })(["-b=false"]) == 0);
1756     static assert(["-b"]        .parseCLIArgs!T.get == T(true));
1757     static assert(["-b=true"]   .parseCLIArgs!T.get == T(true));
1758     static assert(["-b=false"]  .parseCLIArgs!T.get == T(false));
1759     assert(["-b"]        .parseCLIArgs!T.get == T(true));
1760     assert(["-b=true"]   .parseCLIArgs!T.get == T(true));
1761     assert(["-b=false"]  .parseCLIArgs!T.get == T(false));
1762 }
1763 
1764 unittest
1765 {
1766     struct T
1767     {
1768         struct cmd1 { string a; }
1769         struct cmd2
1770         {
1771             string b;
1772 
1773             @TrailingArguments
1774             string[] args;
1775         }
1776 
1777         string c;
1778         string d;
1779 
1780         SumType!(cmd1, cmd2) cmd;
1781     }
1782 
1783     assert(["-c","C","cmd2","-b","B"].parseCLIArgs!T.get == T("C",null,typeof(T.cmd)(T.cmd2("B"))));
1784     assert(["-c","C","cmd2","--","-b","B"].parseCLIArgs!T.get == T("C",null,typeof(T.cmd)(T.cmd2("",["-b","B"]))));
1785 }
1786 
1787 unittest
1788 {
1789     struct T
1790     {
1791         struct cmd1 {}
1792         struct cmd2 {}
1793 
1794         SumType!(cmd1, cmd2) cmd;
1795     }
1796 
1797     assert(["cmd2"].parseCLIArgs!T.get == T(typeof(T.cmd)(T.cmd2.init)));
1798     assert(["cmd1"].parseCLIArgs!T.get == T(typeof(T.cmd)(T.cmd1.init)));
1799 }
1800 
1801 unittest
1802 {
1803     struct T
1804     {
1805         struct cmd1 { string a; }
1806         struct cmd2 { string b; }
1807 
1808         string c;
1809         string d;
1810 
1811         SumType!(cmd1, Default!cmd2) cmd;
1812     }
1813 
1814     assert(["-c","C","-b","B"].parseCLIArgs!T.get == T("C",null,typeof(T.cmd)(Default!(T.cmd2)(T.cmd2("B")))));
1815     assert(!CLI!T.parseArgs!((_) {assert(false);})(["-h"]));
1816     assert(!CLI!T.parseArgs!((_) {assert(false);})(["--help"]));
1817 }
1818 
1819 deprecated("Use CLI!(Config, COMMAND).main or CLI!(COMMAND).main")
1820 struct Main
1821 {
1822     mixin template parseCLIKnownArgs(TYPE, alias newMain, Config config = Config.init)
1823     {
1824         mixin CLI!(config, TYPE).main!newMain;
1825     }
1826 
1827     mixin template parseCLIArgs(TYPE, alias newMain, Config config = Config.init)
1828     {
1829         mixin CLI!(config, TYPE).main!newMain;
1830     }
1831 }
1832 
1833 private template defaultCommandName(COMMAND)
1834 {
1835     static if(getUDAs!(COMMAND, CommandInfo).length > 0)
1836         enum defaultCommandName = getUDAs!(COMMAND, CommandInfo)[0].names[0];
1837     else
1838         enum defaultCommandName = COMMAND.stringof;
1839 }
1840 
1841 private struct Complete(COMMAND)
1842 {
1843     @(Command("init")
1844     .Description("Print initialization script for shell completion.")
1845     .ShortDescription("Print initialization script.")
1846     )
1847     struct Init
1848     {
1849         @MutuallyExclusive
1850         {
1851             @(NamedArgument.Description("Provide completion for bash."))
1852             bool bash;
1853             @(NamedArgument.Description("Provide completion for zsh."))
1854             bool zsh;
1855             @(NamedArgument.Description("Provide completion for tcsh."))
1856             bool tcsh;
1857             @(NamedArgument.Description("Provide completion for fish."))
1858             bool fish;
1859         }
1860 
1861         @(NamedArgument.Description("Path to completer. Default value: path to this executable."))
1862         string completerPath; // path to this binary
1863 
1864         @(NamedArgument.Description("Command name. Default value: "~defaultCommandName!COMMAND~"."))
1865         string commandName = defaultCommandName!COMMAND;   // command to complete
1866 
1867         void execute(Config config)()
1868         {
1869             import std.stdio: writeln;
1870 
1871             if(completerPath.length == 0)
1872             {
1873                 import std.file: thisExePath;
1874                 completerPath = thisExePath();
1875             }
1876 
1877             string commandNameArg;
1878             if(commandName != defaultCommandName!COMMAND)
1879                 commandNameArg = " --commandName "~commandName;
1880 
1881             if(bash)
1882             {
1883                 // According to bash documentation:
1884                 //   When the function or command is invoked, the first argument ($1) is the name of the command whose
1885                 //   arguments are being completed, the second` argument ($2) is the word being completed, and the third
1886                 //   argument ($3) is the word preceding the word being completed on the current command line.
1887                 //
1888                 // So we add "---" argument to distinguish between the end of actual parameters and those that were added by bash
1889 
1890                 writeln("# Add this source command into .bashrc:");
1891                 writeln("#       source <(", completerPath, " init --bash", commandNameArg, ")");
1892                 // 'eval' is used to properly get arguments with spaces. For example, in case of "1 2" argument,
1893                 // we will get "1 2" as is, compare to "\"1", "2\"" without 'eval'.
1894                 writeln("complete -C 'eval ", completerPath, " --bash -- $COMP_LINE ---' ", commandName);
1895             }
1896             else if(zsh)
1897             {
1898                 // We use bash completion for zsh
1899                 writeln("# Ensure that you called compinit and bashcompinit like below in your .zshrc:");
1900                 writeln("#       autoload -Uz compinit && compinit");
1901                 writeln("#       autoload -Uz bashcompinit && bashcompinit");
1902                 writeln("# And then add this source command after them into your .zshrc:");
1903                 writeln("#       source <(", completerPath, " init --zsh", commandNameArg, ")");
1904                 writeln("complete -C 'eval ", completerPath, " --bash -- $COMP_LINE ---' ", commandName);
1905             }
1906             else if(tcsh)
1907             {
1908                 // Comments start with ":" in tsch
1909                 writeln(": Add this eval command into .tcshrc:   ;");
1910                 writeln(":       eval `", completerPath, " init --tcsh", commandNameArg, "`     ;");
1911                 writeln("complete ", commandName, " 'p,*,`", completerPath, " --tcsh -- $COMMAND_LINE`,'");
1912             }
1913             else if(fish)
1914             {
1915                 writeln("# Add this source command into ~/.config/fish/config.fish:");
1916                 writeln("#       ", completerPath, " init --fish", commandNameArg, " | source");
1917                 writeln("complete -c ", commandName, " -a '(COMMAND_LINE=(commandline -p) ", completerPath, " --fish -- (commandline -op))' --no-files");
1918             }
1919         }
1920     }
1921 
1922     @(Command("complete")
1923     .Description("Print completion.")
1924     )
1925     struct Complete
1926     {
1927         @MutuallyExclusive
1928         {
1929             @(NamedArgument.Description("Provide completion for bash."))
1930             bool bash;
1931             @(NamedArgument.Description("Provide completion for tcsh."))
1932             bool tcsh;
1933             @(NamedArgument.Description("Provide completion for fish."))
1934             bool fish;
1935         }
1936 
1937         @TrailingArguments
1938         string[] args;
1939 
1940         void execute(Config config)()
1941         {
1942             import std.process: environment;
1943             import std.stdio: writeln;
1944             import std.algorithm: each;
1945 
1946             if(bash)
1947             {
1948                 // According to bash documentation:
1949                 //   When the function or command is invoked, the first argument ($1) is the name of the command whose
1950                 //   arguments are being completed, the second` argument ($2) is the word being completed, and the third
1951                 //   argument ($3) is the word preceding the word being completed on the current command line.
1952                 //
1953                 // We don't use these arguments so we just remove those after "---" including itself
1954                 while(args.length > 0 && args[$-1] != "---")
1955                     args = args[0..$-1];
1956 
1957                 // Remove "---"
1958                 if(args.length > 0 && args[$-1] == "---")
1959                     args = args[0..$-1];
1960 
1961                 // COMP_LINE environment variable contains current command line so if it ends with space ' ' then we
1962                 // should provide all available arguments. To do so, we add an empty argument
1963                 auto cmdLine = environment.get("COMP_LINE", "");
1964                 if(cmdLine.length > 0 && cmdLine[$-1] == ' ')
1965                     args ~= "";
1966             }
1967             else if(tcsh || fish)
1968             {
1969                 // COMMAND_LINE environment variable contains current command line so if it ends with space ' ' then we
1970                 // should provide all available arguments. To do so, we add an empty argument
1971                 auto cmdLine = environment.get("COMMAND_LINE", "");
1972                 if(cmdLine.length > 0 && cmdLine[$-1] == ' ')
1973                     args ~= "";
1974             }
1975 
1976             CLI!(config, COMMAND).completeArgs(args).each!writeln;
1977         }
1978     }
1979 
1980     @SubCommands
1981     SumType!(Init, Default!Complete) cmd;
1982 }
1983 
1984 template CLI(Config config, COMMANDS...)
1985 {
1986     mixin template main(alias newMain)
1987     {
1988         import std.sumtype: SumType, match;
1989 
1990         private struct Program
1991         {
1992             SumType!COMMANDS cmd;   // Sub-commands
1993         }
1994 
1995         private auto forwardMain(Args...)(Program prog, auto ref Args args)
1996         {
1997             import core.lifetime: forward;
1998             return prog.cmd.match!(_ => newMain(_, forward!args));
1999         }
2000 
2001         mixin CLI!(config, Program).main!forwardMain;
2002     }
2003 }
2004 
2005 template CLI(Config config, COMMAND)
2006 {
2007     static Result parseKnownArgs(ref COMMAND receiver, string[] args, out string[] unrecognizedArgs)
2008     {
2009         auto parser = Parser(config, args);
2010 
2011         auto command = CommandArguments!COMMAND(config);
2012         auto res = parser.parseAll!false(command, receiver);
2013         if(!res)
2014             return res;
2015 
2016         unrecognizedArgs = parser.unrecognizedArgs;
2017 
2018         return Result.Success;
2019     }
2020 
2021     static Result parseKnownArgs(ref COMMAND receiver, ref string[] args)
2022     {
2023         string[] unrecognizedArgs;
2024 
2025         auto res = parseKnownArgs(receiver, args, unrecognizedArgs);
2026         if(res)
2027             args = unrecognizedArgs;
2028 
2029         return res;
2030     }
2031 
2032     static Result parseArgs(ref COMMAND receiver, string[] args)
2033     {
2034         auto res = parseKnownArgs(receiver, args);
2035         if(res && args.length > 0)
2036         {
2037             config.onError("Unrecognized arguments: ", args);
2038             return Result.Failure;
2039         }
2040 
2041         return res;
2042     }
2043 
2044     static int parseArgs(alias newMain)(string[] args, COMMAND initialValue = COMMAND.init)
2045         if(__traits(compiles, { newMain(COMMAND.init); }))
2046     {
2047         alias value = initialValue;
2048 
2049         auto res = parseArgs(value, args);
2050         if(!res)
2051             return res.resultCode;
2052 
2053         static if(__traits(compiles, { int a = cast(int) newMain(value); }))
2054             return cast(int) newMain(value);
2055         else
2056         {
2057             newMain(value);
2058             return 0;
2059         }
2060     }
2061 
2062     static int parseArgs(alias newMain)(string[] args, COMMAND initialValue = COMMAND.init)
2063         if(__traits(compiles, { newMain(COMMAND.init, string[].init); }))
2064     {
2065         alias value = initialValue;
2066 
2067         auto res = parseKnownArgs(value, args);
2068         if(!res)
2069             return res.resultCode;
2070 
2071         static if(__traits(compiles, { int a = cast(int) newMain(value, args); }))
2072             return cast(int) newMain(value, args);
2073         else
2074         {
2075             newMain(value, args);
2076             return 0;
2077         }
2078     }
2079 
2080     string[] completeArgs(string[] args)
2081     {
2082         import std.algorithm: sort, uniq;
2083         import std.array: array;
2084 
2085         auto command = CommandArguments!COMMAND(config);
2086 
2087         auto parser = Parser(config, args.length == 0 ? [""] : args);
2088 
2089         COMMAND dummy;
2090 
2091         auto res = parser.parseAll!true(command, dummy);
2092 
2093         return res ? res.suggestions.dup.sort.uniq.array : [];
2094     }
2095 
2096     int complete(string[] args)
2097     {
2098         // dmd fails with core.exception.OutOfMemoryError@core\lifetime.d(137): Memory allocation failed
2099         // if we call anything from CLI!(config, Complete!COMMAND) so we have to directly call parser here
2100 
2101         Complete!COMMAND receiver;
2102 
2103         auto parser = Parser(config, args);
2104 
2105         auto command = CommandArguments!(Complete!COMMAND)(config);
2106         auto res = parser.parseAll!false(command, receiver);
2107         if(!res)
2108             return 1;
2109 
2110         if(res && parser.unrecognizedArgs.length > 0)
2111         {
2112             config.onError("Unrecognized arguments: ", parser.unrecognizedArgs);
2113             return 1;
2114         }
2115 
2116         receiver.cmd.match!(_ => _.execute!config());
2117 
2118         return 0;
2119     }
2120 
2121     mixin template mainComplete()
2122     {
2123         int main(string[] argv)
2124         {
2125             return CLI!(config, COMMAND).complete(argv[1..$]);
2126         }
2127     }
2128 
2129     mixin template main(alias newMain)
2130     {
2131         version(argparse_completion)
2132         {
2133             mixin CLI!(config, COMMAND).mainComplete;
2134         }
2135         else
2136         {
2137             int main(string[] argv)
2138             {
2139                 return CLI!(config, COMMAND).parseArgs!(newMain)(argv[1..$]);
2140             }
2141         }
2142     }
2143 }
2144 
2145 deprecated("Use CLI!(Config, COMMAND) or CLI!(COMMAND)")
2146 template CLI(Config config)
2147 {
2148     mixin template main(COMMAND, alias newMain)
2149     {
2150         mixin CLI!(config, COMMAND).main!newMain;
2151     }
2152 }
2153 
2154 alias CLI(COMMANDS...) = CLI!(Config.init, COMMANDS);
2155 
2156 
2157 unittest
2158 {
2159     struct T
2160     {
2161         struct cmd1
2162         {
2163             string foo;
2164             string bar;
2165             string baz;
2166         }
2167         struct cmd2
2168         {
2169             string cat,can,dog;
2170         }
2171 
2172         @NamedArgument("apple","a")
2173         string a = "dummyA";
2174         @NamedArgument
2175         string s = "dummyS";
2176         @NamedArgument
2177         string b = "dummyB";
2178 
2179         @SubCommands
2180         SumType!(cmd1, cmd2) cmd;
2181     }
2182 
2183     assert(CLI!T.completeArgs([]) == ["--apple","--help","-a","-b","-h","-s","cmd1","cmd2"]);
2184     assert(CLI!T.completeArgs([""]) == ["--apple","--help","-a","-b","-h","-s","cmd1","cmd2"]);
2185     assert(CLI!T.completeArgs(["-a"]) == ["-a"]);
2186     assert(CLI!T.completeArgs(["c"]) == ["cmd1","cmd2"]);
2187     assert(CLI!T.completeArgs(["cmd1"]) == ["cmd1"]);
2188     assert(CLI!T.completeArgs(["cmd1",""]) == ["--apple","--bar","--baz","--foo","--help","-a","-b","-h","-s","cmd1","cmd2"]);
2189     assert(CLI!T.completeArgs(["-a","val-a",""]) == ["--apple","--help","-a","-b","-h","-s","cmd1","cmd2"]);
2190 
2191     assert(!CLI!T.complete(["init","--bash","--commandName","mytool"]));
2192     assert(!CLI!T.complete(["init","--zsh"]));
2193     assert(!CLI!T.complete(["init","--tcsh"]));
2194     assert(!CLI!T.complete(["init","--fish"]));
2195 
2196     assert(CLI!T.complete(["init","--unknown"]));
2197 
2198     import std.process: environment;
2199     {
2200         environment["COMP_LINE"] = "mytool ";
2201         assert(!CLI!T.complete(["--bash","--","---","foo","foo"]));
2202 
2203         environment["COMP_LINE"] = "mytool c";
2204         assert(!CLI!T.complete(["--bash","--","c","---"]));
2205 
2206         environment.remove("COMP_LINE");
2207     }
2208     {
2209         environment["COMMAND_LINE"] = "mytool ";
2210         assert(!CLI!T.complete(["--tcsh","--"]));
2211 
2212         environment["COMMAND_LINE"] = "mytool c";
2213         assert(!CLI!T.complete(["--fish","--","c"]));
2214 
2215         environment.remove("COMMAND_LINE");
2216     }
2217 }
2218 
2219 
2220 unittest
2221 {
2222     struct T
2223     {
2224         int a;
2225     }
2226 
2227     static assert(__traits(compiles, { mixin CLI!T.main!((params) => 0); }));
2228     static assert(__traits(compiles, { mixin CLI!T.main!((params, args) => 0); }));
2229 }
2230 
2231 
2232 private struct Parsers
2233 {
2234     static auto Convert(T)(string value)
2235     {
2236         import std.conv: to;
2237         return value.length > 0 ? value.to!T : T.init;
2238     }
2239 
2240     static auto PassThrough(string[] values)
2241     {
2242         return values;
2243     }
2244 }
2245 
2246 unittest
2247 {
2248     static assert(Parsers.Convert!int("7") == 7);
2249     static assert(Parsers.Convert!string("7") == "7");
2250     static assert(Parsers.Convert!char("7") == '7');
2251 
2252     static assert(Parsers.PassThrough(["7","8"]) == ["7","8"]);
2253 }
2254 
2255 
2256 private struct Actions
2257 {
2258     static auto Assign(DEST, SRC=DEST)(ref DEST param, SRC value)
2259     {
2260         param  = value;
2261     }
2262 
2263     static auto Append(T)(ref T param, T value)
2264     {
2265         param ~= value;
2266     }
2267 
2268     static auto Extend(T)(ref T[] param, T value)
2269     {
2270         param ~= value;
2271     }
2272 
2273     static auto CallFunction(F)(ref F func, RawParam param)
2274     {
2275         // ... func()
2276         static if(__traits(compiles, { func(); }))
2277         {
2278             func();
2279         }
2280         // ... func(string value)
2281         else static if(__traits(compiles, { func(param.value[0]); }))
2282         {
2283             foreach(value; param.value)
2284                 func(value);
2285         }
2286         // ... func(string[] value)
2287         else static if(__traits(compiles, { func(param.value); }))
2288         {
2289             func(param.value);
2290         }
2291         // ... func(RawParam param)
2292         else static if(__traits(compiles, { func(param); }))
2293         {
2294             func(param);
2295         }
2296         else
2297             static assert(false, "Unsupported callback: " ~ F.stringof);
2298     }
2299 
2300     static auto CallFunctionNoParam(F)(ref F func, Param!void param)
2301     {
2302         // ... func()
2303         static if(__traits(compiles, { func(); }))
2304         {
2305             func();
2306         }
2307         // ... func(string value)
2308         else static if(__traits(compiles, { func(string.init); }))
2309         {
2310             func(string.init);
2311         }
2312         // ... func(string[] value)
2313         else static if(__traits(compiles, { func([]); }))
2314         {
2315             func([]);
2316         }
2317         // ... func(Param!void param)
2318         else static if(__traits(compiles, { func(param); }))
2319         {
2320             func(param);
2321         }
2322         else
2323             static assert(false, "Unsupported callback: " ~ F.stringof);
2324     }
2325 }
2326 
2327 unittest
2328 {
2329     int i;
2330     Actions.Assign!(int)(i,7);
2331     assert(i == 7);
2332 }
2333 
2334 unittest
2335 {
2336     int[] i;
2337     Actions.Append!(int[])(i,[1,2,3]);
2338     Actions.Append!(int[])(i,[7,8,9]);
2339     assert(i == [1,2,3,7,8,9]);
2340 
2341     alias test = (int[] v1, int[] v2) {
2342         int[] res;
2343 
2344         Param!(int[]) param;
2345 
2346         alias F = Actions.Append!(int[]);
2347         param.value = v1;   ActionFunc!(F, int[], int[])(res, param);
2348 
2349         param.value = v2;   ActionFunc!(F, int[], int[])(res, param);
2350 
2351         return res;
2352     };
2353     static assert(test([1,2,3],[7,8,9]) == [1,2,3,7,8,9]);
2354 }
2355 
2356 unittest
2357 {
2358     int[][] i;
2359     Actions.Extend!(int[])(i,[1,2,3]);
2360     Actions.Extend!(int[])(i,[7,8,9]);
2361     assert(i == [[1,2,3],[7,8,9]]);
2362 }
2363 
2364 
2365 private struct Validators
2366 {
2367     static auto ValueInList(alias values, TYPE)(in Param!TYPE param)
2368     {
2369         import std.array : assocArray, join;
2370         import std.range : repeat, front;
2371         import std.conv: to;
2372 
2373         enum valuesAA = assocArray(values, false.repeat);
2374         enum allowedValues = values.to!(string[]).join(',');
2375 
2376         static if(is(typeof(values.front) == TYPE))
2377             auto paramValues = [param.value];
2378         else
2379             auto paramValues = param.value;
2380 
2381         foreach(value; paramValues)
2382             if(!(value in valuesAA))
2383             {
2384                 param.config.onError("Invalid value '", value, "' for argument '", param.name, "'.\nValid argument values are: ", allowedValues);
2385                 return false;
2386             }
2387 
2388         return true;
2389     }
2390 }
2391 
2392 
2393 // values => bool
2394 // bool validate(T value)
2395 // bool validate(T[i] value)
2396 // bool validate(Param!T param)
2397 private struct ValidateFunc(alias F, T, string funcName="Validation")
2398 {
2399     static bool opCall(Param!T param)
2400     {
2401         static if(is(F == void))
2402         {
2403             return true;
2404         }
2405         else static if(__traits(compiles, { F(param); }))
2406         {
2407             // bool validate(Param!T param)
2408             return cast(bool) F(param);
2409         }
2410         else static if(__traits(compiles, { F(param.value); }))
2411         {
2412             // bool validate(T values)
2413             return cast(bool) F(param.value);
2414         }
2415         else static if(/*isArray!T &&*/ __traits(compiles, { F(param.value[0]); }))
2416         {
2417             // bool validate(T[i] value)
2418             foreach(value; param.value)
2419                 if(!F(value))
2420                     return false;
2421             return true;
2422         }
2423         else
2424             static assert(false, funcName~" function is not supported for type "~T.stringof~": "~typeof(F).stringof);
2425     }
2426 }
2427 
2428 unittest
2429 {
2430     auto test(alias F, T)(T[] values)
2431     {
2432         Param!(T[]) param;
2433         param.value = values;
2434         return ValidateFunc!(F, T[])(param);
2435     }
2436 
2437     // bool validate(T[] values)
2438     static assert(test!((string[] a) => true, string)(["1","2","3"]));
2439     static assert(test!((int[] a) => true, int)([1,2,3]));
2440 
2441     // bool validate(T value)
2442     static assert(test!((string a) => true, string)(["1","2","3"]));
2443     static assert(test!((int a) => true, int)([1,2,3]));
2444 
2445     // bool validate(Param!T param)
2446     static assert(test!((RawParam p) => true, string)(["1","2","3"]));
2447     static assert(test!((Param!(int[]) p) => true, int)([1,2,3]));
2448 }
2449 
2450 unittest
2451 {
2452     static assert(ValidateFunc!(void, string[])(RawParam(Config.init, "", ["1","2","3"])));
2453 
2454     static assert(!__traits(compiles, { ValidateFunc!(() {}, string[])(config, "", ["1","2","3"]); }));
2455     static assert(!__traits(compiles, { ValidateFunc!((int,int) {}, string[])(config, "", ["1","2","3"]); }));
2456 }
2457 
2458 
2459 private template ParseType(alias F, T)
2460 {
2461     import std.traits : Unqual;
2462 
2463     static if(is(F == void))
2464         alias ParseType = Unqual!T;
2465     else static if(Parameters!F.length == 0)
2466         static assert(false, "Parse function should take at least one parameter");
2467     else static if(Parameters!F.length == 1)
2468     {
2469         // T action(arg)
2470         alias ParseType = Unqual!(ReturnType!F);
2471         static assert(!is(ParseType == void), "Parse function should return value");
2472     }
2473     else static if(Parameters!F.length == 2 && is(Parameters!F[0] == Config))
2474     {
2475         // T action(Config config, arg)
2476         alias ParseType = Unqual!(ReturnType!F);
2477         static assert(!is(ParseType == void), "Parse function should return value");
2478     }
2479     else static if(Parameters!F.length == 2)
2480     {
2481         // ... action(ref T param, arg)
2482         alias ParseType = Parameters!F[0];
2483     }
2484     else static if(Parameters!F.length == 3)
2485     {
2486         // ... action(Config config, ref T param, arg)
2487         alias ParseType = Parameters!F[1];
2488     }
2489     else static if(Parameters!F.length == 4)
2490     {
2491         // ... action(Config config, string argName, ref T param, arg)
2492         alias ParseType = Parameters!F[2];
2493     }
2494     else
2495         static assert(false, "Parse function has too many parameters: "~Parameters!F.stringof);
2496 }
2497 
2498 unittest
2499 {
2500     static assert(is(ParseType!(void, double) == double));
2501     static assert(!__traits(compiles, { ParseType!((){}, double) p; }));
2502     static assert(!__traits(compiles, { ParseType!((int,int,int,int,int){}, double) p; }));
2503 
2504     // T action(arg)
2505     static assert(is(ParseType!((int)=>3, double) == int));
2506     static assert(!__traits(compiles, { ParseType!((int){}, double) p; }));
2507     // T action(Config config, arg)
2508     static assert(is(ParseType!((Config config, int)=>3, double) == int));
2509     static assert(!__traits(compiles, { ParseType!((Config config, int){}, double) p; }));
2510     // ... action(ref T param, arg)
2511     static assert(is(ParseType!((ref int, string v) {}, double) == int));
2512     // ... action(Config config, ref T param, arg)
2513     static assert(is(ParseType!((Config config, ref int, string v) {}, double) == int));
2514     // ... action(Config config, string argName, ref T param, arg)
2515     //static assert(is(ParseType!((Config config, string argName, ref int, string v) {}, double) == int));
2516 }
2517 
2518 
2519 // T parse(string[] values)
2520 // T parse(string value)
2521 // T parse(RawParam param)
2522 // bool parse(ref T receiver, RawParam param)
2523 // void parse(ref T receiver, RawParam param)
2524 private struct ParseFunc(alias F, T)
2525 {
2526     alias ParseType = .ParseType!(F, T);
2527 
2528     static bool opCall(ref ParseType receiver, RawParam param)
2529     {
2530         static if(is(F == void))
2531         {
2532             foreach(value; param.value)
2533                 receiver = Parsers.Convert!T(value);
2534             return true;
2535         }
2536         // T parse(string[] values)
2537         else static if(__traits(compiles, { receiver = cast(ParseType) F(param.value); }))
2538         {
2539             receiver = cast(ParseType) F(param.value);
2540             return true;
2541         }
2542         // T parse(string value)
2543         else static if(__traits(compiles, { receiver = cast(ParseType) F(param.value[0]); }))
2544         {
2545             foreach(value; param.value)
2546                 receiver = cast(ParseType) F(value);
2547             return true;
2548         }
2549         // T parse(RawParam param)
2550         else static if(__traits(compiles, { receiver = cast(ParseType) F(param); }))
2551         {
2552             receiver = cast(ParseType) F(param);
2553             return true;
2554         }
2555         // bool parse(ref T receiver, RawParam param)
2556         // void parse(ref T receiver, RawParam param)
2557         else static if(__traits(compiles, { F(receiver, param); }))
2558         {
2559             static if(__traits(compiles, { auto res = cast(bool) F(receiver, param); }))
2560             {
2561                 // bool parse(ref T receiver, RawParam param)
2562                 return cast(bool) F(receiver, param);
2563             }
2564             else
2565             {
2566                 // void parse(ref T receiver, RawParam param)
2567                 F(receiver, param);
2568                 return true;
2569             }
2570         }
2571         else
2572             static assert(false, "Parse function is not supported");
2573     }
2574 }
2575 
2576 unittest
2577 {
2578     int i;
2579     RawParam param;
2580     param.value = ["1","2","3"];
2581     assert(ParseFunc!(void, int)(i, param));
2582     assert(i == 3);
2583 }
2584 
2585 unittest
2586 {
2587     auto test(alias F, T)(string[] values)
2588     {
2589         T value;
2590         RawParam param;
2591         param.value = values;
2592         assert(ParseFunc!(F, T)(value, param));
2593         return value;
2594     }
2595 
2596     // T parse(string value)
2597     static assert(test!((string a) => a, string)(["1","2","3"]) == "3");
2598 
2599     // T parse(string[] values)
2600     static assert(test!((string[] a) => a, string[])(["1","2","3"]) == ["1","2","3"]);
2601 
2602     // T parse(RawParam param)
2603     static assert(test!((RawParam p) => p.value[0], string)(["1","2","3"]) == "1");
2604 
2605     // bool parse(ref T receiver, RawParam param)
2606     static assert(test!((ref string[] r, RawParam p) { r = p.value; return true; }, string[])(["1","2","3"]) == ["1","2","3"]);
2607 
2608     // void parse(ref T receiver, RawParam param)
2609     static assert(test!((ref string[] r, RawParam p) { r = p.value; }, string[])(["1","2","3"]) == ["1","2","3"]);
2610 }
2611 
2612 
2613 // bool action(ref T receiver, ParseType value)
2614 // void action(ref T receiver, ParseType value)
2615 // bool action(ref T receiver, Param!ParseType param)
2616 // void action(ref T receiver, Param!ParseType param)
2617 private struct ActionFunc(alias F, T, ParseType)
2618 {
2619     static bool opCall(ref T receiver, Param!ParseType param)
2620     {
2621         static if(is(F == void))
2622         {
2623             Actions.Assign!(T, ParseType)(receiver, param.value);
2624             return true;
2625         }
2626         // bool action(ref T receiver, ParseType value)
2627         // void action(ref T receiver, ParseType value)
2628         else static if(__traits(compiles, { F(receiver, param.value); }))
2629         {
2630             static if(__traits(compiles, { auto res = cast(bool) F(receiver, param.value); }))
2631             {
2632                 // bool action(ref T receiver, ParseType value)
2633                 return cast(bool) F(receiver, param.value);
2634             }
2635             else
2636             {
2637                 // void action(ref T receiver, ParseType value)
2638                 F(receiver, param.value);
2639                 return true;
2640             }
2641         }
2642         // bool action(ref T receiver, Param!ParseType param)
2643         // void action(ref T receiver, Param!ParseType param)
2644         else static if(__traits(compiles, { F(receiver, param); }))
2645         {
2646             static if(__traits(compiles, { auto res = cast(bool) F(receiver, param); }))
2647             {
2648                 // bool action(ref T receiver, Param!ParseType param)
2649                 return cast(bool) F(receiver, param);
2650             }
2651             else
2652             {
2653                 // void action(ref T receiver, Param!ParseType param)
2654                 F(receiver, param);
2655                 return true;
2656             }
2657         }
2658         else
2659             static assert(false, "Action function is not supported");
2660     }
2661 }
2662 
2663 unittest
2664 {
2665     auto param(T)(T values)
2666     {
2667         Param!T param;
2668         param.value = values;
2669         return param;
2670     }
2671     auto test(alias F, T)(T values)
2672     {
2673         T receiver;
2674         assert(ActionFunc!(F, T, T)(receiver, param(values)));
2675         return receiver;
2676     }
2677 
2678     static assert(test!(void, string[])(["1","2","3"]) == ["1","2","3"]);
2679 
2680     static assert(!__traits(compiles, { test!(() {}, string[])(["1","2","3"]); }));
2681     static assert(!__traits(compiles, { test!((int,int) {}, string[])(["1","2","3"]); }));
2682 
2683     // bool action(ref T receiver, ParseType value)
2684     static assert(test!((ref string[] p, string[] a) { p=a; return true; }, string[])(["1","2","3"]) == ["1","2","3"]);
2685 
2686     // void action(ref T receiver, ParseType value)
2687     static assert(test!((ref string[] p, string[] a) { p=a; }, string[])(["1","2","3"]) == ["1","2","3"]);
2688 
2689     // bool action(ref T receiver, Param!ParseType param)
2690     static assert(test!((ref string[] p, Param!(string[]) a) { p=a.value; return true; }, string[]) (["1","2","3"]) == ["1","2","3"]);
2691 
2692     // void action(ref T receiver, Param!ParseType param)
2693     static assert(test!((ref string[] p, Param!(string[]) a) { p=a.value; }, string[])(["1","2","3"]) == ["1","2","3"]);
2694 }
2695 
2696 
2697 // => receiver + bool
2698 // DEST action()
2699 // bool action(ref DEST receiver)
2700 // void action(ref DEST receiver)
2701 // bool action(ref DEST receiver, Param!void param)
2702 // void action(ref DEST receiver, Param!void param)
2703 private struct NoValueActionFunc(alias F, T)
2704 {
2705     static bool opCall(ref T receiver, Param!void param)
2706     {
2707         static if(is(F == void))
2708         {
2709             assert(false, "No-value action function is not provided");
2710         }
2711         else static if(__traits(compiles, { receiver = cast(T) F(); }))
2712         {
2713             // DEST action()
2714             receiver = cast(T) F();
2715             return true;
2716         }
2717         else static if(__traits(compiles, { F(receiver); }))
2718         {
2719             static if(__traits(compiles, { auto res = cast(bool) F(receiver); }))
2720             {
2721                 // bool action(ref DEST receiver)
2722                 return cast(bool) F(receiver);
2723             }
2724             else
2725             {
2726                 // void action(ref DEST receiver)
2727                 F(receiver);
2728                 return true;
2729             }
2730         }
2731         else static if(__traits(compiles, { F(receiver, param); }))
2732         {
2733             static if(__traits(compiles, { auto res = cast(bool) F(receiver, param); }))
2734             {
2735                 // bool action(ref DEST receiver, Param!void param)
2736                 return cast(bool) F(receiver, param);
2737             }
2738             else
2739             {
2740                 // void action(ref DEST receiver, Param!void param)
2741                 F(receiver, param);
2742                 return true;
2743             }
2744         }
2745         else
2746             static assert(false, "No-value action function has too many parameters: "~Parameters!F.stringof);
2747     }
2748 }
2749 
2750 unittest
2751 {
2752     auto test(alias F, T)()
2753     {
2754         T receiver;
2755         assert(NoValueActionFunc!(F, T)(receiver, Param!void.init));
2756         return receiver;
2757     }
2758 
2759     static assert(!__traits(compiles, { NoValueActionFunc!(() {}, int); }));
2760     static assert(!__traits(compiles, { NoValueActionFunc!((int) {}, int); }));
2761     static assert(!__traits(compiles, { NoValueActionFunc!((int,int) {}, int); }));
2762     static assert(!__traits(compiles, { NoValueActionFunc!((int,int,int) {}, int); }));
2763 
2764     // DEST action()
2765     static assert(test!(() => 7, int) == 7);
2766 
2767     // bool action(ref DEST param)
2768     static assert(test!((ref int p) { p=7; return true; }, int) == 7);
2769 
2770     // void action(ref DEST param)
2771     static assert(test!((ref int p) { p=7; }, int) == 7);
2772 
2773     // bool action(ref DEST receiver, Param!void param)
2774     static assert(test!((ref int r, Param!void p) { r=7; return true; }, int) == 7);
2775 
2776     // void action(ref DEST receiver, Param!void param)
2777     static assert(test!((ref int r, Param!void p) { r=7; }, int) == 7);
2778 }
2779 
2780 
2781 private void splitValues(ref RawParam param)
2782 {
2783     if(param.config.arraySep == char.init)
2784         return;
2785 
2786     import std.array : array, split;
2787     import std.algorithm : map, joiner;
2788 
2789     param.value = param.value.map!((string s) => s.split(param.config.arraySep)).joiner.array;
2790 }
2791 
2792 unittest
2793 {
2794     alias test = (char arraySep, string[] values)
2795     {
2796         Config config;
2797         config.arraySep = arraySep;
2798 
2799         auto param = RawParam(config, "", values);
2800 
2801         splitValues(param);
2802 
2803         return param.value;
2804     };
2805 
2806     static assert(test(',', []) == []);
2807     static assert(test(',', ["a","b","c"]) == ["a","b","c"]);
2808     static assert(test(',', ["a,b","c","d,e,f"]) == ["a","b","c","d","e","f"]);
2809     static assert(test(' ', ["a,b","c","d,e,f"]) == ["a,b","c","d,e,f"]);
2810 }
2811 
2812 
2813 private struct ValueParseFunctions(alias PreProcess,
2814                                    alias PreValidation,
2815                                    alias Parse,
2816                                    alias Validation,
2817                                    alias Action,
2818                                    alias NoValueAction)
2819 {
2820     alias changePreProcess   (alias func) = ValueParseFunctions!(      func, PreValidation, Parse, Validation, Action, NoValueAction);
2821     alias changePreValidation(alias func) = ValueParseFunctions!(PreProcess,          func, Parse, Validation, Action, NoValueAction);
2822     alias changeParse        (alias func) = ValueParseFunctions!(PreProcess, PreValidation,  func, Validation, Action, NoValueAction);
2823     alias changeValidation   (alias func) = ValueParseFunctions!(PreProcess, PreValidation, Parse,       func, Action, NoValueAction);
2824     alias changeAction       (alias func) = ValueParseFunctions!(PreProcess, PreValidation, Parse, Validation,   func, NoValueAction);
2825     alias changeNoValueAction(alias func) = ValueParseFunctions!(PreProcess, PreValidation, Parse, Validation, Action,          func);
2826 
2827     template addDefaults(T)
2828     {
2829         static if(is(PreProcess == void))
2830             alias preProc = DefaultValueParseFunctions!T;
2831         else
2832             alias preProc = DefaultValueParseFunctions!T.changePreProcess!PreProcess;
2833 
2834         static if(is(PreValidation == void))
2835             alias preVal = preProc;
2836         else
2837             alias preVal = preProc.changePreValidation!PreValidation;
2838 
2839         static if(is(Parse == void))
2840             alias parse = preVal;
2841         else
2842             alias parse = preVal.changeParse!Parse;
2843 
2844         static if(is(Validation == void))
2845             alias val = parse;
2846         else
2847             alias val = parse.changeValidation!Validation;
2848 
2849         static if(is(Action == void))
2850             alias action = val;
2851         else
2852             alias action = val.changeAction!Action;
2853 
2854         static if(is(NoValueAction == void))
2855             alias addDefaults = action;
2856         else
2857             alias addDefaults = action.changeNoValueAction!NoValueAction;
2858     }
2859 
2860 
2861     // Procedure to process (parse) the values to an argument of type T
2862     //  - if there is a value(s):
2863     //      - pre validate raw strings
2864     //      - parse raw strings
2865     //      - validate parsed values
2866     //      - action with values
2867     //  - if there is no value:
2868     //      - action if no value
2869     // Requirement: rawValues.length must be correct
2870     static bool parse(T)(ref T receiver, RawParam param)
2871     {
2872         return addDefaults!T.parseImpl(receiver, param);
2873     }
2874     static bool parseImpl(T)(ref T receiver, ref RawParam rawParam)
2875     {
2876         alias ParseType(T)     = .ParseType!(Parse, T);
2877 
2878         alias preValidation    = ValidateFunc!(PreValidation, string[], "Pre validation");
2879         alias parse(T)         = ParseFunc!(Parse, T);
2880         alias validation(T)    = ValidateFunc!(Validation, ParseType!T);
2881         alias action(T)        = ActionFunc!(Action, T, ParseType!T);
2882         alias noValueAction(T) = NoValueActionFunc!(NoValueAction, T);
2883 
2884         if(rawParam.value.length == 0)
2885         {
2886             return noValueAction!T(receiver, Param!void(rawParam.config, rawParam.name));
2887         }
2888         else
2889         {
2890             static if(!is(PreProcess == void))
2891                 PreProcess(rawParam);
2892 
2893             if(!preValidation(rawParam))
2894                 return false;
2895 
2896             auto parsedParam = Param!(ParseType!T)(rawParam.config, rawParam.name);
2897 
2898             if(!parse!T(parsedParam.value, rawParam))
2899                 return false;
2900 
2901             if(!validation!T(parsedParam))
2902                 return false;
2903 
2904             if(!action!T(receiver, parsedParam))
2905                 return false;
2906 
2907             return true;
2908         }
2909     }
2910 }
2911 
2912 
2913 private template DefaultValueParseFunctions(T)
2914 if(!is(T == void))
2915 {
2916     import std.traits;
2917     import std.conv: to;
2918 
2919     static if(is(T == enum))
2920     {
2921         alias DefaultValueParseFunctions = ValueParseFunctions!(
2922         void,   // pre process
2923         Validators.ValueInList!(EnumMembersAsStrings!T, typeof(RawParam.value)),   // pre validate
2924         void,   // parse
2925         void,   // validate
2926         void,   // action
2927         void    // no-value action
2928         );
2929     }
2930     else static if(isSomeString!T || isNumeric!T)
2931     {
2932         alias DefaultValueParseFunctions = ValueParseFunctions!(
2933         void,   // pre process
2934         void,   // pre validate
2935         void,   // parse
2936         void,   // validate
2937         void,   // action
2938         void    // no-value action
2939         );
2940     }
2941     else static if(isBoolean!T)
2942     {
2943         alias DefaultValueParseFunctions = ValueParseFunctions!(
2944         void,                               // pre process
2945         void,                               // pre validate
2946         (string value)                      // parse
2947         {
2948             switch(value)
2949             {
2950                 case "":    goto case;
2951                 case "yes": goto case;
2952                 case "y":   return true;
2953                 case "no":  goto case;
2954                 case "n":   return false;
2955                 default:    return value.to!T;
2956             }
2957         },
2958         void,                               // validate
2959         void,                               // action
2960         (ref T result) { result = true; }   // no-value action
2961         );
2962     }
2963     else static if(isSomeChar!T)
2964     {
2965         alias DefaultValueParseFunctions = ValueParseFunctions!(
2966         void,                         // pre process
2967         void,                         // pre validate
2968         (string value)                // parse
2969         {
2970             return value.length > 0 ? value[0].to!T : T.init;
2971         },
2972         void,                         // validate
2973         void,                         // action
2974         void                          // no-value action
2975         );
2976     }
2977     else static if(isArray!T)
2978     {
2979         import std.traits: ForeachType;
2980 
2981         alias TElement = ForeachType!T;
2982 
2983         static if(!isArray!TElement || isSomeString!TElement)  // 1D array
2984         {
2985             static if(!isStaticArray!T)
2986                 alias action = Actions.Append!T;
2987             else
2988                 alias action = Actions.Assign!T;
2989 
2990             alias DefaultValueParseFunctions = DefaultValueParseFunctions!TElement
2991             .changePreProcess!splitValues
2992             .changeParse!((ref T receiver, RawParam param)
2993             {
2994                 static if(!isStaticArray!T)
2995                 {
2996                     if(receiver.length < param.value.length)
2997                         receiver.length = param.value.length;
2998                 }
2999 
3000                 foreach(i, value; param.value)
3001                 {
3002                     if(!DefaultValueParseFunctions!TElement.parse(receiver[i],
3003                         RawParam(param.config, param.name, [value])))
3004                         return false;
3005                 }
3006 
3007                 return true;
3008             })
3009             .changeAction!(action)
3010             .changeNoValueAction!((ref T param) {});
3011         }
3012         else static if(!isArray!(ForeachType!TElement) || isSomeString!(ForeachType!TElement))  // 2D array
3013         {
3014             alias DefaultValueParseFunctions = DefaultValueParseFunctions!TElement
3015             .changeAction!(Actions.Extend!TElement)
3016             .changeNoValueAction!((ref T param) { param ~= TElement.init; });
3017         }
3018         else
3019         {
3020             static assert(false, "Multi-dimentional arrays are not supported: " ~ T.stringof);
3021         }
3022     }
3023     else static if(isAssociativeArray!T)
3024     {
3025         import std.string : indexOf;
3026         alias DefaultValueParseFunctions = ValueParseFunctions!(
3027         splitValues,                                                // pre process
3028         void,                                                       // pre validate
3029         Parsers.PassThrough,                                        // parse
3030         void,                                                       // validate
3031         (ref T recepient, Param!(string[]) param)                   // action
3032         {
3033             alias K = KeyType!T;
3034             alias V = ValueType!T;
3035 
3036             foreach(input; param.value)
3037             {
3038                 auto j = indexOf(input, param.config.assignChar);
3039                 if(j < 0)
3040                     return false;
3041 
3042                 K key;
3043                 if(!DefaultValueParseFunctions!K.parse(key, RawParam(param.config, param.name, [input[0 .. j]])))
3044                     return false;
3045 
3046                 V value;
3047                 if(!DefaultValueParseFunctions!V.parse(value, RawParam(param.config, param.name, [input[j + 1 .. $]])))
3048                     return false;
3049 
3050                 recepient[key] = value;
3051             }
3052             return true;
3053         },
3054         (ref T param) {}    // no-value action
3055         );
3056     }
3057     else static if(is(T == delegate))
3058     {
3059         alias DefaultValueParseFunctions = ValueParseFunctions!(
3060             void,                           // pre process
3061             void,                           // pre validate
3062             Parsers.PassThrough,            // parse
3063             void,                           // validate
3064             Actions.CallFunction!T,         // action
3065             Actions.CallFunctionNoParam!T   // no-value action
3066         );
3067     }
3068      else
3069         static assert(false, "Type is not supported: " ~ T.stringof);
3070 }
3071 
3072 unittest
3073 {
3074     enum MyEnum { foo, bar, }
3075 
3076     import std.meta: AliasSeq;
3077     static foreach(T; AliasSeq!(string, bool, int, double, char, MyEnum))
3078         static foreach(R; AliasSeq!(T, T[], T[][]))
3079         {{
3080             // ensure that this compiles
3081             R receiver;
3082             RawParam param;
3083             param.value = [""];
3084             DefaultValueParseFunctions!R.parse(receiver, param);
3085         }}
3086 }
3087 
3088 unittest
3089 {
3090     alias test(R) = (string[][] values)
3091     {
3092         auto config = Config('=', ',');
3093         R receiver;
3094         foreach(value; values)
3095         {
3096             assert(DefaultValueParseFunctions!R.parse(receiver, RawParam(config, "", value)));
3097         }
3098         return receiver;
3099     };
3100 
3101     static assert(test!(string[])([["1","2","3"], [], ["4"]]) == ["1","2","3","4"]);
3102     static assert(test!(string[][])([["1","2","3"], [], ["4"]]) == [["1","2","3"],[],["4"]]);
3103 
3104     static assert(test!(string[string])([["a=bar","b=foo"], [], ["b=baz","c=boo"]]) == ["a":"bar", "b":"baz", "c":"boo"]);
3105 
3106     static assert(test!(string[])([["1,2,3"], [], ["4"]]) == ["1","2","3","4"]);
3107     static assert(test!(string[string])([["a=bar,b=foo"], [], ["b=baz,c=boo"]]) == ["a":"bar", "b":"baz", "c":"boo"]);
3108 
3109     static assert(test!(int[])([["1","2","3"], [], ["4"]]) == [1,2,3,4]);
3110     static assert(test!(int[][])([["1","2","3"], [], ["4"]]) == [[1,2,3],[],[4]]);
3111 
3112 }
3113 
3114 unittest
3115 {
3116     import std.math: isNaN;
3117     enum MyEnum { foo, bar, }
3118 
3119     alias test(T) = (string[] values)
3120     {
3121         T receiver;
3122         RawParam param;
3123         param.value = values;
3124         assert(DefaultValueParseFunctions!T.parse(receiver, param));
3125         return receiver;
3126     };
3127 
3128     static assert(test!string([""]) == "");
3129     static assert(test!string(["foo"]) == "foo");
3130     static assert(isNaN(test!double([""])));
3131     static assert(test!double(["-12.34"]) == -12.34);
3132     static assert(test!double(["12.34"]) == 12.34);
3133     static assert(test!uint(["1234"]) == 1234);
3134     static assert(test!int([""]) == int.init);
3135     static assert(test!int(["-1234"]) == -1234);
3136     static assert(test!char([""]) == char.init);
3137     static assert(test!char(["f"]) == 'f');
3138     static assert(test!bool([]) == true);
3139     static assert(test!bool([""]) == true);
3140     static assert(test!bool(["yes"]) == true);
3141     static assert(test!bool(["y"]) == true);
3142     static assert(test!bool(["true"]) == true);
3143     static assert(test!bool(["no"]) == false);
3144     static assert(test!bool(["n"]) == false);
3145     static assert(test!bool(["false"]) == false);
3146     static assert(test!MyEnum(["foo"]) == MyEnum.foo);
3147     static assert(test!MyEnum(["bar"]) == MyEnum.bar);
3148     static assert(test!(MyEnum[])(["bar","foo"]) == [MyEnum.bar, MyEnum.foo]);
3149     static assert(test!(string[string])(["a=bar","b=foo"]) == ["a":"bar", "b":"foo"]);
3150     static assert(test!(MyEnum[string])(["a=bar","b=foo"]) == ["a":MyEnum.bar, "b":MyEnum.foo]);
3151     static assert(test!(int[MyEnum])(["bar=3","foo=5"]) == [MyEnum.bar:3, MyEnum.foo:5]);
3152 }
3153 
3154 
3155 private struct ArgumentInfo
3156 {
3157     string[] names;
3158 
3159     string description;
3160     string placeholder;
3161 
3162     private void setAllowedValues(alias names)()
3163     {
3164         if(placeholder.length == 0)
3165         {
3166             import std.conv: to;
3167             import std.array: join;
3168             import std.format: format;
3169             placeholder = "{%s}".format(names.to!(string[]).join(','));
3170         }
3171     }
3172 
3173     bool hideFromHelp = false;      // if true then this argument is not printed on help page
3174 
3175     bool required;
3176 
3177     Nullable!uint position;
3178 
3179     @property bool positional() const { return !position.isNull; }
3180 
3181     Nullable!ulong minValuesCount;
3182     Nullable!ulong maxValuesCount;
3183 
3184     private auto checkValuesCount(string argName, ulong count) const
3185     {
3186         immutable min = minValuesCount.get;
3187         immutable max = maxValuesCount.get;
3188 
3189         // override for boolean flags
3190         if(allowBooleanNegation && count == 1)
3191             return Result.Success;
3192 
3193         if(min == max && count != min)
3194         {
3195             return Result.Error("argument ",argName,": expected ",min,min == 1 ? " value" : " values");
3196         }
3197         if(count < min)
3198         {
3199             return Result.Error("argument ",argName,": expected at least ",min,min == 1 ? " value" : " values");
3200         }
3201         if(count > max)
3202         {
3203             return Result.Error("argument ",argName,": expected at most ",max,max == 1 ? " value" : " values");
3204         }
3205 
3206         return Result.Success;
3207     }
3208 
3209     private bool allowBooleanNegation = true;
3210     private bool ignoreInDefaultCommand;
3211 }
3212 
3213 unittest
3214 {
3215     ArgumentInfo info;
3216     info.allowBooleanNegation = false;
3217     info.minValuesCount = 2;
3218     info.maxValuesCount = 4;
3219 
3220     alias isError = (Result res) => !res && res.errorMsg.length > 0;
3221 
3222     assert( isError(info.checkValuesCount("", 1)));
3223     assert(!isError(info.checkValuesCount("", 2)));
3224     assert(!isError(info.checkValuesCount("", 3)));
3225     assert(!isError(info.checkValuesCount("", 4)));
3226     assert( isError(info.checkValuesCount("", 5)));
3227 }
3228 
3229 unittest
3230 {
3231     ArgumentInfo info;
3232     info.allowBooleanNegation = false;
3233     info.minValuesCount = 2;
3234     info.maxValuesCount = 2;
3235 
3236     alias isError = (Result res) => !res && res.errorMsg.length > 0;
3237 
3238     assert( isError(info.checkValuesCount("", 1)));
3239     assert(!isError(info.checkValuesCount("", 2)));
3240     assert( isError(info.checkValuesCount("", 3)));
3241 }
3242 
3243 
3244 ////////////////////////////////////////////////////////////////////////////////////////////////////
3245 // User defined attributes
3246 ////////////////////////////////////////////////////////////////////////////////////////////////////
3247 private struct ArgumentUDA(alias ValueParseFunctions)
3248 {
3249     ArgumentInfo info;
3250 
3251     alias parsingFunc = ValueParseFunctions;
3252 
3253 
3254 
3255     auto ref Description(string text)
3256     {
3257         info.description = text;
3258         return this;
3259     }
3260 
3261     auto ref HideFromHelp(bool hide = true)
3262     {
3263         info.hideFromHelp = hide;
3264         return this;
3265     }
3266 
3267     auto ref Placeholder(string value)
3268     {
3269         info.placeholder = value;
3270         return this;
3271     }
3272 
3273     auto ref Required()
3274     {
3275         info.required = true;
3276         return this;
3277     }
3278 
3279     auto ref Optional()
3280     {
3281         info.required = false;
3282         return this;
3283     }
3284 
3285     auto ref NumberOfValues(ulong num)
3286     {
3287         info.minValuesCount = num;
3288         info.maxValuesCount = num;
3289         return this;
3290     }
3291 
3292     auto ref NumberOfValues(ulong min, ulong max)
3293     {
3294         info.minValuesCount = min;
3295         info.maxValuesCount = max;
3296         return this;
3297     }
3298 
3299     auto ref MinNumberOfValues(ulong min)
3300     {
3301         assert(min <= info.maxValuesCount.get(ulong.max));
3302 
3303         info.minValuesCount = min;
3304         return this;
3305     }
3306 
3307     auto ref MaxNumberOfValues(ulong max)
3308     {
3309         assert(max >= info.minValuesCount.get(0));
3310 
3311         info.maxValuesCount = max;
3312         return this;
3313     }
3314 
3315     // ReverseSwitch
3316 }
3317 
3318 unittest
3319 {
3320     auto arg = NamedArgument.Description("desc").Placeholder("text");
3321     assert(arg.info.description == "desc");
3322     assert(arg.info.placeholder == "text");
3323     assert(!arg.info.hideFromHelp);
3324     assert(!arg.info.required);
3325     assert(arg.info.minValuesCount.isNull);
3326     assert(arg.info.maxValuesCount.isNull);
3327 
3328     arg = arg.HideFromHelp().Required().NumberOfValues(10);
3329     assert(arg.info.hideFromHelp);
3330     assert(arg.info.required);
3331     assert(arg.info.minValuesCount.get == 10);
3332     assert(arg.info.maxValuesCount.get == 10);
3333 
3334     arg = arg.Optional().NumberOfValues(20,30);
3335     assert(!arg.info.required);
3336     assert(arg.info.minValuesCount.get == 20);
3337     assert(arg.info.maxValuesCount.get == 30);
3338 
3339     arg = arg.MinNumberOfValues(2).MaxNumberOfValues(3);
3340     assert(arg.info.minValuesCount.get == 2);
3341     assert(arg.info.maxValuesCount.get == 3);
3342 }
3343 
3344 unittest
3345 {
3346     struct T
3347     {
3348         @(NamedArgument.NumberOfValues(1,3))
3349         int[] a;
3350         @(NamedArgument.NumberOfValues(2))
3351         int[] b;
3352     }
3353 
3354     assert(CLI!T.parseArgs!((T t) { assert(t == T([1,2,3],[4,5])); })(["-a","1","2","3","-b","4","5"]) == 0);
3355     assert(CLI!T.parseArgs!((T t) { assert(t == T([1],[4,5])); })(["-a","1","-b","4","5"]) == 0);
3356     assert(["-a","1","2","3","-b","4","5"].parseCLIArgs!T.get == T([1,2,3],[4,5]));
3357     assert(["-a","1","-b","4","5"].parseCLIArgs!T.get == T([1],[4,5]));
3358 }
3359 
3360 private enum bool isArgumentUDA(T) = (is(typeof(T.info) == ArgumentInfo) && is(T.parsingFunc));
3361 
3362 
3363 auto PreValidation(alias func, ARG)(ARG arg)
3364 if(isArgumentUDA!ARG)
3365 {
3366     return ArgumentUDA!(arg.parsingFunc.changePreValidation!func)(arg.tupleof);
3367 }
3368 
3369 auto Parse(alias func, ARG)(ARG arg)
3370 if(isArgumentUDA!ARG)
3371 {
3372     return ArgumentUDA!(arg.parsingFunc.changeParse!func)(arg.tupleof);
3373 }
3374 
3375 auto Validation(alias func, ARG)(ARG arg)
3376 if(isArgumentUDA!ARG)
3377 {
3378     return ArgumentUDA!(arg.parsingFunc.changeValidation!func)(arg.tupleof);
3379 }
3380 
3381 auto Action(alias func, ARG)(ARG arg)
3382 if(isArgumentUDA!ARG)
3383 {
3384     return ArgumentUDA!(arg.parsingFunc.changeAction!func)(arg.tupleof);
3385 }
3386 
3387 auto AllowNoValue(alias valueToUse, ARG)(ARG arg)
3388 if(isArgumentUDA!ARG)
3389 {
3390     auto desc = ArgumentUDA!(arg.parsingFunc.changeNoValueAction!(() { return valueToUse; }))(arg.tupleof);
3391     desc.info.minValuesCount = 0;
3392     return desc;
3393 }
3394 
3395 auto RequireNoValue(alias valueToUse, ARG)(ARG arg)
3396 if(isArgumentUDA!ARG)
3397 {
3398     auto desc = arg.AllowNoValue!valueToUse;
3399     desc.info.minValuesCount = 0;
3400     desc.info.maxValuesCount = 0;
3401     return desc;
3402 }
3403 
3404 auto Counter(ARG)(ARG arg)
3405 if(isArgumentUDA!ARG)
3406 {
3407     struct CounterParsingFunction
3408     {
3409         static bool parse(T)(ref T receiver, const ref RawParam param)
3410         {
3411             assert(param.value.length == 0);
3412 
3413             ++receiver;
3414 
3415             return true;
3416         }
3417     }
3418 
3419     auto desc = ArgumentUDA!(CounterParsingFunction)(arg.tupleof);
3420     desc.info.minValuesCount = 0;
3421     desc.info.maxValuesCount = 0;
3422     return desc;
3423 }
3424 
3425 
3426 unittest
3427 {
3428     struct T
3429     {
3430         @(NamedArgument.Counter()) int a;
3431     }
3432 
3433     assert(CLI!T.parseArgs!((T t) { assert(t == T(3)); })(["-a","-a","-a"]) == 0);
3434     static assert(["-a","-a","-a"].parseCLIArgs!T.get == T(3));
3435     assert(["-a","-a","-a"].parseCLIArgs!T.get == T(3));
3436 }
3437 
3438 
3439 auto AllowedValues(alias values, ARG)(ARG arg)
3440 {
3441     import std.array : assocArray;
3442     import std.range : repeat;
3443 
3444     enum valuesAA = assocArray(values, false.repeat);
3445 
3446     auto desc = arg.Validation!(Validators.ValueInList!(values, KeyType!(typeof(valuesAA))));
3447     desc.info.setAllowedValues!values;
3448     return desc;
3449 }
3450 
3451 
3452 unittest
3453 {
3454     struct T
3455     {
3456         @(NamedArgument.AllowedValues!([1,3,5])) int a;
3457     }
3458 
3459     static assert(["-a","3"].parseCLIArgs!T.get == T(3));
3460     assert(["-a","2"].parseCLIArgs!T.isNull);
3461     assert(["-a","3"].parseCLIArgs!T.get == T(3));
3462 }
3463 
3464 unittest
3465 {
3466     struct T
3467     {
3468         @(NamedArgument.AllowedValues!(["apple","pear","banana"]))
3469         string fruit;
3470     }
3471 
3472     assert(CLI!T.parseArgs!((T t) { assert(t == T("apple")); })(["--fruit", "apple"]) == 0);
3473     assert(CLI!T.parseArgs!((T t) { assert(false); })(["--fruit", "kiwi"]) != 0);    // "kiwi" is not allowed
3474     static assert(["--fruit", "apple"].parseCLIArgs!T.get == T("apple"));
3475     assert(["--fruit", "kiwi"].parseCLIArgs!T.isNull);
3476 }
3477 
3478 unittest
3479 {
3480     enum Fruit { apple, pear, banana }
3481     struct T
3482     {
3483         @NamedArgument
3484         Fruit fruit;
3485     }
3486 
3487     static assert(["--fruit", "apple"].parseCLIArgs!T.get == T(Fruit.apple));
3488     assert(["--fruit", "kiwi"].parseCLIArgs!T.isNull);
3489 }
3490 
3491 
3492 auto PositionalArgument(uint pos)
3493 {
3494     auto arg = ArgumentUDA!(ValueParseFunctions!(void, void, void, void, void, void))(ArgumentInfo()).Required();
3495     arg.info.position = pos;
3496     return arg;
3497 }
3498 
3499 auto PositionalArgument(uint pos, string name)
3500 {
3501     auto arg = ArgumentUDA!(ValueParseFunctions!(void, void, void, void, void, void))(ArgumentInfo([name])).Required();
3502     arg.info.position = pos;
3503     return arg;
3504 }
3505 
3506 auto NamedArgument(string[] name...)
3507 {
3508     return ArgumentUDA!(ValueParseFunctions!(void, void, void, void, void, void))(ArgumentInfo(name)).Optional();
3509 }
3510 
3511 auto NamedArgument(string name)
3512 {
3513     return ArgumentUDA!(ValueParseFunctions!(void, void, void, void, void, void))(ArgumentInfo([name])).Optional();
3514 }
3515 
3516 struct TrailingArguments {}
3517 
3518 
3519 unittest
3520 {
3521     struct T
3522     {
3523         string a;
3524         string b;
3525 
3526         @TrailingArguments string[] args;
3527     }
3528 
3529     assert(CLI!T.parseArgs!((T t) { assert(t == T("A","",["-b","B"])); })(["-a","A","--","-b","B"]) == 0);
3530     static assert(["-a","A","--","-b","B"].parseCLIArgs!T.get == T("A","",["-b","B"]));
3531     assert(["-a","A","--","-b","B"].parseCLIArgs!T.get == T("A","",["-b","B"]));
3532 }
3533 
3534 unittest
3535 {
3536     struct T
3537     {
3538         @NamedArgument int i;
3539         @NamedArgument(["u","u1"])  uint u;
3540         @NamedArgument("d","d1")  double d;
3541     }
3542 
3543     assert(CLI!T.parseArgs!((T t) { assert(t == T(-5,8,12.345)); })(["-i","-5","-u","8","-d","12.345"]) == 0);
3544     assert(CLI!T.parseArgs!((T t) { assert(t == T(-5,8,12.345)); })(["-i","-5","-u1","8","-d1","12.345"]) == 0);
3545 
3546     static assert(["-i","-5","-u","8","-d","12.345"].parseCLIArgs!T.get == T(-5,8,12.345));
3547     static assert(["-i","-5","-u1","8","-d1","12.345"].parseCLIArgs!T.get == T(-5,8,12.345));
3548     assert(["-i","-5","-u","8","-d","12.345"].parseCLIArgs!T.get == T(-5,8,12.345));
3549     assert(["-i","-5","-u1","8","-d1","12.345"].parseCLIArgs!T.get == T(-5,8,12.345));
3550 }
3551 
3552 unittest
3553 {
3554     struct T
3555     {
3556         @NamedArgument int[]   a;
3557         @NamedArgument int[][] b;
3558     }
3559 
3560     assert(CLI!T.parseArgs!((T t) { assert(t.a == [1,2,3,4,5]); })(["-a","1","2","3","-a","4","5"]) == 0);
3561     assert(CLI!T.parseArgs!((T t) { assert(t.b == [[1,2,3],[4,5]]); })(["-b","1","2","3","-b","4","5"]) == 0);
3562 
3563     static assert(["-a","1","2","3","-a","4","5"].parseCLIArgs!T.get.a == [1,2,3,4,5]);
3564     static assert(["-b","1","2","3","-b","4","5"].parseCLIArgs!T.get.b == [[1,2,3],[4,5]]);
3565     assert(["-a","1","2","3","-a","4","5"].parseCLIArgs!T.get.a == [1,2,3,4,5]);
3566     assert(["-b","1","2","3","-b","4","5"].parseCLIArgs!T.get.b == [[1,2,3],[4,5]]);
3567 }
3568 
3569 unittest
3570 {
3571     struct T
3572     {
3573         @NamedArgument int[] a;
3574     }
3575 
3576     enum cfg = {
3577         Config cfg;
3578         cfg.arraySep = ',';
3579         return cfg;
3580     }();
3581 
3582     assert(CLI!(cfg, T).parseArgs!((T t) { assert(t == T([1,2,3,4,5])); })(["-a","1,2,3","-a","4","5"]) == 0);
3583     assert(["-a","1,2,3","-a","4","5"].parseCLIArgs!T(cfg).get == T([1,2,3,4,5]));
3584 }
3585 
3586 unittest
3587 {
3588     struct T
3589     {
3590         @NamedArgument int[string] a;
3591     }
3592 
3593     assert(CLI!T.parseArgs!((T t) { assert(t == T(["foo":3,"boo":7])); })(["-a=foo=3","-a","boo=7"]) == 0);
3594     static assert(["-a=foo=3","-a","boo=7"].parseCLIArgs!T.get.a == ["foo":3,"boo":7]);
3595     assert(["-a=foo=3","-a","boo=7"].parseCLIArgs!T.get.a == ["foo":3,"boo":7]);
3596 }
3597 
3598 unittest
3599 {
3600     struct T
3601     {
3602         @NamedArgument int[string] a;
3603     }
3604 
3605     enum cfg = {
3606         Config cfg;
3607         cfg.arraySep = ',';
3608         return cfg;
3609     }();
3610 
3611     assert(CLI!(cfg, T).parseArgs!((T t) { assert(t == T(["foo":3,"boo":7])); })(["-a=foo=3,boo=7"]) == 0);
3612     assert(CLI!(cfg, T).parseArgs!((T t) { assert(t == T(["foo":3,"boo":7])); })(["-a","foo=3,boo=7"]) == 0);
3613     assert(["-a=foo=3,boo=7"].parseCLIArgs!T(cfg).get.a == ["foo":3,"boo":7]);
3614     assert(["-a","foo=3,boo=7"].parseCLIArgs!T(cfg).get.a == ["foo":3,"boo":7]);
3615 }
3616 
3617 unittest
3618 {
3619     struct T
3620     {
3621         enum Fruit { apple, pear };
3622 
3623         @NamedArgument Fruit a;
3624     }
3625 
3626     assert(CLI!T.parseArgs!((T t) { assert(t == T(T.Fruit.apple)); })(["-a","apple"]) == 0);
3627     assert(CLI!T.parseArgs!((T t) { assert(t == T(T.Fruit.pear)); })(["-a=pear"]) == 0);
3628     static assert(["-a","apple"].parseCLIArgs!T.get == T(T.Fruit.apple));
3629     static assert(["-a=pear"].parseCLIArgs!T.get == T(T.Fruit.pear));
3630     assert(["-a","apple"].parseCLIArgs!T.get == T(T.Fruit.apple));
3631     assert(["-a=pear"].parseCLIArgs!T.get == T(T.Fruit.pear));
3632 }
3633 
3634 unittest
3635 {
3636     struct T
3637     {
3638         @NamedArgument string[] a;
3639     }
3640 
3641     assert(CLI!T.parseArgs!((T t) { assert(t == T(["1,2,3","4","5"])); })(["-a","1,2,3","-a","4","5"]) == 0);
3642     assert(["-a","1,2,3","-a","4","5"].parseCLIArgs!T.get == T(["1,2,3","4","5"]));
3643 
3644     enum cfg = {
3645         Config cfg;
3646         cfg.arraySep = ',';
3647         return cfg;
3648     }();
3649 
3650     assert(CLI!(cfg, T).parseArgs!((T t) { assert(t == T(["1","2","3","4","5"])); })(["-a","1,2,3","-a","4","5"]) == 0);
3651     assert(["-a","1,2,3","-a","4","5"].parseCLIArgs!T(cfg).get == T(["1","2","3","4","5"]));
3652 }
3653 
3654 unittest
3655 {
3656     struct T
3657     {
3658         @(NamedArgument.AllowNoValue  !10) int a;
3659         @(NamedArgument.RequireNoValue!20) int b;
3660     }
3661 
3662     assert(CLI!T.parseArgs!((T t) { assert(t.a == 10); })(["-a"]) == 0);
3663     assert(CLI!T.parseArgs!((T t) { assert(t.b == 20); })(["-b"]) == 0);
3664     assert(CLI!T.parseArgs!((T t) { assert(t.a == 30); })(["-a","30"]) == 0);
3665     assert(CLI!T.parseArgs!((T t) { assert(false); })(["-b","30"]) != 0);
3666     static assert(["-a"].parseCLIArgs!T.get.a == 10);
3667     static assert(["-b"].parseCLIArgs!T.get.b == 20);
3668     static assert(["-a", "30"].parseCLIArgs!T.get.a == 30);
3669     assert(["-a"].parseCLIArgs!T.get.a == 10);
3670     assert(["-b"].parseCLIArgs!T.get.b == 20);
3671     assert(["-a", "30"].parseCLIArgs!T.get.a == 30);
3672     assert(["-b", "30"].parseCLIArgs!T.isNull);
3673 }
3674 
3675 unittest
3676 {
3677     struct T
3678     {
3679         @(NamedArgument
3680          .PreValidation!((string s) { return s.length > 1 && s[0] == '!'; })
3681          .Parse        !((string s) { return s[1]; })
3682          .Validation   !((char v) { return v >= '0' && v <= '9'; })
3683          .Action       !((ref int a, char v) { a = v - '0'; })
3684         )
3685         int a;
3686     }
3687 
3688     assert(CLI!T.parseArgs!((T t) { assert(t == T(4)); })(["-a","!4"]) == 0);
3689     static assert(["-a","!4"].parseCLIArgs!T.get.a == 4);
3690     assert(["-a","!4"].parseCLIArgs!T.get.a == 4);
3691 }
3692 
3693 unittest
3694 {
3695     static struct T
3696     {
3697         int a;
3698 
3699         @(NamedArgument("a")) void foo() { a++; }
3700     }
3701 
3702     assert(CLI!T.parseArgs!((T t) { assert(t == T(4)); })(["-a","-a","-a","-a"]) == 0);
3703     static assert(["-a","-a","-a","-a"].parseCLIArgs!T.get.a == 4);
3704     assert(["-a","-a","-a","-a"].parseCLIArgs!T.get.a == 4);
3705 }
3706 
3707 
3708 private string getProgramName()
3709 {
3710     import core.runtime: Runtime;
3711     import std.path: baseName;
3712     return Runtime.args[0].baseName;
3713 }
3714 
3715 unittest
3716 {
3717     assert(getProgramName().length > 0);
3718 }
3719 
3720 
3721 private struct CommandInfo
3722 {
3723     string[] names = [""];
3724     string usage;
3725     string description;
3726     string shortDescription;
3727     string epilog;
3728 
3729     auto ref Usage(string text)
3730     {
3731         usage = text;
3732         return this;
3733     }
3734 
3735     auto ref Description(string text)
3736     {
3737         description = text;
3738         return this;
3739     }
3740 
3741     auto ref ShortDescription(string text)
3742     {
3743         shortDescription = text;
3744         return this;
3745     }
3746 
3747     auto ref Epilog(string text)
3748     {
3749         epilog = text;
3750         return this;
3751     }
3752 }
3753 
3754 auto Command(string[] name...)
3755 {
3756     return CommandInfo(name.dup);
3757 }
3758 
3759 unittest
3760 {
3761     auto a = Command("MYPROG").Usage("usg").Description("desc").ShortDescription("sum").Epilog("epi");
3762     assert(a.names == ["MYPROG"]);
3763     assert(a.usage == "usg");
3764     assert(a.description == "desc");
3765     assert(a.shortDescription == "sum");
3766     assert(a.epilog == "epi");
3767 }
3768 
3769 private mixin template ForwardMemberFunction(string dest)
3770 {
3771     import std.array: split;
3772     mixin("auto "~dest.split('.')[$-1]~"(Args...)(auto ref Args args) inout { import core.lifetime: forward; return "~dest~"(forward!args); }");
3773 }
3774 
3775 
3776 private struct CommandArguments(RECEIVER)
3777 {
3778     static assert(getSymbolsByUDA!(RECEIVER, TrailingArguments).length <= 1,
3779         "Type "~RECEIVER.stringof~" must have at most one 'TrailingArguments' UDA");
3780 
3781     private enum _validate = checkArgumentNames!RECEIVER &&
3782                              checkPositionalIndexes!RECEIVER;
3783 
3784     static assert(getUDAs!(RECEIVER, CommandInfo).length <= 1);
3785 
3786     CommandInfo info;
3787     const(string)[] parentNames;
3788 
3789     Arguments arguments;
3790 
3791     ParseFunction!RECEIVER[] parseArguments;
3792     ParseFunction!RECEIVER[] completeArguments;
3793 
3794     uint level; // (sub-)command level, 0 = top level
3795 
3796     // sub commands
3797     size_t[string] subCommandsByName;
3798     CommandInfo[] subCommands;
3799     ParseSubCommandFunction!RECEIVER[] parseSubCommands;
3800     ParseSubCommandFunction!RECEIVER[] completeSubCommands;
3801     InitSubCommandFunction !RECEIVER[] initSubCommands;
3802 
3803     // completion
3804     string[] completeSuggestion;
3805 
3806 
3807     mixin ForwardMemberFunction!"arguments.findPositionalArgument";
3808     mixin ForwardMemberFunction!"arguments.findNamedArgument";
3809     mixin ForwardMemberFunction!"arguments.checkRestrictions";
3810 
3811 
3812 
3813     private this(in Config config)
3814     {
3815         static if(getUDAs!(RECEIVER, CommandInfo).length > 0)
3816             CommandInfo info = getUDAs!(RECEIVER, CommandInfo)[0];
3817         else
3818             CommandInfo info;
3819 
3820         this(config, info);
3821     }
3822 
3823     private this(PARENT = void)(in Config config, CommandInfo info, const PARENT* parentArguments = null)
3824     {
3825         this.info = info;
3826 
3827         checkArgumentName!RECEIVER(config.namedArgChar);
3828 
3829         static if(is(PARENT == void))
3830         {
3831             level = 0;
3832             arguments = Arguments(config.caseSensitive);
3833         }
3834         else
3835         {
3836             parentNames = parentArguments.parentNames ~ parentArguments.info.names[0];
3837             level = parentArguments.level + 1;
3838             arguments = Arguments(config.caseSensitive, &parentArguments.arguments);
3839         }
3840 
3841         fillArguments();
3842 
3843         if(config.addHelp)
3844         {
3845             arguments.addArgument!helpArgument;
3846             parseArguments ~= delegate (in Config config, string argName, ref RECEIVER receiver, string rawValue, ref string[] rawArgs)
3847             {
3848                 import std.stdio: stdout;
3849 
3850                 printHelp(stdout.lockingTextWriter(), this, config);
3851 
3852                 return Result(0);
3853             };
3854             completeArguments ~= delegate (in Config config, string argName, ref RECEIVER receiver, string rawValue, ref string[] rawArgs)
3855             {
3856                 return Result.Success;
3857             };
3858         }
3859 
3860 
3861         import std.algorithm: sort, map;
3862         import std.range: join;
3863         import std.array: array;
3864 
3865         completeSuggestion = arguments.argsNamed.keys.map!(_ => getArgumentName(_, config)).array;
3866         completeSuggestion ~= subCommandsByName.keys.array;
3867         completeSuggestion.sort;
3868     }
3869 
3870     private void fillArguments()
3871     {
3872         enum hasNoUDAs = getSymbolsByUDA!(RECEIVER, ArgumentUDA  ).length == 0 &&
3873                          getSymbolsByUDA!(RECEIVER, NamedArgument).length == 0 &&
3874                          getSymbolsByUDA!(RECEIVER, SubCommands  ).length == 0;
3875 
3876         static foreach(sym; __traits(allMembers, RECEIVER))
3877         {{
3878             alias mem = __traits(getMember, RECEIVER, sym);
3879 
3880             static if(!is(mem)) // skip types
3881             {
3882                 static if(hasUDA!(mem, ArgumentUDA) || hasUDA!(mem, NamedArgument))
3883                     addArgument!sym;
3884                 else static if(hasUDA!(mem, SubCommands))
3885                     addSubCommands!sym;
3886                 else static if(hasNoUDAs &&
3887                     // skip "op*" functions
3888                     !(is(typeof(mem) == function) && sym.length > 2 && sym[0..2] == "op"))
3889                 {
3890                     import std.sumtype: isSumType;
3891 
3892                     static if(isSumType!(typeof(mem)))
3893                         addSubCommands!sym;
3894                     else
3895                         addArgument!sym;
3896                 }
3897             }
3898         }}
3899     }
3900 
3901     private void addArgument(alias symbol)()
3902     {
3903         alias member = __traits(getMember, RECEIVER, symbol);
3904 
3905         static assert(getUDAs!(member, ArgumentUDA).length <= 1,
3906             "Member "~RECEIVER.stringof~"."~symbol~" has multiple '*Argument' UDAs");
3907 
3908         static assert(getUDAs!(member, Group).length <= 1,
3909             "Member "~RECEIVER.stringof~"."~symbol~" has multiple 'Group' UDAs");
3910 
3911         static if(getUDAs!(member, ArgumentUDA).length > 0)
3912             enum uda = getUDAs!(member, ArgumentUDA)[0];
3913         else
3914             enum uda = NamedArgument();
3915 
3916         enum info = setDefaults!(uda.info, typeof(member), symbol);
3917 
3918         enum restrictions = {
3919             RestrictionGroup[] restrictions;
3920             static foreach(gr; getUDAs!(member, RestrictionGroup))
3921                 restrictions ~= gr;
3922             return restrictions;
3923         }();
3924 
3925         static if(getUDAs!(member, Group).length > 0)
3926             arguments.addArgument!(info, restrictions, getUDAs!(member, Group)[0]);
3927         else
3928             arguments.addArgument!(info, restrictions);
3929 
3930         parseArguments    ~= ParsingArgument!(symbol, uda, info, RECEIVER, false);
3931         completeArguments ~= ParsingArgument!(symbol, uda, info, RECEIVER, true);
3932     }
3933 
3934     private void addSubCommands(alias symbol)()
3935     {
3936         import std.sumtype: isSumType;
3937 
3938         alias member = __traits(getMember, RECEIVER, symbol);
3939 
3940         static assert(isSumType!(typeof(member)), RECEIVER.stringof~"."~symbol~" must have 'SumType' type");
3941 
3942         static assert(getUDAs!(member, SubCommands).length <= 1,
3943             "Member "~RECEIVER.stringof~"."~symbol~" has multiple 'SubCommands' UDAs");
3944 
3945         static foreach(TYPE; typeof(member).Types)
3946         {{
3947             enum defaultCommand = is(TYPE == Default!COMMAND_TYPE, COMMAND_TYPE);
3948             static if(!defaultCommand)
3949                 alias COMMAND_TYPE = TYPE;
3950 
3951             static assert(getUDAs!(COMMAND_TYPE, CommandInfo).length <= 1);
3952 
3953             //static assert(getUDAs!(member, Group).length <= 1,
3954             //    "Member "~RECEIVER.stringof~"."~symbol~" has multiple 'Group' UDAs");
3955 
3956             static if(getUDAs!(COMMAND_TYPE, CommandInfo).length > 0)
3957                 enum info = getUDAs!(COMMAND_TYPE, CommandInfo)[0];
3958             else
3959                 enum info = CommandInfo([COMMAND_TYPE.stringof]);
3960 
3961             static assert(info.names.length > 0 && info.names[0].length > 0);
3962 
3963             //static if(getUDAs!(member, Group).length > 0)
3964             //    args.addArgument!(info, restrictions, getUDAs!(member, Group)[0])(ParsingArgument!(symbol, uda, info, RECEIVER));
3965             //else
3966             //arguments.addSubCommand!(info);
3967 
3968             immutable index = subCommands.length;
3969 
3970             static foreach(name; info.names)
3971             {
3972                 assert(!(name in subCommandsByName), "Duplicated name of subcommand: "~name);
3973                 subCommandsByName[arguments.convertCase(name)] = index;
3974             }
3975 
3976             static if(defaultCommand)
3977             {
3978                 assert(!(DEFAULT_COMMAND in subCommandsByName), "Multiple default subcommands: "~RECEIVER.stringof~"."~symbol);
3979                 subCommandsByName[DEFAULT_COMMAND] = index;
3980             }
3981 
3982             subCommands ~= info;
3983             //group.arguments ~= index;
3984             parseSubCommands    ~= ParsingSubCommandArgument!(TYPE, info, RECEIVER, symbol, false)(&this);
3985             completeSubCommands ~= ParsingSubCommandArgument!(TYPE, info, RECEIVER, symbol, true)(&this);
3986             initSubCommands     ~= ParsingSubCommandInit!(TYPE, RECEIVER, symbol);
3987         }}
3988     }
3989 
3990     auto getParseFunction(bool completionMode)(size_t index) const
3991     {
3992         static if(completionMode)
3993             return completeArguments[index];
3994         else
3995             return parseArguments[index];
3996     }
3997 
3998     auto findSubCommand(string name) const
3999     {
4000         struct Result
4001         {
4002             uint level = uint.max;
4003             ParseSubCommandFunction!RECEIVER parse;
4004             ParseSubCommandFunction!RECEIVER complete;
4005             InitSubCommandFunction !RECEIVER initialize;
4006         }
4007 
4008         auto p = arguments.convertCase(name) in subCommandsByName;
4009         return !p ? Result.init : Result(level+1, parseSubCommands[*p], completeSubCommands[*p], initSubCommands[*p]);
4010     }
4011 
4012     static if(getSymbolsByUDA!(RECEIVER, TrailingArguments).length == 1)
4013     {
4014         private void setTrailingArgs(ref RECEIVER receiver, string[] rawValues) const
4015         {
4016             enum symbol = __traits(identifier, getSymbolsByUDA!(RECEIVER, TrailingArguments)[0]);
4017             auto target = &__traits(getMember, receiver, symbol);
4018 
4019             static if(__traits(compiles, { *target = rawValues; }))
4020                 *target = rawValues;
4021             else
4022                 static assert(false, "Type '"~typeof(*target).stringof~"' of `"~
4023                     RECEIVER.stringof~"."~symbol~"` is not supported for 'TrailingArguments' UDA");
4024         }
4025     }
4026 }
4027 
4028 ////////////////////////////////////////////////////////////////////////////////////////////////////
4029 // Help-printing functions
4030 ////////////////////////////////////////////////////////////////////////////////////////////////////
4031 private void printValue(Output)(auto ref Output output, in ArgumentInfo info)
4032 {
4033     if(info.maxValuesCount.get == 0)
4034         return;
4035 
4036     if(info.minValuesCount.get == 0)
4037         output.put('[');
4038 
4039     output.put(info.placeholder);
4040     if(info.maxValuesCount.get > 1)
4041         output.put(" ...");
4042 
4043     if(info.minValuesCount.get == 0)
4044         output.put(']');
4045 }
4046 
4047 unittest
4048 {
4049     auto test(int min, int max)
4050     {
4051         ArgumentInfo info;
4052         info.placeholder = "v";
4053         info.minValuesCount = min;
4054         info.maxValuesCount = max;
4055 
4056         import std.array: appender;
4057         auto a = appender!string;
4058         a.printValue(info);
4059         return a[];
4060     }
4061 
4062     assert(test(0,0) == "");
4063     assert(test(0,1) == "[v]");
4064     assert(test(0,5) == "[v ...]");
4065     assert(test(1,1) == "v");
4066     assert(test(1,5) == "v ...");
4067     assert(test(3,3) == "v ...");
4068     assert(test(3,5) == "v ...");
4069 }
4070 
4071 
4072 private string getArgumentName(string name, in Config config)
4073 {
4074     import std.conv: to;
4075 
4076     immutable dash = config.namedArgChar.to!string;
4077 
4078     name = dash ~ name;
4079     return name.length > 2 ? dash ~ name : name;
4080 }
4081 
4082 unittest
4083 {
4084     assert(getArgumentName("f", Config.init) == "-f");
4085     assert(getArgumentName("foo", Config.init) == "--foo");
4086 }
4087 
4088 
4089 private void printInvocation(Output)(auto ref Output output, in ArgumentInfo info, in string[] names, in Config config)
4090 {
4091     if(info.positional)
4092         output.printValue(info);
4093     else
4094     {
4095         import std.algorithm: each;
4096 
4097         names.each!((i, name)
4098         {
4099             if(i > 0)
4100                 output.put(", ");
4101 
4102             output.put(getArgumentName(name, config));
4103 
4104             if(info.maxValuesCount.get > 0)
4105             {
4106                 output.put(' ');
4107                 output.printValue(info);
4108             }
4109         });
4110     }
4111 }
4112 
4113 unittest
4114 {
4115     auto test(bool positional)()
4116     {
4117         enum info = {
4118             ArgumentInfo info;
4119             info.placeholder = "v";
4120             static if (positional)
4121                 info.position = 0;
4122             return info;
4123         }();
4124 
4125         import std.array: appender;
4126         auto a = appender!string;
4127         a.printInvocation(setDefaults!(info, int, "foo"), ["f","foo"], Config.init);
4128         return a[];
4129     }
4130 
4131     assert(test!false == "-f v, --foo v");
4132     assert(test!true == "v");
4133 }
4134 
4135 
4136 private void printUsage(Output)(auto ref Output output, in ArgumentInfo info, in Config config)
4137 {
4138     if(!info.required)
4139         output.put('[');
4140 
4141     output.printInvocation(info, [info.names[0]], config);
4142 
4143     if(!info.required)
4144         output.put(']');
4145 }
4146 
4147 unittest
4148 {
4149     auto test(bool required, bool positional)()
4150     {
4151         enum info = {
4152             ArgumentInfo info;
4153             info.names ~= "foo";
4154             info.placeholder = "v";
4155             info.required = required;
4156             static if (positional)
4157                 info.position = 0;
4158             return info;
4159         }();
4160 
4161         import std.array: appender;
4162         auto a = appender!string;
4163         a.printUsage(setDefaults!(info, int, "foo"), Config.init);
4164         return a[];
4165     }
4166 
4167     assert(test!(false, false) == "[--foo v]");
4168     assert(test!(false, true) == "[v]");
4169     assert(test!(true, false) == "--foo v");
4170     assert(test!(true, true) == "v");
4171 }
4172 
4173 
4174 private void substituteProg(Output)(auto ref Output output, string text, string prog)
4175 {
4176     import std.array: replaceInto;
4177     output.replaceInto(text, "%(PROG)", prog);
4178 }
4179 
4180 unittest
4181 {
4182     import std.array: appender;
4183     auto a = appender!string;
4184     a.substituteProg("this is some text where %(PROG) is substituted but PROG and prog are not", "-myprog-");
4185     assert(a[] == "this is some text where -myprog- is substituted but PROG and prog are not");
4186 }
4187 
4188 
4189 private string spaces(ulong num)
4190 {
4191     import std.range: repeat;
4192     import std.array: array;
4193     return ' '.repeat(num).array;
4194 }
4195 
4196 unittest
4197 {
4198     assert(spaces(0) == "");
4199     assert(spaces(1) == " ");
4200     assert(spaces(5) == "     ");
4201 }
4202 
4203 private void printUsage(T, Output)(auto ref Output output, in CommandArguments!T cmd, in Config config)
4204 {
4205     import std.algorithm: map;
4206     import std.array: join;
4207 
4208     string progName = (cmd.parentNames ~ cmd.info.names[0]).map!(_ => _.length > 0 ? _ : getProgramName()).join(" ");
4209 
4210     output.put("Usage: ");
4211 
4212     if(cmd.info.usage.length > 0)
4213         substituteProg(output, cmd.info.usage, progName);
4214     else
4215     {
4216         import std.algorithm: filter, each, map;
4217 
4218         alias print = (r) => r
4219             .filter!((ref _) => !_.hideFromHelp)
4220             .each!((ref _)
4221             {
4222                 output.put(' ');
4223                 output.printUsage(_, config);
4224             });
4225 
4226         output.put(progName);
4227 
4228         // named args
4229         print(cmd.arguments.arguments.filter!((ref _) => !_.positional));
4230         // positional args
4231         print(cmd.arguments.positionalArguments.map!(ref (_) => cmd.arguments.arguments[_]));
4232         // sub commands
4233         if(cmd.subCommands.length > 0)
4234             output.put(" <command> [<args>]");
4235     }
4236 
4237     output.put('\n');
4238 }
4239 
4240 void printUsage(T, Output)(auto ref Output output, in Config config)
4241 {
4242     printUsage(output, CommandArguments!T(config), config);
4243 }
4244 
4245 unittest
4246 {
4247     @(Command("MYPROG").Usage("custom usage of %(PROG)"))
4248     struct T
4249     {
4250         string s;
4251     }
4252 
4253     auto test(string usage)
4254     {
4255         import std.array: appender;
4256 
4257         auto a = appender!string;
4258         a.printUsage!T(Config.init);
4259         return a[];
4260     }
4261 
4262     enum expected = "Usage: custom usage of MYPROG\n";
4263     static assert(test("custom usage of %(PROG)") == expected);
4264     assert(test("custom usage of %(PROG)") == expected);
4265 }
4266 
4267 
4268 private void printHelp(Output, ARGS)(auto ref Output output, in Group group, ARGS args, int helpPosition)
4269 {
4270     import std.string: leftJustify;
4271 
4272     if(group.arguments.length == 0 || group.name.length == 0)
4273         return;
4274 
4275     alias printDescription = {
4276         output.put(group.name);
4277         output.put(":\n");
4278 
4279         if (group.description.length > 0)
4280         {
4281             output.put("  ");
4282             output.put(group.description);
4283             output.put("\n\n");
4284         }
4285     };
4286     bool descriptionIsPrinted = false;
4287 
4288     immutable ident = spaces(helpPosition + 2);
4289 
4290     foreach(idx; group.arguments)
4291     {
4292         auto arg = &args[idx];
4293 
4294         if(arg.invocation.length == 0)
4295             continue;
4296 
4297         if(!descriptionIsPrinted)
4298         {
4299             printDescription();
4300             descriptionIsPrinted = true;
4301         }
4302 
4303         if(arg.invocation.length <= helpPosition - 4) // 2=indent, 2=two spaces between invocation and help text
4304         {
4305             import std.array: appender;
4306 
4307             auto invocation = appender!string;
4308             invocation ~= "  ";
4309             invocation ~= arg.invocation.leftJustify(helpPosition);
4310             output.wrapMutiLine(arg.help, 80-2, invocation[], ident);
4311         }
4312         else
4313         {
4314             // long action name; start on the next line
4315             output.put("  ");
4316             output.put(arg.invocation);
4317             output.put("\n");
4318             output.wrapMutiLine(arg.help, 80-2, ident, ident);
4319         }
4320     }
4321 
4322     output.put('\n');
4323 }
4324 
4325 
4326 private void printHelp(Output)(auto ref Output output, in Arguments arguments, in Config config, bool helpArgIsPrinted = false)
4327 {
4328     import std.algorithm: map, maxElement, min;
4329     import std.array: appender, array;
4330 
4331     // pre-compute the output
4332     auto args =
4333         arguments.arguments
4334         .map!((ref _)
4335         {
4336             struct Result
4337             {
4338                 string invocation, help;
4339             }
4340 
4341             if(_.hideFromHelp)
4342                 return Result.init;
4343 
4344             if(isHelpArgument(_.names[0]))
4345             {
4346                 if(helpArgIsPrinted)
4347                     return Result.init;
4348 
4349                 helpArgIsPrinted = true;
4350             }
4351 
4352             auto invocation = appender!string;
4353             invocation.printInvocation(_, _.names, config);
4354 
4355             return Result(invocation[], _.description);
4356         }).array;
4357 
4358     immutable maxInvocationWidth = args.map!(_ => _.invocation.length).maxElement;
4359     immutable helpPosition = min(maxInvocationWidth + 4, 24);
4360 
4361     //user-defined groups
4362     foreach(ref group; arguments.groups[2..$])
4363         output.printHelp(group, args, helpPosition);
4364 
4365     //required args
4366     output.printHelp(arguments.requiredGroup, args, helpPosition);
4367 
4368     //optionals args
4369     output.printHelp(arguments.optionalGroup, args, helpPosition);
4370 
4371     if(arguments.parentArguments)
4372         output.printHelp(*arguments.parentArguments, config, helpArgIsPrinted);
4373 }
4374 
4375 private void printHelp(Output)(auto ref Output output, in CommandInfo[] commands, in Config config)
4376 {
4377     import std.algorithm: map, maxElement, min;
4378     import std.array: appender, array, join;
4379 
4380     if(commands.length == 0)
4381         return;
4382 
4383     output.put("Available commands:\n");
4384 
4385     // pre-compute the output
4386     auto cmds = commands
4387         .map!((ref _)
4388         {
4389             struct Result
4390             {
4391                 string invocation, help;
4392             }
4393 
4394             //if(_.hideFromHelp)
4395             //    return Result.init;
4396 
4397             return Result(_.names.join(","), _.shortDescription.length > 0 ? _.shortDescription : _.description);
4398         }).array;
4399 
4400     immutable maxInvocationWidth = cmds.map!(_ => _.invocation.length).maxElement;
4401     immutable helpPosition = min(maxInvocationWidth + 4, 24);
4402 
4403 
4404     immutable ident = spaces(helpPosition + 2);
4405 
4406     foreach(const ref cmd; cmds)
4407     {
4408         if(cmd.invocation.length == 0)
4409             continue;
4410 
4411         if(cmd.invocation.length <= helpPosition - 4) // 2=indent, 2=two spaces between invocation and help text
4412         {
4413             import std.array: appender;
4414             import std.string: leftJustify;
4415 
4416             auto invocation = appender!string;
4417             invocation ~= "  ";
4418             invocation ~= cmd.invocation.leftJustify(helpPosition);
4419             output.wrapMutiLine(cmd.help, 80-2, invocation[], ident);
4420         }
4421         else
4422         {
4423             // long action name; start on the next line
4424             output.put("  ");
4425             output.put(cmd.invocation);
4426             output.put("\n");
4427             output.wrapMutiLine(cmd.help, 80-2, ident, ident);
4428         }
4429     }
4430 
4431     output.put('\n');
4432 }
4433 
4434 
4435 private void printHelp(T, Output)(auto ref Output output, in CommandArguments!T cmd, in Config config)
4436 {
4437     printUsage(output, cmd, config);
4438     output.put('\n');
4439 
4440     if(cmd.info.description.length > 0)
4441     {
4442         output.put(cmd.info.description);
4443         output.put("\n\n");
4444     }
4445 
4446     // sub commands
4447     output.printHelp(cmd.subCommands, config);
4448 
4449     output.printHelp(cmd.arguments, config);
4450 
4451     if(cmd.info.epilog.length > 0)
4452     {
4453         output.put(cmd.info.epilog);
4454         output.put('\n');
4455     }
4456 }
4457 
4458 void printHelp(T, Output)(auto ref Output output, in Config config)
4459 {
4460     printHelp(output, CommandArguments!T(config), config);
4461 }
4462 
4463 unittest
4464 {
4465     @(Command("MYPROG")
4466      .Description("custom description")
4467      .Epilog("custom epilog")
4468     )
4469     struct T
4470     {
4471         @NamedArgument  string s;
4472         @(NamedArgument.Placeholder("VALUE"))  string p;
4473 
4474         @(NamedArgument.HideFromHelp())  string hidden;
4475 
4476         enum Fruit { apple, pear };
4477         @(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;
4478 
4479         @(NamedArgument.AllowedValues!([1,4,16,8])) int i;
4480 
4481         @(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;
4482         @(PositionalArgument(1).AllowedValues!(["q","a"])) string param1;
4483 
4484         @TrailingArguments string[] args;
4485     }
4486 
4487     auto test(alias func)()
4488     {
4489         import std.array: appender;
4490 
4491         auto a = appender!string;
4492         func!T(a, Config.init);
4493         return a[];
4494     }
4495     static assert(test!printUsage.length > 0);  // ensure that it works at compile time
4496     static assert(test!printHelp .length > 0);  // ensure that it works at compile time
4497 
4498     assert(test!printUsage == "Usage: MYPROG [-s S] [-p VALUE] -f {apple,pear} [-i {1,4,16,8}] [-h] param0 {q,a}\n");
4499     assert(test!printHelp  == "Usage: MYPROG [-s S] [-p VALUE] -f {apple,pear} [-i {1,4,16,8}] [-h] param0 {q,a}\n\n"~
4500         "custom description\n\n"~
4501         "Required arguments:\n"~
4502         "  -f {apple,pear}, --fruit {apple,pear}\n"~
4503         "                          This is a help text for fruit. Very very very very\n"~
4504         "                          very very very very very very very very very very\n"~
4505         "                          very very very very very long text\n"~
4506         "  param0                  This is a help text for param0. Very very very very\n"~
4507         "                          very very very very very very very very very very\n"~
4508         "                          very very very very very long text\n"~
4509         "  {q,a}                   \n\n"~
4510         "Optional arguments:\n"~
4511         "  -s S                    \n"~
4512         "  -p VALUE                \n"~
4513         "  -i {1,4,16,8}           \n"~
4514         "  -h, --help              Show this help message and exit\n\n"~
4515         "custom epilog\n");
4516 }
4517 
4518 unittest
4519 {
4520     @Command("MYPROG")
4521     struct T
4522     {
4523         @(ArgumentGroup("group1").Description("group1 description"))
4524         {
4525             @NamedArgument
4526             {
4527                 string a;
4528                 string b;
4529             }
4530             @PositionalArgument(0) string p;
4531         }
4532 
4533         @(ArgumentGroup("group2").Description("group2 description"))
4534         @NamedArgument
4535         {
4536             string c;
4537             string d;
4538         }
4539         @PositionalArgument(1) string q;
4540     }
4541 
4542     auto test(alias func)()
4543     {
4544         import std.array: appender;
4545 
4546         auto a = appender!string;
4547         func!T(a, Config.init);
4548         return a[];
4549     }
4550 
4551     assert(test!printHelp  == "Usage: MYPROG [-a A] [-b B] [-c C] [-d D] [-h] p q\n\n"~
4552         "group1:\n"~
4553         "  group1 description\n\n"~
4554         "  -a A          \n"~
4555         "  -b B          \n"~
4556         "  p             \n\n"~
4557         "group2:\n"~
4558         "  group2 description\n\n"~
4559         "  -c C          \n"~
4560         "  -d D          \n\n"~
4561         "Required arguments:\n"~
4562         "  q             \n\n"~
4563         "Optional arguments:\n"~
4564         "  -h, --help    Show this help message and exit\n\n");
4565 }
4566 
4567 unittest
4568 {
4569     @Command("MYPROG")
4570     struct T
4571     {
4572         @(Command("cmd1").ShortDescription("Perform cmd 1"))
4573         struct CMD1
4574         {
4575             string a;
4576         }
4577         @(Command("very-long-command-name-2").ShortDescription("Perform cmd 2"))
4578         struct CMD2
4579         {
4580             string b;
4581         }
4582 
4583         string c;
4584         string d;
4585 
4586         SumType!(CMD1, CMD2) cmd;
4587     }
4588 
4589     auto test(alias func)()
4590     {
4591         import std.array: appender;
4592 
4593         auto a = appender!string;
4594         func!T(a, Config.init);
4595         return a[];
4596     }
4597 
4598     assert(test!printHelp  == "Usage: MYPROG [-c C] [-d D] [-h] <command> [<args>]\n\n"~
4599         "Available commands:\n"~
4600         "  cmd1                    Perform cmd 1\n"~
4601         "  very-long-command-name-2\n"~
4602         "                          Perform cmd 2\n\n"~
4603         "Optional arguments:\n"~
4604         "  -c C          \n"~
4605         "  -d D          \n"~
4606         "  -h, --help    Show this help message and exit\n\n");
4607 }
4608 
4609 unittest
4610 {
4611     @Command("MYPROG")
4612     struct T
4613     {
4614         @(NamedArgument.HideFromHelp())  string s;
4615     }
4616 
4617     assert(parseCLIArgs!T(["-h","-s","asd"]).isNull());
4618     assert(parseCLIArgs!T(["-h"], (T t) { assert(false); }) == 0);
4619 
4620     auto args = ["-h","-s","asd"];
4621     assert(parseCLIKnownArgs!T(args).isNull());
4622     assert(args.length == 3);
4623     assert(parseCLIKnownArgs!T(["-h"], (T t, string[] args) { assert(false); }) == 0);
4624 }
4625 
4626 unittest
4627 {
4628     @Command("MYPROG")
4629     struct T
4630     {
4631         @(NamedArgument.Required())  string s;
4632     }
4633 
4634     assert(parseCLIArgs!T([], (T t) { assert(false); }) != 0);
4635 }
4636 
4637 unittest
4638 {
4639     @Command("MYPROG")
4640     struct T
4641     {
4642         @MutuallyExclusive()
4643         {
4644             string a;
4645             string b;
4646         }
4647     }
4648 
4649     // Either or no argument is allowed
4650     assert(CLI!T.parseArgs!((T t) {})(["-a","a"]) == 0);
4651     assert(CLI!T.parseArgs!((T t) {})(["-b","b"]) == 0);
4652     assert(CLI!T.parseArgs!((T t) {})([]) == 0);
4653 
4654     // Both arguments are not allowed
4655     assert(CLI!T.parseArgs!((T t) { assert(false); })(["-a","a","-b","b"]) != 0);
4656 
4657     assert(parseCLIArgs!T(["-a","a","-b","b"], (T t) { assert(false); }) != 0);
4658     assert(parseCLIArgs!T(["-a","a"], (T t) {}) == 0);
4659     assert(parseCLIArgs!T(["-b","b"], (T t) {}) == 0);
4660     assert(parseCLIArgs!T([], (T t) {}) == 0);
4661 }
4662 
4663 unittest
4664 {
4665     @Command("MYPROG")
4666     struct T
4667     {
4668         @(MutuallyExclusive().Required())
4669         {
4670             string a;
4671             string b;
4672         }
4673     }
4674 
4675     // Either argument is allowed
4676     assert(CLI!T.parseArgs!((T t) {})(["-a","a"]) == 0);
4677     assert(CLI!T.parseArgs!((T t) {})(["-b","b"]) == 0);
4678 
4679     // Both arguments or no argument is not allowed
4680     assert(CLI!T.parseArgs!((T t) { assert(false); })([]) != 0);
4681     assert(CLI!T.parseArgs!((T t) { assert(false); })(["-a","a","-b","b"]) != 0);
4682 
4683     assert(parseCLIArgs!T(["-a","a","-b","b"], (T t) { assert(false); }) != 0);
4684     assert(parseCLIArgs!T(["-a","a"], (T t) {}) == 0);
4685     assert(parseCLIArgs!T(["-b","b"], (T t) {}) == 0);
4686     assert(parseCLIArgs!T([], (T t) { assert(false); }) != 0);
4687 }
4688 
4689 unittest
4690 {
4691     @Command("MYPROG")
4692     struct T
4693     {
4694         @RequiredTogether()
4695         {
4696             string a;
4697             string b;
4698         }
4699     }
4700 
4701     // Both or no argument is allowed
4702     assert(CLI!T.parseArgs!((T t) {})(["-a","a","-b","b"]) == 0);
4703     assert(CLI!T.parseArgs!((T t) {})([]) == 0);
4704 
4705     // Single argument is not allowed
4706     assert(CLI!T.parseArgs!((T t) { assert(false); })(["-a","a"]) != 0);
4707     assert(CLI!T.parseArgs!((T t) { assert(false); })(["-b","b"]) != 0);
4708 
4709     assert(parseCLIArgs!T(["-a","a","-b","b"], (T t) {}) == 0);
4710     assert(parseCLIArgs!T(["-a","a"], (T t) { assert(false); }) != 0);
4711     assert(parseCLIArgs!T(["-b","b"], (T t) { assert(false); }) != 0);
4712     assert(parseCLIArgs!T([], (T t) {}) == 0);
4713 }
4714 
4715 unittest
4716 {
4717     @Command("MYPROG")
4718     struct T
4719     {
4720         @(RequiredTogether().Required())
4721         {
4722             string a;
4723             string b;
4724         }
4725     }
4726 
4727     // Both arguments are allowed
4728     assert(CLI!T.parseArgs!((T t) {})(["-a","a","-b","b"]) == 0);
4729 
4730     // Single argument or no argument is not allowed
4731     assert(CLI!T.parseArgs!((T t) { assert(false); })(["-a","a"]) != 0);
4732     assert(CLI!T.parseArgs!((T t) { assert(false); })(["-b","b"]) != 0);
4733     assert(CLI!T.parseArgs!((T t) { assert(false); })([]) != 0);
4734 
4735     assert(parseCLIArgs!T(["-a","a","-b","b"], (T t) {}) == 0);
4736     assert(parseCLIArgs!T(["-a","a"], (T t) { assert(false); }) != 0);
4737     assert(parseCLIArgs!T(["-b","b"], (T t) { assert(false); }) != 0);
4738     assert(parseCLIArgs!T([], (T t) { assert(false); }) != 0);
4739 }
4740 
4741 
4742 private void wrapMutiLine(Output, S)(auto ref Output output,
4743                                      S s,
4744                                      in size_t columns = 80,
4745                                      S firstindent = null,
4746                                      S indent = null,
4747                                      in size_t tabsize = 8)
4748 if (isSomeString!S)
4749 {
4750     import std.string: wrap, lineSplitter, join;
4751     import std.algorithm: map, copy;
4752 
4753     auto lines = s.lineSplitter;
4754     if(lines.empty)
4755     {
4756         output.put(firstindent);
4757         output.put("\n");
4758         return;
4759     }
4760 
4761     output.put(lines.front.wrap(columns, firstindent, indent, tabsize));
4762     lines.popFront;
4763 
4764     lines.map!(s => s.wrap(columns, indent, indent, tabsize)).copy(output);
4765 }
4766 
4767 unittest
4768 {
4769     string test(string s, size_t columns, string firstindent = null, string indent = null)
4770     {
4771         import std.array: appender;
4772         auto a = appender!string;
4773         a.wrapMutiLine(s, columns, firstindent, indent);
4774         return a[];
4775     }
4776     assert(test("a short string", 7) == "a short\nstring\n");
4777     assert(test("a\nshort string", 7) == "a\nshort\nstring\n");
4778 
4779     // wrap will not break inside of a word, but at the next space
4780     assert(test("a short string", 4) == "a\nshort\nstring\n");
4781 
4782     assert(test("a short string", 7, "\t") == "\ta\nshort\nstring\n");
4783     assert(test("a short string", 7, "\t", "    ") == "\ta\n    short\n    string\n");
4784 }