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