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