1 /*
2  * Hunt - A refined core library for D programming language.
3  *
4  * Copyright (C) 2018-2019 HuntLabs
5  *
6  * Website: https://www.huntlabs.net/
7  *
8  * Licensed under the Apache-2.0 License.
9  *
10  */
11 
12 module hunt.util.Argument;
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     {
147         this.name = name;
148         this.lowerBound = count;
149         this.upperBound = count;
150     }
151 
152     /**
153      * Constructs an argument with the given name and an upper and lower bound
154      * for how many argument elements should be consumed from the command line.
155      */
156     this(string name, size_t lowerBound, size_t upperBound) pure nothrow
157     {
158         assert(lowerBound < upperBound);
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 template isOptionHandler(Func)
309 {
310     import std.meta : AliasSeq;
311     import std.traits : arity, hasFunctionAttributes, isFunction, ReturnType;
312 
313     static if (isFunction!Func)
314     {
315         enum isOptionHandler =
316             hasFunctionAttributes!(Func, "pure") &&
317             is(ReturnType!Func == void) &&
318             arity!Func == 0;
319     }
320     else
321     {
322         enum isOptionHandler = false;
323     }
324 }
325 
326 template isArgumentHandler(Func)
327 {
328     import std.meta : AliasSeq;
329     import std.traits : hasFunctionAttributes, isFunction, Parameters, ReturnType;
330 
331     static if (isFunction!Func)
332     {
333         enum isArgumentHandler =
334             hasFunctionAttributes!(Func, "pure") &&
335             is(ReturnType!Func == void) &&
336             is(Parameters!Func == AliasSeq!(string));
337     }
338     else
339     {
340         enum isArgumentHandler = false;
341     }
342 }
343 
344 unittest
345 {
346     struct TemplateOptions(T)
347     {
348         void optionHandler() pure { }
349         void argumentHandler(string) pure { }
350         string foo() pure { return ""; }
351         void bar() { import std.stdio : writeln; writeln("bar"); }
352         void baz(int) pure { }
353     }
354 
355     TemplateOptions!int options;
356 
357     static assert(!is(typeof(options.optionHandler) : OptionHandler));
358     static assert(!is(typeof(options.argumentHandler) : ArgumentHandler));
359     static assert(!is(typeof(options.foo) : ArgumentHandler));
360     static assert(!is(typeof(options.bar) : ArgumentHandler));
361     static assert(!is(typeof(options.baz) : ArgumentHandler));
362 
363     static assert(isOptionHandler!(typeof(options.optionHandler)));
364     static assert(!isOptionHandler!(typeof(options.argumentHandler)));
365     static assert(!isOptionHandler!(typeof(options.foo)));
366     static assert(!isOptionHandler!(typeof(options.bar)));
367     static assert(!isOptionHandler!(typeof(options.baz)));
368 
369     static assert(!isArgumentHandler!(typeof(options.optionHandler)));
370     static assert(isArgumentHandler!(typeof(options.argumentHandler)));
371     static assert(!isArgumentHandler!(typeof(options.foo)));
372     static assert(!isArgumentHandler!(typeof(options.bar)));
373     static assert(!isArgumentHandler!(typeof(options.baz)));
374 }
375 
376 /**
377  * Returns true if the given argument is a short option. That is, if it starts
378  * with a '-'.
379  */
380 bool isShortOption(string arg) pure nothrow
381 {
382     return arg.length > 1 && arg[0] == '-' && arg[1] != '-';
383 }
384 
385 unittest
386 {
387     static assert(!isShortOption(""));
388     static assert(!isShortOption("-"));
389     static assert(!isShortOption("a"));
390     static assert(!isShortOption("ab"));
391     static assert( isShortOption("-a"));
392     static assert( isShortOption("-ab"));
393     static assert(!isShortOption("--a"));
394     static assert(!isShortOption("--abc"));
395 }
396 
397 /**
398  * Returns true if the given argument is a long option. That is, if it starts
399  * with "--".
400  */
401 bool isLongOption(string arg) pure nothrow
402 {
403     return arg.length > 2 && arg[0 .. 2] == "--" && arg[2] != '-';
404 }
405 
406 unittest
407 {
408     static assert(!isLongOption(""));
409     static assert(!isLongOption("a"));
410     static assert(!isLongOption("ab"));
411     static assert(!isLongOption("abc"));
412     static assert(!isLongOption("-"));
413     static assert(!isLongOption("-a"));
414     static assert(!isLongOption("--"));
415     static assert( isLongOption("--a"));
416     static assert( isLongOption("--arg"));
417     static assert( isLongOption("--arg=asdf"));
418 }
419 
420 /**
421  * Returns true if the given argument is an option. That is, it is either a
422  * short option or a long option.
423  */
424 bool isOption(string arg) pure nothrow
425 {
426     return isShortOption(arg) || isLongOption(arg);
427 }
428 
429 private static struct OptionSplit
430 {
431     string head;
432     string tail;
433 }
434 
435 /**
436  * Splits an option on "=".
437  */
438 private auto splitOption(string option) pure
439 {
440     size_t i = 0;
441     while (i < option.length && option[i] != '=')
442         ++i;
443 
444     return OptionSplit(
445             option[0 .. i],
446             (i < option.length) ? option[i+1 .. $] : null
447             );
448 }
449 
450 unittest
451 {
452     static assert(splitOption("") == OptionSplit("", null));
453     static assert(splitOption("--foo") == OptionSplit("--foo", null));
454     static assert(splitOption("--foo=") == OptionSplit("--foo", ""));
455     static assert(splitOption("--foo=bar") == OptionSplit("--foo", "bar"));
456 }
457 
458 private static struct ArgSplit
459 {
460     const(string)[] head;
461     const(string)[] tail;
462 }
463 
464 /**
465  * Splits arguments on "--".
466  */
467 private auto splitArgs(const(string)[] args) pure
468 {
469     size_t i = 0;
470     while (i < args.length && args[i] != "--")
471         ++i;
472 
473     return ArgSplit(
474             args[0 .. i],
475             (i < args.length) ? args[i+1 .. $] : []
476             );
477 }
478 
479 unittest
480 {
481     static assert(splitArgs([]) == ArgSplit([], []));
482     static assert(splitArgs(["a", "b"]) == ArgSplit(["a", "b"], []));
483     static assert(splitArgs(["a", "--"]) == ArgSplit(["a"], []));
484     static assert(splitArgs(["a", "--", "b"]) == ArgSplit(["a"], ["b"]));
485     static assert(splitArgs(["a", "--", "b", "c"]) == ArgSplit(["a"], ["b", "c"]));
486 }
487 
488 /**
489  * Returns an option name without the leading ("--" or "-"). If it is not an
490  * option, returns null.
491  */
492 private string optionToName(string option) pure nothrow
493 {
494     if (isLongOption(option))
495         return option[2 .. $];
496 
497     if (isShortOption(option))
498         return option[1 .. $];
499 
500     return null;
501 }
502 
503 unittest
504 {
505     static assert(optionToName("--opt") == "opt");
506     static assert(optionToName("-opt") == "opt");
507     static assert(optionToName("-o") == "o");
508     static assert(optionToName("opt") is null);
509     static assert(optionToName("o") is null);
510     static assert(optionToName("") is null);
511 }
512 
513 /**
514  * Returns the appropriate long or short option corresponding to the given name.
515  */
516 private string nameToOption(string name) pure nothrow
517 {
518     switch (name.length)
519     {
520         case 0:
521             return null;
522         case 1:
523             return "-" ~ name;
524         default:
525             return "--" ~ name;
526     }
527 }
528 
529 unittest
530 {
531     static assert(nameToOption("opt") == "--opt");
532     static assert(nameToOption("o") == "-o");
533     static assert(nameToOption("") is null);
534 }
535 
536 unittest
537 {
538     immutable identities = ["opt", "o", ""];
539 
540     foreach (identity; identities)
541         assert(identity.nameToOption.optionToName == identity);
542 }
543 
544 /**
545  * Check if the given type is valid for an option.
546  */
547 private template isValidOptionType(T)
548 {
549     import std.traits : isBasicType, isSomeString;
550 
551     static if (isBasicType!T ||
552                isSomeString!T ||
553                isOptionHandler!T ||
554                isArgumentHandler!T
555         )
556     {
557         enum isValidOptionType = true;
558     }
559     else static if (is(T A : A[]))
560     {
561         enum isValidOptionType = isValidOptionType!A;
562     }
563     else
564     {
565         enum isValidOptionType = false;
566     }
567 }
568 
569 unittest
570 {
571     static assert(isValidOptionType!bool);
572     static assert(isValidOptionType!int);
573     static assert(isValidOptionType!float);
574     static assert(isValidOptionType!char);
575     static assert(isValidOptionType!string);
576     static assert(isValidOptionType!(int[]));
577 
578     alias void Func1() pure;
579     alias void Func2(string) pure;
580     alias int Func3();
581     alias int Func4(string);
582     alias void Func5();
583     alias void Func6(string);
584 
585     static assert(isValidOptionType!Func1);
586     static assert(isValidOptionType!Func2);
587     static assert(!isValidOptionType!Func3);
588     static assert(!isValidOptionType!Func4);
589     static assert(!isValidOptionType!Func5);
590     static assert(!isValidOptionType!Func6);
591 }
592 
593 /**
594  * Count the number of positional arguments.
595  */
596 size_t countArgs(Options)() pure nothrow
597 {
598     import std.meta : Alias;
599     import std.traits : getUDAs;
600     import std.algorithm.comparison : min;
601 
602     size_t count = 0;
603 
604     foreach (member; __traits(allMembers, Options))
605     {
606         alias symbol = Alias!(__traits(getMember, Options, member));
607         alias argUDAs = getUDAs!(symbol, Argument);
608         count += min(argUDAs.length, 1);
609     }
610 
611     return count;
612 }
613 
614 /**
615  * Count the number of options.
616  */
617 size_t countOpts(Options)() pure nothrow
618 {
619     import std.meta : Alias;
620     import std.traits : getUDAs;
621     import std.algorithm.comparison : min;
622 
623     size_t count = 0;
624 
625     foreach (member; __traits(allMembers, Options))
626     {
627         alias symbol = Alias!(__traits(getMember, Options, member));
628         alias optUDAs = getUDAs!(symbol, Option);
629         count += min(optUDAs.length, 1);
630     }
631 
632     return count;
633 }
634 
635 unittest
636 {
637     static struct A {}
638 
639     static assert(countArgs!A == 0);
640     static assert(countOpts!A == 0);
641 
642     static struct B
643     {
644         @Argument("test1")
645         string test1;
646         @Argument("test2")
647         string test2;
648 
649         @Option("test3", "test3a")
650         @Option("test3b", "test3c")
651         string test3;
652     }
653 
654     static assert(countArgs!B == 2);
655     static assert(countOpts!B == 1);
656 
657     static struct C
658     {
659         string test;
660 
661         @Argument("test1")
662         string test1;
663 
664         @Argument("test2")
665         @Argument("test2a")
666         string test2;
667 
668         @Option("test3")
669         string test3;
670 
671         @Option("test4")
672         string test4;
673     }
674 
675     static assert(countArgs!C == 2);
676     static assert(countOpts!C == 2);
677 }
678 
679 /**
680  * Checks if the given options are valid.
681  */
682 private void validateOptions(Options)() pure nothrow
683 {
684     import std.meta : Alias;
685     import std.traits : getUDAs, fullyQualifiedName;
686 
687     foreach (member; __traits(allMembers, Options))
688     {
689         alias symbol = Alias!(__traits(getMember, Options, member));
690         alias optUDAs = getUDAs!(symbol, Option);
691         alias argUDAs = getUDAs!(symbol, Argument);
692 
693         // Basic error checking
694         static assert(!(optUDAs.length > 0 && argUDAs.length > 0),
695             fullyQualifiedName!symbol ~" cannot be both an Option and an Argument"
696             );
697         static assert(optUDAs.length <= 1,
698             fullyQualifiedName!symbol ~" cannot have multiple Option attributes"
699             );
700         static assert(argUDAs.length <= 1,
701             fullyQualifiedName!symbol ~" cannot have multiple Argument attributes"
702             );
703 
704         static if (argUDAs.length > 0)
705             static assert(isValidOptionType!(typeof(symbol)),
706                 fullyQualifiedName!symbol ~" is not a valid Argument type"
707                 );
708 
709         static if (optUDAs.length > 0)
710             static assert(isValidOptionType!(typeof(symbol)),
711                 fullyQualifiedName!symbol ~" is not a valid Option type"
712                 );
713     }
714 }
715 
716 /**
717  * Checks if the given option type has an associated argument. Currently, only
718  * an OptionFlag does not have an argument.
719  */
720 private template hasArgument(T)
721 {
722     static if (is(T : OptionFlag) || isOptionHandler!T)
723         enum hasArgument = false;
724     else
725         enum hasArgument = true;
726 }
727 
728 unittest
729 {
730     static assert(hasArgument!string);
731     static assert(hasArgument!ArgumentHandler);
732     static assert(hasArgument!int);
733     static assert(hasArgument!bool);
734     static assert(!hasArgument!OptionFlag);
735     static assert(!hasArgument!OptionHandler);
736 }
737 
738 /**
739  * Parses an argument.
740  *
741  * Throws: ArgParseError if the given argument cannot be converted to the
742  * requested type.
743  */
744 T parseArg(T)(string arg) pure
745 {
746     import std.conv : to, ConvException;
747 
748     try
749     {
750         return to!T(arg);
751     }
752     catch (ConvException e)
753     {
754         throw new ArgParseError(e.msg);
755     }
756 }
757 
758 unittest
759 {
760     import std.exception : ce = collectException;
761 
762     assert(parseArg!int("42") == 42);
763     assert(parseArg!string("42") == "42");
764     assert(ce!ArgParseError(parseArg!size_t("-42")));
765 }
766 
767 /**
768  * Returns the canonical name of the given member's argument. If there is no
769  * argument for the given member, null is returned.
770  */
771 private string argumentName(Options, string member)() pure
772 {
773     import std.meta : Alias;
774     import std.traits : getUDAs;
775     import std.string : toUpper;
776 
777     alias symbol = Alias!(__traits(getMember, Options, member));
778 
779     static if (hasArgument!(typeof(symbol)))
780     {
781         alias metavar = getUDAs!(symbol, MetaVar);
782         static if (metavar.length > 0)
783             return metavar[0].name;
784         else static if (isArgumentHandler!(typeof(symbol)))
785             return member.toUpper;
786         else
787             return "<"~ typeof(symbol).stringof ~ ">";
788     }
789     else
790     {
791         return null;
792     }
793 }
794 
795 unittest
796 {
797     static struct Options
798     {
799         @Option("test1")
800         OptionFlag test1;
801 
802         @Option("test2")
803         string test2;
804 
805         @Option("test3")
806         @MetaVar("asdf")
807         string test3;
808 
809         private string _arg;
810 
811         @Option("test4")
812         void test4(string arg) pure
813         {
814             _arg = arg;
815         }
816 
817         @Option("test5")
818         @MetaVar("metavar")
819         void test5(string arg) pure
820         {
821             _arg = arg;
822         }
823     }
824 
825     static assert(argumentName!(Options, "test1") == null);
826     static assert(argumentName!(Options, "test2") == "<string>");
827     static assert(argumentName!(Options, "test3") == "asdf");
828     static assert(argumentName!(Options, "test4") == "TEST4");
829     static assert(argumentName!(Options, "test5") == "metavar");
830 }
831 
832 /**
833  * Constructs a printable usage string at compile time from the given options
834  * structure.
835  */
836 string usageString(Options)(string program) pure
837     if (is(Options == struct))
838 {
839     import std.meta : Alias;
840     import std.traits : getUDAs;
841     import std.array : replicate;
842     import std.string : wrap, toUpper;
843 
844     string output = "Usage: "~ program;
845 
846     string indent = " ".replicate(output.length + 1);
847 
848     // List all options
849     foreach (member; __traits(allMembers, Options))
850     {
851         alias symbol = Alias!(__traits(getMember, Options, member));
852         alias optUDAs = getUDAs!(symbol, Option);
853 
854         static if (optUDAs.length > 0 && optUDAs[0].names.length > 0)
855         {
856             output ~= " ["~ nameToOption(optUDAs[0].names[0]);
857 
858             if (immutable name = argumentName!(Options, member))
859                 output ~= "="~ name;
860 
861             output ~= "]";
862         }
863     }
864 
865     // List all arguments
866     foreach (member; __traits(allMembers, Options))
867     {
868         alias symbol = Alias!(__traits(getMember, Options, member));
869         alias argUDAs = getUDAs!(symbol, Argument);
870 
871         static if (argUDAs.length > 0)
872             output ~= " "~ argUDAs[0].usage;
873     }
874 
875     return output.wrap(80, null, indent, 4);
876 }
877 
878 /**
879  * Generates a help string for a single argument. Returns null if the given
880  * member is not an argument.
881  */
882 private string argumentHelp(Options, string member)(size_t padding = 14) pure
883 {
884     import std.meta : Alias;
885     import std.traits : getUDAs;
886     import std.array : replicate;
887     import std.string : wrap;
888 
889     string output;
890 
891     alias symbol = Alias!(__traits(getMember, Options, member));
892     alias argUDAs = getUDAs!(symbol, Argument);
893 
894     static if (argUDAs.length > 0)
895     {
896         enum name = argUDAs[0].name;
897         output ~= " "~ name;
898 
899         alias helpUDAs = getUDAs!(symbol, Help);
900         static if (helpUDAs.length > 0)
901         {
902             if (name.length > padding)
903                 output ~= "\n";
904 
905             immutable indent = " ".replicate(padding + 3);
906             immutable firstIndent = (name.length > padding) ? indent :
907                 " ".replicate(padding - name.length + 2);
908 
909             output ~= helpUDAs[0].help.wrap(80, firstIndent, indent, 4);
910         }
911         else
912         {
913             output ~= "\n";
914         }
915     }
916 
917     return output;
918 }
919 
920 /**
921  * Generates a help string for a single option. Returns null if the given member
922  * is not an option.
923  */
924 private string optionHelp(Options, string member)(size_t padding = 14) pure
925 {
926     import std.meta : Alias;
927     import std.traits : getUDAs;
928     import std.array : replicate;
929     import std.string : wrap;
930 
931     string output;
932 
933     alias symbol = Alias!(__traits(getMember, Options, member));
934     alias optUDAs = getUDAs!(symbol, Option);
935 
936     static if (optUDAs.length > 0)
937     {
938         enum names = optUDAs[0].names;
939         static if (names.length > 0)
940         {
941             output ~= " " ~ nameToOption(names[0]);
942 
943             foreach (name; names[1 .. $])
944                 output ~= ", " ~ nameToOption(name);
945 
946             if (string arg = argumentName!(Options, member))
947                 output ~= " " ~ arg;
948 
949             immutable len = output.length;
950 
951             alias helpUDAs = getUDAs!(symbol, Help);
952             static if (helpUDAs.length > 0)
953             {
954                 immutable overflow = len > padding+1;
955                 if (overflow)
956                     output ~= "\n";
957 
958                 immutable indent = " ".replicate(padding+3);
959                 immutable firstIndent = overflow
960                     ? indent : " ".replicate((padding + 1) - len + 2);
961 
962                 output ~= helpUDAs[0].help.wrap(80, firstIndent, indent, 4);
963             }
964             else
965             {
966                 output ~= "\n";
967             }
968         }
969     }
970 
971     return output;
972 }
973 
974 /**
975  * Constructs a printable help string at compile time for the given options
976  * structure.
977  */
978 string helpString(Options)(string description = null) pure
979     if (is(Options == struct))
980 {
981     import std.format : format;
982     import std.string : wrap;
983 
984     string output;
985 
986     if (description)
987         output ~= description.wrap(80) ~ "\n";
988 
989     // List all positional arguments.
990     static if(countArgs!Options > 0)
991     {
992         output ~= "Positional arguments:\n";
993 
994         foreach (member; __traits(allMembers, Options))
995             output ~= argumentHelp!(Options, member);
996 
997         static if (countOpts!Options > 0)
998             output ~= "\n";
999     }
1000 
1001     // List all options
1002     static if (countOpts!Options > 0)
1003     {
1004         output ~= "Optional arguments:\n";
1005 
1006         foreach (member; __traits(allMembers, Options))
1007             output ~= optionHelp!(Options, member);
1008     }
1009 
1010     return output;
1011 }
1012 
1013 /**
1014  * Parsing configuration.
1015  */
1016 enum Config
1017 {
1018     /**
1019      * Enables option bundling. That is, multiple single character options can
1020      * be bundled together.
1021      */
1022     bundling = 1 << 0,
1023 
1024     /**
1025      * Ignore unknown options. These are then parsed as positional arguments.
1026      */
1027     ignoreUnknown = 1 << 1,
1028 
1029     /**
1030      * Throw the ArgParseHelp exception when the option "help" is specified.
1031      * This requires the option to exist in the options struct.
1032      */
1033     handleHelp = 1 << 2,
1034 
1035     /**
1036      * Default configuration options.
1037      */
1038     default_ = bundling | handleHelp,
1039 }
1040 
1041 /**
1042  * Parses options from the given list of arguments. Note that the first argument
1043  * is assumed to be the program name and is ignored.
1044  *
1045  * Returns: Options structure filled out with values.
1046  *
1047  * Throws: ArgParseError if arguments are invalid.
1048  */
1049 T parseArgs(T)(
1050         const(string[]) arguments,
1051         Config config = Config.default_
1052         ) pure
1053     if (is(T == struct))
1054 {
1055     import std.meta : Alias;
1056     import std.traits : getUDAs;
1057     import std.format : format;
1058     import std.range : chain, enumerate, empty, front, popFront;
1059     import std.algorithm.iteration : map, filter;
1060 
1061     validateOptions!T;
1062 
1063     T options;
1064 
1065     auto args = splitArgs(arguments);
1066 
1067     // Arguments that have been parsed
1068     bool[] parsed;
1069     parsed.length = args.head.length;
1070 
1071     // Parsing occurs in two passes:
1072     //
1073     //  1. Parse all options
1074     //  2. Parse all positional arguments
1075     //
1076     // After the first pass, only positional arguments and invalid options will
1077     // be left.
1078 
1079     for (size_t i = 0; i < args.head.length; ++i)
1080     {
1081         auto opt = splitOption(args.head[i]);
1082 
1083         if (immutable name = optionToName(opt.head))
1084         {
1085             foreach (member; __traits(allMembers, T))
1086             {
1087                 alias symbol = Alias!(__traits(getMember, options, member));
1088                 alias optUDAs = getUDAs!(symbol, Option);
1089 
1090                 static if (optUDAs.length > 0)
1091                 {
1092                     if (optUDAs[0] == name)
1093                     {
1094                         parsed[i] = true;
1095 
1096                         static if (hasArgument!(typeof(symbol)))
1097                         {
1098                             if (opt.tail)
1099                             {
1100                                 static if (isArgumentHandler!(typeof(symbol)))
1101                                     __traits(getMember, options, member)(opt.tail);
1102                                 else
1103                                     __traits(getMember, options, member) =
1104                                         parseArg!(typeof(symbol))(opt.tail);
1105                             }
1106                             else
1107                             {
1108                                 ++i;
1109 
1110                                 if (i >= args.head.length || isOption(args.head[i]))
1111                                     throw new ArgParseError(
1112                                             "Expected argument for option '%s'"
1113                                             .format(opt.head)
1114                                             );
1115 
1116                                 static if (isArgumentHandler!(typeof(symbol)))
1117                                     __traits(getMember, options, member)(args.head[i]);
1118                                 else
1119                                     __traits(getMember, options, member) =
1120                                         parseArg!(typeof(symbol))(args.head[i]);
1121 
1122                                 parsed[i] = true;
1123                             }
1124                         }
1125                         else
1126                         {
1127                             if (opt.tail)
1128                                 throw new ArgParseError(
1129                                         "Option '%s' does not take an argument"
1130                                         .format(opt.head)
1131                                         );
1132 
1133                             // Handle a request for help
1134                             if ((config & Config.handleHelp) ==
1135                                     Config.handleHelp && optUDAs[0] == "help")
1136                                 throw new ArgParseHelp("");
1137 
1138                             static if (isOptionHandler!(typeof(symbol)))
1139                                 __traits(getMember, options, member)();
1140                             else static if (is(typeof(symbol) : OptionFlag))
1141                                 __traits(getMember, options, member) =
1142                                     OptionFlag.yes;
1143                             else
1144                                 static assert(false);
1145                         }
1146                     }
1147                 }
1148             }
1149         }
1150     }
1151 
1152     if ((config & Config.ignoreUnknown) != Config.ignoreUnknown)
1153     {
1154         // Any left over options are erroneous
1155         for (size_t i = 0; i < args.head.length; ++i)
1156         {
1157             if (!parsed[i] && isOption(args.head[i]))
1158             {
1159                 throw new ArgParseError(
1160                     "Unknown option '"~ args.head[i] ~"'"
1161                     );
1162             }
1163         }
1164     }
1165 
1166     // Left over arguments
1167     auto leftOver = args.head
1168         .enumerate
1169         .filter!(a => !parsed[a[0]])
1170         .map!(a => a[1])
1171         .chain(args.tail);
1172 
1173     // Only positional arguments are left
1174     foreach (member; __traits(allMembers, T))
1175     {
1176         alias symbol = Alias!(__traits(getMember, options, member));
1177         alias argUDAs = getUDAs!(symbol, Argument);
1178 
1179         static if (argUDAs.length > 0)
1180         {
1181             // Keep consuming arguments until the multiplicity is satisfied
1182             for (size_t i = 0; i < argUDAs[0].upperBound; ++i)
1183             {
1184                 // Out of arguments?
1185                 if (leftOver.empty)
1186                 {
1187                     if (i >= argUDAs[0].lowerBound)
1188                         break; // Multiplicity is satisfied
1189 
1190                     throw new ArgParseError(argUDAs[0].multiplicityError(i));
1191                 }
1192 
1193                 // Set argument or add to list of arguments.
1194                 static if (argUDAs[0].upperBound <= 1)
1195                 {
1196                     static if (isArgumentHandler!(typeof(symbol)))
1197                         __traits(getMember, options, member)(leftOver.front);
1198                     else
1199                         __traits(getMember, options, member) =
1200                             parseArg!(typeof(symbol))(leftOver.front);
1201                 }
1202                 else
1203                 {
1204                     static if (isArgumentHandler!(typeof(symbol)))
1205                         __traits(getMember, options, member)(leftOver.front);
1206                     else
1207                     {
1208                         import std.range.primitives : ElementType;
1209                         __traits(getMember, options, member) ~=
1210                             parseArg!(ElementType!(typeof(symbol)))(leftOver.front);
1211                     }
1212                 }
1213 
1214                 leftOver.popFront();
1215             }
1216         }
1217     }
1218 
1219     if (!leftOver.empty)
1220         throw new ArgParseError("Too many arguments specified");
1221 
1222     return options;
1223 }
1224 
1225 ///
1226 unittest
1227 {
1228     static struct Options
1229     {
1230         string testValue;
1231 
1232         @Option("test")
1233         void test(string arg) pure
1234         {
1235             testValue = arg;
1236         }
1237 
1238         @Option("help")
1239         @Help("Prints help on command line arguments.")
1240         OptionFlag help;
1241 
1242         @Option("version")
1243         @Help("Prints version information.")
1244         OptionFlag version_;
1245 
1246         @Argument("path", Multiplicity.oneOrMore)
1247         @Help("Path to the build description.")
1248         string[] path;
1249 
1250         @Option("dryrun", "n")
1251         @Help("Don't make any functional changes. Just print what might" ~
1252               " happen.")
1253         OptionFlag dryRun;
1254 
1255         @Option("threads", "j")
1256         @Help("The number of threads to use. Default is the number of" ~
1257               " logical cores.")
1258         size_t threads;
1259 
1260         @Option("color")
1261         @Help("When to colorize the output.")
1262         @MetaVar("{auto,always,never}")
1263         string color = "auto";
1264     }
1265 
1266     immutable options = parseArgs!Options([
1267             "arg1",
1268             "--version",
1269             "--test",
1270             "test test",
1271             "--dryrun",
1272             "--threads",
1273             "42",
1274             "--color=test",
1275             "--",
1276             "arg2",
1277         ]);
1278 
1279     assert(options == Options(
1280             "test test",
1281             OptionFlag.no,
1282             OptionFlag.yes,
1283             ["arg1", "arg2"],
1284             OptionFlag.yes,
1285             42,
1286             "test",
1287             ));
1288 }
1289 
1290 ///
1291 unittest
1292 {
1293     static struct Options
1294     {
1295         @Option("help")
1296         @Help("Prints help on command line usage.")
1297         OptionFlag help;
1298 
1299         @Option("version")
1300         @Help("Prints version information.")
1301         OptionFlag version_;
1302 
1303         @Argument("command", Multiplicity.optional)
1304         @Help("Subcommand")
1305         string command;
1306 
1307         @Argument("args", Multiplicity.zeroOrMore)
1308         @Help("Arguments for the command.")
1309         const(string)[] args;
1310     }
1311 
1312     immutable options = parseArgs!Options([
1313             "--version",
1314             "status",
1315             "--asdf",
1316             "blah blah"
1317         ], Config.ignoreUnknown);
1318 
1319     assert(options == Options(
1320             OptionFlag.no,
1321             OptionFlag.yes,
1322             "status",
1323             ["--asdf", "blah blah"]
1324             ));
1325 }