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 ParsingArgument(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             auto res = info.checkValuesCount(argName, rawValues.length);
687             if(!res)
688             {
689                 config.onError(res.errorMsg);
690                 return res;
691             }
692 
693             auto param = RawParam(config, argName, rawValues);
694 
695             auto target = &__traits(getMember, receiver, symbol);
696 
697             static if(is(typeof(target) == function) || is(typeof(target) == delegate))
698                 return uda.parsingFunc.parse(target, param) ? Result.Success : Result.Failure;
699             else
700                 return uda.parsingFunc.parse(*target, param) ? Result.Success : Result.Failure;
701         }
702         catch(Exception e)
703         {
704             config.onError(argName, ": ", e.msg);
705             return Result.Failure;
706         }
707     };
708 
709 private auto ParsingSubCommand(COMMAND_TYPE, CommandInfo info, RECEIVER, alias symbol)(const CommandArguments!RECEIVER* parentArguments)
710 {
711     return delegate(ref Parser parser, const ref Parser.Argument arg, ref RECEIVER receiver)
712     {
713         import std.sumtype: match;
714 
715         auto target = &__traits(getMember, receiver, symbol);
716 
717         alias parse = (ref COMMAND_TYPE cmdTarget)
718         {
719             static if(!is(COMMAND_TYPE == Default!TYPE, TYPE))
720                 alias TYPE = COMMAND_TYPE;
721 
722             auto command = CommandArguments!TYPE(parser.config, info, parentArguments);
723 
724             return arg.match!(_ => parser.parse(command, cmdTarget, _));
725         };
726 
727 
728         static if(typeof(*target).Types.length == 1)
729             return (*target).match!parse;
730         else
731         {
732             if((*target).match!((COMMAND_TYPE t) => false, _ => true))
733                 *target = COMMAND_TYPE.init;
734 
735             return (*target).match!(parse,
736                 (_)
737                 {
738                     assert(false, "This should never happen");
739                     return Result.Failure;
740                 }
741             );
742         }
743     };
744 }
745 
746 struct SubCommands {}
747 
748 // Default subcommand
749 struct Default(COMMAND)
750 {
751     COMMAND command;
752     alias command this;
753 }
754 
755 unittest
756 {
757     struct T
758     {
759         @NamedArgument
760         int a;
761         @(NamedArgument.Optional())
762         int b;
763         @(NamedArgument.Required())
764         int c;
765         @NamedArgument
766         int d;
767         @(NamedArgument.Required())
768         int e;
769         @NamedArgument
770         int f;
771     }
772 
773     enum config = {
774         Config config;
775         config.addHelp = false;
776         return config;
777     }();
778 
779     static assert(CommandArguments!T(config).arguments.arguments.length == 6);
780 
781     auto a = CommandArguments!T(config);
782     assert(a.arguments.requiredGroup.arguments == [2,4]);
783     assert(a.arguments.argsNamed == ["a":0LU, "b":1LU, "c":2LU, "d":3LU, "e":4LU, "f":5LU]);
784     assert(a.arguments.argsPositional == []);
785 }
786 
787 unittest
788 {
789     struct T
790     {
791         int a,b,c,d,e,f;
792     }
793 
794     enum config = {
795         Config config;
796         config.addHelp = false;
797         return config;
798     }();
799 
800     static assert(CommandArguments!T(config).arguments.arguments.length == 6);
801 
802     auto a = CommandArguments!T(config);
803     assert(a.arguments.requiredGroup.arguments == []);
804     assert(a.arguments.argsNamed == ["a":0LU, "b":1LU, "c":2LU, "d":3LU, "e":4LU, "f":5LU]);
805     assert(a.arguments.argsPositional == []);
806 }
807 
808 unittest
809 {
810     struct T1
811     {
812         @(NamedArgument("1"))
813         @(NamedArgument("2"))
814         int a;
815     }
816     static assert(!__traits(compiles, { CommandArguments!T1(Config.init); }));
817 
818     struct T2
819     {
820         @(NamedArgument("1"))
821         int a;
822         @(NamedArgument("1"))
823         int b;
824     }
825     static assert(!__traits(compiles, { CommandArguments!T1(Config.init); }));
826 
827     struct T3
828     {
829         @(PositionalArgument(0)) int a;
830         @(PositionalArgument(0)) int b;
831     }
832     static assert(!__traits(compiles, { CommandArguments!T3(Config.init); }));
833 
834     struct T4
835     {
836         @(PositionalArgument(0)) int a;
837         @(PositionalArgument(2)) int b;
838     }
839     static assert(!__traits(compiles, { CommandArguments!T4(Config.init); }));
840 }
841 
842 private void checkArgumentName(T)(char namedArgChar)
843 {
844     import std.exception: enforce;
845 
846     static foreach(sym; getSymbolsByUDA!(T, ArgumentUDA))
847         static foreach(name; getUDAs!(__traits(getMember, T, __traits(identifier, sym)), ArgumentUDA)[0].info.names)
848             enforce(name[0] != namedArgChar, "Name of argument should not begin with '"~namedArgChar~"': "~name);
849 }
850 
851 private auto consumeValuesFromCLI(ref string[] args, in ArgumentInfo argumentInfo, in Config config)
852 {
853     import std.range: empty, front, popFront;
854 
855     immutable minValuesCount = argumentInfo.minValuesCount.get;
856     immutable maxValuesCount = argumentInfo.maxValuesCount.get;
857 
858     string[] values;
859 
860     if(minValuesCount > 0)
861     {
862         if(minValuesCount < args.length)
863         {
864             values = args[0..minValuesCount];
865             args = args[minValuesCount..$];
866         }
867         else
868         {
869             values = args;
870             args = [];
871         }
872     }
873 
874     while(!args.empty &&
875         values.length < maxValuesCount &&
876         (args.front.length == 0 || args.front[0] != config.namedArgChar))
877     {
878         values ~= args.front;
879         args.popFront();
880     }
881 
882     return values;
883 }
884 
885 
886 private enum helpArgument = {
887     ArgumentInfo arg;
888     arg.names = ["h","help"];
889     arg.description = "Show this help message and exit";
890     arg.minValuesCount = 0;
891     arg.maxValuesCount = 0;
892     arg.allowBooleanNegation = false;
893     return arg;
894 }();
895 
896 private bool isHelpArgument(string name)
897 {
898     static foreach(n; helpArgument.names)
899         if(n == name)
900             return true;
901 
902     return false;
903 }
904 
905 unittest
906 {
907     assert(isHelpArgument("h"));
908     assert(isHelpArgument("help"));
909     assert(!isHelpArgument("a"));
910     assert(!isHelpArgument("help1"));
911 }
912 
913 struct Result
914 {
915     int  resultCode;
916 
917     private enum Status { failure, success, unknownArgument };
918     private Status status;
919 
920     private string errorMsg;
921 
922     bool opCast(type)() const if (is(type == bool))
923     {
924         return status == Status.success;
925     }
926 
927     private static enum Failure = Result(1, Status.failure);
928     private static enum Success = Result(0, Status.success);
929     private static enum UnknownArgument = Result(0, Status.unknownArgument);
930 
931     private static auto Error(A...)(A args) nothrow
932     {
933         import std.conv: text;
934         import std.stdio: stderr, writeln;
935 
936         return Result(1, Status.failure, text!A(args));
937     }
938 }
939 
940 private struct Parser
941 {
942     struct Unknown {}
943     struct EndOfArgs {}
944     struct Positional {}
945     struct NamedShort {
946         string name;
947         string nameWithDash;
948         string value = null;  // null when there is no value
949     }
950     struct NamedLong {
951         string name;
952         string nameWithDash;
953         string value = null;  // null when there is no value
954     }
955 
956     import std.sumtype: SumType;
957     alias Argument = SumType!(Unknown, EndOfArgs, Positional, NamedShort, NamedLong);
958 
959     immutable Config config;
960 
961     string[] args;
962     string[] unrecognizedArgs;
963 
964     bool[size_t] idxParsedArgs;
965     size_t idxNextPositional = 0;
966 
967     private alias CmdParser = Result delegate(const ref Argument);
968 
969     CmdParser[] cmdStack;
970 
971     Argument splitArgumentNameValue(string arg)
972     {
973         import std.string : indexOf;
974 
975         if(arg.length == 0)
976             return Argument.init;
977 
978         if(arg == config.endOfArgs)
979             return Argument(EndOfArgs.init);
980 
981         if(arg[0] != config.namedArgChar)
982             return Argument(Positional.init);
983 
984         if(arg.length == 1 || arg.length == 2 && arg[1] == config.namedArgChar)
985             return Argument.init;
986 
987         auto idxAssignChar = config.assignChar == char.init ? -1 : arg.indexOf(config.assignChar);
988 
989         immutable string nameWithDash = idxAssignChar < 0 ? arg  : arg[0 .. idxAssignChar];
990         immutable string value        = idxAssignChar < 0 ? null : arg[idxAssignChar + 1 .. $];
991 
992         return arg[1] == config.namedArgChar
993         ? Argument(NamedLong (nameWithDash[2..$], nameWithDash, value))
994         : Argument(NamedShort(nameWithDash[1..$], nameWithDash, value));
995     }
996 
997     auto parseArgument(T, PARSE)(PARSE parse, ref T receiver, string value, string nameWithDash, size_t argIndex)
998     {
999         immutable res = parse(config, nameWithDash, receiver, value, args);
1000         if(!res)
1001             return res;
1002 
1003         idxParsedArgs[argIndex] = true;
1004 
1005         return Result.Success;
1006     }
1007 
1008     auto parseSubCommand(T)(const ref CommandArguments!T cmd, ref T receiver)
1009     {
1010         import std.range: front, popFront;
1011 
1012         auto found = cmd.findSubCommand(args.front);
1013         if(found.parse is null)
1014             return Result.UnknownArgument;
1015 
1016         if(found.level < cmdStack.length)
1017             cmdStack.length = found.level;
1018 
1019         cmdStack ~= (const ref arg) => found.parse(this, arg, receiver);
1020 
1021         args.popFront();
1022 
1023         return Result.Success;
1024     }
1025 
1026     auto parse(T)(const ref CommandArguments!T cmd, ref T receiver, Unknown)
1027     {
1028         return Result.UnknownArgument;
1029     }
1030 
1031     auto parse(T)(const ref CommandArguments!T cmd, ref T receiver, EndOfArgs)
1032     {
1033         static if(is(typeof(cmd.setTrailingArgs)))
1034             cmd.setTrailingArgs(receiver, args[1..$]);
1035         else
1036             unrecognizedArgs ~= args[1..$];
1037 
1038         args = [];
1039 
1040         return Result.Success;
1041     }
1042 
1043     auto parse(T)(const ref CommandArguments!T cmd, ref T receiver, Positional)
1044     {
1045         auto foundArg = cmd.findPositionalArgument(idxNextPositional);
1046         if(foundArg.arg is null)
1047             return parseSubCommand(cmd, receiver);
1048 
1049         immutable res = parseArgument(cmd.parseFunctions[foundArg.index], receiver, null, foundArg.arg.names[0], foundArg.index);
1050         if(!res)
1051             return res;
1052 
1053         idxNextPositional++;
1054 
1055         return Result.Success;
1056     }
1057 
1058     auto parse(T)(const ref CommandArguments!T cmd, ref T receiver, NamedLong arg)
1059     {
1060         import std.algorithm : startsWith;
1061         import std.range: popFront;
1062 
1063         auto foundArg = cmd.findNamedArgument(arg.name);
1064 
1065         if(foundArg.arg is null && arg.name.startsWith("no-"))
1066         {
1067             foundArg = cmd.findNamedArgument(arg.name[3..$]);
1068             if(foundArg.arg is null || !foundArg.arg.allowBooleanNegation)
1069                 return Result.UnknownArgument;
1070 
1071             arg.value = "false";
1072         }
1073 
1074         if(foundArg.arg is null)
1075             return Result.UnknownArgument;
1076 
1077         args.popFront();
1078         return parseArgument(cmd.parseFunctions[foundArg.index], receiver, arg.value, arg.nameWithDash, foundArg.index);
1079     }
1080 
1081     auto parse(T)(const ref CommandArguments!T cmd, ref T receiver, NamedShort arg)
1082     {
1083         import std.range: popFront;
1084 
1085         auto foundArg = cmd.findNamedArgument(arg.name);
1086         if(foundArg.arg !is null)
1087         {
1088             args.popFront();
1089             return parseArgument(cmd.parseFunctions[foundArg.index], receiver, arg.value, arg.nameWithDash, foundArg.index);
1090         }
1091 
1092         // Try to parse "-ABC..." where "A","B","C" are different single-letter arguments
1093         do
1094         {
1095             auto name = [arg.name[0]];
1096             foundArg = cmd.findNamedArgument(name);
1097             if(foundArg.arg is null)
1098                 return Result.UnknownArgument;
1099 
1100             // In case of bundling there can be no or one argument value
1101             if(config.bundling && foundArg.arg.minValuesCount.get > 1)
1102                 return Result.UnknownArgument;
1103 
1104             // In case of NO bundling there MUST be one argument value
1105             if(!config.bundling && foundArg.arg.minValuesCount.get != 1)
1106                 return Result.UnknownArgument;
1107 
1108             string value;
1109             if(foundArg.arg.minValuesCount == 0)
1110                 arg.name = arg.name[1..$];
1111             else
1112             {
1113                 // Bundling case: try to parse "-ABvalue" where "A","B" are different single-letter arguments and "value" is a value for "B"
1114                 // No bundling case: try to parse "-Avalue" where "A" is a single-letter argument and "value" is its value
1115                 value = arg.name[1..$];
1116                 arg.name = "";
1117             }
1118 
1119             immutable res = parseArgument(cmd.parseFunctions[foundArg.index], receiver, value, "-"~name, foundArg.index);
1120             if(!res)
1121                 return res;
1122         }
1123         while(arg.name.length > 0);
1124 
1125         args.popFront();
1126         return Result.Success;
1127     }
1128 
1129     auto parse(Argument arg)
1130     {
1131         import std.range: front, popFront;
1132 
1133         foreach_reverse(cmdParser; cmdStack)
1134         {
1135             immutable res = cmdParser(arg);
1136             if(res.status != Result.Status.unknownArgument)
1137                 return res;
1138         }
1139 
1140         unrecognizedArgs ~= args.front;
1141         args.popFront();
1142 
1143         return Result.Success;
1144     }
1145 
1146     auto parseAll(T)(const ref CommandArguments!T cmd, ref T receiver)
1147     {
1148         import std.range: empty, front;
1149 
1150         cmdStack ~= (const ref arg)
1151         {
1152             import std.sumtype: match;
1153 
1154             return arg.match!(_ => parse(cmd, receiver, _));
1155         };
1156 
1157         auto found = cmd.findSubCommand(DEFAULT_COMMAND);
1158         if(found.parse !is null)
1159             cmdStack ~= (const ref arg) => found.parse(this, arg, receiver);
1160 
1161         while(!args.empty)
1162         {
1163             immutable res = parse(splitArgumentNameValue(args.front));
1164             if(!res)
1165                 return res;
1166         }
1167 
1168         if(!cmd.checkRestrictions(idxParsedArgs, config))
1169             return Result.Failure;
1170 
1171         return Result.Success;
1172     }
1173 }
1174 
1175 unittest
1176 {
1177     assert(Parser.init.splitArgumentNameValue("") == Parser.Argument(Parser.Unknown.init));
1178     assert(Parser.init.splitArgumentNameValue("-") == Parser.Argument(Parser.Unknown.init));
1179     assert(Parser.init.splitArgumentNameValue("--") == Parser.Argument(Parser.EndOfArgs.init));
1180     assert(Parser.init.splitArgumentNameValue("abc=4") == Parser.Argument(Parser.Positional.init));
1181     assert(Parser.init.splitArgumentNameValue("-abc") == Parser.Argument(Parser.NamedShort("abc", "-abc", null)));
1182     assert(Parser.init.splitArgumentNameValue("--abc") == Parser.Argument(Parser.NamedLong("abc", "--abc", null)));
1183     assert(Parser.init.splitArgumentNameValue("-abc=fd") == Parser.Argument(Parser.NamedShort("abc", "-abc", "fd")));
1184     assert(Parser.init.splitArgumentNameValue("--abc=fd") == Parser.Argument(Parser.NamedLong("abc", "--abc", "fd")));
1185     assert(Parser.init.splitArgumentNameValue("-abc=") == Parser.Argument(Parser.NamedShort("abc", "-abc", "")));
1186     assert(Parser.init.splitArgumentNameValue("--abc=") == Parser.Argument(Parser.NamedLong("abc", "--abc", "")));
1187     assert(Parser.init.splitArgumentNameValue("-=abc") == Parser.Argument(Parser.NamedShort("", "-", "abc")));
1188     assert(Parser.init.splitArgumentNameValue("--=abc") == Parser.Argument(Parser.NamedLong("", "--", "abc")));
1189 }
1190 
1191 
1192 private Result parseCLIKnownArgs(T)(ref T receiver,
1193                                     string[] args,
1194                                     out string[] unrecognizedArgs,
1195                                     const ref CommandArguments!T cmd,
1196                                     in Config config)
1197 {
1198     auto parser = Parser(config, args);
1199 
1200     immutable res = parser.parseAll(cmd, receiver);
1201     if(!res)
1202         return res;
1203 
1204     unrecognizedArgs = parser.unrecognizedArgs;
1205 
1206     return Result.Success;
1207 }
1208 
1209 deprecated("Use CLI!(config, COMMAND).parseKnownArgs")
1210 Result parseCLIKnownArgs(T)(ref T receiver,
1211                             string[] args,
1212                             out string[] unrecognizedArgs,
1213                             in Config config)
1214 {
1215     auto command = CommandArguments!T(config);
1216     return parseCLIKnownArgs(receiver, args, unrecognizedArgs, command, config);
1217 }
1218 
1219 deprecated("Use CLI!COMMAND.parseKnownArgs")
1220 Result parseCLIKnownArgs(T)(ref T receiver,
1221                             string[] args,
1222                             out string[] unrecognizedArgs)
1223 {
1224     return CLI!T.parseKnownArgs(receiver, args, unrecognizedArgs);
1225 }
1226 
1227 deprecated("Use CLI!(config, COMMAND).parseKnownArgs")
1228 auto parseCLIKnownArgs(T)(ref T receiver, ref string[] args, in Config config)
1229 {
1230     string[] unrecognizedArgs;
1231 
1232     auto res = parseCLIKnownArgs(receiver, args, unrecognizedArgs, config);
1233     if(res)
1234         args = unrecognizedArgs;
1235 
1236     return res;
1237 }
1238 
1239 deprecated("Use CLI!COMMAND.parseKnownArgs")
1240 auto parseCLIKnownArgs(T)(ref T receiver, ref string[] args)
1241 {
1242     return CLI!T.parseKnownArgs(receiver, args);
1243 }
1244 
1245 deprecated("Use CLI!(config, COMMAND).parseKnownArgs")
1246 Nullable!T parseCLIKnownArgs(T)(ref string[] args, in Config config)
1247 {
1248     import std.typecons : nullable;
1249 
1250     T receiver;
1251 
1252     return parseCLIKnownArgs(receiver, args, config) ? receiver.nullable : Nullable!T.init;
1253 }
1254 
1255 deprecated("Use CLI!COMMAND.parseKnownArgs")
1256 Nullable!T parseCLIKnownArgs(T)(ref string[] args)
1257 {
1258     import std.typecons : nullable;
1259 
1260     T receiver;
1261 
1262     return CLI!T.parseKnownArgs(receiver, args) ? receiver.nullable : Nullable!T.init;
1263 }
1264 
1265 deprecated("Use CLI!(config, COMMAND).parseArgs")
1266 int parseCLIKnownArgs(T, FUNC)(string[] args, FUNC func, in Config config, T initialValue = T.init)
1267 if(__traits(compiles, { func(T.init, args); }))
1268 {
1269     alias value = initialValue;
1270 
1271     auto res = parseCLIKnownArgs(value, args, config);
1272     if(!res)
1273         return res.resultCode;
1274 
1275     static if(__traits(compiles, { int a = cast(int) func(value, args); }))
1276         return cast(int) func(value, args);
1277     else
1278     {
1279         func(value, args);
1280         return 0;
1281     }
1282 }
1283 
1284 deprecated("Use CLI!COMMAND.parseArgs")
1285 int parseCLIKnownArgs(T, FUNC)(string[] args, FUNC func)
1286 if(__traits(compiles, { func(T.init, args); }))
1287 {
1288     T value;
1289 
1290     auto res = CLI!T.parseKnownArgs(value, args);
1291     if(!res)
1292         return res.resultCode;
1293 
1294     static if(__traits(compiles, { int a = cast(int) func(value, args); }))
1295         return cast(int) func(value, args);
1296     else
1297     {
1298         func(value, args);
1299         return 0;
1300     }
1301 }
1302 
1303 
1304 deprecated("Use CLI!(config, COMMAND).parseArgs")
1305 auto parseCLIArgs(T)(ref T receiver, string[] args, in Config config)
1306 {
1307     string[] unrecognizedArgs;
1308 
1309     auto res = parseCLIKnownArgs(receiver, args, unrecognizedArgs, config);
1310 
1311     if(res && unrecognizedArgs.length > 0)
1312     {
1313         config.onError("Unrecognized arguments: ", unrecognizedArgs);
1314         return Result.Failure;
1315     }
1316 
1317     return res;
1318 }
1319 
1320 deprecated("Use CLI!COMMAND.parseArgs")
1321 auto parseCLIArgs(T)(ref T receiver, string[] args)
1322 {
1323     return CLI!T.parseArgs(receiver, args);
1324 }
1325 
1326 deprecated("Use CLI!(config, COMMAND).parseArgs")
1327 Nullable!T parseCLIArgs(T)(string[] args, in Config config)
1328 {
1329     import std.typecons : nullable;
1330 
1331     T receiver;
1332 
1333     return parseCLIArgs(receiver, args, config) ? receiver.nullable : Nullable!T.init;
1334 }
1335 
1336 deprecated("Use CLI!COMMAND.parseArgs")
1337 Nullable!T parseCLIArgs(T)(string[] args)
1338 {
1339     import std.typecons : nullable;
1340 
1341     T receiver;
1342 
1343     return CLI!T.parseArgs(receiver, args) ? receiver.nullable : Nullable!T.init;
1344 }
1345 
1346 deprecated("Use CLI!(config, COMMAND).parseArgs")
1347 int parseCLIArgs(T, FUNC)(string[] args, FUNC func, in Config config, T initialValue = T.init)
1348 if(__traits(compiles, { func(T.init); }))
1349 {
1350     alias value = initialValue;
1351 
1352     auto res = parseCLIArgs(value, args, config);
1353     if(!res)
1354         return res.resultCode;
1355 
1356     static if(__traits(compiles, { int a = cast(int) func(value); }))
1357         return cast(int) func(value);
1358     else
1359     {
1360         func(value);
1361         return 0;
1362     }
1363 }
1364 
1365 deprecated("Use CLI!COMMAND.parseArgs")
1366 int parseCLIArgs(T, FUNC)(string[] args, FUNC func)
1367 if(__traits(compiles, { func(T.init); }))
1368 {
1369     T value;
1370 
1371     auto res = CLI!T.parseArgs(value, args);
1372     if(!res)
1373         return res.resultCode;
1374 
1375     static if(__traits(compiles, { int a = cast(int) func(value); }))
1376         return cast(int) func(value);
1377     else
1378     {
1379         func(value);
1380         return 0;
1381     }
1382 }
1383 
1384 unittest
1385 {
1386     import std.exception;
1387 
1388     struct T
1389     {
1390         @(NamedArgument("--"))
1391         int a;
1392     }
1393     static assert(!__traits(compiles, { enum p = parseCLIArgs!T([]); }));
1394     assertThrown(parseCLIArgs!T([]));
1395 }
1396 
1397 unittest
1398 {
1399 
1400     import std.conv;
1401     import std.traits;
1402 
1403     struct params
1404     {
1405         int no_a;
1406 
1407         @(PositionalArgument(0, "a")
1408         .Description("Argument 'a'")
1409         .Validation!((int a) { return a > 3;})
1410         .PreValidation!((string s) { return s.length > 0;})
1411         .Validation!((int a) { return a > 0;})
1412         )
1413         int a;
1414 
1415         int no_b;
1416 
1417         @(NamedArgument(["b", "boo"]).Description("Flag boo")
1418         .AllowNoValue!55
1419         )
1420         int b;
1421 
1422         int no_c;
1423     }
1424 
1425     enum p = CommandArguments!params(Config.init);
1426     static assert(p.findNamedArgument("a").arg is null);
1427     static assert(p.findNamedArgument("b").arg !is null);
1428     static assert(p.findNamedArgument("boo").arg !is null);
1429     static assert(p.findPositionalArgument(0).arg !is null);
1430     static assert(p.findPositionalArgument(1).arg is null);
1431 }
1432 
1433 unittest
1434 {
1435     import std.typecons : tuple;
1436 
1437     struct T
1438     {
1439         string a;
1440         string b;
1441     }
1442 
1443     auto test(string[] args)
1444     {
1445         return tuple(args.parseCLIKnownArgs!T.get, args);
1446     }
1447 
1448     assert(test(["-a","A","--"]) == tuple(T("A"), []));
1449     static assert(test(["-a","A","--","-b","B"]) == tuple(T("A"), ["-b","B"]));
1450 
1451     {
1452         T args;
1453 
1454         args.parseCLIArgs([ "-a", "A"]);
1455         args.parseCLIArgs([ "-b", "B"]);
1456 
1457         assert(args == T("A","B"));
1458     }
1459 }
1460 
1461 unittest
1462 {
1463     struct T
1464     {
1465         string a;
1466     }
1467 
1468     {
1469         auto test_called(string[] args)
1470         {
1471             bool called;
1472             auto dg = (T t) {
1473                 called = true;
1474             };
1475             assert(args.parseCLIArgs!T(dg) == 0 || !called);
1476             return called;
1477         }
1478 
1479         static assert(test_called([]));
1480         assert(test_called([]));
1481         assert(!test_called(["-g"]));
1482     }
1483     {
1484         auto test_called(string[] args)
1485         {
1486             bool called;
1487             auto dg = (T t, string[] args) {
1488                 assert(args.length == 0 || args == ["-g"]);
1489                 called = true;
1490             };
1491             assert(args.parseCLIKnownArgs!T(dg) == 0);
1492             return called;
1493         }
1494 
1495         assert(test_called([]));
1496         static assert(test_called(["-g"]));
1497     }
1498 }
1499 
1500 unittest
1501 {
1502     struct T
1503     {
1504         string a;
1505     }
1506 
1507     int my_main(T command)
1508     {
1509         // do something
1510         return 0;
1511     }
1512 
1513     static assert(["-a","aa"].parseCLIArgs!T(&my_main) == 0);
1514     assert(["-a","aa"].parseCLIArgs!T(&my_main) == 0);
1515 }
1516 
1517 unittest
1518 {
1519     struct T
1520     {
1521         string a;
1522     }
1523 
1524     auto args = [ "-a", "A", "-c", "C" ];
1525 
1526     assert(parseCLIKnownArgs!T(args).get == T("A"));
1527     assert(args == ["-c", "C"]);
1528 }
1529 
1530 unittest
1531 {
1532 
1533     struct T
1534     {
1535         @NamedArgument                           string x;
1536         @NamedArgument                           string foo;
1537         @(PositionalArgument(0, "a").Optional()) string a;
1538         @(PositionalArgument(1, "b").Optional()) string[] b;
1539     }
1540     static assert(["--foo","FOO","-x","X"].parseCLIArgs!T.get == T("X", "FOO"));
1541     static assert(["--foo=FOO","-x=X"].parseCLIArgs!T.get == T("X", "FOO"));
1542     static assert(["--foo=FOO","1","-x=X"].parseCLIArgs!T.get == T("X", "FOO", "1"));
1543     static assert(["--foo=FOO","1","2","3","4"].parseCLIArgs!T.get == T(string.init, "FOO", "1",["2","3","4"]));
1544     static assert(["-xX"].parseCLIArgs!T.get == T("X"));
1545     assert(["--foo","FOO","-x","X"].parseCLIArgs!T.get == T("X", "FOO"));
1546     assert(["--foo=FOO","-x=X"].parseCLIArgs!T.get == T("X", "FOO"));
1547     assert(["--foo=FOO","1","-x=X"].parseCLIArgs!T.get == T("X", "FOO", "1"));
1548     assert(["--foo=FOO","1","2","3","4"].parseCLIArgs!T.get == T(string.init, "FOO", "1",["2","3","4"]));
1549     assert(["-xX"].parseCLIArgs!T.get == T("X"));
1550 
1551     struct T1
1552     {
1553         @(PositionalArgument(0, "a")) string[3] a;
1554         @(PositionalArgument(1, "b")) string[] b;
1555     }
1556     static assert(["1","2","3","4","5","6"].parseCLIArgs!T1.get == T1(["1","2","3"],["4","5","6"]));
1557     assert(["1","2","3","4","5","6"].parseCLIArgs!T1.get == T1(["1","2","3"],["4","5","6"]));
1558 
1559     struct T2
1560     {
1561         bool foo = true;
1562     }
1563     static assert(["--no-foo"].parseCLIArgs!T2.get == T2(false));
1564     assert(["--no-foo"].parseCLIArgs!T2.get == T2(false));
1565 }
1566 
1567 unittest
1568 {
1569     struct T
1570     {
1571         @(PositionalArgument(0, "a").Optional())
1572         string a = "not set";
1573 
1574         @(NamedArgument.Required())
1575         int b;
1576     }
1577 
1578     assert(CLI!T.parseArgs!((T t) { assert(t == T("not set", 4)); })(["-b", "4"]) == 0);
1579     static assert(["-b", "4"].parseCLIArgs!T.get == T("not set", 4));
1580     assert(["-b", "4"].parseCLIArgs!T.get == T("not set", 4));
1581 }
1582 
1583 unittest
1584 {
1585     struct T
1586     {
1587         string x;
1588         string foo;
1589     }
1590 
1591     auto test(T)(string[] args)
1592     {
1593         Config config;
1594         config.caseSensitive = false;
1595 
1596         return args.parseCLIArgs!T(config).get;
1597     }
1598 
1599     static assert(test!T(["--Foo","FOO","-X","X"]) == T("X", "FOO"));
1600     static assert(test!T(["--FOo=FOO","-X=X"]) == T("X", "FOO"));
1601     assert(test!T(["--Foo","FOO","-X","X"]) == T("X", "FOO"));
1602     assert(test!T(["--FOo=FOO","-X=X"]) == T("X", "FOO"));
1603 }
1604 
1605 unittest
1606 {
1607     auto test(T)(string[] args)
1608     {
1609         Config config;
1610         config.bundling = true;
1611 
1612         return args.parseCLIArgs!T(config).get;
1613     }
1614 
1615     struct T
1616     {
1617         bool a;
1618         bool b;
1619     }
1620     static assert(test!T(["-a","-b"]) == T(true, true));
1621     static assert(test!T(["-ab"]) == T(true, true));
1622     assert(test!T(["-a","-b"]) == T(true, true));
1623     assert(test!T(["-ab"]) == T(true, true));
1624 }
1625 
1626 unittest
1627 {
1628     struct T
1629     {
1630         bool b;
1631     }
1632 
1633     assert(CLI!T.parseArgs!((T t) { assert(t == T(true)); })(["-b"]) == 0);
1634     assert(CLI!T.parseArgs!((T t) { assert(t == T(true)); })(["-b=true"]) == 0);
1635     assert(CLI!T.parseArgs!((T t) { assert(t == T(false)); })(["-b=false"]) == 0);
1636     static assert(["-b"]        .parseCLIArgs!T.get == T(true));
1637     static assert(["-b=true"]   .parseCLIArgs!T.get == T(true));
1638     static assert(["-b=false"]  .parseCLIArgs!T.get == T(false));
1639     assert(["-b"]        .parseCLIArgs!T.get == T(true));
1640     assert(["-b=true"]   .parseCLIArgs!T.get == T(true));
1641     assert(["-b=false"]  .parseCLIArgs!T.get == T(false));
1642 }
1643 
1644 unittest
1645 {
1646     import std.sumtype: SumType, match;
1647 
1648     struct T
1649     {
1650         struct cmd1 { string a; }
1651         struct cmd2
1652         {
1653             string b;
1654 
1655             @TrailingArguments
1656             string[] args;
1657         }
1658 
1659         string c;
1660         string d;
1661 
1662         SumType!(cmd1, cmd2) cmd;
1663     }
1664 
1665     assert(["-c","C","cmd2","-b","B"].parseCLIArgs!T.get == T("C",null,typeof(T.cmd)(T.cmd2("B"))));
1666     assert(["-c","C","cmd2","--","-b","B"].parseCLIArgs!T.get == T("C",null,typeof(T.cmd)(T.cmd2("",["-b","B"]))));
1667 }
1668 
1669 unittest
1670 {
1671     import std.sumtype: SumType, match;
1672 
1673     struct T
1674     {
1675         struct cmd1 { string a; }
1676         struct cmd2 { string b; }
1677 
1678         string c;
1679         string d;
1680 
1681         SumType!(cmd1, Default!cmd2) cmd;
1682     }
1683 
1684     assert(["-c","C","-b","B"].parseCLIArgs!T.get == T("C",null,typeof(T.cmd)(Default!(T.cmd2)(T.cmd2("B")))));
1685 }
1686 
1687 deprecated("Use CLI!(Config, COMMAND).main or CLI!(COMMAND).main")
1688 struct Main
1689 {
1690     mixin template parseCLIKnownArgs(TYPE, alias newMain, Config config = Config.init)
1691     {
1692         mixin CLI!(config, TYPE).main!newMain;
1693     }
1694 
1695     mixin template parseCLIArgs(TYPE, alias newMain, Config config = Config.init)
1696     {
1697         mixin CLI!(config, TYPE).main!newMain;
1698     }
1699 }
1700 
1701 template CLI(Config config, COMMANDS...)
1702 {
1703     mixin template main(alias newMain)
1704     {
1705         import std.sumtype: SumType, match;
1706 
1707         private struct Program
1708         {
1709             SumType!COMMANDS cmd;   // Sub-commands
1710         }
1711 
1712         private auto forwardMain(Args...)(Program prog, auto ref Args args)
1713         {
1714             import core.lifetime: forward;
1715             return prog.cmd.match!(_ => newMain(_, forward!args));
1716         }
1717 
1718         mixin CLI!(config, Program).main!forwardMain;
1719     }
1720 }
1721 
1722 template CLI(Config config, COMMAND)
1723 {
1724     static Result parseKnownArgs(ref COMMAND receiver, string[] args, out string[] unrecognizedArgs)
1725     {
1726         auto parser = Parser(config, args);
1727 
1728         auto command = CommandArguments!COMMAND(config);
1729         auto res = parser.parseAll(command, receiver);
1730         if(!res)
1731             return res;
1732 
1733         unrecognizedArgs = parser.unrecognizedArgs;
1734 
1735         return Result.Success;
1736     }
1737 
1738     static Result parseKnownArgs(ref COMMAND receiver, ref string[] args)
1739     {
1740         string[] unrecognizedArgs;
1741 
1742         auto res = parseKnownArgs(receiver, args, unrecognizedArgs);
1743         if(res)
1744             args = unrecognizedArgs;
1745 
1746         return res;
1747     }
1748 
1749     static Result parseArgs(ref COMMAND receiver, string[] args)
1750     {
1751         auto res = parseKnownArgs(receiver, args);
1752         if(res && args.length > 0)
1753         {
1754             config.onError("Unrecognized arguments: ", args);
1755             return Result.Failure;
1756         }
1757 
1758         return res;
1759     }
1760 
1761     static int parseArgs(alias newMain)(string[] args, COMMAND initialValue = COMMAND.init)
1762         if(__traits(compiles, { newMain(COMMAND.init); }))
1763     {
1764         alias value = initialValue;
1765 
1766         auto res = parseArgs(value, args);
1767         if(!res)
1768             return res.resultCode;
1769 
1770         static if(__traits(compiles, { int a = cast(int) newMain(value); }))
1771             return cast(int) newMain(value);
1772         else
1773         {
1774             newMain(value);
1775             return 0;
1776         }
1777     }
1778 
1779     static int parseArgs(alias newMain)(string[] args, COMMAND initialValue = COMMAND.init)
1780         if(__traits(compiles, { newMain(COMMAND.init, string[].init); }))
1781     {
1782         alias value = initialValue;
1783 
1784         auto res = parseKnownArgs(value, args);
1785         if(!res)
1786             return res.resultCode;
1787 
1788         static if(__traits(compiles, { int a = cast(int) newMain(value, args); }))
1789             return cast(int) newMain(value, args);
1790         else
1791         {
1792             newMain(value, args);
1793             return 0;
1794         }
1795     }
1796 
1797     mixin template main(alias newMain)
1798     {
1799         int main(string[] argv)
1800         {
1801             return CLI!(config, COMMAND).parseArgs!(newMain)(argv[1..$]);
1802         }
1803     }
1804 }
1805 
1806 deprecated("Use CLI!(Config, COMMAND) or CLI!(COMMAND)")
1807 template CLI(Config config)
1808 {
1809     mixin template main(COMMAND, alias newMain)
1810     {
1811         mixin CLI!(config, COMMAND).main!newMain;
1812     }
1813 }
1814 
1815 alias CLI(COMMANDS...) = CLI!(Config.init, COMMANDS);
1816 
1817 
1818 unittest
1819 {
1820     struct T
1821     {
1822         int a;
1823     }
1824 
1825     static assert(__traits(compiles, { mixin CLI!T.main!((params) => 0); }));
1826     static assert(__traits(compiles, { mixin CLI!T.main!((params, args) => 0); }));
1827 }
1828 
1829 
1830 private struct Parsers
1831 {
1832     static auto Convert(T)(string value)
1833     {
1834         import std.conv: to;
1835         return value.length > 0 ? value.to!T : T.init;
1836     }
1837 
1838     static auto PassThrough(string[] values)
1839     {
1840         return values;
1841     }
1842 }
1843 
1844 unittest
1845 {
1846     static assert(Parsers.Convert!int("7") == 7);
1847     static assert(Parsers.Convert!string("7") == "7");
1848     static assert(Parsers.Convert!char("7") == '7');
1849 
1850     static assert(Parsers.PassThrough(["7","8"]) == ["7","8"]);
1851 }
1852 
1853 
1854 private struct Actions
1855 {
1856     static auto Assign(DEST, SRC=DEST)(ref DEST param, SRC value)
1857     {
1858         param  = value;
1859     }
1860 
1861     static auto Append(T)(ref T param, T value)
1862     {
1863         param ~= value;
1864     }
1865 
1866     static auto Extend(T)(ref T[] param, T value)
1867     {
1868         param ~= value;
1869     }
1870 
1871     static auto CallFunction(F)(ref F func, RawParam param)
1872     {
1873         // ... func()
1874         static if(__traits(compiles, { func(); }))
1875         {
1876             func();
1877         }
1878         // ... func(string value)
1879         else static if(__traits(compiles, { func(param.value[0]); }))
1880         {
1881             foreach(value; param.value)
1882                 func(value);
1883         }
1884         // ... func(string[] value)
1885         else static if(__traits(compiles, { func(param.value); }))
1886         {
1887             func(param.value);
1888         }
1889         // ... func(RawParam param)
1890         else static if(__traits(compiles, { func(param); }))
1891         {
1892             func(param);
1893         }
1894         else
1895             static assert(false, "Unsupported callback: " ~ F.stringof);
1896     }
1897 
1898     static auto CallFunctionNoParam(F)(ref F func, Param!void param)
1899     {
1900         // ... func()
1901         static if(__traits(compiles, { func(); }))
1902         {
1903             func();
1904         }
1905         // ... func(string value)
1906         else static if(__traits(compiles, { func(string.init); }))
1907         {
1908             func(string.init);
1909         }
1910         // ... func(string[] value)
1911         else static if(__traits(compiles, { func([]); }))
1912         {
1913             func([]);
1914         }
1915         // ... func(Param!void param)
1916         else static if(__traits(compiles, { func(param); }))
1917         {
1918             func(param);
1919         }
1920         else
1921             static assert(false, "Unsupported callback: " ~ F.stringof);
1922     }
1923 }
1924 
1925 unittest
1926 {
1927     int i;
1928     Actions.Assign!(int)(i,7);
1929     assert(i == 7);
1930 }
1931 
1932 unittest
1933 {
1934     int[] i;
1935     Actions.Append!(int[])(i,[1,2,3]);
1936     Actions.Append!(int[])(i,[7,8,9]);
1937     assert(i == [1,2,3,7,8,9]);
1938 
1939     alias test = (int[] v1, int[] v2) {
1940         int[] res;
1941 
1942         Param!(int[]) param;
1943 
1944         alias F = Actions.Append!(int[]);
1945         param.value = v1;   ActionFunc!(F, int[], int[])(res, param);
1946 
1947         param.value = v2;   ActionFunc!(F, int[], int[])(res, param);
1948 
1949         return res;
1950     };
1951     static assert(test([1,2,3],[7,8,9]) == [1,2,3,7,8,9]);
1952 }
1953 
1954 unittest
1955 {
1956     int[][] i;
1957     Actions.Extend!(int[])(i,[1,2,3]);
1958     Actions.Extend!(int[])(i,[7,8,9]);
1959     assert(i == [[1,2,3],[7,8,9]]);
1960 }
1961 
1962 
1963 private struct Validators
1964 {
1965     static auto ValueInList(alias values, TYPE)(in Param!TYPE param)
1966     {
1967         import std.array : assocArray, join;
1968         import std.range : repeat, front;
1969         import std.conv: to;
1970 
1971         enum valuesAA = assocArray(values, false.repeat);
1972         enum allowedValues = values.to!(string[]).join(',');
1973 
1974         static if(is(typeof(values.front) == TYPE))
1975             auto paramValues = [param.value];
1976         else
1977             auto paramValues = param.value;
1978 
1979         foreach(value; paramValues)
1980             if(!(value in valuesAA))
1981             {
1982                 param.config.onError("Invalid value '", value, "' for argument '", param.name, "'.\nValid argument values are: ", allowedValues);
1983                 return false;
1984             }
1985 
1986         return true;
1987     }
1988 }
1989 
1990 
1991 // values => bool
1992 // bool validate(T value)
1993 // bool validate(T[i] value)
1994 // bool validate(Param!T param)
1995 private struct ValidateFunc(alias F, T, string funcName="Validation")
1996 {
1997     static bool opCall(Param!T param)
1998     {
1999         static if(is(F == void))
2000         {
2001             return true;
2002         }
2003         else static if(__traits(compiles, { F(param); }))
2004         {
2005             // bool validate(Param!T param)
2006             return cast(bool) F(param);
2007         }
2008         else static if(__traits(compiles, { F(param.value); }))
2009         {
2010             // bool validate(T values)
2011             return cast(bool) F(param.value);
2012         }
2013         else static if(/*isArray!T &&*/ __traits(compiles, { F(param.value[0]); }))
2014         {
2015             // bool validate(T[i] value)
2016             foreach(value; param.value)
2017                 if(!F(value))
2018                     return false;
2019             return true;
2020         }
2021         else
2022             static assert(false, funcName~" function is not supported for type "~T.stringof~": "~typeof(F).stringof);
2023     }
2024 }
2025 
2026 unittest
2027 {
2028     auto test(alias F, T)(T[] values)
2029     {
2030         Param!(T[]) param;
2031         param.value = values;
2032         return ValidateFunc!(F, T[])(param);
2033     }
2034 
2035     // bool validate(T[] values)
2036     static assert(test!((string[] a) => true, string)(["1","2","3"]));
2037     static assert(test!((int[] a) => true, int)([1,2,3]));
2038 
2039     // bool validate(T value)
2040     static assert(test!((string a) => true, string)(["1","2","3"]));
2041     static assert(test!((int a) => true, int)([1,2,3]));
2042 
2043     // bool validate(Param!T param)
2044     static assert(test!((RawParam p) => true, string)(["1","2","3"]));
2045     static assert(test!((Param!(int[]) p) => true, int)([1,2,3]));
2046 }
2047 
2048 unittest
2049 {
2050     static assert(ValidateFunc!(void, string[])(RawParam(Config.init, "", ["1","2","3"])));
2051 
2052     static assert(!__traits(compiles, { ValidateFunc!(() {}, string[])(config, "", ["1","2","3"]); }));
2053     static assert(!__traits(compiles, { ValidateFunc!((int,int) {}, string[])(config, "", ["1","2","3"]); }));
2054 }
2055 
2056 
2057 private template ParseType(alias F, T)
2058 {
2059     import std.traits : Unqual;
2060 
2061     static if(is(F == void))
2062         alias ParseType = Unqual!T;
2063     else static if(Parameters!F.length == 0)
2064         static assert(false, "Parse function should take at least one parameter");
2065     else static if(Parameters!F.length == 1)
2066     {
2067         // T action(arg)
2068         alias ParseType = Unqual!(ReturnType!F);
2069         static assert(!is(ParseType == void), "Parse function should return value");
2070     }
2071     else static if(Parameters!F.length == 2 && is(Parameters!F[0] == Config))
2072     {
2073         // T action(Config config, arg)
2074         alias ParseType = Unqual!(ReturnType!F);
2075         static assert(!is(ParseType == void), "Parse function should return value");
2076     }
2077     else static if(Parameters!F.length == 2)
2078     {
2079         // ... action(ref T param, arg)
2080         alias ParseType = Parameters!F[0];
2081     }
2082     else static if(Parameters!F.length == 3)
2083     {
2084         // ... action(Config config, ref T param, arg)
2085         alias ParseType = Parameters!F[1];
2086     }
2087     else static if(Parameters!F.length == 4)
2088     {
2089         // ... action(Config config, string argName, ref T param, arg)
2090         alias ParseType = Parameters!F[2];
2091     }
2092     else
2093         static assert(false, "Parse function has too many parameters: "~Parameters!F.stringof);
2094 }
2095 
2096 unittest
2097 {
2098     static assert(is(ParseType!(void, double) == double));
2099     static assert(!__traits(compiles, { ParseType!((){}, double) p; }));
2100     static assert(!__traits(compiles, { ParseType!((int,int,int,int,int){}, double) p; }));
2101 
2102     // T action(arg)
2103     static assert(is(ParseType!((int)=>3, double) == int));
2104     static assert(!__traits(compiles, { ParseType!((int){}, double) p; }));
2105     // T action(Config config, arg)
2106     static assert(is(ParseType!((Config config, int)=>3, double) == int));
2107     static assert(!__traits(compiles, { ParseType!((Config config, int){}, double) p; }));
2108     // ... action(ref T param, arg)
2109     static assert(is(ParseType!((ref int, string v) {}, double) == int));
2110     // ... action(Config config, ref T param, arg)
2111     static assert(is(ParseType!((Config config, ref int, string v) {}, double) == int));
2112     // ... action(Config config, string argName, ref T param, arg)
2113     //static assert(is(ParseType!((Config config, string argName, ref int, string v) {}, double) == int));
2114 }
2115 
2116 
2117 // T parse(string[] values)
2118 // T parse(string value)
2119 // T parse(RawParam param)
2120 // bool parse(ref T receiver, RawParam param)
2121 // void parse(ref T receiver, RawParam param)
2122 private struct ParseFunc(alias F, T)
2123 {
2124     alias ParseType = .ParseType!(F, T);
2125 
2126     static bool opCall(ref ParseType receiver, RawParam param)
2127     {
2128         static if(is(F == void))
2129         {
2130             foreach(value; param.value)
2131                 receiver = Parsers.Convert!T(value);
2132             return true;
2133         }
2134         // T parse(string[] values)
2135         else static if(__traits(compiles, { receiver = cast(ParseType) F(param.value); }))
2136         {
2137             receiver = cast(ParseType) F(param.value);
2138             return true;
2139         }
2140         // T parse(string value)
2141         else static if(__traits(compiles, { receiver = cast(ParseType) F(param.value[0]); }))
2142         {
2143             foreach(value; param.value)
2144                 receiver = cast(ParseType) F(value);
2145             return true;
2146         }
2147         // T parse(RawParam param)
2148         else static if(__traits(compiles, { receiver = cast(ParseType) F(param); }))
2149         {
2150             receiver = cast(ParseType) F(param);
2151             return true;
2152         }
2153         // bool parse(ref T receiver, RawParam param)
2154         // void parse(ref T receiver, RawParam param)
2155         else static if(__traits(compiles, { F(receiver, param); }))
2156         {
2157             static if(__traits(compiles, { auto res = cast(bool) F(receiver, param); }))
2158             {
2159                 // bool parse(ref T receiver, RawParam param)
2160                 return cast(bool) F(receiver, param);
2161             }
2162             else
2163             {
2164                 // void parse(ref T receiver, RawParam param)
2165                 F(receiver, param);
2166                 return true;
2167             }
2168         }
2169         else
2170             static assert(false, "Parse function is not supported");
2171     }
2172 }
2173 
2174 unittest
2175 {
2176     int i;
2177     RawParam param;
2178     param.value = ["1","2","3"];
2179     assert(ParseFunc!(void, int)(i, param));
2180     assert(i == 3);
2181 }
2182 
2183 unittest
2184 {
2185     auto test(alias F, T)(string[] values)
2186     {
2187         T value;
2188         RawParam param;
2189         param.value = values;
2190         assert(ParseFunc!(F, T)(value, param));
2191         return value;
2192     }
2193 
2194     // T parse(string value)
2195     static assert(test!((string a) => a, string)(["1","2","3"]) == "3");
2196 
2197     // T parse(string[] values)
2198     static assert(test!((string[] a) => a, string[])(["1","2","3"]) == ["1","2","3"]);
2199 
2200     // T parse(RawParam param)
2201     static assert(test!((RawParam p) => p.value[0], string)(["1","2","3"]) == "1");
2202 
2203     // bool parse(ref T receiver, RawParam param)
2204     static assert(test!((ref string[] r, RawParam p) { r = p.value; return true; }, string[])(["1","2","3"]) == ["1","2","3"]);
2205 
2206     // void parse(ref T receiver, RawParam param)
2207     static assert(test!((ref string[] r, RawParam p) { r = p.value; }, string[])(["1","2","3"]) == ["1","2","3"]);
2208 }
2209 
2210 
2211 // bool action(ref T receiver, ParseType value)
2212 // void action(ref T receiver, ParseType value)
2213 // bool action(ref T receiver, Param!ParseType param)
2214 // void action(ref T receiver, Param!ParseType param)
2215 private struct ActionFunc(alias F, T, ParseType)
2216 {
2217     static bool opCall(ref T receiver, Param!ParseType param)
2218     {
2219         static if(is(F == void))
2220         {
2221             Actions.Assign!(T, ParseType)(receiver, param.value);
2222             return true;
2223         }
2224         // bool action(ref T receiver, ParseType value)
2225         // void action(ref T receiver, ParseType value)
2226         else static if(__traits(compiles, { F(receiver, param.value); }))
2227         {
2228             static if(__traits(compiles, { auto res = cast(bool) F(receiver, param.value); }))
2229             {
2230                 // bool action(ref T receiver, ParseType value)
2231                 return cast(bool) F(receiver, param.value);
2232             }
2233             else
2234             {
2235                 // void action(ref T receiver, ParseType value)
2236                 F(receiver, param.value);
2237                 return true;
2238             }
2239         }
2240         // bool action(ref T receiver, Param!ParseType param)
2241         // void action(ref T receiver, Param!ParseType param)
2242         else static if(__traits(compiles, { F(receiver, param); }))
2243         {
2244             static if(__traits(compiles, { auto res = cast(bool) F(receiver, param); }))
2245             {
2246                 // bool action(ref T receiver, Param!ParseType param)
2247                 return cast(bool) F(receiver, param);
2248             }
2249             else
2250             {
2251                 // void action(ref T receiver, Param!ParseType param)
2252                 F(receiver, param);
2253                 return true;
2254             }
2255         }
2256         else
2257             static assert(false, "Action function is not supported");
2258     }
2259 }
2260 
2261 unittest
2262 {
2263     auto param(T)(T values)
2264     {
2265         Param!T param;
2266         param.value = values;
2267         return param;
2268     }
2269     auto test(alias F, T)(T values)
2270     {
2271         T receiver;
2272         assert(ActionFunc!(F, T, T)(receiver, param(values)));
2273         return receiver;
2274     }
2275 
2276     static assert(test!(void, string[])(["1","2","3"]) == ["1","2","3"]);
2277 
2278     static assert(!__traits(compiles, { test!(() {}, string[])(["1","2","3"]); }));
2279     static assert(!__traits(compiles, { test!((int,int) {}, string[])(["1","2","3"]); }));
2280 
2281     // bool action(ref T receiver, ParseType value)
2282     static assert(test!((ref string[] p, string[] a) { p=a; return true; }, string[])(["1","2","3"]) == ["1","2","3"]);
2283 
2284     // void action(ref T receiver, ParseType value)
2285     static assert(test!((ref string[] p, string[] a) { p=a; }, string[])(["1","2","3"]) == ["1","2","3"]);
2286 
2287     // bool action(ref T receiver, Param!ParseType param)
2288     static assert(test!((ref string[] p, Param!(string[]) a) { p=a.value; return true; }, string[]) (["1","2","3"]) == ["1","2","3"]);
2289 
2290     // void action(ref T receiver, Param!ParseType param)
2291     static assert(test!((ref string[] p, Param!(string[]) a) { p=a.value; }, string[])(["1","2","3"]) == ["1","2","3"]);
2292 }
2293 
2294 
2295 // => receiver + bool
2296 // DEST action()
2297 // bool action(ref DEST receiver)
2298 // void action(ref DEST receiver)
2299 // bool action(ref DEST receiver, Param!void param)
2300 // void action(ref DEST receiver, Param!void param)
2301 private struct NoValueActionFunc(alias F, T)
2302 {
2303     static bool opCall(ref T receiver, Param!void param)
2304     {
2305         static if(is(F == void))
2306         {
2307             assert(false, "No-value action function is not provided");
2308         }
2309         else static if(__traits(compiles, { receiver = cast(T) F(); }))
2310         {
2311             // DEST action()
2312             receiver = cast(T) F();
2313             return true;
2314         }
2315         else static if(__traits(compiles, { F(receiver); }))
2316         {
2317             static if(__traits(compiles, { auto res = cast(bool) F(receiver); }))
2318             {
2319                 // bool action(ref DEST receiver)
2320                 return cast(bool) F(receiver);
2321             }
2322             else
2323             {
2324                 // void action(ref DEST receiver)
2325                 F(receiver);
2326                 return true;
2327             }
2328         }
2329         else static if(__traits(compiles, { F(receiver, param); }))
2330         {
2331             static if(__traits(compiles, { auto res = cast(bool) F(receiver, param); }))
2332             {
2333                 // bool action(ref DEST receiver, Param!void param)
2334                 return cast(bool) F(receiver, param);
2335             }
2336             else
2337             {
2338                 // void action(ref DEST receiver, Param!void param)
2339                 F(receiver, param);
2340                 return true;
2341             }
2342         }
2343         else
2344             static assert(false, "No-value action function has too many parameters: "~Parameters!F.stringof);
2345     }
2346 }
2347 
2348 unittest
2349 {
2350     auto test(alias F, T)()
2351     {
2352         T receiver;
2353         assert(NoValueActionFunc!(F, T)(receiver, Param!void.init));
2354         return receiver;
2355     }
2356 
2357     static assert(!__traits(compiles, { NoValueActionFunc!(() {}, int); }));
2358     static assert(!__traits(compiles, { NoValueActionFunc!((int) {}, int); }));
2359     static assert(!__traits(compiles, { NoValueActionFunc!((int,int) {}, int); }));
2360     static assert(!__traits(compiles, { NoValueActionFunc!((int,int,int) {}, int); }));
2361 
2362     // DEST action()
2363     static assert(test!(() => 7, int) == 7);
2364 
2365     // bool action(ref DEST param)
2366     static assert(test!((ref int p) { p=7; return true; }, int) == 7);
2367 
2368     // void action(ref DEST param)
2369     static assert(test!((ref int p) { p=7; }, int) == 7);
2370 
2371     // bool action(ref DEST receiver, Param!void param)
2372     static assert(test!((ref int r, Param!void p) { r=7; return true; }, int) == 7);
2373 
2374     // void action(ref DEST receiver, Param!void param)
2375     static assert(test!((ref int r, Param!void p) { r=7; }, int) == 7);
2376 }
2377 
2378 
2379 private void splitValues(ref RawParam param)
2380 {
2381     if(param.config.arraySep == char.init)
2382         return;
2383 
2384     import std.array : array, split;
2385     import std.algorithm : map, joiner;
2386 
2387     param.value = param.value.map!((string s) => s.split(param.config.arraySep)).joiner.array;
2388 }
2389 
2390 unittest
2391 {
2392     alias test = (char arraySep, string[] values)
2393     {
2394         Config config;
2395         config.arraySep = arraySep;
2396 
2397         auto param = RawParam(config, "", values);
2398 
2399         splitValues(param);
2400 
2401         return param.value;
2402     };
2403 
2404     static assert(test(',', []) == []);
2405     static assert(test(',', ["a","b","c"]) == ["a","b","c"]);
2406     static assert(test(',', ["a,b","c","d,e,f"]) == ["a","b","c","d","e","f"]);
2407     static assert(test(' ', ["a,b","c","d,e,f"]) == ["a,b","c","d,e,f"]);
2408 }
2409 
2410 
2411 private struct ValueParseFunctions(alias PreProcess,
2412                                    alias PreValidation,
2413                                    alias Parse,
2414                                    alias Validation,
2415                                    alias Action,
2416                                    alias NoValueAction)
2417 {
2418     alias changePreProcess   (alias func) = ValueParseFunctions!(      func, PreValidation, Parse, Validation, Action, NoValueAction);
2419     alias changePreValidation(alias func) = ValueParseFunctions!(PreProcess,          func, Parse, Validation, Action, NoValueAction);
2420     alias changeParse        (alias func) = ValueParseFunctions!(PreProcess, PreValidation,  func, Validation, Action, NoValueAction);
2421     alias changeValidation   (alias func) = ValueParseFunctions!(PreProcess, PreValidation, Parse,       func, Action, NoValueAction);
2422     alias changeAction       (alias func) = ValueParseFunctions!(PreProcess, PreValidation, Parse, Validation,   func, NoValueAction);
2423     alias changeNoValueAction(alias func) = ValueParseFunctions!(PreProcess, PreValidation, Parse, Validation, Action,          func);
2424 
2425     template addDefaults(T)
2426     {
2427         static if(is(PreProcess == void))
2428             alias preProc = DefaultValueParseFunctions!T;
2429         else
2430             alias preProc = DefaultValueParseFunctions!T.changePreProcess!PreProcess;
2431 
2432         static if(is(PreValidation == void))
2433             alias preVal = preProc;
2434         else
2435             alias preVal = preProc.changePreValidation!PreValidation;
2436 
2437         static if(is(Parse == void))
2438             alias parse = preVal;
2439         else
2440             alias parse = preVal.changeParse!Parse;
2441 
2442         static if(is(Validation == void))
2443             alias val = parse;
2444         else
2445             alias val = parse.changeValidation!Validation;
2446 
2447         static if(is(Action == void))
2448             alias action = val;
2449         else
2450             alias action = val.changeAction!Action;
2451 
2452         static if(is(NoValueAction == void))
2453             alias addDefaults = action;
2454         else
2455             alias addDefaults = action.changeNoValueAction!NoValueAction;
2456     }
2457 
2458 
2459     // Procedure to process (parse) the values to an argument of type T
2460     //  - if there is a value(s):
2461     //      - pre validate raw strings
2462     //      - parse raw strings
2463     //      - validate parsed values
2464     //      - action with values
2465     //  - if there is no value:
2466     //      - action if no value
2467     // Requirement: rawValues.length must be correct
2468     static bool parse(T)(ref T receiver, RawParam param)
2469     {
2470         return addDefaults!T.parseImpl(receiver, param);
2471     }
2472     static bool parseImpl(T)(ref T receiver, ref RawParam rawParam)
2473     {
2474         alias ParseType(T)     = .ParseType!(Parse, T);
2475 
2476         alias preValidation    = ValidateFunc!(PreValidation, string[], "Pre validation");
2477         alias parse(T)         = ParseFunc!(Parse, T);
2478         alias validation(T)    = ValidateFunc!(Validation, ParseType!T);
2479         alias action(T)        = ActionFunc!(Action, T, ParseType!T);
2480         alias noValueAction(T) = NoValueActionFunc!(NoValueAction, T);
2481 
2482         if(rawParam.value.length == 0)
2483         {
2484             return noValueAction!T(receiver, Param!void(rawParam.config, rawParam.name));
2485         }
2486         else
2487         {
2488             static if(!is(PreProcess == void))
2489                 PreProcess(rawParam);
2490 
2491             if(!preValidation(rawParam))
2492                 return false;
2493 
2494             auto parsedParam = Param!(ParseType!T)(rawParam.config, rawParam.name);
2495 
2496             if(!parse!T(parsedParam.value, rawParam))
2497                 return false;
2498 
2499             if(!validation!T(parsedParam))
2500                 return false;
2501 
2502             if(!action!T(receiver, parsedParam))
2503                 return false;
2504 
2505             return true;
2506         }
2507     }
2508 }
2509 
2510 
2511 private template DefaultValueParseFunctions(T)
2512 if(!is(T == void))
2513 {
2514     import std.traits;
2515     import std.conv: to;
2516 
2517     static if(is(T == enum))
2518     {
2519         alias DefaultValueParseFunctions = ValueParseFunctions!(
2520         void,   // pre process
2521         Validators.ValueInList!(EnumMembersAsStrings!T, typeof(RawParam.value)),   // pre validate
2522         void,   // parse
2523         void,   // validate
2524         void,   // action
2525         void    // no-value action
2526         );
2527     }
2528     else static if(isSomeString!T || isNumeric!T)
2529     {
2530         alias DefaultValueParseFunctions = ValueParseFunctions!(
2531         void,   // pre process
2532         void,   // pre validate
2533         void,   // parse
2534         void,   // validate
2535         void,   // action
2536         void    // no-value action
2537         );
2538     }
2539     else static if(isBoolean!T)
2540     {
2541         alias DefaultValueParseFunctions = ValueParseFunctions!(
2542         void,                               // pre process
2543         void,                               // pre validate
2544         (string value)                      // parse
2545         {
2546             switch(value)
2547             {
2548                 case "":    goto case;
2549                 case "yes": goto case;
2550                 case "y":   return true;
2551                 case "no":  goto case;
2552                 case "n":   return false;
2553                 default:    return value.to!T;
2554             }
2555         },
2556         void,                               // validate
2557         void,                               // action
2558         (ref T result) { result = true; }   // no-value action
2559         );
2560     }
2561     else static if(isSomeChar!T)
2562     {
2563         alias DefaultValueParseFunctions = ValueParseFunctions!(
2564         void,                         // pre process
2565         void,                         // pre validate
2566         (string value)                // parse
2567         {
2568             return value.length > 0 ? value[0].to!T : T.init;
2569         },
2570         void,                         // validate
2571         void,                         // action
2572         void                          // no-value action
2573         );
2574     }
2575     else static if(isArray!T)
2576     {
2577         import std.traits: ForeachType;
2578 
2579         alias TElement = ForeachType!T;
2580 
2581         static if(!isArray!TElement || isSomeString!TElement)  // 1D array
2582         {
2583             static if(!isStaticArray!T)
2584                 alias action = Actions.Append!T;
2585             else
2586                 alias action = Actions.Assign!T;
2587 
2588             alias DefaultValueParseFunctions = DefaultValueParseFunctions!TElement
2589             .changePreProcess!splitValues
2590             .changeParse!((ref T receiver, RawParam param)
2591             {
2592                 static if(!isStaticArray!T)
2593                 {
2594                     if(receiver.length < param.value.length)
2595                         receiver.length = param.value.length;
2596                 }
2597 
2598                 foreach(i, value; param.value)
2599                 {
2600                     if(!DefaultValueParseFunctions!TElement.parse(receiver[i],
2601                         RawParam(param.config, param.name, [value])))
2602                         return false;
2603                 }
2604 
2605                 return true;
2606             })
2607             .changeAction!(action)
2608             .changeNoValueAction!((ref T param) {});
2609         }
2610         else static if(!isArray!(ForeachType!TElement) || isSomeString!(ForeachType!TElement))  // 2D array
2611         {
2612             alias DefaultValueParseFunctions = DefaultValueParseFunctions!TElement
2613             .changeAction!(Actions.Extend!TElement)
2614             .changeNoValueAction!((ref T param) { param ~= TElement.init; });
2615         }
2616         else
2617         {
2618             static assert(false, "Multi-dimentional arrays are not supported: " ~ T.stringof);
2619         }
2620     }
2621     else static if(isAssociativeArray!T)
2622     {
2623         import std.string : indexOf;
2624         alias DefaultValueParseFunctions = ValueParseFunctions!(
2625         splitValues,                                                // pre process
2626         void,                                                       // pre validate
2627         Parsers.PassThrough,                                        // parse
2628         void,                                                       // validate
2629         (ref T recepient, Param!(string[]) param)                   // action
2630         {
2631             alias K = KeyType!T;
2632             alias V = ValueType!T;
2633 
2634             foreach(input; param.value)
2635             {
2636                 auto j = indexOf(input, param.config.assignChar);
2637                 if(j < 0)
2638                     return false;
2639 
2640                 K key;
2641                 if(!DefaultValueParseFunctions!K.parse(key, RawParam(param.config, param.name, [input[0 .. j]])))
2642                     return false;
2643 
2644                 V value;
2645                 if(!DefaultValueParseFunctions!V.parse(value, RawParam(param.config, param.name, [input[j + 1 .. $]])))
2646                     return false;
2647 
2648                 recepient[key] = value;
2649             }
2650             return true;
2651         },
2652         (ref T param) {}    // no-value action
2653         );
2654     }
2655     else static if(is(T == delegate))
2656     {
2657         alias DefaultValueParseFunctions = ValueParseFunctions!(
2658             void,                           // pre process
2659             void,                           // pre validate
2660             Parsers.PassThrough,            // parse
2661             void,                           // validate
2662             Actions.CallFunction!T,         // action
2663             Actions.CallFunctionNoParam!T   // no-value action
2664         );
2665     }
2666      else
2667         static assert(false, "Type is not supported: " ~ T.stringof);
2668 }
2669 
2670 unittest
2671 {
2672     enum MyEnum { foo, bar, }
2673 
2674     import std.meta: AliasSeq;
2675     static foreach(T; AliasSeq!(string, bool, int, double, char, MyEnum))
2676         static foreach(R; AliasSeq!(T, T[], T[][]))
2677         {{
2678             // ensure that this compiles
2679             R receiver;
2680             RawParam param;
2681             param.value = [""];
2682             DefaultValueParseFunctions!R.parse(receiver, param);
2683         }}
2684 }
2685 
2686 unittest
2687 {
2688     alias test(R) = (string[][] values)
2689     {
2690         auto config = Config('=', ',');
2691         R receiver;
2692         foreach(value; values)
2693         {
2694             assert(DefaultValueParseFunctions!R.parse(receiver, RawParam(config, "", value)));
2695         }
2696         return receiver;
2697     };
2698 
2699     static assert(test!(string[])([["1","2","3"], [], ["4"]]) == ["1","2","3","4"]);
2700     static assert(test!(string[][])([["1","2","3"], [], ["4"]]) == [["1","2","3"],[],["4"]]);
2701 
2702     static assert(test!(string[string])([["a=bar","b=foo"], [], ["b=baz","c=boo"]]) == ["a":"bar", "b":"baz", "c":"boo"]);
2703 
2704     static assert(test!(string[])([["1,2,3"], [], ["4"]]) == ["1","2","3","4"]);
2705     static assert(test!(string[string])([["a=bar,b=foo"], [], ["b=baz,c=boo"]]) == ["a":"bar", "b":"baz", "c":"boo"]);
2706 
2707     static assert(test!(int[])([["1","2","3"], [], ["4"]]) == [1,2,3,4]);
2708     static assert(test!(int[][])([["1","2","3"], [], ["4"]]) == [[1,2,3],[],[4]]);
2709 
2710 }
2711 
2712 unittest
2713 {
2714     import std.math: isNaN;
2715     enum MyEnum { foo, bar, }
2716 
2717     alias test(T) = (string[] values)
2718     {
2719         T receiver;
2720         RawParam param;
2721         param.value = values;
2722         assert(DefaultValueParseFunctions!T.parse(receiver, param));
2723         return receiver;
2724     };
2725 
2726     static assert(test!string([""]) == "");
2727     static assert(test!string(["foo"]) == "foo");
2728     static assert(isNaN(test!double([""])));
2729     static assert(test!double(["-12.34"]) == -12.34);
2730     static assert(test!double(["12.34"]) == 12.34);
2731     static assert(test!uint(["1234"]) == 1234);
2732     static assert(test!int([""]) == int.init);
2733     static assert(test!int(["-1234"]) == -1234);
2734     static assert(test!char([""]) == char.init);
2735     static assert(test!char(["f"]) == 'f');
2736     static assert(test!bool([]) == true);
2737     static assert(test!bool([""]) == true);
2738     static assert(test!bool(["yes"]) == true);
2739     static assert(test!bool(["y"]) == true);
2740     static assert(test!bool(["true"]) == true);
2741     static assert(test!bool(["no"]) == false);
2742     static assert(test!bool(["n"]) == false);
2743     static assert(test!bool(["false"]) == false);
2744     static assert(test!MyEnum(["foo"]) == MyEnum.foo);
2745     static assert(test!MyEnum(["bar"]) == MyEnum.bar);
2746     static assert(test!(MyEnum[])(["bar","foo"]) == [MyEnum.bar, MyEnum.foo]);
2747     static assert(test!(string[string])(["a=bar","b=foo"]) == ["a":"bar", "b":"foo"]);
2748     static assert(test!(MyEnum[string])(["a=bar","b=foo"]) == ["a":MyEnum.bar, "b":MyEnum.foo]);
2749     static assert(test!(int[MyEnum])(["bar=3","foo=5"]) == [MyEnum.bar:3, MyEnum.foo:5]);
2750 }
2751 
2752 
2753 private struct ArgumentInfo
2754 {
2755     string[] names;
2756 
2757     string description;
2758     string placeholder;
2759 
2760     private void setAllowedValues(alias names)()
2761     {
2762         if(placeholder.length == 0)
2763         {
2764             import std.conv: to;
2765             import std.array: join;
2766             import std.format: format;
2767             placeholder = "{%s}".format(names.to!(string[]).join(','));
2768         }
2769     }
2770 
2771     bool hideFromHelp = false;      // if true then this argument is not printed on help page
2772 
2773     bool required;
2774 
2775     Nullable!uint position;
2776 
2777     @property bool positional() const { return !position.isNull; }
2778 
2779     Nullable!ulong minValuesCount;
2780     Nullable!ulong maxValuesCount;
2781 
2782     private auto checkValuesCount(string argName, ulong count) const
2783     {
2784         immutable min = minValuesCount.get;
2785         immutable max = maxValuesCount.get;
2786 
2787         // override for boolean flags
2788         if(allowBooleanNegation && count == 1)
2789             return Result.Success;
2790 
2791         if(min == max && count != min)
2792         {
2793             return Result.Error("argument ",argName,": expected ",min,min == 1 ? " value" : " values");
2794         }
2795         if(count < min)
2796         {
2797             return Result.Error("argument ",argName,": expected at least ",min,min == 1 ? " value" : " values");
2798         }
2799         if(count > max)
2800         {
2801             return Result.Error("argument ",argName,": expected at most ",max,max == 1 ? " value" : " values");
2802         }
2803 
2804         return Result.Success;
2805     }
2806 
2807     private bool allowBooleanNegation = true;
2808 }
2809 
2810 unittest
2811 {
2812     ArgumentInfo info;
2813     info.allowBooleanNegation = false;
2814     info.minValuesCount = 2;
2815     info.maxValuesCount = 4;
2816 
2817     alias isError = (Result res) => !res && res.errorMsg.length > 0;
2818 
2819     assert( isError(info.checkValuesCount("", 1)));
2820     assert(!isError(info.checkValuesCount("", 2)));
2821     assert(!isError(info.checkValuesCount("", 3)));
2822     assert(!isError(info.checkValuesCount("", 4)));
2823     assert( isError(info.checkValuesCount("", 5)));
2824 }
2825 
2826 unittest
2827 {
2828     ArgumentInfo info;
2829     info.allowBooleanNegation = false;
2830     info.minValuesCount = 2;
2831     info.maxValuesCount = 2;
2832 
2833     alias isError = (Result res) => !res && res.errorMsg.length > 0;
2834 
2835     assert( isError(info.checkValuesCount("", 1)));
2836     assert(!isError(info.checkValuesCount("", 2)));
2837     assert( isError(info.checkValuesCount("", 3)));
2838 }
2839 
2840 
2841 ////////////////////////////////////////////////////////////////////////////////////////////////////
2842 // User defined attributes
2843 ////////////////////////////////////////////////////////////////////////////////////////////////////
2844 private struct ArgumentUDA(alias ValueParseFunctions)
2845 {
2846     ArgumentInfo info;
2847 
2848     alias parsingFunc = ValueParseFunctions;
2849 
2850 
2851 
2852     auto ref Description(string text)
2853     {
2854         info.description = text;
2855         return this;
2856     }
2857 
2858     auto ref HideFromHelp(bool hide = true)
2859     {
2860         info.hideFromHelp = hide;
2861         return this;
2862     }
2863 
2864     auto ref Placeholder(string value)
2865     {
2866         info.placeholder = value;
2867         return this;
2868     }
2869 
2870     auto ref Required()
2871     {
2872         info.required = true;
2873         return this;
2874     }
2875 
2876     auto ref Optional()
2877     {
2878         info.required = false;
2879         return this;
2880     }
2881 
2882     auto ref NumberOfValues(ulong num)
2883     {
2884         info.minValuesCount = num;
2885         info.maxValuesCount = num;
2886         return this;
2887     }
2888 
2889     auto ref NumberOfValues(ulong min, ulong max)
2890     {
2891         info.minValuesCount = min;
2892         info.maxValuesCount = max;
2893         return this;
2894     }
2895 
2896     auto ref MinNumberOfValues(ulong min)
2897     {
2898         assert(min <= info.maxValuesCount.get(ulong.max));
2899 
2900         info.minValuesCount = min;
2901         return this;
2902     }
2903 
2904     auto ref MaxNumberOfValues(ulong max)
2905     {
2906         assert(max >= info.minValuesCount.get(0));
2907 
2908         info.maxValuesCount = max;
2909         return this;
2910     }
2911 
2912     // ReverseSwitch
2913 }
2914 
2915 unittest
2916 {
2917     auto arg = NamedArgument.Description("desc").Placeholder("text");
2918     assert(arg.info.description == "desc");
2919     assert(arg.info.placeholder == "text");
2920     assert(!arg.info.hideFromHelp);
2921     assert(!arg.info.required);
2922     assert(arg.info.minValuesCount.isNull);
2923     assert(arg.info.maxValuesCount.isNull);
2924 
2925     arg = arg.HideFromHelp().Required().NumberOfValues(10);
2926     assert(arg.info.hideFromHelp);
2927     assert(arg.info.required);
2928     assert(arg.info.minValuesCount.get == 10);
2929     assert(arg.info.maxValuesCount.get == 10);
2930 
2931     arg = arg.Optional().NumberOfValues(20,30);
2932     assert(!arg.info.required);
2933     assert(arg.info.minValuesCount.get == 20);
2934     assert(arg.info.maxValuesCount.get == 30);
2935 
2936     arg = arg.MinNumberOfValues(2).MaxNumberOfValues(3);
2937     assert(arg.info.minValuesCount.get == 2);
2938     assert(arg.info.maxValuesCount.get == 3);
2939 }
2940 
2941 unittest
2942 {
2943     struct T
2944     {
2945         @(NamedArgument.NumberOfValues(1,3))
2946         int[] a;
2947         @(NamedArgument.NumberOfValues(2))
2948         int[] b;
2949     }
2950 
2951     assert(CLI!T.parseArgs!((T t) { assert(t == T([1,2,3],[4,5])); })(["-a","1","2","3","-b","4","5"]) == 0);
2952     assert(CLI!T.parseArgs!((T t) { assert(t == T([1],[4,5])); })(["-a","1","-b","4","5"]) == 0);
2953     assert(["-a","1","2","3","-b","4","5"].parseCLIArgs!T.get == T([1,2,3],[4,5]));
2954     assert(["-a","1","-b","4","5"].parseCLIArgs!T.get == T([1],[4,5]));
2955 }
2956 
2957 private enum bool isArgumentUDA(T) = (is(typeof(T.info) == ArgumentInfo) && is(T.parsingFunc));
2958 
2959 
2960 auto PreValidation(alias func, ARG)(ARG arg)
2961 if(isArgumentUDA!ARG)
2962 {
2963     return ArgumentUDA!(arg.parsingFunc.changePreValidation!func)(arg.tupleof);
2964 }
2965 
2966 auto Parse(alias func, ARG)(ARG arg)
2967 if(isArgumentUDA!ARG)
2968 {
2969     return ArgumentUDA!(arg.parsingFunc.changeParse!func)(arg.tupleof);
2970 }
2971 
2972 auto Validation(alias func, ARG)(ARG arg)
2973 if(isArgumentUDA!ARG)
2974 {
2975     return ArgumentUDA!(arg.parsingFunc.changeValidation!func)(arg.tupleof);
2976 }
2977 
2978 auto Action(alias func, ARG)(ARG arg)
2979 if(isArgumentUDA!ARG)
2980 {
2981     return ArgumentUDA!(arg.parsingFunc.changeAction!func)(arg.tupleof);
2982 }
2983 
2984 auto AllowNoValue(alias valueToUse, ARG)(ARG arg)
2985 if(isArgumentUDA!ARG)
2986 {
2987     auto desc = ArgumentUDA!(arg.parsingFunc.changeNoValueAction!(() { return valueToUse; }))(arg.tupleof);
2988     desc.info.minValuesCount = 0;
2989     return desc;
2990 }
2991 
2992 auto RequireNoValue(alias valueToUse, ARG)(ARG arg)
2993 if(isArgumentUDA!ARG)
2994 {
2995     auto desc = arg.AllowNoValue!valueToUse;
2996     desc.info.minValuesCount = 0;
2997     desc.info.maxValuesCount = 0;
2998     return desc;
2999 }
3000 
3001 auto Counter(ARG)(ARG arg)
3002 if(isArgumentUDA!ARG)
3003 {
3004     struct CounterParsingFunction
3005     {
3006         static bool parse(T)(ref T receiver, const ref RawParam param)
3007         {
3008             assert(param.value.length == 0);
3009 
3010             ++receiver;
3011 
3012             return true;
3013         }
3014     }
3015 
3016     auto desc = ArgumentUDA!(CounterParsingFunction)(arg.tupleof);
3017     desc.info.minValuesCount = 0;
3018     desc.info.maxValuesCount = 0;
3019     return desc;
3020 }
3021 
3022 
3023 unittest
3024 {
3025     struct T
3026     {
3027         @(NamedArgument.Counter()) int a;
3028     }
3029 
3030     assert(CLI!T.parseArgs!((T t) { assert(t == T(3)); })(["-a","-a","-a"]) == 0);
3031     static assert(["-a","-a","-a"].parseCLIArgs!T.get == T(3));
3032     assert(["-a","-a","-a"].parseCLIArgs!T.get == T(3));
3033 }
3034 
3035 
3036 auto AllowedValues(alias values, ARG)(ARG arg)
3037 {
3038     import std.array : assocArray;
3039     import std.range : repeat;
3040 
3041     enum valuesAA = assocArray(values, false.repeat);
3042 
3043     auto desc = arg.Validation!(Validators.ValueInList!(values, KeyType!(typeof(valuesAA))));
3044     desc.info.setAllowedValues!values;
3045     return desc;
3046 }
3047 
3048 
3049 unittest
3050 {
3051     struct T
3052     {
3053         @(NamedArgument.AllowedValues!([1,3,5])) int a;
3054     }
3055 
3056     static assert(["-a","3"].parseCLIArgs!T.get == T(3));
3057     assert(["-a","2"].parseCLIArgs!T.isNull);
3058     assert(["-a","3"].parseCLIArgs!T.get == T(3));
3059 }
3060 
3061 unittest
3062 {
3063     struct T
3064     {
3065         @(NamedArgument.AllowedValues!(["apple","pear","banana"]))
3066         string fruit;
3067     }
3068 
3069     assert(CLI!T.parseArgs!((T t) { assert(t == T("apple")); })(["--fruit", "apple"]) == 0);
3070     assert(CLI!T.parseArgs!((T t) { assert(false); })(["--fruit", "kiwi"]) != 0);    // "kiwi" is not allowed
3071     static assert(["--fruit", "apple"].parseCLIArgs!T.get == T("apple"));
3072     assert(["--fruit", "kiwi"].parseCLIArgs!T.isNull);
3073 }
3074 
3075 unittest
3076 {
3077     enum Fruit { apple, pear, banana }
3078     struct T
3079     {
3080         @NamedArgument
3081         Fruit fruit;
3082     }
3083 
3084     static assert(["--fruit", "apple"].parseCLIArgs!T.get == T(Fruit.apple));
3085     assert(["--fruit", "kiwi"].parseCLIArgs!T.isNull);
3086 }
3087 
3088 
3089 auto PositionalArgument(uint pos)
3090 {
3091     auto arg = ArgumentUDA!(ValueParseFunctions!(void, void, void, void, void, void))(ArgumentInfo()).Required();
3092     arg.info.position = pos;
3093     return arg;
3094 }
3095 
3096 auto PositionalArgument(uint pos, string name)
3097 {
3098     auto arg = ArgumentUDA!(ValueParseFunctions!(void, void, void, void, void, void))(ArgumentInfo([name])).Required();
3099     arg.info.position = pos;
3100     return arg;
3101 }
3102 
3103 auto NamedArgument(string[] name...)
3104 {
3105     return ArgumentUDA!(ValueParseFunctions!(void, void, void, void, void, void))(ArgumentInfo(name)).Optional();
3106 }
3107 
3108 auto NamedArgument(string name)
3109 {
3110     return ArgumentUDA!(ValueParseFunctions!(void, void, void, void, void, void))(ArgumentInfo([name])).Optional();
3111 }
3112 
3113 struct TrailingArguments {}
3114 
3115 
3116 unittest
3117 {
3118     struct T
3119     {
3120         string a;
3121         string b;
3122 
3123         @TrailingArguments string[] args;
3124     }
3125 
3126     assert(CLI!T.parseArgs!((T t) { assert(t == T("A","",["-b","B"])); })(["-a","A","--","-b","B"]) == 0);
3127     static assert(["-a","A","--","-b","B"].parseCLIArgs!T.get == T("A","",["-b","B"]));
3128     assert(["-a","A","--","-b","B"].parseCLIArgs!T.get == T("A","",["-b","B"]));
3129 }
3130 
3131 unittest
3132 {
3133     struct T
3134     {
3135         @NamedArgument int i;
3136         @NamedArgument(["u","u1"])  uint u;
3137         @NamedArgument("d","d1")  double d;
3138     }
3139 
3140     assert(CLI!T.parseArgs!((T t) { assert(t == T(-5,8,12.345)); })(["-i","-5","-u","8","-d","12.345"]) == 0);
3141     assert(CLI!T.parseArgs!((T t) { assert(t == T(-5,8,12.345)); })(["-i","-5","-u1","8","-d1","12.345"]) == 0);
3142 
3143     static assert(["-i","-5","-u","8","-d","12.345"].parseCLIArgs!T.get == T(-5,8,12.345));
3144     static assert(["-i","-5","-u1","8","-d1","12.345"].parseCLIArgs!T.get == T(-5,8,12.345));
3145     assert(["-i","-5","-u","8","-d","12.345"].parseCLIArgs!T.get == T(-5,8,12.345));
3146     assert(["-i","-5","-u1","8","-d1","12.345"].parseCLIArgs!T.get == T(-5,8,12.345));
3147 }
3148 
3149 unittest
3150 {
3151     struct T
3152     {
3153         @NamedArgument int[]   a;
3154         @NamedArgument int[][] b;
3155     }
3156 
3157     assert(CLI!T.parseArgs!((T t) { assert(t.a == [1,2,3,4,5]); })(["-a","1","2","3","-a","4","5"]) == 0);
3158     assert(CLI!T.parseArgs!((T t) { assert(t.b == [[1,2,3],[4,5]]); })(["-b","1","2","3","-b","4","5"]) == 0);
3159 
3160     static assert(["-a","1","2","3","-a","4","5"].parseCLIArgs!T.get.a == [1,2,3,4,5]);
3161     static assert(["-b","1","2","3","-b","4","5"].parseCLIArgs!T.get.b == [[1,2,3],[4,5]]);
3162     assert(["-a","1","2","3","-a","4","5"].parseCLIArgs!T.get.a == [1,2,3,4,5]);
3163     assert(["-b","1","2","3","-b","4","5"].parseCLIArgs!T.get.b == [[1,2,3],[4,5]]);
3164 }
3165 
3166 unittest
3167 {
3168     struct T
3169     {
3170         @NamedArgument int[] a;
3171     }
3172 
3173     enum cfg = {
3174         Config cfg;
3175         cfg.arraySep = ',';
3176         return cfg;
3177     }();
3178 
3179     assert(CLI!(cfg, T).parseArgs!((T t) { assert(t == T([1,2,3,4,5])); })(["-a","1,2,3","-a","4","5"]) == 0);
3180     assert(["-a","1,2,3","-a","4","5"].parseCLIArgs!T(cfg).get == T([1,2,3,4,5]));
3181 }
3182 
3183 unittest
3184 {
3185     struct T
3186     {
3187         @NamedArgument int[string] a;
3188     }
3189 
3190     assert(CLI!T.parseArgs!((T t) { assert(t == T(["foo":3,"boo":7])); })(["-a=foo=3","-a","boo=7"]) == 0);
3191     static assert(["-a=foo=3","-a","boo=7"].parseCLIArgs!T.get.a == ["foo":3,"boo":7]);
3192     assert(["-a=foo=3","-a","boo=7"].parseCLIArgs!T.get.a == ["foo":3,"boo":7]);
3193 }
3194 
3195 unittest
3196 {
3197     struct T
3198     {
3199         @NamedArgument int[string] a;
3200     }
3201 
3202     enum cfg = {
3203         Config cfg;
3204         cfg.arraySep = ',';
3205         return cfg;
3206     }();
3207 
3208     assert(CLI!(cfg, T).parseArgs!((T t) { assert(t == T(["foo":3,"boo":7])); })(["-a=foo=3,boo=7"]) == 0);
3209     assert(CLI!(cfg, T).parseArgs!((T t) { assert(t == T(["foo":3,"boo":7])); })(["-a","foo=3,boo=7"]) == 0);
3210     assert(["-a=foo=3,boo=7"].parseCLIArgs!T(cfg).get.a == ["foo":3,"boo":7]);
3211     assert(["-a","foo=3,boo=7"].parseCLIArgs!T(cfg).get.a == ["foo":3,"boo":7]);
3212 }
3213 
3214 unittest
3215 {
3216     struct T
3217     {
3218         enum Fruit { apple, pear };
3219 
3220         @NamedArgument Fruit a;
3221     }
3222 
3223     assert(CLI!T.parseArgs!((T t) { assert(t == T(T.Fruit.apple)); })(["-a","apple"]) == 0);
3224     assert(CLI!T.parseArgs!((T t) { assert(t == T(T.Fruit.pear)); })(["-a=pear"]) == 0);
3225     static assert(["-a","apple"].parseCLIArgs!T.get == T(T.Fruit.apple));
3226     static assert(["-a=pear"].parseCLIArgs!T.get == T(T.Fruit.pear));
3227     assert(["-a","apple"].parseCLIArgs!T.get == T(T.Fruit.apple));
3228     assert(["-a=pear"].parseCLIArgs!T.get == T(T.Fruit.pear));
3229 }
3230 
3231 unittest
3232 {
3233     struct T
3234     {
3235         @NamedArgument string[] a;
3236     }
3237 
3238     assert(CLI!T.parseArgs!((T t) { assert(t == T(["1,2,3","4","5"])); })(["-a","1,2,3","-a","4","5"]) == 0);
3239     assert(["-a","1,2,3","-a","4","5"].parseCLIArgs!T.get == T(["1,2,3","4","5"]));
3240 
3241     enum cfg = {
3242         Config cfg;
3243         cfg.arraySep = ',';
3244         return cfg;
3245     }();
3246 
3247     assert(CLI!(cfg, T).parseArgs!((T t) { assert(t == T(["1","2","3","4","5"])); })(["-a","1,2,3","-a","4","5"]) == 0);
3248     assert(["-a","1,2,3","-a","4","5"].parseCLIArgs!T(cfg).get == T(["1","2","3","4","5"]));
3249 }
3250 
3251 unittest
3252 {
3253     struct T
3254     {
3255         @(NamedArgument.AllowNoValue  !10) int a;
3256         @(NamedArgument.RequireNoValue!20) int b;
3257     }
3258 
3259     assert(CLI!T.parseArgs!((T t) { assert(t.a == 10); })(["-a"]) == 0);
3260     assert(CLI!T.parseArgs!((T t) { assert(t.b == 20); })(["-b"]) == 0);
3261     assert(CLI!T.parseArgs!((T t) { assert(t.a == 30); })(["-a","30"]) == 0);
3262     assert(CLI!T.parseArgs!((T t) { assert(false); })(["-b","30"]) != 0);
3263     static assert(["-a"].parseCLIArgs!T.get.a == 10);
3264     static assert(["-b"].parseCLIArgs!T.get.b == 20);
3265     static assert(["-a", "30"].parseCLIArgs!T.get.a == 30);
3266     assert(["-a"].parseCLIArgs!T.get.a == 10);
3267     assert(["-b"].parseCLIArgs!T.get.b == 20);
3268     assert(["-a", "30"].parseCLIArgs!T.get.a == 30);
3269     assert(["-b", "30"].parseCLIArgs!T.isNull);
3270 }
3271 
3272 unittest
3273 {
3274     struct T
3275     {
3276         @(NamedArgument
3277          .PreValidation!((string s) { return s.length > 1 && s[0] == '!'; })
3278          .Parse        !((string s) { return s[1]; })
3279          .Validation   !((char v) { return v >= '0' && v <= '9'; })
3280          .Action       !((ref int a, char v) { a = v - '0'; })
3281         )
3282         int a;
3283     }
3284 
3285     assert(CLI!T.parseArgs!((T t) { assert(t == T(4)); })(["-a","!4"]) == 0);
3286     static assert(["-a","!4"].parseCLIArgs!T.get.a == 4);
3287     assert(["-a","!4"].parseCLIArgs!T.get.a == 4);
3288 }
3289 
3290 unittest
3291 {
3292     static struct T
3293     {
3294         int a;
3295 
3296         @(NamedArgument("a")) void foo() { a++; }
3297     }
3298 
3299     assert(CLI!T.parseArgs!((T t) { assert(t == T(4)); })(["-a","-a","-a","-a"]) == 0);
3300     static assert(["-a","-a","-a","-a"].parseCLIArgs!T.get.a == 4);
3301     assert(["-a","-a","-a","-a"].parseCLIArgs!T.get.a == 4);
3302 }
3303 
3304 
3305 private string getProgramName()
3306 {
3307     import core.runtime: Runtime;
3308     import std.path: baseName;
3309     return Runtime.args[0].baseName;
3310 }
3311 
3312 unittest
3313 {
3314     assert(getProgramName().length > 0);
3315 }
3316 
3317 
3318 private struct CommandInfo
3319 {
3320     string[] names = [""];
3321     string usage;
3322     string description;
3323     string shortDescription;
3324     string epilog;
3325 
3326     auto ref Usage(string text)
3327     {
3328         usage = text;
3329         return this;
3330     }
3331 
3332     auto ref Description(string text)
3333     {
3334         description = text;
3335         return this;
3336     }
3337 
3338     auto ref ShortDescription(string text)
3339     {
3340         shortDescription = text;
3341         return this;
3342     }
3343 
3344     auto ref Epilog(string text)
3345     {
3346         epilog = text;
3347         return this;
3348     }
3349 }
3350 
3351 auto Command(string[] name...)
3352 {
3353     return CommandInfo(name);
3354 }
3355 
3356 unittest
3357 {
3358     auto a = Command("MYPROG").Usage("usg").Description("desc").ShortDescription("sum").Epilog("epi");
3359     assert(a.names == ["MYPROG"]);
3360     assert(a.usage == "usg");
3361     assert(a.description == "desc");
3362     assert(a.shortDescription == "sum");
3363     assert(a.epilog == "epi");
3364 }
3365 
3366 private mixin template ForwardMemberFunction(string dest)
3367 {
3368     import std.array: split;
3369     mixin("auto "~dest.split('.')[$-1]~"(Args...)(auto ref Args args) inout { import core.lifetime: forward; return "~dest~"(forward!args); }");
3370 }
3371 
3372 
3373 private struct CommandArguments(RECEIVER)
3374 {
3375     static assert(getSymbolsByUDA!(RECEIVER, TrailingArguments).length <= 1,
3376         "Type "~RECEIVER.stringof~" must have at most one 'TrailingArguments' UDA");
3377 
3378     private enum _validate = checkArgumentNames!RECEIVER &&
3379                              checkPositionalIndexes!RECEIVER;
3380 
3381     static assert(getUDAs!(RECEIVER, CommandInfo).length <= 1);
3382 
3383     CommandInfo info;
3384     const(string)[] parentNames;
3385 
3386     Arguments arguments;
3387 
3388     ParseFunction!RECEIVER[] parseFunctions;
3389 
3390     uint level; // (sub-)command level, 0 = top level
3391 
3392     // sub commands
3393     size_t[string] subCommandsByName;
3394     CommandInfo[] subCommands;
3395     ParseSubCommandFunction!RECEIVER[] parseSubCommands;
3396 
3397     mixin ForwardMemberFunction!"arguments.findPositionalArgument";
3398     mixin ForwardMemberFunction!"arguments.findNamedArgument";
3399     mixin ForwardMemberFunction!"arguments.checkRestrictions";
3400 
3401 
3402 
3403     private this(in Config config)
3404     {
3405         static if(getUDAs!(RECEIVER, CommandInfo).length > 0)
3406             CommandInfo info = getUDAs!(RECEIVER, CommandInfo)[0];
3407         else
3408             CommandInfo info;
3409 
3410         this(config, info);
3411     }
3412 
3413     private this(PARENT = void)(in Config config, CommandInfo info, const PARENT* parentArguments = null)
3414     {
3415         this.info = info;
3416 
3417         checkArgumentName!RECEIVER(config.namedArgChar);
3418 
3419         static if(is(PARENT == void))
3420         {
3421             level = 0;
3422             arguments = Arguments(config.caseSensitive);
3423         }
3424         else
3425         {
3426             parentNames = parentArguments.parentNames ~ parentArguments.info.names[0];
3427             level = parentArguments.level + 1;
3428             arguments = Arguments(config.caseSensitive, &parentArguments.arguments);
3429         }
3430 
3431         fillArguments();
3432 
3433         if(config.addHelp)
3434         {
3435             arguments.addArgument!helpArgument;
3436             parseFunctions ~= delegate (in Config config, string argName, ref RECEIVER receiver, string rawValue, ref string[] rawArgs)
3437             {
3438                 import std.stdio: stdout;
3439 
3440                 printHelp(stdout.lockingTextWriter(), this, config);
3441 
3442                 return Result(0);
3443             };
3444         }
3445     }
3446 
3447     private void fillArguments()
3448     {
3449         enum hasNoUDAs = getSymbolsByUDA!(RECEIVER, ArgumentUDA  ).length == 0 &&
3450                          getSymbolsByUDA!(RECEIVER, NamedArgument).length == 0 &&
3451                          getSymbolsByUDA!(RECEIVER, SubCommands  ).length == 0;
3452 
3453         static foreach(sym; __traits(allMembers, RECEIVER))
3454         {{
3455             alias mem = __traits(getMember, RECEIVER, sym);
3456 
3457             static if(!is(mem)) // skip types
3458             {
3459                 static if(hasUDA!(mem, ArgumentUDA) || hasUDA!(mem, NamedArgument))
3460                     addArgument!sym;
3461                 else static if(hasUDA!(mem, SubCommands))
3462                     addSubCommands!sym;
3463                 else static if(hasNoUDAs &&
3464                     // skip "op*" functions
3465                     !(is(typeof(mem) == function) && sym.length > 2 && sym[0..2] == "op"))
3466                 {
3467                     import std.sumtype: isSumType;
3468 
3469                     static if(isSumType!(typeof(mem)))
3470                         addSubCommands!sym;
3471                     else
3472                         addArgument!sym;
3473                 }
3474             }
3475         }}
3476     }
3477 
3478     private void addArgument(alias symbol)()
3479     {
3480         alias member = __traits(getMember, RECEIVER, symbol);
3481 
3482         static assert(getUDAs!(member, ArgumentUDA).length <= 1,
3483             "Member "~RECEIVER.stringof~"."~symbol~" has multiple '*Argument' UDAs");
3484 
3485         static assert(getUDAs!(member, Group).length <= 1,
3486             "Member "~RECEIVER.stringof~"."~symbol~" has multiple 'Group' UDAs");
3487 
3488         static if(getUDAs!(member, ArgumentUDA).length > 0)
3489             enum uda = getUDAs!(member, ArgumentUDA)[0];
3490         else
3491             enum uda = NamedArgument();
3492 
3493         enum info = setDefaults!(uda.info, typeof(member), symbol);
3494 
3495         enum restrictions = {
3496             RestrictionGroup[] restrictions;
3497             static foreach(gr; getUDAs!(member, RestrictionGroup))
3498                 restrictions ~= gr;
3499             return restrictions;
3500         }();
3501 
3502         static if(getUDAs!(member, Group).length > 0)
3503             arguments.addArgument!(info, restrictions, getUDAs!(member, Group)[0]);
3504         else
3505             arguments.addArgument!(info, restrictions);
3506 
3507         parseFunctions ~= ParsingArgument!(symbol, uda, info, RECEIVER);
3508     }
3509 
3510     private void addSubCommands(alias symbol)()
3511     {
3512         import std.sumtype: isSumType;
3513 
3514         alias member = __traits(getMember, RECEIVER, symbol);
3515 
3516         static assert(isSumType!(typeof(member)), RECEIVER.stringof~"."~symbol~" must have 'SumType' type");
3517 
3518         static assert(getUDAs!(member, SubCommands).length <= 1,
3519             "Member "~RECEIVER.stringof~"."~symbol~" has multiple 'SubCommands' UDAs");
3520 
3521         static foreach(TYPE; typeof(member).Types)
3522         {{
3523             enum defaultCommand = is(TYPE == Default!COMMAND_TYPE, COMMAND_TYPE);
3524             static if(!defaultCommand)
3525                 alias COMMAND_TYPE = TYPE;
3526 
3527             static assert(getUDAs!(COMMAND_TYPE, CommandInfo).length <= 1);
3528 
3529             //static assert(getUDAs!(member, Group).length <= 1,
3530             //    "Member "~RECEIVER.stringof~"."~symbol~" has multiple 'Group' UDAs");
3531 
3532             static if(getUDAs!(COMMAND_TYPE, CommandInfo).length > 0)
3533                 enum info = getUDAs!(COMMAND_TYPE, CommandInfo)[0];
3534             else
3535                 enum info = CommandInfo([COMMAND_TYPE.stringof]);
3536 
3537             static assert(info.names.length > 0 && info.names[0].length > 0);
3538 
3539             //static if(getUDAs!(member, Group).length > 0)
3540             //    args.addArgument!(info, restrictions, getUDAs!(member, Group)[0])(ParsingArgument!(symbol, uda, info, RECEIVER));
3541             //else
3542             //arguments.addSubCommand!(info);
3543 
3544             immutable index = subCommands.length;
3545 
3546             static foreach(name; info.names)
3547             {
3548                 assert(!(name in subCommandsByName), "Duplicated name of subcommand: "~name);
3549                 subCommandsByName[arguments.convertCase(name)] = index;
3550             }
3551 
3552             static if(defaultCommand)
3553             {
3554                 assert(!(DEFAULT_COMMAND in subCommandsByName), "Multiple default subcommands: "~RECEIVER.stringof~"."~symbol);
3555                 subCommandsByName[DEFAULT_COMMAND] = index;
3556             }
3557 
3558             subCommands ~= info;
3559             //group.arguments ~= index;
3560             parseSubCommands ~= ParsingSubCommand!(TYPE, info, RECEIVER, symbol)(&this);
3561         }}
3562     }
3563 
3564     auto findSubCommand(string name) const
3565     {
3566         struct Result
3567         {
3568             uint level = uint.max;
3569             ParseSubCommandFunction!RECEIVER parse;
3570         }
3571 
3572         auto p = arguments.convertCase(name) in subCommandsByName;
3573         return !p ? Result.init : Result(level+1, parseSubCommands[*p]);
3574     }
3575 
3576     static if(getSymbolsByUDA!(RECEIVER, TrailingArguments).length == 1)
3577     {
3578         private void setTrailingArgs(ref RECEIVER receiver, string[] rawValues) const
3579         {
3580             enum symbol = __traits(identifier, getSymbolsByUDA!(RECEIVER, TrailingArguments)[0]);
3581             auto target = &__traits(getMember, receiver, symbol);
3582 
3583             static if(__traits(compiles, { *target = rawValues; }))
3584                 *target = rawValues;
3585             else
3586                 static assert(false, "Type '"~typeof(*target).stringof~"' of `"~
3587                     RECEIVER.stringof~"."~symbol~"` is not supported for 'TrailingArguments' UDA");
3588         }
3589     }
3590 }
3591 
3592 ////////////////////////////////////////////////////////////////////////////////////////////////////
3593 // Help-printing functions
3594 ////////////////////////////////////////////////////////////////////////////////////////////////////
3595 private void printValue(Output)(auto ref Output output, in ArgumentInfo info)
3596 {
3597     if(info.maxValuesCount.get == 0)
3598         return;
3599 
3600     if(info.minValuesCount.get == 0)
3601         output.put('[');
3602 
3603     output.put(info.placeholder);
3604     if(info.maxValuesCount.get > 1)
3605         output.put(" ...");
3606 
3607     if(info.minValuesCount.get == 0)
3608         output.put(']');
3609 }
3610 
3611 unittest
3612 {
3613     auto test(int min, int max)
3614     {
3615         ArgumentInfo info;
3616         info.placeholder = "v";
3617         info.minValuesCount = min;
3618         info.maxValuesCount = max;
3619 
3620         import std.array: appender;
3621         auto a = appender!string;
3622         a.printValue(info);
3623         return a[];
3624     }
3625 
3626     assert(test(0,0) == "");
3627     assert(test(0,1) == "[v]");
3628     assert(test(0,5) == "[v ...]");
3629     assert(test(1,1) == "v");
3630     assert(test(1,5) == "v ...");
3631     assert(test(3,3) == "v ...");
3632     assert(test(3,5) == "v ...");
3633 }
3634 
3635 
3636 private string getArgumentName(string name, in Config config)
3637 {
3638     name = config.namedArgChar ~ name;
3639     return name.length > 2 ? config.namedArgChar ~ name : name;
3640 }
3641 
3642 unittest
3643 {
3644     assert(getArgumentName("f", Config.init) == "-f");
3645     assert(getArgumentName("foo", Config.init) == "--foo");
3646 }
3647 
3648 
3649 private void printInvocation(Output)(auto ref Output output, in ArgumentInfo info, in string[] names, in Config config)
3650 {
3651     if(info.positional)
3652         output.printValue(info);
3653     else
3654     {
3655         import std.algorithm: each;
3656 
3657         names.each!((i, name)
3658         {
3659             if(i > 0)
3660                 output.put(", ");
3661 
3662             output.put(getArgumentName(name, config));
3663 
3664             if(info.maxValuesCount.get > 0)
3665             {
3666                 output.put(' ');
3667                 output.printValue(info);
3668             }
3669         });
3670     }
3671 }
3672 
3673 unittest
3674 {
3675     auto test(bool positional)()
3676     {
3677         enum info = {
3678             ArgumentInfo info;
3679             info.placeholder = "v";
3680             static if (positional)
3681                 info.position = 0;
3682             return info;
3683         }();
3684 
3685         import std.array: appender;
3686         auto a = appender!string;
3687         a.printInvocation(setDefaults!(info, int, "foo"), ["f","foo"], Config.init);
3688         return a[];
3689     }
3690 
3691     assert(test!false == "-f v, --foo v");
3692     assert(test!true == "v");
3693 }
3694 
3695 
3696 private void printUsage(Output)(auto ref Output output, in ArgumentInfo info, in Config config)
3697 {
3698     if(!info.required)
3699         output.put('[');
3700 
3701     output.printInvocation(info, [info.names[0]], config);
3702 
3703     if(!info.required)
3704         output.put(']');
3705 }
3706 
3707 unittest
3708 {
3709     auto test(bool required, bool positional)()
3710     {
3711         enum info = {
3712             ArgumentInfo info;
3713             info.names ~= "foo";
3714             info.placeholder = "v";
3715             info.required = required;
3716             static if (positional)
3717                 info.position = 0;
3718             return info;
3719         }();
3720 
3721         import std.array: appender;
3722         auto a = appender!string;
3723         a.printUsage(setDefaults!(info, int, "foo"), Config.init);
3724         return a[];
3725     }
3726 
3727     assert(test!(false, false) == "[--foo v]");
3728     assert(test!(false, true) == "[v]");
3729     assert(test!(true, false) == "--foo v");
3730     assert(test!(true, true) == "v");
3731 }
3732 
3733 
3734 private void substituteProg(Output)(auto ref Output output, string text, string prog)
3735 {
3736     import std.array: replaceInto;
3737     output.replaceInto(text, "%(PROG)", prog);
3738 }
3739 
3740 unittest
3741 {
3742     import std.array: appender;
3743     auto a = appender!string;
3744     a.substituteProg("this is some text where %(PROG) is substituted but PROG and prog are not", "-myprog-");
3745     assert(a[] == "this is some text where -myprog- is substituted but PROG and prog are not");
3746 }
3747 
3748 
3749 private string spaces(ulong num)
3750 {
3751     import std.range: repeat;
3752     import std.array: array;
3753     return ' '.repeat(num).array;
3754 }
3755 
3756 unittest
3757 {
3758     assert(spaces(0) == "");
3759     assert(spaces(1) == " ");
3760     assert(spaces(5) == "     ");
3761 }
3762 
3763 private void printUsage(T, Output)(auto ref Output output, in CommandArguments!T cmd, in Config config)
3764 {
3765     import std.algorithm: map;
3766     import std.array: join;
3767 
3768     string progName = (cmd.parentNames ~ cmd.info.names[0]).map!(_ => _.length > 0 ? _ : getProgramName()).join(" ");
3769 
3770     output.put("Usage: ");
3771 
3772     if(cmd.info.usage.length > 0)
3773         substituteProg(output, cmd.info.usage, progName);
3774     else
3775     {
3776         import std.algorithm: filter, each, map;
3777 
3778         alias print = (r) => r
3779             .filter!((ref _) => !_.hideFromHelp)
3780             .each!((ref _)
3781             {
3782                 output.put(' ');
3783                 output.printUsage(_, config);
3784             });
3785 
3786         output.put(progName);
3787 
3788         // named args
3789         print(cmd.arguments.arguments.filter!((ref _) => !_.positional));
3790         // positional args
3791         print(cmd.arguments.positionalArguments.map!(ref (_) => cmd.arguments.arguments[_]));
3792         // sub commands
3793         if(cmd.subCommands.length > 0)
3794             output.put(" <command> [<args>]");
3795     }
3796 
3797     output.put('\n');
3798 }
3799 
3800 void printUsage(T, Output)(auto ref Output output, in Config config)
3801 {
3802     printUsage(output, CommandArguments!T(config), config);
3803 }
3804 
3805 unittest
3806 {
3807     @(Command("MYPROG").Usage("custom usage of %(PROG)"))
3808     struct T
3809     {
3810         string s;
3811     }
3812 
3813     auto test(string usage)
3814     {
3815         import std.array: appender;
3816 
3817         auto a = appender!string;
3818         a.printUsage!T(Config.init);
3819         return a[];
3820     }
3821 
3822     enum expected = "Usage: custom usage of MYPROG\n";
3823     static assert(test("custom usage of %(PROG)") == expected);
3824     assert(test("custom usage of %(PROG)") == expected);
3825 }
3826 
3827 
3828 private void printHelp(Output, ARGS)(auto ref Output output, in Group group, ARGS args, int helpPosition)
3829 {
3830     import std.string: leftJustify;
3831 
3832     if(group.arguments.length == 0 || group.name.length == 0)
3833         return;
3834 
3835     alias printDescription = {
3836         output.put(group.name);
3837         output.put(":\n");
3838 
3839         if (group.description.length > 0)
3840         {
3841             output.put("  ");
3842             output.put(group.description);
3843             output.put("\n\n");
3844         }
3845     };
3846     bool descriptionIsPrinted = false;
3847 
3848     immutable ident = spaces(helpPosition + 2);
3849 
3850     foreach(idx; group.arguments)
3851     {
3852         auto arg = &args[idx];
3853 
3854         if(arg.invocation.length == 0)
3855             continue;
3856 
3857         if(!descriptionIsPrinted)
3858         {
3859             printDescription();
3860             descriptionIsPrinted = true;
3861         }
3862 
3863         if(arg.invocation.length <= helpPosition - 4) // 2=indent, 2=two spaces between invocation and help text
3864         {
3865             import std.array: appender;
3866 
3867             auto invocation = appender!string;
3868             invocation ~= "  ";
3869             invocation ~= arg.invocation.leftJustify(helpPosition);
3870             output.wrapMutiLine(arg.help, 80-2, invocation[], ident);
3871         }
3872         else
3873         {
3874             // long action name; start on the next line
3875             output.put("  ");
3876             output.put(arg.invocation);
3877             output.put("\n");
3878             output.wrapMutiLine(arg.help, 80-2, ident, ident);
3879         }
3880     }
3881 
3882     output.put('\n');
3883 }
3884 
3885 
3886 private void printHelp(Output)(auto ref Output output, in Arguments arguments, in Config config, bool helpArgIsPrinted = false)
3887 {
3888     import std.algorithm: map, maxElement, min;
3889     import std.array: appender, array;
3890 
3891     // pre-compute the output
3892     auto args =
3893         arguments.arguments
3894         .map!((ref _)
3895         {
3896             struct Result
3897             {
3898                 string invocation, help;
3899             }
3900 
3901             if(_.hideFromHelp)
3902                 return Result.init;
3903 
3904             if(isHelpArgument(_.names[0]))
3905             {
3906                 if(helpArgIsPrinted)
3907                     return Result.init;
3908 
3909                 helpArgIsPrinted = true;
3910             }
3911 
3912             auto invocation = appender!string;
3913             invocation.printInvocation(_, _.names, config);
3914 
3915             return Result(invocation[], _.description);
3916         }).array;
3917 
3918     immutable maxInvocationWidth = args.map!(_ => _.invocation.length).maxElement;
3919     immutable helpPosition = min(maxInvocationWidth + 4, 24);
3920 
3921     //user-defined groups
3922     foreach(ref group; arguments.groups[2..$])
3923         output.printHelp(group, args, helpPosition);
3924 
3925     //required args
3926     output.printHelp(arguments.requiredGroup, args, helpPosition);
3927 
3928     //optionals args
3929     output.printHelp(arguments.optionalGroup, args, helpPosition);
3930 
3931     if(arguments.parentArguments)
3932         output.printHelp(*arguments.parentArguments, config, helpArgIsPrinted);
3933 }
3934 
3935 private void printHelp(Output)(auto ref Output output, in CommandInfo[] commands, in Config config)
3936 {
3937     import std.algorithm: map, maxElement, min;
3938     import std.array: appender, array, join;
3939 
3940     if(commands.length == 0)
3941         return;
3942 
3943     output.put("Available commands:\n");
3944 
3945     // pre-compute the output
3946     auto cmds = commands
3947         .map!((ref _)
3948         {
3949             struct Result
3950             {
3951                 string invocation, help;
3952             }
3953 
3954             //if(_.hideFromHelp)
3955             //    return Result.init;
3956 
3957             return Result(_.names.join(","), _.shortDescription);
3958         }).array;
3959 
3960     immutable maxInvocationWidth = cmds.map!(_ => _.invocation.length).maxElement;
3961     immutable helpPosition = min(maxInvocationWidth + 4, 24);
3962 
3963 
3964     immutable ident = spaces(helpPosition + 2);
3965 
3966     foreach(const ref cmd; cmds)
3967     {
3968         if(cmd.invocation.length == 0)
3969             continue;
3970 
3971         if(cmd.invocation.length <= helpPosition - 4) // 2=indent, 2=two spaces between invocation and help text
3972         {
3973             import std.array: appender;
3974             import std.string: leftJustify;
3975 
3976             auto invocation = appender!string;
3977             invocation ~= "  ";
3978             invocation ~= cmd.invocation.leftJustify(helpPosition);
3979             output.wrapMutiLine(cmd.help, 80-2, invocation[], ident);
3980         }
3981         else
3982         {
3983             // long action name; start on the next line
3984             output.put("  ");
3985             output.put(cmd.invocation);
3986             output.put("\n");
3987             output.wrapMutiLine(cmd.help, 80-2, ident, ident);
3988         }
3989     }
3990 
3991     output.put('\n');
3992 }
3993 
3994 
3995 private void printHelp(T, Output)(auto ref Output output, in CommandArguments!T cmd, in Config config)
3996 {
3997     printUsage(output, cmd, config);
3998     output.put('\n');
3999 
4000     if(cmd.info.description.length > 0)
4001     {
4002         output.put(cmd.info.description);
4003         output.put("\n\n");
4004     }
4005 
4006     // sub commands
4007     output.printHelp(cmd.subCommands, config);
4008 
4009     output.printHelp(cmd.arguments, config);
4010 
4011     if(cmd.info.epilog.length > 0)
4012     {
4013         output.put(cmd.info.epilog);
4014         output.put('\n');
4015     }
4016 }
4017 
4018 void printHelp(T, Output)(auto ref Output output, in Config config)
4019 {
4020     printHelp(output, CommandArguments!T(config), config);
4021 }
4022 
4023 unittest
4024 {
4025     @(Command("MYPROG")
4026      .Description("custom description")
4027      .Epilog("custom epilog")
4028     )
4029     struct T
4030     {
4031         @NamedArgument  string s;
4032         @(NamedArgument.Placeholder("VALUE"))  string p;
4033 
4034         @(NamedArgument.HideFromHelp())  string hidden;
4035 
4036         enum Fruit { apple, pear };
4037         @(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;
4038 
4039         @(NamedArgument.AllowedValues!([1,4,16,8])) int i;
4040 
4041         @(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;
4042         @(PositionalArgument(1).AllowedValues!(["q","a"])) string param1;
4043 
4044         @TrailingArguments string[] args;
4045     }
4046 
4047     auto test(alias func)()
4048     {
4049         import std.array: appender;
4050 
4051         auto a = appender!string;
4052         func!T(a, Config.init);
4053         return a[];
4054     }
4055     static assert(test!printUsage.length > 0);  // ensure that it works at compile time
4056     static assert(test!printHelp .length > 0);  // ensure that it works at compile time
4057 
4058     assert(test!printUsage == "Usage: MYPROG [-s S] [-p VALUE] -f {apple,pear} [-i {1,4,16,8}] [-h] param0 {q,a}\n");
4059     assert(test!printHelp  == "Usage: MYPROG [-s S] [-p VALUE] -f {apple,pear} [-i {1,4,16,8}] [-h] param0 {q,a}\n\n"~
4060         "custom description\n\n"~
4061         "Required arguments:\n"~
4062         "  -f {apple,pear}, --fruit {apple,pear}\n"~
4063         "                          This is a help text for fruit. Very very very very\n"~
4064         "                          very very very very very very very very very very\n"~
4065         "                          very very very very very long text\n"~
4066         "  param0                  This is a help text for param0. Very very very very\n"~
4067         "                          very very very very very very very very very very\n"~
4068         "                          very very very very very long text\n"~
4069         "  {q,a}                   \n\n"~
4070         "Optional arguments:\n"~
4071         "  -s S                    \n"~
4072         "  -p VALUE                \n"~
4073         "  -i {1,4,16,8}           \n"~
4074         "  -h, --help              Show this help message and exit\n\n"~
4075         "custom epilog\n");
4076 }
4077 
4078 unittest
4079 {
4080     @Command("MYPROG")
4081     struct T
4082     {
4083         @(ArgumentGroup("group1").Description("group1 description"))
4084         {
4085             @NamedArgument
4086             {
4087                 string a;
4088                 string b;
4089             }
4090             @PositionalArgument(0) string p;
4091         }
4092 
4093         @(ArgumentGroup("group2").Description("group2 description"))
4094         @NamedArgument
4095         {
4096             string c;
4097             string d;
4098         }
4099         @PositionalArgument(1) string q;
4100     }
4101 
4102     auto test(alias func)()
4103     {
4104         import std.array: appender;
4105 
4106         auto a = appender!string;
4107         func!T(a, Config.init);
4108         return a[];
4109     }
4110 
4111     assert(test!printHelp  == "Usage: MYPROG [-a A] [-b B] [-c C] [-d D] [-h] p q\n\n"~
4112         "group1:\n"~
4113         "  group1 description\n\n"~
4114         "  -a A          \n"~
4115         "  -b B          \n"~
4116         "  p             \n\n"~
4117         "group2:\n"~
4118         "  group2 description\n\n"~
4119         "  -c C          \n"~
4120         "  -d D          \n\n"~
4121         "Required arguments:\n"~
4122         "  q             \n\n"~
4123         "Optional arguments:\n"~
4124         "  -h, --help    Show this help message and exit\n\n");
4125 }
4126 
4127 unittest
4128 {
4129     import std.sumtype: SumType, match;
4130 
4131     @Command("MYPROG")
4132     struct T
4133     {
4134         @(Command("cmd1").ShortDescription("Perform cmd 1"))
4135         struct CMD1
4136         {
4137             string a;
4138         }
4139         @(Command("very-long-command-name-2").ShortDescription("Perform cmd 2"))
4140         struct CMD2
4141         {
4142             string b;
4143         }
4144 
4145         string c;
4146         string d;
4147 
4148         SumType!(CMD1, CMD2) cmd;
4149     }
4150 
4151     auto test(alias func)()
4152     {
4153         import std.array: appender;
4154 
4155         auto a = appender!string;
4156         func!T(a, Config.init);
4157         return a[];
4158     }
4159 
4160     assert(test!printHelp  == "Usage: MYPROG [-c C] [-d D] [-h] <command> [<args>]\n\n"~
4161         "Available commands:\n"~
4162         "  cmd1                    Perform cmd 1\n"~
4163         "  very-long-command-name-2\n"~
4164         "                          Perform cmd 2\n\n"~
4165         "Optional arguments:\n"~
4166         "  -c C          \n"~
4167         "  -d D          \n"~
4168         "  -h, --help    Show this help message and exit\n\n");
4169 }
4170 
4171 unittest
4172 {
4173     @Command("MYPROG")
4174     struct T
4175     {
4176         @(NamedArgument.HideFromHelp())  string s;
4177     }
4178 
4179     assert(parseCLIArgs!T(["-h","-s","asd"]).isNull());
4180     assert(parseCLIArgs!T(["-h"], (T t) { assert(false); }) == 0);
4181 
4182     auto args = ["-h","-s","asd"];
4183     assert(parseCLIKnownArgs!T(args).isNull());
4184     assert(args.length == 3);
4185     assert(parseCLIKnownArgs!T(["-h"], (T t, string[] args) { assert(false); }) == 0);
4186 }
4187 
4188 unittest
4189 {
4190     @Command("MYPROG")
4191     struct T
4192     {
4193         @(NamedArgument.Required())  string s;
4194     }
4195 
4196     assert(parseCLIArgs!T([], (T t) { assert(false); }) != 0);
4197 }
4198 
4199 unittest
4200 {
4201     @Command("MYPROG")
4202     struct T
4203     {
4204         @MutuallyExclusive()
4205         {
4206             string a;
4207             string b;
4208         }
4209     }
4210 
4211     // Either or no argument is allowed
4212     assert(CLI!T.parseArgs!((T t) {})(["-a","a"]) == 0);
4213     assert(CLI!T.parseArgs!((T t) {})(["-b","b"]) == 0);
4214     assert(CLI!T.parseArgs!((T t) {})([]) == 0);
4215 
4216     // Both arguments are not allowed
4217     assert(CLI!T.parseArgs!((T t) { assert(false); })(["-a","a","-b","b"]) != 0);
4218 
4219     assert(parseCLIArgs!T(["-a","a","-b","b"], (T t) { assert(false); }) != 0);
4220     assert(parseCLIArgs!T(["-a","a"], (T t) {}) == 0);
4221     assert(parseCLIArgs!T(["-b","b"], (T t) {}) == 0);
4222     assert(parseCLIArgs!T([], (T t) {}) == 0);
4223 }
4224 
4225 unittest
4226 {
4227     @Command("MYPROG")
4228     struct T
4229     {
4230         @RequiredTogether()
4231         {
4232             string a;
4233             string b;
4234         }
4235     }
4236 
4237     // Both or no argument is allowed
4238     assert(CLI!T.parseArgs!((T t) {})(["-a","a","-b","b"]) == 0);
4239     assert(CLI!T.parseArgs!((T t) {})([]) == 0);
4240 
4241     // Only one argument is not allowed
4242     assert(CLI!T.parseArgs!((T t) { assert(false); })(["-a","a"]) != 0);
4243     assert(CLI!T.parseArgs!((T t) { assert(false); })(["-b","b"]) != 0);
4244 
4245     assert(parseCLIArgs!T(["-a","a","-b","b"], (T t) {}) == 0);
4246     assert(parseCLIArgs!T(["-a","a"], (T t) { assert(false); }) != 0);
4247     assert(parseCLIArgs!T(["-b","b"], (T t) { assert(false); }) != 0);
4248     assert(parseCLIArgs!T([], (T t) {}) == 0);
4249 }
4250 
4251 
4252 private void wrapMutiLine(Output, S)(auto ref Output output,
4253                                      S s,
4254                                      in size_t columns = 80,
4255                                      S firstindent = null,
4256                                      S indent = null,
4257                                      in size_t tabsize = 8)
4258 if (isSomeString!S)
4259 {
4260     import std.string: wrap, lineSplitter, join;
4261     import std.algorithm: map, copy;
4262 
4263     auto lines = s.lineSplitter;
4264     if(lines.empty)
4265     {
4266         output.put(firstindent);
4267         output.put("\n");
4268         return;
4269     }
4270 
4271     output.put(lines.front.wrap(columns, firstindent, indent, tabsize));
4272     lines.popFront;
4273 
4274     lines.map!(s => s.wrap(columns, indent, indent, tabsize)).copy(output);
4275 }
4276 
4277 unittest
4278 {
4279     string test(string s, size_t columns, string firstindent = null, string indent = null)
4280     {
4281         import std.array: appender;
4282         auto a = appender!string;
4283         a.wrapMutiLine(s, columns, firstindent, indent);
4284         return a[];
4285     }
4286     assert(test("a short string", 7) == "a short\nstring\n");
4287     assert(test("a\nshort string", 7) == "a\nshort\nstring\n");
4288 
4289     // wrap will not break inside of a word, but at the next space
4290     assert(test("a short string", 4) == "a\nshort\nstring\n");
4291 
4292     assert(test("a short string", 7, "\t") == "\ta\nshort\nstring\n");
4293     assert(test("a short string", 7, "\t", "    ") == "\ta\n    short\n    string\n");
4294 }