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