1 /**
2  * Copyright: Copyright Jason White, 2015-2016
3  * License:   MIT
4  * Authors:   Jason White
5  *
6  * Description:
7  * Parses arguments.
8  *
9  * TODO:
10  *  - Handle bundled options
11  */
12 module darg;
13 
14 /**
15  * Generic argument parsing exception.
16  */
17 class ArgParseError : Exception
18 {
19     /**
20      */
21     this(string msg) pure nothrow
22     {
23         super(msg);
24     }
25 }
26 
27 /**
28  * Thrown when help is requested.
29  */
30 class ArgParseHelp : Exception
31 {
32     this(string msg) pure nothrow
33     {
34         super(msg);
35     }
36 }
37 
38 /**
39  * User defined attribute for an option.
40  */
41 struct Option
42 {
43     /// List of names the option can have.
44     string[] names;
45 
46     /**
47      * Constructs the option with a list of names. Note that any leading "-" or
48      * "--" should be omitted. This is added automatically depending on the
49      * length of the name.
50      */
51     this(string[] names...) pure nothrow
52     {
53         this.names = names;
54     }
55 
56     /**
57      * Returns true if the given option name is equivalent to this option.
58      */
59     bool opEquals(string opt) const pure nothrow
60     {
61         foreach (name; names)
62         {
63             if (name == opt)
64                 return true;
65         }
66 
67         return false;
68     }
69 
70     unittest
71     {
72         static assert(Option("foo") == "foo");
73         static assert(Option("foo", "f") == "foo");
74         static assert(Option("foo", "f") == "f");
75         static assert(Option("foo", "bar", "baz") == "foo");
76         static assert(Option("foo", "bar", "baz") == "bar");
77         static assert(Option("foo", "bar", "baz") == "baz");
78 
79         static assert(Option("foo", "bar") != "baz");
80     }
81 
82     /**
83      * Returns the canonical name of this option. That is, its first name.
84      */
85     string toString() const pure nothrow
86     {
87         return names.length > 0 ? (nameToOption(names[0])) : null;
88     }
89 
90     unittest
91     {
92         static assert(Option().toString is null);
93         static assert(Option("foo", "bar", "baz").toString == "--foo");
94         static assert(Option("f", "bar", "baz").toString == "-f");
95     }
96 }
97 
98 /**
99  * An option flag. These types of options are handled specially and never have
100  * an argument.
101  */
102 enum OptionFlag : bool
103 {
104     /// The option flag was not specified.
105     no = false,
106 
107     /// The option flag was specified.
108     yes = true,
109 }
110 
111 /**
112  * Multiplicity of an argument.
113  */
114 enum Multiplicity
115 {
116     optional,
117     zeroOrMore,
118     oneOrMore,
119 }
120 
121 /**
122  * User defined attribute for a positional argument.
123  */
124 struct Argument
125 {
126     /**
127      * Name of the argument. Since this is a positional argument, this value is
128      * only used in the help string.
129      */
130     string name;
131 
132     /**
133      * Lower and upper bounds for the number of values this argument can have.
134      */
135     size_t lowerBound = 1;
136 
137     /// Ditto
138     size_t upperBound = 1;
139 
140     /**
141      * Constructs an argument with the given name and count. The count specifies
142      * how many argument elements should be consumed from the command line. By
143      * default, this is 1.
144      */
145     this(string name, size_t count = 1) pure nothrow
146     body
147     {
148         this.name = name;
149         this.lowerBound = count;
150         this.upperBound = count;
151     }
152 
153     /**
154      * Constructs an argument with the given name and an upper and lower bound
155      * for how many argument elements should be consumed from the command line.
156      */
157     this(string name, size_t lowerBound, size_t upperBound) pure nothrow
158     in { assert(lowerBound < upperBound); }
159     body
160     {
161         this.name = name;
162         this.lowerBound = lowerBound;
163         this.upperBound = upperBound;
164     }
165 
166     /**
167      * An argument with a multiplicity specifier.
168      */
169     this(string name, Multiplicity multiplicity) pure nothrow
170     {
171         this.name = name;
172 
173         final switch (multiplicity)
174         {
175             case Multiplicity.optional:
176                 this.lowerBound = 0;
177                 this.upperBound = 1;
178                 break;
179             case Multiplicity.zeroOrMore:
180                 this.lowerBound = 0;
181                 this.upperBound = size_t.max;
182                 break;
183             case Multiplicity.oneOrMore:
184                 this.lowerBound = 1;
185                 this.upperBound = size_t.max;
186                 break;
187         }
188     }
189 
190     /**
191      * Convert to a usage string.
192      */
193     @property string usage() const pure
194     {
195         import std.format : format;
196 
197         if (lowerBound == 0)
198         {
199             if (upperBound == 1)
200                 return "["~ name ~"]";
201             else if (upperBound == upperBound.max)
202                 return "["~ name ~"...]";
203 
204             return "["~ name ~"... (up to %d times)]".format(upperBound);
205         }
206         else if (lowerBound == 1)
207         {
208             if (upperBound == 1)
209                 return name;
210             else if (upperBound == upperBound.max)
211                 return name ~ " ["~ name ~"...]";
212 
213             return name ~ " ["~ name ~"... (up to %d times)]"
214                 .format(upperBound-1);
215         }
216 
217         if (lowerBound == upperBound)
218             return name ~" (multiplicity of %d)"
219                 .format(upperBound);
220 
221         return name ~" ["~ name ~"... (between %d and %d times)]"
222             .format(lowerBound-1, upperBound-1);
223     }
224 
225     /**
226      * Get a multiplicity error string.
227      *
228      * Params:
229      *   specified = The number of parameters that were specified.
230      *
231      * Returns:
232      * If there is an error, returns a string explaining the error. Otherwise,
233      * returns $(D null).
234      */
235     @property string multiplicityError(size_t specified) const pure
236     {
237         import std.format : format;
238 
239         if (specified >= lowerBound && specified <= upperBound)
240             return null;
241 
242         if (specified < lowerBound)
243         {
244             if (lowerBound == 1 && upperBound == 1)
245                 return "Expected a value for positional argument '%s'"
246                     .format(name);
247             else
248                 return ("Expected at least %d values for positional argument" ~
249                     " '%s'. Only %d values were specified.")
250                     .format(lowerBound, name, specified);
251         }
252 
253         // This should never happen. Argument parsing is not greedy.
254         return "Too many values specified for positional argument '%s'.";
255     }
256 }
257 
258 unittest
259 {
260     with (Argument("lion"))
261     {
262         assert(name == "lion");
263         assert(lowerBound == 1);
264         assert(upperBound == 1);
265     }
266 
267     with (Argument("tiger", Multiplicity.optional))
268     {
269         assert(lowerBound == 0);
270         assert(upperBound == 1);
271     }
272 
273     with (Argument("bear", Multiplicity.zeroOrMore))
274     {
275         assert(lowerBound == 0);
276         assert(upperBound == size_t.max);
277     }
278 
279     with (Argument("dinosaur", Multiplicity.oneOrMore))
280     {
281         assert(lowerBound == 1);
282         assert(upperBound == size_t.max);
283     }
284 }
285 
286 /**
287  * Help string for an option or positional argument.
288  */
289 struct Help
290 {
291     /// Help string.
292     string help;
293 }
294 
295 /**
296  * Meta variable name.
297  */
298 struct MetaVar
299 {
300     /// Name of the meta variable.
301     string name;
302 }
303 
304 /**
305  * Function signatures that can handle arguments or options.
306  */
307 private alias void OptionHandler() pure;
308 private alias void ArgumentHandler(string) pure; /// Ditto
309 
310 /**
311  * Returns true if the given argument is a short option. That is, if it starts
312  * with a '-'.
313  */
314 bool isShortOption(string arg) pure nothrow
315 {
316     return arg.length > 1 && arg[0] == '-' && arg[1] != '-';
317 }
318 
319 unittest
320 {
321     static assert(!isShortOption(""));
322     static assert(!isShortOption("-"));
323     static assert(!isShortOption("a"));
324     static assert(!isShortOption("ab"));
325     static assert( isShortOption("-a"));
326     static assert( isShortOption("-ab"));
327     static assert(!isShortOption("--a"));
328     static assert(!isShortOption("--abc"));
329 }
330 
331 /**
332  * Returns true if the given argument is a long option. That is, if it starts
333  * with "--".
334  */
335 bool isLongOption(string arg) pure nothrow
336 {
337     return arg.length > 2 && arg[0 .. 2] == "--" && arg[2] != '-';
338 }
339 
340 unittest
341 {
342     static assert(!isLongOption(""));
343     static assert(!isLongOption("a"));
344     static assert(!isLongOption("ab"));
345     static assert(!isLongOption("abc"));
346     static assert(!isLongOption("-"));
347     static assert(!isLongOption("-a"));
348     static assert(!isLongOption("--"));
349     static assert( isLongOption("--a"));
350     static assert( isLongOption("--arg"));
351     static assert( isLongOption("--arg=asdf"));
352 }
353 
354 /**
355  * Returns true if the given argument is an option. That is, it is either a
356  * short option or a long option.
357  */
358 bool isOption(string arg) pure nothrow
359 {
360     return isShortOption(arg) || isLongOption(arg);
361 }
362 
363 private static struct OptionSplit
364 {
365     string head;
366     string tail;
367 }
368 
369 /**
370  * Splits an option on "=".
371  */
372 private auto splitOption(string option) pure
373 {
374     size_t i = 0;
375     while (i < option.length && option[i] != '=')
376         ++i;
377 
378     return OptionSplit(
379             option[0 .. i],
380             (i < option.length) ? option[i+1 .. $] : null
381             );
382 }
383 
384 unittest
385 {
386     static assert(splitOption("") == OptionSplit("", null));
387     static assert(splitOption("--foo") == OptionSplit("--foo", null));
388     static assert(splitOption("--foo=") == OptionSplit("--foo", ""));
389     static assert(splitOption("--foo=bar") == OptionSplit("--foo", "bar"));
390 }
391 
392 private static struct ArgSplit
393 {
394     const(string)[] head;
395     const(string)[] tail;
396 }
397 
398 /**
399  * Splits arguments on "--".
400  */
401 private auto splitArgs(const(string)[] args) pure
402 {
403     size_t i = 0;
404     while (i < args.length && args[i] != "--")
405         ++i;
406 
407     return ArgSplit(
408             args[0 .. i],
409             (i < args.length) ? args[i+1 .. $] : []
410             );
411 }
412 
413 unittest
414 {
415     static assert(splitArgs([]) == ArgSplit([], []));
416     static assert(splitArgs(["a", "b"]) == ArgSplit(["a", "b"], []));
417     static assert(splitArgs(["a", "--"]) == ArgSplit(["a"], []));
418     static assert(splitArgs(["a", "--", "b"]) == ArgSplit(["a"], ["b"]));
419     static assert(splitArgs(["a", "--", "b", "c"]) == ArgSplit(["a"], ["b", "c"]));
420 }
421 
422 /**
423  * Returns an option name without the leading ("--" or "-"). If it is not an
424  * option, returns null.
425  */
426 private string optionToName(string option) pure nothrow
427 {
428     if (isLongOption(option))
429         return option[2 .. $];
430 
431     if (isShortOption(option))
432         return option[1 .. $];
433 
434     return null;
435 }
436 
437 unittest
438 {
439     static assert(optionToName("--opt") == "opt");
440     static assert(optionToName("-opt") == "opt");
441     static assert(optionToName("-o") == "o");
442     static assert(optionToName("opt") is null);
443     static assert(optionToName("o") is null);
444     static assert(optionToName("") is null);
445 }
446 
447 /**
448  * Returns the appropriate long or short option corresponding to the given name.
449  */
450 private string nameToOption(string name) pure nothrow
451 {
452     switch (name.length)
453     {
454         case 0:
455             return null;
456         case 1:
457             return "-" ~ name;
458         default:
459             return "--" ~ name;
460     }
461 }
462 
463 unittest
464 {
465     static assert(nameToOption("opt") == "--opt");
466     static assert(nameToOption("o") == "-o");
467     static assert(nameToOption("") is null);
468 }
469 
470 unittest
471 {
472     immutable identities = ["opt", "o", ""];
473 
474     foreach (identity; identities)
475         assert(identity.nameToOption.optionToName == identity);
476 }
477 
478 /**
479  * Check if the given type is valid for an option.
480  */
481 private template isValidOptionType(T)
482 {
483     import std.traits : isBasicType, isSomeString;
484 
485     static if (isBasicType!T ||
486                isSomeString!T ||
487                is(T : OptionHandler) ||
488                is(T : ArgumentHandler)
489         )
490     {
491         enum isValidOptionType = true;
492     }
493     else static if (is(T A : A[]))
494     {
495         enum isValidOptionType = isValidOptionType!A;
496     }
497     else
498     {
499         enum isValidOptionType = false;
500     }
501 }
502 
503 unittest
504 {
505     static assert(isValidOptionType!bool);
506     static assert(isValidOptionType!int);
507     static assert(isValidOptionType!float);
508     static assert(isValidOptionType!char);
509     static assert(isValidOptionType!string);
510     static assert(isValidOptionType!(int[]));
511 
512     alias void Func1() pure;
513     alias void Func2(string) pure;
514     alias int Func3();
515     alias int Func4(string);
516     alias void Func5();
517     alias void Func6(string);
518 
519     static assert(isValidOptionType!Func1);
520     static assert(isValidOptionType!Func2);
521     static assert(!isValidOptionType!Func3);
522     static assert(!isValidOptionType!Func4);
523     static assert(!isValidOptionType!Func5);
524     static assert(!isValidOptionType!Func6);
525 }
526 
527 /**
528  * Count the number of positional arguments.
529  */
530 size_t countArgs(Options)() pure nothrow
531 {
532     import std.meta : Alias;
533     import std.traits : getUDAs;
534     import std.algorithm.comparison : min;
535 
536     size_t count = 0;
537 
538     foreach (member; __traits(allMembers, Options))
539     {
540         alias symbol = Alias!(__traits(getMember, Options, member));
541         alias argUDAs = getUDAs!(symbol, Argument);
542         count += min(argUDAs.length, 1);
543     }
544 
545     return count;
546 }
547 
548 /**
549  * Count the number of options.
550  */
551 size_t countOpts(Options)() pure nothrow
552 {
553     import std.meta : Alias;
554     import std.traits : getUDAs;
555     import std.algorithm.comparison : min;
556 
557     size_t count = 0;
558 
559     foreach (member; __traits(allMembers, Options))
560     {
561         alias symbol = Alias!(__traits(getMember, Options, member));
562         alias optUDAs = getUDAs!(symbol, Option);
563         count += min(optUDAs.length, 1);
564     }
565 
566     return count;
567 }
568 
569 unittest
570 {
571     static struct A {}
572 
573     static assert(countArgs!A == 0);
574     static assert(countOpts!A == 0);
575 
576     static struct B
577     {
578         @Argument("test1")
579         string test1;
580         @Argument("test2")
581         string test2;
582 
583         @Option("test3", "test3a")
584         @Option("test3b", "test3c")
585         string test3;
586     }
587 
588     static assert(countArgs!B == 2);
589     static assert(countOpts!B == 1);
590 
591     static struct C
592     {
593         string test;
594 
595         @Argument("test1")
596         string test1;
597 
598         @Argument("test2")
599         @Argument("test2a")
600         string test2;
601 
602         @Option("test3")
603         string test3;
604 
605         @Option("test4")
606         string test4;
607     }
608 
609     static assert(countArgs!C == 2);
610     static assert(countOpts!C == 2);
611 }
612 
613 /**
614  * Checks if the given options are valid.
615  */
616 private void validateOptions(Options)() pure nothrow
617 {
618     import std.meta : Alias;
619     import std.traits : getUDAs, fullyQualifiedName;
620 
621     foreach (member; __traits(allMembers, Options))
622     {
623         alias symbol = Alias!(__traits(getMember, Options, member));
624         alias optUDAs = getUDAs!(symbol, Option);
625         alias argUDAs = getUDAs!(symbol, Argument);
626 
627         // Basic error checking
628         static assert(!(optUDAs.length > 0 && argUDAs.length > 0),
629             fullyQualifiedName!symbol ~" cannot be both an Option and an Argument"
630             );
631         static assert(optUDAs.length <= 1,
632             fullyQualifiedName!symbol ~" cannot have multiple Option attributes"
633             );
634         static assert(argUDAs.length <= 1,
635             fullyQualifiedName!symbol ~" cannot have multiple Argument attributes"
636             );
637 
638         static if (argUDAs.length > 0)
639             static assert(isValidOptionType!(typeof(symbol)),
640                 fullyQualifiedName!symbol ~" is not a valid Argument type"
641                 );
642 
643         static if (optUDAs.length > 0)
644             static assert(isValidOptionType!(typeof(symbol)),
645                 fullyQualifiedName!symbol ~" is not a valid Option type"
646                 );
647     }
648 }
649 
650 /**
651  * Checks if the given option type has an associated argument. Currently, only
652  * an OptionFlag does not have an argument.
653  */
654 private template hasArgument(T)
655 {
656     static if (is(T : OptionFlag) || is(T : OptionHandler))
657         enum hasArgument = false;
658     else
659         enum hasArgument = true;
660 }
661 
662 unittest
663 {
664     static assert(hasArgument!string);
665     static assert(hasArgument!ArgumentHandler);
666     static assert(hasArgument!int);
667     static assert(hasArgument!bool);
668     static assert(!hasArgument!OptionFlag);
669     static assert(!hasArgument!OptionHandler);
670 }
671 
672 /**
673  * Parses an argument.
674  *
675  * Throws: ArgParseError if the given argument cannot be converted to the
676  * requested type.
677  */
678 T parseArg(T)(string arg) pure
679 {
680     import std.conv : to, ConvException;
681 
682     try
683     {
684         return to!T(arg);
685     }
686     catch (ConvException e)
687     {
688         throw new ArgParseError(e.msg);
689     }
690 }
691 
692 unittest
693 {
694     import std.exception : ce = collectException;
695 
696     assert(parseArg!int("42") == 42);
697     assert(parseArg!string("42") == "42");
698     assert(ce!ArgParseError(parseArg!size_t("-42")));
699 }
700 
701 /**
702  * Returns the canonical name of the given member's argument. If there is no
703  * argument for the given member, null is returned.
704  */
705 private string argumentName(Options, string member)() pure
706 {
707     import std.meta : Alias;
708     import std.traits : getUDAs;
709     import std..string : toUpper;
710 
711     alias symbol = Alias!(__traits(getMember, Options, member));
712 
713     static if (hasArgument!(typeof(symbol)))
714     {
715         alias metavar = getUDAs!(symbol, MetaVar);
716         static if (metavar.length > 0)
717             return metavar[0].name;
718         else static if (is(typeof(symbol) : ArgumentHandler))
719             return member.toUpper;
720         else
721             return "<"~ typeof(symbol).stringof ~ ">";
722     }
723     else
724     {
725         return null;
726     }
727 }
728 
729 unittest
730 {
731     static struct Options
732     {
733         @Option("test1")
734         OptionFlag test1;
735 
736         @Option("test2")
737         string test2;
738 
739         @Option("test3")
740         @MetaVar("asdf")
741         string test3;
742 
743         private string _arg;
744 
745         @Option("test4")
746         void test4(string arg) pure
747         {
748             _arg = arg;
749         }
750 
751         @Option("test5")
752         @MetaVar("metavar")
753         void test5(string arg) pure
754         {
755             _arg = arg;
756         }
757     }
758 
759     static assert(argumentName!(Options, "test1") == null);
760     static assert(argumentName!(Options, "test2") == "<string>");
761     static assert(argumentName!(Options, "test3") == "asdf");
762     static assert(argumentName!(Options, "test4") == "TEST4");
763     static assert(argumentName!(Options, "test5") == "metavar");
764 }
765 
766 /**
767  * Constructs a printable usage string at compile time from the given options
768  * structure.
769  */
770 string usageString(Options)(string program) pure
771     if (is(Options == struct))
772 {
773     import std.meta : Alias;
774     import std.traits : getUDAs;
775     import std.array : replicate;
776     import std..string : wrap, toUpper;
777 
778     string output = "Usage: "~ program;
779 
780     string indent = " ".replicate(output.length + 1);
781 
782     // List all options
783     foreach (member; __traits(allMembers, Options))
784     {
785         alias symbol = Alias!(__traits(getMember, Options, member));
786         alias optUDAs = getUDAs!(symbol, Option);
787 
788         static if (optUDAs.length > 0 && optUDAs[0].names.length > 0)
789         {
790             output ~= " ["~ nameToOption(optUDAs[0].names[0]);
791 
792             if (immutable name = argumentName!(Options, member))
793                 output ~= "="~ name;
794 
795             output ~= "]";
796         }
797     }
798 
799     // List all arguments
800     foreach (member; __traits(allMembers, Options))
801     {
802         alias symbol = Alias!(__traits(getMember, Options, member));
803         alias argUDAs = getUDAs!(symbol, Argument);
804 
805         static if (argUDAs.length > 0)
806             output ~= " "~ argUDAs[0].usage;
807     }
808 
809     return output.wrap(80, null, indent, 4);
810 }
811 
812 /**
813  * Generates a help string for a single argument. Returns null if the given
814  * member is not an argument.
815  */
816 private string argumentHelp(Options, string member)(size_t padding = 14) pure
817 {
818     import std.meta : Alias;
819     import std.traits : getUDAs;
820     import std.array : replicate;
821     import std..string : wrap;
822 
823     string output;
824 
825     alias symbol = Alias!(__traits(getMember, Options, member));
826     alias argUDAs = getUDAs!(symbol, Argument);
827 
828     static if (argUDAs.length > 0)
829     {
830         enum name = argUDAs[0].name;
831         output ~= " "~ name;
832 
833         alias helpUDAs = getUDAs!(symbol, Help);
834         static if (helpUDAs.length > 0)
835         {
836             if (name.length > padding)
837                 output ~= "\n";
838 
839             immutable indent = " ".replicate(padding + 3);
840             immutable firstIndent = (name.length > padding) ? indent :
841                 " ".replicate(padding - name.length + 2);
842 
843             output ~= helpUDAs[0].help.wrap(80, firstIndent, indent, 4);
844         }
845         else
846         {
847             output ~= "\n";
848         }
849     }
850 
851     return output;
852 }
853 
854 /**
855  * Generates a help string for a single option. Returns null if the given member
856  * is not an option.
857  */
858 private string optionHelp(Options, string member)(size_t padding = 14) pure
859 {
860     import std.meta : Alias;
861     import std.traits : getUDAs;
862     import std.array : replicate;
863     import std..string : wrap;
864 
865     string output;
866 
867     alias symbol = Alias!(__traits(getMember, Options, member));
868     alias optUDAs = getUDAs!(symbol, Option);
869 
870     static if (optUDAs.length > 0)
871     {
872         enum names = optUDAs[0].names;
873         static if (names.length > 0)
874         {
875             output ~= " " ~ nameToOption(names[0]);
876 
877             foreach (name; names[1 .. $])
878                 output ~= ", " ~ nameToOption(name);
879 
880             if (string arg = argumentName!(Options, member))
881                 output ~= " " ~ arg;
882 
883             immutable len = output.length;
884 
885             alias helpUDAs = getUDAs!(symbol, Help);
886             static if (helpUDAs.length > 0)
887             {
888                 immutable overflow = len > padding+1;
889                 if (overflow)
890                     output ~= "\n";
891 
892                 immutable indent = " ".replicate(padding+3);
893                 immutable firstIndent = overflow
894                     ? indent : " ".replicate((padding + 1) - len + 2);
895 
896                 output ~= helpUDAs[0].help.wrap(80, firstIndent, indent, 4);
897             }
898             else
899             {
900                 output ~= "\n";
901             }
902         }
903     }
904 
905     return output;
906 }
907 
908 /**
909  * Constructs a printable help string at compile time for the given options
910  * structure.
911  */
912 string helpString(Options)(string description = null) pure
913     if (is(Options == struct))
914 {
915     import std.format : format;
916     import std..string : wrap;
917 
918     string output;
919 
920     if (description)
921         output ~= description.wrap(80) ~ "\n";
922 
923     // List all positional arguments.
924     static if(countArgs!Options > 0)
925     {
926         output ~= "Positional arguments:\n";
927 
928         foreach (member; __traits(allMembers, Options))
929             output ~= argumentHelp!(Options, member);
930 
931         static if (countOpts!Options > 0)
932             output ~= "\n";
933     }
934 
935     // List all options
936     static if (countOpts!Options > 0)
937     {
938         output ~= "Optional arguments:\n";
939 
940         foreach (member; __traits(allMembers, Options))
941             output ~= optionHelp!(Options, member);
942     }
943 
944     return output;
945 }
946 
947 /**
948  * Parsing configuration.
949  */
950 enum Config
951 {
952     /**
953      * Enables option bundling. That is, multiple single character options can
954      * be bundled together.
955      */
956     bundling = 1 << 0,
957 
958     /**
959      * Ignore unknown options. These are then parsed as positional arguments.
960      */
961     ignoreUnknown = 1 << 1,
962 
963     /**
964      * Throw the ArgParseHelp exception when the option "help" is specified.
965      * This requires the option to exist in the options struct.
966      */
967     handleHelp = 1 << 2,
968 
969     /**
970      * Default configuration options.
971      */
972     default_ = bundling | handleHelp,
973 }
974 
975 /**
976  * Parses options from the given list of arguments. Note that the first argument
977  * is assumed to be the program name and is ignored.
978  *
979  * Returns: Options structure filled out with values.
980  *
981  * Throws: ArgParseError if arguments are invalid.
982  */
983 T parseArgs(T)(
984         const(string[]) arguments,
985         Config config = Config.default_
986         ) pure
987     if (is(T == struct))
988 {
989     import std.meta : Alias;
990     import std.traits : getUDAs;
991     import std.format : format;
992     import std.range : chain, enumerate, empty, front, popFront;
993     import std.algorithm.iteration : map, filter;
994 
995     validateOptions!T;
996 
997     T options;
998 
999     auto args = splitArgs(arguments);
1000 
1001     // Arguments that have been parsed
1002     bool[] parsed;
1003     parsed.length = args.head.length;
1004 
1005     // Parsing occurs in two passes:
1006     //
1007     //  1. Parse all options
1008     //  2. Parse all positional arguments
1009     //
1010     // After the first pass, only positional arguments and invalid options will
1011     // be left.
1012 
1013     for (size_t i = 0; i < args.head.length; ++i)
1014     {
1015         auto opt = splitOption(args.head[i]);
1016 
1017         if (immutable name = optionToName(opt.head))
1018         {
1019             foreach (member; __traits(allMembers, T))
1020             {
1021                 alias symbol = Alias!(__traits(getMember, options, member));
1022                 alias optUDAs = getUDAs!(symbol, Option);
1023 
1024                 static if (optUDAs.length > 0)
1025                 {
1026                     if (optUDAs[0] == name)
1027                     {
1028                         parsed[i] = true;
1029 
1030                         static if (hasArgument!(typeof(symbol)))
1031                         {
1032                             if (opt.tail)
1033                             {
1034                                 static if (is(typeof(symbol) : ArgumentHandler))
1035                                     __traits(getMember, options, member)(opt.tail);
1036                                 else
1037                                     __traits(getMember, options, member) =
1038                                         parseArg!(typeof(symbol))(opt.tail);
1039                             }
1040                             else
1041                             {
1042                                 ++i;
1043 
1044                                 if (i >= args.head.length || isOption(args.head[i]))
1045                                     throw new ArgParseError(
1046                                             "Expected argument for option '%s'"
1047                                             .format(opt.head)
1048                                             );
1049 
1050                                 static if (is(typeof(symbol) : ArgumentHandler))
1051                                     __traits(getMember, options, member)(args.head[i]);
1052                                 else
1053                                     __traits(getMember, options, member) =
1054                                         parseArg!(typeof(symbol))(args.head[i]);
1055 
1056                                 parsed[i] = true;
1057                             }
1058                         }
1059                         else
1060                         {
1061                             if (opt.tail)
1062                                 throw new ArgParseError(
1063                                         "Option '%s' does not take an argument"
1064                                         .format(opt.head)
1065                                         );
1066 
1067                             // Handle a request for help
1068                             if ((config & Config.handleHelp) ==
1069                                     Config.handleHelp && optUDAs[0] == "help")
1070                                 throw new ArgParseHelp("");
1071 
1072                             static if (is(typeof(symbol) : OptionHandler))
1073                                 __traits(getMember, options, member)();
1074                             else static if (is(typeof(symbol) : OptionFlag))
1075                                 __traits(getMember, options, member) =
1076                                     OptionFlag.yes;
1077                             else
1078                                 static assert(false);
1079                         }
1080                     }
1081                 }
1082             }
1083         }
1084     }
1085 
1086     if ((config & Config.ignoreUnknown) != Config.ignoreUnknown)
1087     {
1088         // Any left over options are erroneous
1089         for (size_t i = 0; i < args.head.length; ++i)
1090         {
1091             if (!parsed[i] && isOption(args.head[i]))
1092             {
1093                 throw new ArgParseError(
1094                     "Unknown option '"~ args.head[i] ~"'"
1095                     );
1096             }
1097         }
1098     }
1099 
1100     // Left over arguments
1101     auto leftOver = args.head
1102         .enumerate
1103         .filter!(a => !parsed[a[0]])
1104         .map!(a => a[1])
1105         .chain(args.tail);
1106 
1107     // Only positional arguments are left
1108     foreach (member; __traits(allMembers, T))
1109     {
1110         alias symbol = Alias!(__traits(getMember, options, member));
1111         alias argUDAs = getUDAs!(symbol, Argument);
1112 
1113         static if (argUDAs.length > 0)
1114         {
1115             // Keep consuming arguments until the multiplicity is satisfied
1116             for (size_t i = 0; i < argUDAs[0].upperBound; ++i)
1117             {
1118                 // Out of arguments?
1119                 if (leftOver.empty)
1120                 {
1121                     if (i >= argUDAs[0].lowerBound)
1122                         break; // Multiplicity is satisfied
1123 
1124                     throw new ArgParseError(argUDAs[0].multiplicityError(i));
1125                 }
1126 
1127                 // Set argument or add to list of arguments.
1128                 static if (argUDAs[0].upperBound <= 1)
1129                 {
1130                     static if (is(typeof(symbol) : ArgumentHandler))
1131                         __traits(getMember, options, member)(leftOver.front);
1132                     else
1133                         __traits(getMember, options, member) =
1134                             parseArg!(typeof(symbol))(leftOver.front);
1135                 }
1136                 else
1137                 {
1138                     static if (is(typeof(symbol) : ArgumentHandler))
1139                         __traits(getMember, options, member)(leftOver.front);
1140                     else
1141                     {
1142                         import std.range.primitives : ElementType;
1143                         __traits(getMember, options, member) ~=
1144                             parseArg!(ElementType!(typeof(symbol)))(leftOver.front);
1145                     }
1146                 }
1147 
1148                 leftOver.popFront();
1149             }
1150         }
1151     }
1152 
1153     if (!leftOver.empty)
1154         throw new ArgParseError("Too many arguments specified");
1155 
1156     return options;
1157 }
1158 
1159 ///
1160 unittest
1161 {
1162     static struct Options
1163     {
1164         string testValue;
1165 
1166         @Option("test")
1167         void test(string arg) pure
1168         {
1169             testValue = arg;
1170         }
1171 
1172         @Option("help")
1173         @Help("Prints help on command line arguments.")
1174         OptionFlag help;
1175 
1176         @Option("version")
1177         @Help("Prints version information.")
1178         OptionFlag version_;
1179 
1180         @Argument("path", Multiplicity.oneOrMore)
1181         @Help("Path to the build description.")
1182         string[] path;
1183 
1184         @Option("dryrun", "n")
1185         @Help("Don't make any functional changes. Just print what might" ~
1186               " happen.")
1187         OptionFlag dryRun;
1188 
1189         @Option("threads", "j")
1190         @Help("The number of threads to use. Default is the number of" ~
1191               " logical cores.")
1192         size_t threads;
1193 
1194         @Option("color")
1195         @Help("When to colorize the output.")
1196         @MetaVar("{auto,always,never}")
1197         string color = "auto";
1198     }
1199 
1200     immutable options = parseArgs!Options([
1201             "arg1",
1202             "--version",
1203             "--test",
1204             "test test",
1205             "--dryrun",
1206             "--threads",
1207             "42",
1208             "--color=test",
1209             "--",
1210             "arg2",
1211         ]);
1212 
1213     assert(options == Options(
1214             "test test",
1215             OptionFlag.no,
1216             OptionFlag.yes,
1217             ["arg1", "arg2"],
1218             OptionFlag.yes,
1219             42,
1220             "test",
1221             ));
1222 }
1223 
1224 ///
1225 unittest
1226 {
1227     static struct Options
1228     {
1229         @Option("help")
1230         @Help("Prints help on command line usage.")
1231         OptionFlag help;
1232 
1233         @Option("version")
1234         @Help("Prints version information.")
1235         OptionFlag version_;
1236 
1237         @Argument("command", Multiplicity.optional)
1238         @Help("Subcommand")
1239         string command;
1240 
1241         @Argument("args", Multiplicity.zeroOrMore)
1242         @Help("Arguments for the command.")
1243         const(string)[] args;
1244     }
1245 
1246     immutable options = parseArgs!Options([
1247             "--version",
1248             "status",
1249             "--asdf",
1250             "blah blah"
1251         ], Config.ignoreUnknown);
1252 
1253     assert(options == Options(
1254             OptionFlag.no,
1255             OptionFlag.yes,
1256             "status",
1257             ["--asdf", "blah blah"]
1258             ));
1259 }