1 module argparse;
2 
3 
4 import argparse.internal;
5 import argparse.parser: callParser;
6 
7 
8 ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
9 /// Public API
10 ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
11 
12 struct Config
13 {
14     /**
15        The assignment character used in options with parameters.
16        Defaults to '='.
17      */
18     char assignChar = '=';
19 
20     /**
21        When set to char.init, parameters to array and associative array receivers are
22        treated as an individual argument. That is, only one argument is appended or
23        inserted per appearance of the option switch. If `arraySep` is set to
24        something else, then each parameter is first split by the separator, and the
25        individual pieces are treated as arguments to the same option.
26 
27        Defaults to char.init
28      */
29     char arraySep = char.init;
30 
31     /**
32        The option character.
33        Defaults to '-'.
34      */
35     char namedArgChar = '-';
36 
37     /**
38        The string that conventionally marks the end of all options.
39        Assigning an empty string to `endOfArgs` effectively disables it.
40        Defaults to "--".
41      */
42     string endOfArgs = "--";
43 
44     /**
45        If set then argument names are case-sensitive.
46        Defaults to true.
47      */
48     bool caseSensitive = true;
49 
50     /**
51         Single-letter arguments can be bundled together, i.e. "-abc" is the same as "-a -b -c".
52         Disabled by default.
53      */
54     bool bundling = false;
55 
56     /**
57        Add a -h/--help option to the parser.
58        Defaults to true.
59      */
60     bool addHelp = true;
61 
62     /**
63        Delegate that processes error messages if they happen during argument parsing.
64        By default all errors are printed to stderr.
65      */
66     package void delegate(string s) nothrow errorHandlerFunc;
67 
68     @property auto errorHandler(void function(string s) nothrow func)
69     {
70         return errorHandlerFunc = (string msg) { func(msg); };
71     }
72 
73     @property auto errorHandler(void delegate(string s) nothrow func)
74     {
75         return errorHandlerFunc = func;
76     }
77 
78 
79     package void onError(A...)(A args) const nothrow
80     {
81         import std.conv: text;
82         import std.stdio: stderr, writeln;
83 
84         try
85         {
86             if(errorHandlerFunc)
87                 errorHandlerFunc(text!A(args));
88             else
89                 stderr.writeln("Error: ", args);
90         }
91         catch(Exception e)
92         {
93             throw new Error(e.msg);
94         }
95     }
96 }
97 
98 unittest
99 {
100     Config.init.onError("--just testing error func--",1,2.3,false);
101     Config c;
102     c.errorHandler = (string s){};
103     c.onError("--just testing error func--",1,2.3,false);
104 }
105 
106 ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
107 
108 struct Param(VALUE_TYPE)
109 {
110     const Config config;
111     string name;
112 
113     static if(!is(VALUE_TYPE == void))
114         VALUE_TYPE value;
115 }
116 
117 alias RawParam = Param!(string[]);
118 
119 ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
120 
121 struct Result
122 {
123     int  resultCode;
124 
125     package enum Status { failure, success, unknownArgument };
126     package Status status;
127 
128     package string errorMsg;
129 
130     package const(string)[] suggestions;
131 
132     package static enum Failure = Result(1, Status.failure);
133     package static enum Success = Result(0, Status.success);
134     package static enum UnknownArgument = Result(0, Status.unknownArgument);
135 
136     bool opCast(type)() const if (is(type == bool))
137     {
138         return status == Status.success;
139     }
140 
141     package static auto Error(A...)(A args)
142     {
143         import std.conv: text;
144         import std.stdio: stderr, writeln;
145 
146         return Result(1, Status.failure, text!A(args));
147     }
148 
149     version(unittest)
150     {
151         package bool isError(string text)
152         {
153             import std.algorithm: canFind;
154             return (!cast(bool) this) && errorMsg.canFind(text);
155         }
156     }
157 }
158 
159 ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
160 
161 package struct ArgumentInfo
162 {
163     import std.typecons: Nullable;
164 
165     package:
166 
167     string[] names;
168 
169     string description;
170     string placeholder;
171 
172     void setAllowedValues(alias names)()
173     {
174         if(placeholder.length == 0)
175         {
176             import std.conv: to;
177             import std.array: join;
178             import std.format: format;
179             placeholder = "{%s}".format(names.to!(string[]).join(','));
180         }
181     }
182 
183     bool hideFromHelp = false;      // if true then this argument is not printed on help page
184 
185     bool required;
186 
187     Nullable!uint position;
188 
189     @property bool positional() const { return !position.isNull; }
190 
191     Nullable!ulong minValuesCount;
192     Nullable!ulong maxValuesCount;
193 
194     auto checkValuesCount(string argName, ulong count) const
195     {
196         immutable min = minValuesCount.get;
197         immutable max = maxValuesCount.get;
198 
199         // override for boolean flags
200         if(allowBooleanNegation && count == 1)
201             return Result.Success;
202 
203         if(min == max && count != min)
204         {
205             return Result.Error("argument ",argName,": expected ",min,min == 1 ? " value" : " values");
206         }
207         if(count < min)
208         {
209             return Result.Error("argument ",argName,": expected at least ",min,min == 1 ? " value" : " values");
210         }
211         if(count > max)
212         {
213             return Result.Error("argument ",argName,": expected at most ",max,max == 1 ? " value" : " values");
214         }
215 
216         return Result.Success;
217     }
218 
219     bool allowBooleanNegation = true;
220     bool ignoreInDefaultCommand;
221 }
222 
223 
224 unittest
225 {
226     auto info(int min, int max)
227     {
228         ArgumentInfo info;
229         info.allowBooleanNegation = false;
230         info.minValuesCount = min;
231         info.maxValuesCount = max;
232         return info;
233     }
234 
235     assert(info(2,4).checkValuesCount("", 1).isError("expected at least 2 values"));
236     assert(info(2,4).checkValuesCount("", 2));
237     assert(info(2,4).checkValuesCount("", 3));
238     assert(info(2,4).checkValuesCount("", 4));
239     assert(info(2,4).checkValuesCount("", 5).isError("expected at most 4 values"));
240 
241     assert(info(2,2).checkValuesCount("", 1).isError("expected 2 values"));
242     assert(info(2,2).checkValuesCount("", 2));
243     assert(info(2,2).checkValuesCount("", 3).isError("expected 2 values"));
244 
245     assert(info(1,1).checkValuesCount("", 0).isError("expected 1 value"));
246     assert(info(1,1).checkValuesCount("", 1));
247     assert(info(1,1).checkValuesCount("", 2).isError("expected 1 value"));
248 
249     assert(info(0,1).checkValuesCount("", 0));
250     assert(info(0,1).checkValuesCount("", 1));
251     assert(info(0,1).checkValuesCount("", 2).isError("expected at most 1 value"));
252 
253     assert(info(1,2).checkValuesCount("", 0).isError("expected at least 1 value"));
254     assert(info(1,2).checkValuesCount("", 1));
255     assert(info(1,2).checkValuesCount("", 2));
256     assert(info(1,2).checkValuesCount("", 3).isError("expected at most 2 values"));
257 }
258 
259 ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
260 
261 package struct ArgumentUDA(alias ValueParseFunctions)
262 {
263     package ArgumentInfo info;
264 
265     package alias parsingFunc = ValueParseFunctions;
266 
267     public auto ref Description(string text)
268     {
269         info.description = text;
270         return this;
271     }
272 
273     public auto ref HideFromHelp(bool hide = true)
274     {
275         info.hideFromHelp = hide;
276         return this;
277     }
278 
279     public auto ref Placeholder(string value)
280     {
281         info.placeholder = value;
282         return this;
283     }
284 
285     public auto ref Required()
286     {
287         info.required = true;
288         return this;
289     }
290 
291     public auto ref Optional()
292     {
293         info.required = false;
294         return this;
295     }
296 
297     public auto ref NumberOfValues(ulong num)
298     {
299         info.minValuesCount = num;
300         info.maxValuesCount = num;
301         return this;
302     }
303 
304     public auto ref NumberOfValues(ulong min, ulong max)
305     {
306         info.minValuesCount = min;
307         info.maxValuesCount = max;
308         return this;
309     }
310 
311     public auto ref MinNumberOfValues(ulong min)
312     {
313         assert(min <= info.maxValuesCount.get(ulong.max));
314 
315         info.minValuesCount = min;
316         return this;
317     }
318 
319     public auto ref MaxNumberOfValues(ulong max)
320     {
321         assert(max >= info.minValuesCount.get(0));
322 
323         info.maxValuesCount = max;
324         return this;
325     }
326 }
327 
328 package enum bool isArgumentUDA(T) = (is(typeof(T.info) == ArgumentInfo) && is(T.parsingFunc));
329 
330 unittest
331 {
332     ArgumentUDA!void arg;
333     assert(!arg.info.hideFromHelp);
334     assert(!arg.info.required);
335     assert(arg.info.minValuesCount.isNull);
336     assert(arg.info.maxValuesCount.isNull);
337 
338     arg = arg.Description("desc").Placeholder("text");
339     assert(arg.info.description == "desc");
340     assert(arg.info.placeholder == "text");
341 
342     arg = arg.HideFromHelp().Required().NumberOfValues(10);
343     assert(arg.info.hideFromHelp);
344     assert(arg.info.required);
345     assert(arg.info.minValuesCount.get == 10);
346     assert(arg.info.maxValuesCount.get == 10);
347 
348     arg = arg.Optional().NumberOfValues(20,30);
349     assert(!arg.info.required);
350     assert(arg.info.minValuesCount.get == 20);
351     assert(arg.info.maxValuesCount.get == 30);
352 
353     arg = arg.MinNumberOfValues(2).MaxNumberOfValues(3);
354     assert(arg.info.minValuesCount.get == 2);
355     assert(arg.info.maxValuesCount.get == 3);
356 }
357 
358 ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
359 
360 auto PositionalArgument(uint pos)
361 {
362     auto arg = ArgumentUDA!(ValueParseFunctions!(void, void, void, void, void, void))(ArgumentInfo()).Required();
363     arg.info.position = pos;
364     return arg;
365 }
366 
367 auto PositionalArgument(uint pos, string name)
368 {
369     auto arg = ArgumentUDA!(ValueParseFunctions!(void, void, void, void, void, void))(ArgumentInfo([name])).Required();
370     arg.info.position = pos;
371     return arg;
372 }
373 
374 auto NamedArgument(string[] name...)
375 {
376     return ArgumentUDA!(ValueParseFunctions!(void, void, void, void, void, void))(ArgumentInfo(name)).Optional();
377 }
378 
379 auto NamedArgument(string name)
380 {
381     return ArgumentUDA!(ValueParseFunctions!(void, void, void, void, void, void))(ArgumentInfo([name])).Optional();
382 }
383 
384 struct TrailingArguments {}
385 
386 ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
387 
388 package struct Group
389 {
390     package string name;
391     package string description;
392     package size_t[] arguments;
393 
394     public auto ref Description(string text)
395     {
396         description = text;
397         return this;
398     }
399 
400 }
401 
402 auto ArgumentGroup(string name)
403 {
404     return Group(name);
405 }
406 
407 unittest
408 {
409     auto g = ArgumentGroup("name").Description("description");
410     assert(g.name == "name");
411     assert(g.description == "description");
412 }
413 
414 ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
415 
416 package struct RestrictionGroup
417 {
418     package string location;
419 
420     package enum Type { together, exclusive }
421     package Type type;
422 
423     package size_t[] arguments;
424 
425     package bool required;
426 
427     public auto ref Required()
428     {
429         required = true;
430         return this;
431     }
432 }
433 
434 auto RequiredTogether(string file=__FILE__, uint line = __LINE__)()
435 {
436     import std.conv: to;
437     return RestrictionGroup(file~":"~line.to!string, RestrictionGroup.Type.together);
438 }
439 
440 auto MutuallyExclusive(string file=__FILE__, uint line = __LINE__)()
441 {
442     import std.conv: to;
443     return RestrictionGroup(file~":"~line.to!string, RestrictionGroup.Type.exclusive);
444 }
445 
446 unittest
447 {
448     auto t = RequiredTogether();
449     assert(t.location.length > 0);
450     assert(t.type == RestrictionGroup.Type.together);
451 
452     auto e = MutuallyExclusive();
453     assert(e.location.length > 0);
454     assert(e.type == RestrictionGroup.Type.exclusive);
455 }
456 
457 ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
458 
459 struct SubCommands {}
460 
461 // Default subcommand
462 struct Default(COMMAND)
463 {
464     COMMAND command;
465     alias command this;
466 }
467 
468 ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
469 
470 package struct CommandInfo
471 {
472     package string[] names = [""];
473     package string usage;
474     package string description;
475     package string shortDescription;
476     package string epilog;
477 
478     public auto ref Usage(string text)
479     {
480         usage = text;
481         return this;
482     }
483 
484     public auto ref Description(string text)
485     {
486         description = text;
487         return this;
488     }
489 
490     public auto ref ShortDescription(string text)
491     {
492         shortDescription = text;
493         return this;
494     }
495 
496     public auto ref Epilog(string text)
497     {
498         epilog = text;
499         return this;
500     }
501 }
502 
503 unittest
504 {
505     CommandInfo c;
506     c = c.Usage("usg").Description("desc").ShortDescription("sum").Epilog("epi");
507     assert(c.names == [""]);
508     assert(c.usage == "usg");
509     assert(c.description == "desc");
510     assert(c.shortDescription == "sum");
511     assert(c.epilog == "epi");
512 }
513 
514 ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
515 
516 
517 unittest
518 {
519     struct T
520     {
521         @NamedArgument
522         int a;
523         @(NamedArgument.Optional())
524         int b;
525         @(NamedArgument.Required())
526         int c;
527         @NamedArgument
528         int d;
529         @(NamedArgument.Required())
530         int e;
531         @NamedArgument
532         int f;
533     }
534 
535     enum config = {
536         Config config;
537         config.addHelp = false;
538         return config;
539     }();
540 
541     static assert(CommandArguments!T(config).arguments.arguments.length == 6);
542 
543     auto a = CommandArguments!T(config);
544     assert(a.arguments.requiredGroup.arguments == [2,4]);
545     assert(a.arguments.argsNamed == ["a":0LU, "b":1LU, "c":2LU, "d":3LU, "e":4LU, "f":5LU]);
546     assert(a.arguments.argsPositional == []);
547 }
548 
549 unittest
550 {
551     struct T
552     {
553         int a,b,c,d,e,f;
554     }
555 
556     enum config = {
557         Config config;
558         config.addHelp = false;
559         return config;
560     }();
561 
562     static assert(CommandArguments!T(config).arguments.arguments.length == 6);
563 
564     auto a = CommandArguments!T(config);
565     assert(a.arguments.requiredGroup.arguments == []);
566     assert(a.arguments.argsNamed == ["a":0LU, "b":1LU, "c":2LU, "d":3LU, "e":4LU, "f":5LU]);
567     assert(a.arguments.argsPositional == []);
568 }
569 
570 unittest
571 {
572     struct T1
573     {
574         @(NamedArgument("1"))
575         @(NamedArgument("2"))
576         int a;
577     }
578     static assert(!__traits(compiles, { CommandArguments!T1(Config.init); }));
579 
580     struct T2
581     {
582         @(NamedArgument("1"))
583         int a;
584         @(NamedArgument("1"))
585         int b;
586     }
587     static assert(!__traits(compiles, { CommandArguments!T1(Config.init); }));
588 
589     struct T3
590     {
591         @(PositionalArgument(0)) int a;
592         @(PositionalArgument(0)) int b;
593     }
594     static assert(!__traits(compiles, { CommandArguments!T3(Config.init); }));
595 
596     struct T4
597     {
598         @(PositionalArgument(0)) int a;
599         @(PositionalArgument(2)) int b;
600     }
601     static assert(!__traits(compiles, { CommandArguments!T4(Config.init); }));
602 }
603 
604 unittest
605 {
606     import std.exception;
607 
608     struct T
609     {
610         @(NamedArgument("--"))
611         int a;
612     }
613     static assert(!__traits(compiles, { enum p = CLI!T.parseArgs!((T t){})([]); }));
614     assertThrown(CLI!T.parseArgs!((T t){})([]));
615 }
616 
617 unittest
618 {
619 
620     import std.conv;
621     import std.traits;
622 
623     struct params
624     {
625         int no_a;
626 
627         @(PositionalArgument(0, "a")
628         .Description("Argument 'a'")
629         .Validation!((int a) { return a > 3;})
630         .PreValidation!((string s) { return s.length > 0;})
631         .Validation!((int a) { return a > 0;})
632         )
633         int a;
634 
635         int no_b;
636 
637         @(NamedArgument(["b", "boo"]).Description("Flag boo")
638         .AllowNoValue!55
639         )
640         int b;
641 
642         int no_c;
643     }
644 
645     enum p = CommandArguments!params(Config.init);
646     static assert(p.findNamedArgument("a").arg is null);
647     static assert(p.findNamedArgument("b").arg !is null);
648     static assert(p.findNamedArgument("boo").arg !is null);
649     static assert(p.findPositionalArgument(0).arg !is null);
650     static assert(p.findPositionalArgument(1).arg is null);
651     static assert(p.getParseFunction!false(p.findNamedArgument("b").index) !is null);
652     static assert(p.getParseFunction!true(p.findNamedArgument("b").index) !is null);
653 }
654 
655 unittest
656 {
657     import std.typecons : tuple;
658 
659     struct T
660     {
661         string a;
662         string b;
663     }
664 
665     assert(CLI!T.parseArgs!((T t, string[] args) {
666         assert(t == T("A"));
667         assert(args == []);
668         return 12345;
669     })(["-a","A","--"]) == 12345);
670     assert(CLI!T.parseArgs!((T t, string[] args) {
671         assert(t == T("A"));
672         assert(args == []);
673         return 12345;
674     })(["-a","A","--"]) == 12345);
675 
676     {
677         T args;
678 
679         assert(CLI!T.parseArgs(args, [ "-a", "A"]));
680         assert(CLI!T.parseArgs(args, [ "-b", "B"]));
681 
682         assert(args == T("A","B"));
683     }
684 }
685 
686 unittest
687 {
688     struct T
689     {
690         string a;
691     }
692 
693     assert(CLI!T.parseArgs!((T t) { assert(false); })(["-g"]) != 0);
694     assert(CLI!T.parseArgs!((T t) { assert(t == T.init); return 12345; })([]) == 12345);
695     assert(CLI!T.parseArgs!((T t, string[] args) {
696         assert(t == T.init);
697         assert(args.length == 0);
698         return 12345;
699     })([]) == 12345);
700     assert(CLI!T.parseArgs!((T t, string[] args) {
701         assert(t == T("aa"));
702         assert(args == ["-g"]);
703         return 12345;
704     })(["-a","aa","-g"]) == 12345);
705     static assert(CLI!T.parseArgs!((T t, string[] args) {
706         assert(t == T.init);
707         assert(args.length == 0);
708         return 12345;
709     })([]) == 12345);
710     static assert(CLI!T.parseArgs!((T t, string[] args) {
711         assert(t == T("aa"));
712         assert(args == ["-g"]);
713         return 12345;
714     })(["-a","aa","-g"]) == 12345);
715 }
716 
717 unittest
718 {
719     void test(string[] args, alias expected)()
720     {
721         assert(CLI!(typeof(expected)).parseArgs!((t) {
722             assert(t == expected);
723             return 12345;
724         })(args) == 12345);
725     }
726 
727     struct T
728     {
729         @NamedArgument                           string x;
730         @NamedArgument                           string foo;
731         @(PositionalArgument(0, "a").Optional()) string a;
732         @(PositionalArgument(1, "b").Optional()) string[] b;
733     }
734     test!(["--foo","FOO","-x","X"], T("X", "FOO"));
735     test!(["--foo=FOO","-x=X"], T("X", "FOO"));
736     test!(["--foo=FOO","1","-x=X"], T("X", "FOO", "1"));
737     test!(["--foo=FOO","1","2","3","4"], T(string.init, "FOO", "1",["2","3","4"]));
738     test!(["-xX"], T("X"));
739 
740     struct T1
741     {
742         @(PositionalArgument(0, "a")) string[3] a;
743         @(PositionalArgument(1, "b")) string[] b;
744     }
745     test!(["1","2","3","4","5","6"], T1(["1","2","3"],["4","5","6"]));
746 
747     struct T2
748     {
749         bool foo = true;
750     }
751     test!(["--no-foo"], T2(false));
752 
753     struct T3
754     {
755         @(PositionalArgument(0, "a").Optional())
756         string a = "not set";
757 
758         @(NamedArgument.Required())
759         int b;
760     }
761     test!(["-b", "4"], T3("not set", 4));
762 }
763 
764 unittest
765 {
766     struct T
767     {
768         string x;
769         string foo;
770     }
771 
772     enum config = {
773         Config config;
774         config.caseSensitive = false;
775         return config;
776     }();
777 
778     assert(CLI!(config, T).parseArgs!((T t) { assert(t == T("X", "FOO")); return 12345; })(["--Foo","FOO","-X","X"]) == 12345);
779     assert(CLI!(config, T).parseArgs!((T t) { assert(t == T("X", "FOO")); return 12345; })(["--FOo=FOO","-X=X"]) == 12345);
780 }
781 
782 unittest
783 {
784     struct T
785     {
786         bool a;
787         bool b;
788     }
789     enum config = {
790         Config config;
791         config.bundling = true;
792         return config;
793     }();
794 
795     assert(CLI!(config, T).parseArgs!((T t) { assert(t == T(true, true)); return 12345; })(["-a","-b"]) == 12345);
796     assert(CLI!(config, T).parseArgs!((T t) { assert(t == T(true, true)); return 12345; })(["-ab"]) == 12345);
797 }
798 
799 unittest
800 {
801     struct T
802     {
803         bool b;
804     }
805 
806     assert(CLI!T.parseArgs!((T t) { assert(t == T(true)); return 12345; })(["-b"]) == 12345);
807     assert(CLI!T.parseArgs!((T t) { assert(t == T(true)); return 12345; })(["-b=true"]) == 12345);
808     assert(CLI!T.parseArgs!((T t) { assert(t == T(false)); return 12345; })(["-b=false"]) == 12345);
809 }
810 
811 unittest
812 {
813     struct T
814     {
815         import std.sumtype: SumType;
816 
817         struct cmd1 { string a; }
818         struct cmd2
819         {
820             string b;
821 
822             @TrailingArguments
823             string[] args;
824         }
825 
826         string c;
827         string d;
828 
829         SumType!(cmd1, cmd2) cmd;
830     }
831 
832     assert(CLI!T.parseArgs!((T t) { assert(t == T("C",null,typeof(T.cmd)(T.cmd2("B")))); return 12345; })(["-c","C","cmd2","-b","B"]) == 12345);
833     assert(CLI!T.parseArgs!((T t) { assert(t == T("C",null,typeof(T.cmd)(T.cmd2("",["-b","B"])))); return 12345; })(["-c","C","cmd2","--","-b","B"]) == 12345);
834 }
835 
836 unittest
837 {
838     struct T
839     {
840         import std.sumtype: SumType;
841 
842         struct cmd1 {}
843         struct cmd2 {}
844 
845         SumType!(cmd1, cmd2) cmd;
846     }
847 
848     assert(CLI!T.parseArgs!((T t) { assert(t == T(typeof(T.cmd)(T.cmd1.init))); return 12345; })(["cmd1"]) == 12345);
849     assert(CLI!T.parseArgs!((T t) { assert(t == T(typeof(T.cmd)(T.cmd2.init))); return 12345; })(["cmd2"]) == 12345);
850 }
851 
852 unittest
853 {
854     struct T
855     {
856         import std.sumtype: SumType;
857 
858         struct cmd1 { string a; }
859         struct cmd2 { string b; }
860 
861         string c;
862         string d;
863 
864         SumType!(cmd1, Default!cmd2) cmd;
865     }
866 
867     assert(CLI!T.parseArgs!((T t) { assert(t == T("C",null,typeof(T.cmd)(Default!(T.cmd2)(T.cmd2("B"))))); return 12345; })(["-c","C","-b","B"]) == 12345);
868     assert(CLI!T.parseArgs!((_) {assert(false);})(["-h"]) == 0);
869     assert(CLI!T.parseArgs!((_) {assert(false);})(["--help"]) == 0);
870 }
871 
872 template CLI(Config config, COMMANDS...)
873 {
874     mixin template main(alias newMain)
875     {
876         import std.sumtype: SumType, match;
877 
878         private struct Program
879         {
880             SumType!COMMANDS cmd;   // Sub-commands
881         }
882 
883         private auto forwardMain(Args...)(Program prog, auto ref Args args)
884         {
885             import core.lifetime: forward;
886             return prog.cmd.match!(_ => newMain(_, forward!args));
887         }
888 
889         mixin CLI!(config, Program).main!forwardMain;
890     }
891 }
892 
893 template CLI(Config config, COMMAND)
894 {
895     static Result parseKnownArgs(ref COMMAND receiver, string[] args, out string[] unrecognizedArgs)
896     {
897         return callParser!(config, false)(receiver, args, unrecognizedArgs);
898     }
899 
900     static Result parseKnownArgs(ref COMMAND receiver, ref string[] args)
901     {
902         string[] unrecognizedArgs;
903 
904         auto res = parseKnownArgs(receiver, args, unrecognizedArgs);
905         if(res)
906             args = unrecognizedArgs;
907 
908         return res;
909     }
910 
911     static Result parseArgs(ref COMMAND receiver, string[] args)
912     {
913         auto res = parseKnownArgs(receiver, args);
914         if(res && args.length > 0)
915         {
916             res = Result.Error("Unrecognized arguments: ", args);
917             config.onError(res.errorMsg);
918         }
919 
920         return res;
921     }
922 
923     static int parseArgs(alias newMain)(string[] args, COMMAND initialValue = COMMAND.init)
924         if(__traits(compiles, { newMain(COMMAND.init); }))
925     {
926         alias value = initialValue;
927 
928         auto res = parseArgs(value, args);
929         if(!res)
930             return res.resultCode;
931 
932         static if(__traits(compiles, { int a = cast(int) newMain(value); }))
933             return cast(int) newMain(value);
934         else
935         {
936             newMain(value);
937             return 0;
938         }
939     }
940 
941     static int parseArgs(alias newMain)(string[] args, COMMAND initialValue = COMMAND.init)
942         if(__traits(compiles, { newMain(COMMAND.init, string[].init); }))
943     {
944         alias value = initialValue;
945 
946         auto res = parseKnownArgs(value, args);
947         if(!res)
948             return res.resultCode;
949 
950         static if(__traits(compiles, { int a = cast(int) newMain(value, args); }))
951             return cast(int) newMain(value, args);
952         else
953         {
954             newMain(value, args);
955             return 0;
956         }
957     }
958 
959     string[] completeArgs(string[] args)
960     {
961         import std.algorithm: sort, uniq;
962         import std.array: array;
963 
964         COMMAND dummy;
965         string[] unrecognizedArgs;
966 
967         auto res = callParser!(config, true)(dummy, args.length == 0 ? [""] : args, unrecognizedArgs);
968 
969         return res ? res.suggestions.dup.sort.uniq.array : [];
970     }
971 
972     int complete(string[] args)
973     {
974         import argparse.completer;
975         import std.sumtype: match;
976 
977         // dmd fails with core.exception.OutOfMemoryError@core\lifetime.d(137): Memory allocation failed
978         // if we call anything from CLI!(config, Complete!COMMAND) so we have to directly call parser here
979 
980         Complete!COMMAND receiver;
981         string[] unrecognizedArgs;
982 
983         auto res = callParser!(config, false)(receiver, args, unrecognizedArgs);
984         if(!res)
985             return 1;
986 
987         if(res && unrecognizedArgs.length > 0)
988         {
989             config.onError("Unrecognized arguments: ", unrecognizedArgs);
990             return 1;
991         }
992 
993         receiver.cmd.match!(_ => _.execute!config());
994 
995         return 0;
996     }
997 
998     mixin template mainComplete()
999     {
1000         int main(string[] argv)
1001         {
1002             return CLI!(config, COMMAND).complete(argv[1..$]);
1003         }
1004     }
1005 
1006     mixin template main(alias newMain)
1007     {
1008         version(argparse_completion)
1009         {
1010             mixin CLI!(config, COMMAND).mainComplete;
1011         }
1012         else
1013         {
1014             int main(string[] argv)
1015             {
1016                 return CLI!(config, COMMAND).parseArgs!(newMain)(argv[1..$]);
1017             }
1018         }
1019     }
1020 }
1021 
1022 alias CLI(COMMANDS...) = CLI!(Config.init, COMMANDS);
1023 
1024 
1025 unittest
1026 {
1027     struct T
1028     {
1029         import std.sumtype: SumType;
1030 
1031         struct cmd1
1032         {
1033             string foo;
1034             string bar;
1035             string baz;
1036         }
1037         struct cmd2
1038         {
1039             string cat,can,dog;
1040         }
1041 
1042         @NamedArgument("apple","a")
1043         string a = "dummyA";
1044         @NamedArgument
1045         string s = "dummyS";
1046         @NamedArgument
1047         string b = "dummyB";
1048 
1049         @SubCommands
1050         SumType!(cmd1, cmd2) cmd;
1051     }
1052 
1053     assert(CLI!T.completeArgs([]) == ["--apple","--help","-a","-b","-h","-s","cmd1","cmd2"]);
1054     assert(CLI!T.completeArgs([""]) == ["--apple","--help","-a","-b","-h","-s","cmd1","cmd2"]);
1055     assert(CLI!T.completeArgs(["-a"]) == ["-a"]);
1056     assert(CLI!T.completeArgs(["c"]) == ["cmd1","cmd2"]);
1057     assert(CLI!T.completeArgs(["cmd1"]) == ["cmd1"]);
1058     assert(CLI!T.completeArgs(["cmd1",""]) == ["--apple","--bar","--baz","--foo","--help","-a","-b","-h","-s","cmd1","cmd2"]);
1059     assert(CLI!T.completeArgs(["-a","val-a",""]) == ["--apple","--help","-a","-b","-h","-s","cmd1","cmd2"]);
1060 
1061     assert(!CLI!T.complete(["init","--bash","--commandName","mytool"]));
1062     assert(!CLI!T.complete(["init","--zsh"]));
1063     assert(!CLI!T.complete(["init","--tcsh"]));
1064     assert(!CLI!T.complete(["init","--fish"]));
1065 
1066     assert(CLI!T.complete(["init","--unknown"]));
1067 
1068     import std.process: environment;
1069     {
1070         environment["COMP_LINE"] = "mytool ";
1071         assert(!CLI!T.complete(["--bash","--","---","foo","foo"]));
1072 
1073         environment["COMP_LINE"] = "mytool c";
1074         assert(!CLI!T.complete(["--bash","--","c","---"]));
1075 
1076         environment.remove("COMP_LINE");
1077     }
1078     {
1079         environment["COMMAND_LINE"] = "mytool ";
1080         assert(!CLI!T.complete(["--tcsh","--"]));
1081 
1082         environment["COMMAND_LINE"] = "mytool c";
1083         assert(!CLI!T.complete(["--fish","--","c"]));
1084 
1085         environment.remove("COMMAND_LINE");
1086     }
1087 }
1088 
1089 
1090 unittest
1091 {
1092     struct T
1093     {
1094         int a;
1095     }
1096 
1097     static assert(__traits(compiles, { mixin CLI!T.main!((params) => 0); }));
1098     static assert(__traits(compiles, { mixin CLI!T.main!((params, args) => 0); }));
1099 }
1100 
1101 
1102 
1103 
1104 
1105 
1106 
1107 
1108 ////////////////////////////////////////////////////////////////////////////////////////////////////
1109 // User defined attributes
1110 ////////////////////////////////////////////////////////////////////////////////////////////////////
1111 
1112 unittest
1113 {
1114     struct T
1115     {
1116         @(NamedArgument.NumberOfValues(1,3))
1117         int[] a;
1118         @(NamedArgument.NumberOfValues(2))
1119         int[] b;
1120     }
1121 
1122     assert(CLI!T.parseArgs!((T t) { assert(t == T([1,2,3],[4,5])); return 12345; })(["-a","1","2","3","-b","4","5"]) == 12345);
1123     assert(CLI!T.parseArgs!((T t) { assert(t == T([1],[4,5])); return 12345; })(["-a","1","-b","4","5"]) == 12345);
1124 }
1125 
1126 
1127 auto PreValidation(alias func, ARG)(ARG arg)
1128 if(isArgumentUDA!ARG)
1129 {
1130     return ArgumentUDA!(arg.parsingFunc.changePreValidation!func)(arg.tupleof);
1131 }
1132 
1133 auto Parse(alias func, ARG)(ARG arg)
1134 if(isArgumentUDA!ARG)
1135 {
1136     return ArgumentUDA!(arg.parsingFunc.changeParse!func)(arg.tupleof);
1137 }
1138 
1139 auto Validation(alias func, ARG)(ARG arg)
1140 if(isArgumentUDA!ARG)
1141 {
1142     return ArgumentUDA!(arg.parsingFunc.changeValidation!func)(arg.tupleof);
1143 }
1144 
1145 auto Action(alias func, ARG)(ARG arg)
1146 if(isArgumentUDA!ARG)
1147 {
1148     return ArgumentUDA!(arg.parsingFunc.changeAction!func)(arg.tupleof);
1149 }
1150 
1151 auto AllowNoValue(alias valueToUse, ARG)(ARG arg)
1152 if(isArgumentUDA!ARG)
1153 {
1154     auto desc = ArgumentUDA!(arg.parsingFunc.changeNoValueAction!(() { return valueToUse; }))(arg.tupleof);
1155     desc.info.minValuesCount = 0;
1156     return desc;
1157 }
1158 
1159 auto RequireNoValue(alias valueToUse, ARG)(ARG arg)
1160 if(isArgumentUDA!ARG)
1161 {
1162     auto desc = arg.AllowNoValue!valueToUse;
1163     desc.info.minValuesCount = 0;
1164     desc.info.maxValuesCount = 0;
1165     return desc;
1166 }
1167 
1168 auto Counter(ARG)(ARG arg)
1169 if(isArgumentUDA!ARG)
1170 {
1171     struct CounterParsingFunction
1172     {
1173         static bool parse(T)(ref T receiver, const ref RawParam param)
1174         {
1175             assert(param.value.length == 0);
1176 
1177             ++receiver;
1178 
1179             return true;
1180         }
1181     }
1182 
1183     auto desc = ArgumentUDA!(CounterParsingFunction)(arg.tupleof);
1184     desc.info.minValuesCount = 0;
1185     desc.info.maxValuesCount = 0;
1186     return desc;
1187 }
1188 
1189 
1190 unittest
1191 {
1192     struct T
1193     {
1194         @(NamedArgument.Counter()) int a;
1195     }
1196 
1197     assert(CLI!T.parseArgs!((T t) { assert(t == T(3)); return 12345; })(["-a","-a","-a"]) == 12345);
1198 }
1199 
1200 
1201 auto AllowedValues(alias values, ARG)(ARG arg)
1202 {
1203     import std.array : assocArray;
1204     import std.range : repeat;
1205     import std.traits: KeyType;
1206 
1207     enum valuesAA = assocArray(values, false.repeat);
1208 
1209     auto desc = arg.Validation!(Validators.ValueInList!(values, KeyType!(typeof(valuesAA))));
1210     desc.info.setAllowedValues!values;
1211     return desc;
1212 }
1213 
1214 
1215 unittest
1216 {
1217     struct T
1218     {
1219         @(NamedArgument.AllowedValues!([1,3,5])) int a;
1220     }
1221 
1222     assert(CLI!T.parseArgs!((T t) { assert(t == T(3)); return 12345; })(["-a", "3"]) == 12345);
1223     assert(CLI!T.parseArgs!((T t) { assert(false); })(["-a", "2"]) != 0);    // "kiwi" is not allowed
1224 }
1225 
1226 unittest
1227 {
1228     struct T
1229     {
1230         @(NamedArgument.AllowedValues!(["apple","pear","banana"]))
1231         string fruit;
1232     }
1233 
1234     assert(CLI!T.parseArgs!((T t) { assert(t == T("apple")); return 12345; })(["--fruit", "apple"]) == 12345);
1235     assert(CLI!T.parseArgs!((T t) { assert(false); })(["--fruit", "kiwi"]) != 0);    // "kiwi" is not allowed
1236 }
1237 
1238 unittest
1239 {
1240     enum Fruit { apple, pear, banana }
1241     struct T
1242     {
1243         @NamedArgument
1244         Fruit fruit;
1245     }
1246 
1247     assert(CLI!T.parseArgs!((T t) { assert(t == T(Fruit.apple)); return 12345; })(["--fruit", "apple"]) == 12345);
1248     assert(CLI!T.parseArgs!((T t) { assert(false); })(["--fruit", "kiwi"]) != 0);    // "kiwi" is not allowed
1249 }
1250 
1251 
1252 
1253 unittest
1254 {
1255     struct T
1256     {
1257         string a;
1258         string b;
1259 
1260         @TrailingArguments string[] args;
1261     }
1262 
1263     assert(CLI!T.parseArgs!((T t) { assert(t == T("A","",["-b","B"])); return 12345; })(["-a","A","--","-b","B"]) == 12345);
1264 }
1265 
1266 unittest
1267 {
1268     struct T
1269     {
1270         @NamedArgument int i;
1271         @NamedArgument(["u","u1"])  uint u;
1272         @NamedArgument("d","d1")  double d;
1273     }
1274 
1275     assert(CLI!T.parseArgs!((T t) { assert(t == T(-5,8,12.345)); return 12345; })(["-i","-5","-u","8","-d","12.345"]) == 12345);
1276     assert(CLI!T.parseArgs!((T t) { assert(t == T(-5,8,12.345)); return 12345; })(["-i","-5","-u1","8","-d1","12.345"]) == 12345);
1277 }
1278 
1279 unittest
1280 {
1281     struct T
1282     {
1283         @NamedArgument int[]   a;
1284         @NamedArgument int[][] b;
1285     }
1286 
1287     assert(CLI!T.parseArgs!((T t) { assert(t.a == [1,2,3,4,5]); return 12345; })(["-a","1","2","3","-a","4","5"]) == 12345);
1288     assert(CLI!T.parseArgs!((T t) { assert(t.b == [[1,2,3],[4,5]]); return 12345; })(["-b","1","2","3","-b","4","5"]) == 12345);
1289 }
1290 
1291 unittest
1292 {
1293     struct T
1294     {
1295         @NamedArgument int[] a;
1296     }
1297 
1298     enum cfg = {
1299         Config cfg;
1300         cfg.arraySep = ',';
1301         return cfg;
1302     }();
1303 
1304     assert(CLI!(cfg, T).parseArgs!((T t) { assert(t == T([1,2,3,4,5])); return 12345; })(["-a","1,2,3","-a","4","5"]) == 12345);
1305 }
1306 
1307 unittest
1308 {
1309     struct T
1310     {
1311         @NamedArgument int[string] a;
1312     }
1313 
1314     assert(CLI!T.parseArgs!((T t) { assert(t == T(["foo":3,"boo":7])); return 12345; })(["-a=foo=3","-a","boo=7"]) == 12345);
1315 }
1316 
1317 unittest
1318 {
1319     struct T
1320     {
1321         @NamedArgument int[string] a;
1322     }
1323 
1324     enum cfg = {
1325         Config cfg;
1326         cfg.arraySep = ',';
1327         return cfg;
1328     }();
1329 
1330     assert(CLI!(cfg, T).parseArgs!((T t) { assert(t == T(["foo":3,"boo":7])); return 12345; })(["-a=foo=3,boo=7"]) == 12345);
1331     assert(CLI!(cfg, T).parseArgs!((T t) { assert(t == T(["foo":3,"boo":7])); return 12345; })(["-a","foo=3,boo=7"]) == 12345);
1332 }
1333 
1334 unittest
1335 {
1336     struct T
1337     {
1338         enum Fruit { apple, pear };
1339 
1340         @NamedArgument Fruit a;
1341     }
1342 
1343     assert(CLI!T.parseArgs!((T t) { assert(t == T(T.Fruit.apple)); return 12345; })(["-a","apple"]) == 12345);
1344     assert(CLI!T.parseArgs!((T t) { assert(t == T(T.Fruit.pear)); return 12345; })(["-a=pear"]) == 12345);
1345 }
1346 
1347 unittest
1348 {
1349     struct T
1350     {
1351         @NamedArgument string[] a;
1352     }
1353 
1354     assert(CLI!T.parseArgs!((T t) { assert(t == T(["1,2,3","4","5"])); return 12345; })(["-a","1,2,3","-a","4","5"]) == 12345);
1355 
1356     enum cfg = {
1357         Config cfg;
1358         cfg.arraySep = ',';
1359         return cfg;
1360     }();
1361 
1362     assert(CLI!(cfg, T).parseArgs!((T t) { assert(t == T(["1","2","3","4","5"])); return 12345; })(["-a","1,2,3","-a","4","5"]) == 12345);
1363 }
1364 
1365 unittest
1366 {
1367     struct T
1368     {
1369         @(NamedArgument.AllowNoValue  !10) int a;
1370         @(NamedArgument.RequireNoValue!20) int b;
1371     }
1372 
1373     assert(CLI!T.parseArgs!((T t) { assert(t.a == 10); return 12345; })(["-a"]) == 12345);
1374     assert(CLI!T.parseArgs!((T t) { assert(t.b == 20); return 12345; })(["-b"]) == 12345);
1375     assert(CLI!T.parseArgs!((T t) { assert(t.a == 30); return 12345; })(["-a","30"]) == 12345);
1376     assert(CLI!T.parseArgs!((T t) { assert(false); })(["-b","30"]) != 0);
1377 }
1378 
1379 unittest
1380 {
1381     struct T
1382     {
1383         @(NamedArgument
1384          .PreValidation!((string s) { return s.length > 1 && s[0] == '!'; })
1385          .Parse        !((string s) { return s[1]; })
1386          .Validation   !((char v) { return v >= '0' && v <= '9'; })
1387          .Action       !((ref int a, char v) { a = v - '0'; })
1388         )
1389         int a;
1390     }
1391 
1392     assert(CLI!T.parseArgs!((T t) { assert(t == T(4)); return 12345; })(["-a","!4"]) == 12345);
1393 }
1394 
1395 unittest
1396 {
1397     static struct T
1398     {
1399         int a;
1400 
1401         @(NamedArgument("a")) void foo() { a++; }
1402     }
1403 
1404     assert(CLI!T.parseArgs!((T t) { assert(t == T(4)); return 12345; })(["-a","-a","-a","-a"]) == 12345);
1405 }
1406 
1407 
1408 auto Command(string[] name...)
1409 {
1410     return CommandInfo(name.dup);
1411 }
1412 
1413 unittest
1414 {
1415     auto a = Command("MYPROG");
1416     assert(a.names == ["MYPROG"]);
1417 }
1418 
1419 
1420 
1421 unittest
1422 {
1423     @Command("MYPROG")
1424     struct T
1425     {
1426         @(NamedArgument.HideFromHelp())  string s;
1427     }
1428 
1429     assert(CLI!T.parseArgs!((T t) { assert(false); })(["-h","-s","asd"]) == 0);
1430     assert(CLI!T.parseArgs!((T t) { assert(false); })(["-h"]) == 0);
1431 
1432     assert(CLI!T.parseArgs!((T t, string[] args) { assert(false); })(["-h","-s","asd"]) == 0);
1433     assert(CLI!T.parseArgs!((T t, string[] args) { assert(false); })(["-h"]) == 0);
1434 }
1435 
1436 unittest
1437 {
1438     @Command("MYPROG")
1439     struct T
1440     {
1441         @(NamedArgument.Required())  string s;
1442     }
1443 
1444     assert(CLI!T.parseArgs!((T t) { assert(false); })([]) != 0);
1445 }
1446 
1447 unittest
1448 {
1449     @Command("MYPROG")
1450     struct T
1451     {
1452         @MutuallyExclusive()
1453         {
1454             string a;
1455             string b;
1456         }
1457     }
1458 
1459     // Either or no argument is allowed
1460     assert(CLI!T.parseArgs!((T t) {})(["-a","a"]) == 0);
1461     assert(CLI!T.parseArgs!((T t) {})(["-b","b"]) == 0);
1462     assert(CLI!T.parseArgs!((T t) {})([]) == 0);
1463 
1464     // Both arguments are not allowed
1465     assert(CLI!T.parseArgs!((T t) { assert(false); })(["-a","a","-b","b"]) != 0);
1466 }
1467 
1468 unittest
1469 {
1470     @Command("MYPROG")
1471     struct T
1472     {
1473         @(MutuallyExclusive().Required())
1474         {
1475             string a;
1476             string b;
1477         }
1478     }
1479 
1480     // Either argument is allowed
1481     assert(CLI!T.parseArgs!((T t) {})(["-a","a"]) == 0);
1482     assert(CLI!T.parseArgs!((T t) {})(["-b","b"]) == 0);
1483 
1484     // Both arguments or no argument is not allowed
1485     assert(CLI!T.parseArgs!((T t) { assert(false); })([]) != 0);
1486     assert(CLI!T.parseArgs!((T t) { assert(false); })(["-a","a","-b","b"]) != 0);
1487 }
1488 
1489 unittest
1490 {
1491     @Command("MYPROG")
1492     struct T
1493     {
1494         @RequiredTogether()
1495         {
1496             string a;
1497             string b;
1498         }
1499     }
1500 
1501     // Both or no argument is allowed
1502     assert(CLI!T.parseArgs!((T t) {})(["-a","a","-b","b"]) == 0);
1503     assert(CLI!T.parseArgs!((T t) {})([]) == 0);
1504 
1505     // Single argument is not allowed
1506     assert(CLI!T.parseArgs!((T t) { assert(false); })(["-a","a"]) != 0);
1507     assert(CLI!T.parseArgs!((T t) { assert(false); })(["-b","b"]) != 0);
1508 }
1509 
1510 unittest
1511 {
1512     @Command("MYPROG")
1513     struct T
1514     {
1515         @(RequiredTogether().Required())
1516         {
1517             string a;
1518             string b;
1519         }
1520     }
1521 
1522     // Both arguments are allowed
1523     assert(CLI!T.parseArgs!((T t) {})(["-a","a","-b","b"]) == 0);
1524 
1525     // Single argument or no argument is not allowed
1526     assert(CLI!T.parseArgs!((T t) { assert(false); })(["-a","a"]) != 0);
1527     assert(CLI!T.parseArgs!((T t) { assert(false); })(["-b","b"]) != 0);
1528     assert(CLI!T.parseArgs!((T t) { assert(false); })([]) != 0);
1529 }
1530 
1531