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