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