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.traits : Identity, getUDAs;
533     import std.algorithm.comparison : min;
534 
535     size_t count = 0;
536 
537     foreach (member; __traits(allMembers, Options))
538     {
539         alias symbol = Identity!(__traits(getMember, Options, member));
540         alias argUDAs = getUDAs!(symbol, Argument);
541         count += min(argUDAs.length, 1);
542     }
543 
544     return count;
545 }
546 
547 /**
548  * Count the number of options.
549  */
550 size_t countOpts(Options)() pure nothrow
551 {
552     import std.traits : Identity, getUDAs;
553     import std.algorithm.comparison : min;
554 
555     size_t count = 0;
556 
557     foreach (member; __traits(allMembers, Options))
558     {
559         alias symbol = Identity!(__traits(getMember, Options, member));
560         alias optUDAs = getUDAs!(symbol, Option);
561         count += min(optUDAs.length, 1);
562     }
563 
564     return count;
565 }
566 
567 unittest
568 {
569     static struct A {}
570 
571     static assert(countArgs!A == 0);
572     static assert(countOpts!A == 0);
573 
574     static struct B
575     {
576         @Argument("test1")
577         string test1;
578         @Argument("test2")
579         string test2;
580 
581         @Option("test3", "test3a")
582         @Option("test3b", "test3c")
583         string test3;
584     }
585 
586     static assert(countArgs!B == 2);
587     static assert(countOpts!B == 1);
588 
589     static struct C
590     {
591         string test;
592 
593         @Argument("test1")
594         string test1;
595 
596         @Argument("test2")
597         @Argument("test2a")
598         string test2;
599 
600         @Option("test3")
601         string test3;
602 
603         @Option("test4")
604         string test4;
605     }
606 
607     static assert(countArgs!C == 2);
608     static assert(countOpts!C == 2);
609 }
610 
611 /**
612  * Checks if the given options are valid.
613  */
614 private void validateOptions(Options)() pure nothrow
615 {
616     import std.traits : Identity, getUDAs, fullyQualifiedName;
617 
618     foreach (member; __traits(allMembers, Options))
619     {
620         alias symbol = Identity!(__traits(getMember, Options, member));
621         alias optUDAs = getUDAs!(symbol, Option);
622         alias argUDAs = getUDAs!(symbol, Argument);
623 
624         // Basic error checking
625         static assert(!(optUDAs.length > 0 && argUDAs.length > 0),
626             fullyQualifiedName!symbol ~" cannot be both an Option and an Argument"
627             );
628         static assert(optUDAs.length <= 1,
629             fullyQualifiedName!symbol ~" cannot have multiple Option attributes"
630             );
631         static assert(argUDAs.length <= 1,
632             fullyQualifiedName!symbol ~" cannot have multiple Argument attributes"
633             );
634 
635         static if (argUDAs.length > 0)
636             static assert(isValidOptionType!(typeof(symbol)),
637                 fullyQualifiedName!symbol ~" is not a valid Argument type"
638                 );
639 
640         static if (optUDAs.length > 0)
641             static assert(isValidOptionType!(typeof(symbol)),
642                 fullyQualifiedName!symbol ~" is not a valid Option type"
643                 );
644     }
645 }
646 
647 /**
648  * Checks if the given option type has an associated argument. Currently, only
649  * an OptionFlag does not have an argument.
650  */
651 private template hasArgument(T)
652 {
653     static if (is(T : OptionFlag) || is(T : OptionHandler))
654         enum hasArgument = false;
655     else
656         enum hasArgument = true;
657 }
658 
659 unittest
660 {
661     static assert(hasArgument!string);
662     static assert(hasArgument!ArgumentHandler);
663     static assert(hasArgument!int);
664     static assert(hasArgument!bool);
665     static assert(!hasArgument!OptionFlag);
666     static assert(!hasArgument!OptionHandler);
667 }
668 
669 /**
670  * Parses an argument.
671  *
672  * Throws: ArgParseError if the given argument cannot be converted to the
673  * requested type.
674  */
675 T parseArg(T)(string arg) pure
676 {
677     import std.conv : to, ConvException;
678 
679     try
680     {
681         return to!T(arg);
682     }
683     catch (ConvException e)
684     {
685         throw new ArgParseError(e.msg);
686     }
687 }
688 
689 unittest
690 {
691     import std.exception : ce = collectException;
692 
693     assert(parseArg!int("42") == 42);
694     assert(parseArg!string("42") == "42");
695     assert(ce!ArgParseError(parseArg!size_t("-42")));
696 }
697 
698 /**
699  * Returns the canonical name of the given member's argument. If there is no
700  * argument for the given member, null is returned.
701  */
702 private string argumentName(Options, string member)() pure
703 {
704     import std.traits : Identity, getUDAs;
705     import std..string : toUpper;
706 
707     alias symbol = Identity!(__traits(getMember, Options, member));
708 
709     static if (hasArgument!(typeof(symbol)))
710     {
711         alias metavar = getUDAs!(symbol, MetaVar);
712         static if (metavar.length > 0)
713             return metavar[0].name;
714         else static if (is(typeof(symbol) : ArgumentHandler))
715             return member.toUpper;
716         else
717             return "<"~ typeof(symbol).stringof ~ ">";
718     }
719     else
720     {
721         return null;
722     }
723 }
724 
725 unittest
726 {
727     static struct Options
728     {
729         @Option("test1")
730         OptionFlag test1;
731 
732         @Option("test2")
733         string test2;
734 
735         @Option("test3")
736         @MetaVar("asdf")
737         string test3;
738 
739         private string _arg;
740 
741         @Option("test4")
742         void test4(string arg) pure
743         {
744             _arg = arg;
745         }
746 
747         @Option("test5")
748         @MetaVar("metavar")
749         void test5(string arg) pure
750         {
751             _arg = arg;
752         }
753     }
754 
755     static assert(argumentName!(Options, "test1") == null);
756     static assert(argumentName!(Options, "test2") == "<string>");
757     static assert(argumentName!(Options, "test3") == "asdf");
758     static assert(argumentName!(Options, "test4") == "TEST4");
759     static assert(argumentName!(Options, "test5") == "metavar");
760 }
761 
762 /**
763  * Constructs a printable usage string at compile time from the given options
764  * structure.
765  */
766 string usageString(Options)(string program) pure
767     if (is(Options == struct))
768 {
769     import std.traits : Identity, getUDAs;
770     import std.array : replicate;
771     import std..string : wrap, toUpper;
772 
773     string output = "Usage: "~ program;
774 
775     string indent = " ".replicate(output.length + 1);
776 
777     // List all options
778     foreach (member; __traits(allMembers, Options))
779     {
780         alias symbol = Identity!(__traits(getMember, Options, member));
781         alias optUDAs = getUDAs!(symbol, Option);
782 
783         static if (optUDAs.length > 0 && optUDAs[0].names.length > 0)
784         {
785             output ~= " ["~ nameToOption(optUDAs[0].names[0]);
786 
787             if (immutable name = argumentName!(Options, member))
788                 output ~= "="~ name;
789 
790             output ~= "]";
791         }
792     }
793 
794     // List all arguments
795     foreach (member; __traits(allMembers, Options))
796     {
797         alias symbol = Identity!(__traits(getMember, Options, member));
798         alias argUDAs = getUDAs!(symbol, Argument);
799 
800         static if (argUDAs.length > 0)
801             output ~= " "~ argUDAs[0].usage;
802     }
803 
804     return output.wrap(80, null, indent, 4);
805 }
806 
807 /**
808  * Generates a help string for a single argument. Returns null if the given
809  * member is not an argument.
810  */
811 private string argumentHelp(Options, string member)(size_t padding = 14) pure
812 {
813     import std.traits : Identity, getUDAs;
814     import std.array : replicate;
815     import std..string : wrap;
816 
817     string output;
818 
819     alias symbol = Identity!(__traits(getMember, Options, member));
820     alias argUDAs = getUDAs!(symbol, Argument);
821 
822     static if (argUDAs.length > 0)
823     {
824         enum name = argUDAs[0].name;
825         output ~= " "~ name;
826 
827         alias helpUDAs = getUDAs!(symbol, Help);
828         static if (helpUDAs.length > 0)
829         {
830             if (name.length > padding)
831                 output ~= "\n";
832 
833             immutable indent = " ".replicate(padding + 3);
834             immutable firstIndent = (name.length > padding) ? indent :
835                 " ".replicate(padding - name.length + 2);
836 
837             output ~= helpUDAs[0].help.wrap(80, firstIndent, indent, 4);
838         }
839         else
840         {
841             output ~= "\n";
842         }
843     }
844 
845     return output;
846 }
847 
848 /**
849  * Generates a help string for a single option. Returns null if the given member
850  * is not an option.
851  */
852 private string optionHelp(Options, string member)(size_t padding = 14) pure
853 {
854     import std.traits : Identity, getUDAs;
855     import std.array : replicate;
856     import std..string : wrap;
857 
858     string output;
859 
860     alias symbol = Identity!(__traits(getMember, Options, member));
861     alias optUDAs = getUDAs!(symbol, Option);
862 
863     static if (optUDAs.length > 0)
864     {
865         enum names = optUDAs[0].names;
866         static if (names.length > 0)
867         {
868             output ~= " " ~ nameToOption(names[0]);
869 
870             foreach (name; names[1 .. $])
871                 output ~= ", " ~ nameToOption(name);
872 
873             if (string arg = argumentName!(Options, member))
874                 output ~= " " ~ arg;
875 
876             immutable len = output.length;
877 
878             alias helpUDAs = getUDAs!(symbol, Help);
879             static if (helpUDAs.length > 0)
880             {
881                 immutable overflow = len > padding+1;
882                 if (overflow)
883                     output ~= "\n";
884 
885                 immutable indent = " ".replicate(padding+3);
886                 immutable firstIndent = overflow
887                     ? indent : " ".replicate((padding + 1) - len + 2);
888 
889                 output ~= helpUDAs[0].help.wrap(80, firstIndent, indent, 4);
890             }
891             else
892             {
893                 output ~= "\n";
894             }
895         }
896     }
897 
898     return output;
899 }
900 
901 /**
902  * Constructs a printable help string at compile time for the given options
903  * structure.
904  */
905 string helpString(Options)(string description = null) pure
906     if (is(Options == struct))
907 {
908     import std.format : format;
909     import std..string : wrap;
910 
911     string output;
912 
913     if (description)
914         output ~= description.wrap(80) ~ "\n";
915 
916     // List all positional arguments.
917     static if(countArgs!Options > 0)
918     {
919         output ~= "Positional arguments:\n";
920 
921         foreach (member; __traits(allMembers, Options))
922             output ~= argumentHelp!(Options, member);
923 
924         static if (countOpts!Options > 0)
925             output ~= "\n";
926     }
927 
928     // List all options
929     static if (countOpts!Options > 0)
930     {
931         output ~= "Optional arguments:\n";
932 
933         foreach (member; __traits(allMembers, Options))
934             output ~= optionHelp!(Options, member);
935     }
936 
937     return output;
938 }
939 
940 /**
941  * Parsing configuration.
942  */
943 enum Config
944 {
945     /**
946      * Enables option bundling. That is, multiple single character options can
947      * be bundled together.
948      */
949     bundling = 1 << 0,
950 
951     /**
952      * Ignore unknown options. These are then parsed as positional arguments.
953      */
954     ignoreUnknown = 1 << 1,
955 
956     /**
957      * Throw the ArgParseHelp exception when the option "help" is specified.
958      * This requires the option to exist in the options struct.
959      */
960     handleHelp = 1 << 2,
961 
962     /**
963      * Default configuration options.
964      */
965     default_ = bundling | handleHelp,
966 }
967 
968 /**
969  * Parses options from the given list of arguments. Note that the first argument
970  * is assumed to be the program name and is ignored.
971  *
972  * Returns: Options structure filled out with values.
973  *
974  * Throws: ArgParseError if arguments are invalid.
975  */
976 T parseArgs(T)(
977         const(string[]) arguments,
978         Config config = Config.default_
979         ) pure
980     if (is(T == struct))
981 {
982     import std.traits : Identity, getUDAs;
983     import std.format : format;
984     import std.range : chain, enumerate, empty, front, popFront;
985     import std.algorithm.iteration : map, filter;
986 
987     validateOptions!T;
988 
989     T options;
990 
991     auto args = splitArgs(arguments);
992 
993     // Arguments that have been parsed
994     bool[] parsed;
995     parsed.length = args.head.length;
996 
997     // Parsing occurs in two passes:
998     //
999     //  1. Parse all options
1000     //  2. Parse all positional arguments
1001     //
1002     // After the first pass, only positional arguments and invalid options will
1003     // be left.
1004 
1005     for (size_t i = 0; i < args.head.length; ++i)
1006     {
1007         auto opt = splitOption(args.head[i]);
1008 
1009         if (immutable name = optionToName(opt.head))
1010         {
1011             foreach (member; __traits(allMembers, T))
1012             {
1013                 alias symbol = Identity!(__traits(getMember, options, member));
1014                 alias optUDAs = getUDAs!(symbol, Option);
1015 
1016                 static if (optUDAs.length > 0)
1017                 {
1018                     if (optUDAs[0] == name)
1019                     {
1020                         parsed[i] = true;
1021 
1022                         static if (hasArgument!(typeof(symbol)))
1023                         {
1024                             if (opt.tail)
1025                             {
1026                                 static if (is(typeof(symbol) : ArgumentHandler))
1027                                     __traits(getMember, options, member)(opt.tail);
1028                                 else
1029                                     __traits(getMember, options, member) =
1030                                         parseArg!(typeof(symbol))(opt.tail);
1031                             }
1032                             else
1033                             {
1034                                 ++i;
1035 
1036                                 if (i >= args.head.length || isOption(args.head[i]))
1037                                     throw new ArgParseError(
1038                                             "Expected argument for option '%s'"
1039                                             .format(opt.head)
1040                                             );
1041 
1042                                 static if (is(typeof(symbol) : ArgumentHandler))
1043                                     __traits(getMember, options, member)(args.head[i]);
1044                                 else
1045                                     __traits(getMember, options, member) =
1046                                         parseArg!(typeof(symbol))(args.head[i]);
1047 
1048                                 parsed[i] = true;
1049                             }
1050                         }
1051                         else
1052                         {
1053                             if (opt.tail)
1054                                 throw new ArgParseError(
1055                                         "Option '%s' does not take an argument"
1056                                         .format(opt.head)
1057                                         );
1058 
1059                             // Handle a request for help
1060                             if ((config & Config.handleHelp) ==
1061                                     Config.handleHelp && optUDAs[0] == "help")
1062                                 throw new ArgParseHelp("");
1063 
1064                             static if (is(typeof(symbol) : OptionHandler))
1065                                 __traits(getMember, options, member)();
1066                             else static if (is(typeof(symbol) : OptionFlag))
1067                                 __traits(getMember, options, member) =
1068                                     OptionFlag.yes;
1069                             else
1070                                 static assert(false);
1071                         }
1072                     }
1073                 }
1074             }
1075         }
1076     }
1077 
1078     if ((config & Config.ignoreUnknown) != Config.ignoreUnknown)
1079     {
1080         // Any left over options are erroneous
1081         for (size_t i = 0; i < args.head.length; ++i)
1082         {
1083             if (!parsed[i] && isOption(args.head[i]))
1084             {
1085                 throw new ArgParseError(
1086                     "Unknown option '"~ args.head[i] ~"'"
1087                     );
1088             }
1089         }
1090     }
1091 
1092     // Left over arguments
1093     auto leftOver = args.head
1094         .enumerate
1095         .filter!(a => !parsed[a[0]])
1096         .map!(a => a[1])
1097         .chain(args.tail);
1098 
1099     // Only positional arguments are left
1100     foreach (member; __traits(allMembers, T))
1101     {
1102         alias symbol = Identity!(__traits(getMember, options, member));
1103         alias argUDAs = getUDAs!(symbol, Argument);
1104 
1105         static if (argUDAs.length > 0)
1106         {
1107             // Keep consuming arguments until the multiplicity is satisfied
1108             for (size_t i = 0; i < argUDAs[0].upperBound; ++i)
1109             {
1110                 // Out of arguments?
1111                 if (leftOver.empty)
1112                 {
1113                     if (i >= argUDAs[0].lowerBound)
1114                         break; // Multiplicity is satisfied
1115 
1116                     throw new ArgParseError(argUDAs[0].multiplicityError(i));
1117                 }
1118 
1119                 // Set argument or add to list of arguments.
1120                 static if (argUDAs[0].upperBound <= 1)
1121                 {
1122                     static if (is(typeof(symbol) : ArgumentHandler))
1123                         __traits(getMember, options, member)(leftOver.front);
1124                     else
1125                         __traits(getMember, options, member) =
1126                             parseArg!(typeof(symbol))(leftOver.front);
1127                 }
1128                 else
1129                 {
1130                     static if (is(typeof(symbol) : ArgumentHandler))
1131                         __traits(getMember, options, member)(leftOver.front);
1132                     else
1133                     {
1134                         import std.range.primitives : ElementType;
1135                         __traits(getMember, options, member) ~=
1136                             parseArg!(ElementType!(typeof(symbol)))(leftOver.front);
1137                     }
1138                 }
1139 
1140                 leftOver.popFront();
1141             }
1142         }
1143     }
1144 
1145     if (!leftOver.empty)
1146         throw new ArgParseError("Too many arguments specified");
1147 
1148     return options;
1149 }
1150 
1151 ///
1152 unittest
1153 {
1154     static struct Options
1155     {
1156         string testValue;
1157 
1158         @Option("test")
1159         void test(string arg) pure
1160         {
1161             testValue = arg;
1162         }
1163 
1164         @Option("help")
1165         @Help("Prints help on command line arguments.")
1166         OptionFlag help;
1167 
1168         @Option("version")
1169         @Help("Prints version information.")
1170         OptionFlag version_;
1171 
1172         @Argument("path", Multiplicity.oneOrMore)
1173         @Help("Path to the build description.")
1174         string[] path;
1175 
1176         @Option("dryrun", "n")
1177         @Help("Don't make any functional changes. Just print what might" ~
1178               " happen.")
1179         OptionFlag dryRun;
1180 
1181         @Option("threads", "j")
1182         @Help("The number of threads to use. Default is the number of" ~
1183               " logical cores.")
1184         size_t threads;
1185 
1186         @Option("color")
1187         @Help("When to colorize the output.")
1188         @MetaVar("{auto,always,never}")
1189         string color = "auto";
1190     }
1191 
1192     immutable options = parseArgs!Options([
1193             "arg1",
1194             "--version",
1195             "--test",
1196             "test test",
1197             "--dryrun",
1198             "--threads",
1199             "42",
1200             "--color=test",
1201             "--",
1202             "arg2",
1203         ]);
1204 
1205     assert(options == Options(
1206             "test test",
1207             OptionFlag.no,
1208             OptionFlag.yes,
1209             ["arg1", "arg2"],
1210             OptionFlag.yes,
1211             42,
1212             "test",
1213             ));
1214 }
1215 
1216 ///
1217 unittest
1218 {
1219     static struct Options
1220     {
1221         @Option("help")
1222         @Help("Prints help on command line usage.")
1223         OptionFlag help;
1224 
1225         @Option("version")
1226         @Help("Prints version information.")
1227         OptionFlag version_;
1228 
1229         @Argument("command", Multiplicity.optional)
1230         @Help("Subcommand")
1231         string command;
1232 
1233         @Argument("args", Multiplicity.zeroOrMore)
1234         @Help("Arguments for the command.")
1235         const(string)[] args;
1236     }
1237 
1238     immutable options = parseArgs!Options([
1239             "--version",
1240             "status",
1241             "--asdf",
1242             "blah blah"
1243         ], Config.ignoreUnknown);
1244 
1245     assert(options == Options(
1246             OptionFlag.no,
1247             OptionFlag.yes,
1248             "status",
1249             ["--asdf", "blah blah"]
1250             ));
1251 }