1 // Written in the D programming language.
2 
3 /**
4 Processing of command line options.
5 
6 The getopt module implements a `getopt` function, which adheres to
7 the POSIX syntax for command line options. GNU extensions are
8 supported in the form of long options introduced by a double dash
9 ("--"). Support for bundling of command line options, as was the case
10 with the more traditional single-letter approach, is provided but not
11 enabled by default.
12 
13 Copyright: Copyright Andrei Alexandrescu 2008 - 2015.
14 License:   $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0).
15 Authors:   $(HTTP erdani.org, Andrei Alexandrescu)
16 Credits:   This module and its documentation are inspired by Perl's
17            $(HTTPS perldoc.perl.org/Getopt/Long.html, Getopt::Long) module. The syntax of
18            D's `getopt` is simpler than its Perl counterpart because $(D
19            getopt) infers the expected parameter types from the static types of
20            the passed-in pointers.
21 Source:    $(PHOBOSSRC std/getopt.d)
22 */
23 /*
24          Copyright Andrei Alexandrescu 2008 - 2015.
25 Distributed under the Boost Software License, Version 1.0.
26    (See accompanying file LICENSE_1_0.txt or copy at
27          http://www.boost.org/LICENSE_1_0.txt)
28 */
29 module std.getopt;
30 
31 import std.exception : basicExceptionCtors;
32 import std.traits;
33 
34 /**
35 Thrown on one of the following conditions:
36 $(UL
37   $(LI An unrecognized command-line argument is passed, and
38        `std.getopt.config.passThrough` was not present.)
39   $(LI A command-line option was not found, and
40        `std.getopt.config.required` was present.)
41   $(LI A callback option is missing a value.)
42 )
43 */
44 class GetOptException : Exception
45 {
46     mixin basicExceptionCtors;
47 }
48 
49 static assert(is(typeof(new GetOptException("message"))));
50 static assert(is(typeof(new GetOptException("message", Exception.init))));
51 
52 /**
53    Parse and remove command line options from a string array.
54 
55    Synopsis:
56 
57 ---------
58 import std.getopt;
59 
60 string data = "file.dat";
61 int length = 24;
62 bool verbose;
63 enum Color { no, yes };
64 Color color;
65 
66 void main(string[] args)
67 {
68   auto helpInformation = getopt(
69     args,
70     "length",  &length,    // numeric
71     "file",    &data,      // string
72     "verbose", &verbose,   // flag
73     "color", "Information about this color", &color);    // enum
74   ...
75 
76   if (helpInformation.helpWanted)
77   {
78     defaultGetoptPrinter("Some information about the program.",
79       helpInformation.options);
80   }
81 }
82 ---------
83 
84  The `getopt` function takes a reference to the command line
85  (as received by `main`) as its first argument, and an
86  unbounded number of pairs of strings and pointers. Each string is an
87  option meant to "fill" the value referenced by the pointer to its
88  right (the "bound" pointer). The option string in the call to
89  `getopt` should not start with a dash.
90 
91  In all cases, the command-line options that were parsed and used by
92  `getopt` are removed from `args`. Whatever in the
93  arguments did not look like an option is left in `args` for
94  further processing by the program. Values that were unaffected by the
95  options are not touched, so a common idiom is to initialize options
96  to their defaults and then invoke `getopt`. If a
97  command-line argument is recognized as an option with a parameter and
98  the parameter cannot be parsed properly (e.g., a number is expected
99  but not present), a `ConvException` exception is thrown.
100  If `std.getopt.config.passThrough` was not passed to `getopt`
101  and an unrecognized command-line argument is found, or if a required
102  argument is missing a `GetOptException` is thrown.
103 
104  Depending on the type of the pointer being bound, `getopt`
105  recognizes the following kinds of options:
106 
107  $(OL
108     $(LI $(I Boolean options). A lone argument sets the option to `true`.
109     Additionally $(B true) or $(B false) can be set within the option separated
110     with an "=" sign:
111 
112 ---------
113   bool verbose = false, debugging = true;
114   getopt(args, "verbose", &verbose, "debug", &debugging);
115 ---------
116 
117     To set `verbose` to `true`, invoke the program with either
118     `--verbose` or `--verbose=true`.
119 
120     To set `debugging` to `false`, invoke the program with
121     `--debugging=false`.
122     )
123 
124     $(LI $(I Numeric options.) If an option is bound to a numeric type, a
125     number is expected as the next option, or right within the option separated
126     with an "=" sign:
127 
128 ---------
129   uint timeout;
130   getopt(args, "timeout", &timeout);
131 ---------
132 
133     To set `timeout` to `5`, invoke the program with either
134     `--timeout=5` or $(D --timeout 5).
135     )
136 
137     $(LI $(I Incremental options.) If an option name has a "+" suffix and is
138     bound to a numeric type, then the option's value tracks the number of times
139     the option occurred on the command line:
140 
141 ---------
142   uint paranoid;
143   getopt(args, "paranoid+", &paranoid);
144 ---------
145 
146     Invoking the program with "--paranoid --paranoid --paranoid" will set $(D
147     paranoid) to 3. Note that an incremental option never expects a parameter,
148     e.g., in the command line "--paranoid 42 --paranoid", the "42" does not set
149     `paranoid` to 42; instead, `paranoid` is set to 2 and "42" is not
150     considered as part of the normal program arguments.
151     )
152 
153     $(LI $(I Enum options.) If an option is bound to an enum, an enum symbol as
154     a string is expected as the next option, or right within the option
155     separated with an "=" sign:
156 
157 ---------
158   enum Color { no, yes };
159   Color color; // default initialized to Color.no
160   getopt(args, "color", &color);
161 ---------
162 
163     To set `color` to `Color.yes`, invoke the program with either
164     `--color=yes` or $(D --color yes).
165     )
166 
167     $(LI $(I String options.) If an option is bound to a string, a string is
168     expected as the next option, or right within the option separated with an
169     "=" sign:
170 
171 ---------
172 string outputFile;
173 getopt(args, "output", &outputFile);
174 ---------
175 
176     Invoking the program with "--output=myfile.txt" or "--output myfile.txt"
177     will set `outputFile` to "myfile.txt". If you want to pass a string
178     containing spaces, you need to use the quoting that is appropriate to your
179     shell, e.g. --output='my file.txt'.
180     )
181 
182     $(LI $(I Array options.) If an option is bound to an array, a new element
183     is appended to the array each time the option occurs:
184 
185 ---------
186 string[] outputFiles;
187 getopt(args, "output", &outputFiles);
188 ---------
189 
190     Invoking the program with "--output=myfile.txt --output=yourfile.txt" or
191     "--output myfile.txt --output yourfile.txt" will set `outputFiles` to
192     $(D [ "myfile.txt", "yourfile.txt" ]).
193 
194     Alternatively you can set $(LREF arraySep) to allow multiple elements in
195     one parameter.
196 
197 ---------
198 string[] outputFiles;
199 arraySep = ",";  // defaults to "", meaning one element per parameter
200 getopt(args, "output", &outputFiles);
201 ---------
202 
203     With the above code you can invoke the program with
204     "--output=myfile.txt,yourfile.txt", or "--output myfile.txt,yourfile.txt".)
205 
206     $(LI $(I Hash options.) If an option is bound to an associative array, a
207     string of the form "name=value" is expected as the next option, or right
208     within the option separated with an "=" sign:
209 
210 ---------
211 double[string] tuningParms;
212 getopt(args, "tune", &tuningParms);
213 ---------
214 
215     Invoking the program with e.g. "--tune=alpha=0.5 --tune beta=0.6" will set
216     `tuningParms` to [ "alpha" : 0.5, "beta" : 0.6 ].
217 
218     Alternatively you can set $(LREF arraySep) as the element separator:
219 
220 ---------
221 double[string] tuningParms;
222 arraySep = ",";  // defaults to "", meaning one element per parameter
223 getopt(args, "tune", &tuningParms);
224 ---------
225 
226     With the above code you can invoke the program with
227     "--tune=alpha=0.5,beta=0.6", or "--tune alpha=0.5,beta=0.6".
228 
229     In general, the keys and values can be of any parsable types.
230     )
231 
232     $(LI $(I Callback options.) An option can be bound to a function or
233     delegate with the signature $(D void function()), $(D void function(string
234     option)), $(D void function(string option, string value)), or their
235     delegate equivalents.
236 
237     $(UL
238         $(LI If the callback doesn't take any arguments, the callback is
239         invoked whenever the option is seen.
240         )
241 
242         $(LI If the callback takes one string argument, the option string
243         (without the leading dash(es)) is passed to the callback.  After that,
244         the option string is considered handled and removed from the options
245         array.
246 
247 ---------
248 void main(string[] args)
249 {
250   uint verbosityLevel = 1;
251   void myHandler(string option)
252   {
253     if (option == "quiet")
254     {
255       verbosityLevel = 0;
256     }
257     else
258     {
259       assert(option == "verbose");
260       verbosityLevel = 2;
261     }
262   }
263   getopt(args, "verbose", &myHandler, "quiet", &myHandler);
264 }
265 ---------
266 
267         )
268 
269         $(LI If the callback takes two string arguments, the option string is
270         handled as an option with one argument, and parsed accordingly. The
271         option and its value are passed to the callback. After that, whatever
272         was passed to the callback is considered handled and removed from the
273         list.
274 
275 ---------
276 int main(string[] args)
277 {
278   uint verbosityLevel = 1;
279   bool handlerFailed = false;
280   void myHandler(string option, string value)
281   {
282     switch (value)
283     {
284       case "quiet": verbosityLevel = 0; break;
285       case "verbose": verbosityLevel = 2; break;
286       case "shouting": verbosityLevel = verbosityLevel.max; break;
287       default :
288         stderr.writeln("Unknown verbosity level ", value);
289         handlerFailed = true;
290         break;
291     }
292   }
293   getopt(args, "verbosity", &myHandler);
294   return handlerFailed ? 1 : 0;
295 }
296 ---------
297         )
298     ))
299 )
300 
301 Options_with_multiple_names:
302 Sometimes option synonyms are desirable, e.g. "--verbose",
303 "--loquacious", and "--garrulous" should have the same effect. Such
304 alternate option names can be included in the option specification,
305 using "|" as a separator:
306 
307 ---------
308 bool verbose;
309 getopt(args, "verbose|loquacious|garrulous", &verbose);
310 ---------
311 
312 Case:
313 By default options are case-insensitive. You can change that behavior
314 by passing `getopt` the `caseSensitive` directive like this:
315 
316 ---------
317 bool foo, bar;
318 getopt(args,
319     std.getopt.config.caseSensitive,
320     "foo", &foo,
321     "bar", &bar);
322 ---------
323 
324 In the example above, "--foo" and "--bar" are recognized, but "--Foo", "--Bar",
325 "--FOo", "--bAr", etc. are rejected.
326 The directive is active until the end of `getopt`, or until the
327 converse directive `caseInsensitive` is encountered:
328 
329 ---------
330 bool foo, bar;
331 getopt(args,
332     std.getopt.config.caseSensitive,
333     "foo", &foo,
334     std.getopt.config.caseInsensitive,
335     "bar", &bar);
336 ---------
337 
338 The option "--Foo" is rejected due to $(D
339 std.getopt.config.caseSensitive), but not "--Bar", "--bAr"
340 etc. because the directive $(D
341 std.getopt.config.caseInsensitive) turned sensitivity off before
342 option "bar" was parsed.
343 
344 Short_versus_long_options:
345 Traditionally, programs accepted single-letter options preceded by
346 only one dash (e.g. `-t`). `getopt` accepts such parameters
347 seamlessly. When used with a double-dash (e.g. `--t`), a
348 single-letter option behaves the same as a multi-letter option. When
349 used with a single dash, a single-letter option is accepted.
350 
351 To set `timeout` to `5`, use either of the following: `--timeout=5`,
352 `--timeout 5`, `--t=5`, `--t 5`, `-t5`, or `-t 5`. Forms such as
353 `-timeout=5` will be not accepted.
354 
355 For more details about short options, refer also to the next section.
356 
357 Bundling:
358 Single-letter options can be bundled together, i.e. "-abc" is the same as
359 $(D "-a -b -c"). By default, this option is turned off. You can turn it on
360 with the `std.getopt.config.bundling` directive:
361 
362 ---------
363 bool foo, bar;
364 getopt(args,
365     std.getopt.config.bundling,
366     "foo|f", &foo,
367     "bar|b", &bar);
368 ---------
369 
370 In case you want to only enable bundling for some of the parameters,
371 bundling can be turned off with `std.getopt.config.noBundling`.
372 
373 Required:
374 An option can be marked as required. If that option is not present in the
375 arguments an exception will be thrown.
376 
377 ---------
378 bool foo, bar;
379 getopt(args,
380     std.getopt.config.required,
381     "foo|f", &foo,
382     "bar|b", &bar);
383 ---------
384 
385 Only the option directly following `std.getopt.config.required` is
386 required.
387 
388 Passing_unrecognized_options_through:
389 If an application needs to do its own processing of whichever arguments
390 `getopt` did not understand, it can pass the
391 `std.getopt.config.passThrough` directive to `getopt`:
392 
393 ---------
394 bool foo, bar;
395 getopt(args,
396     std.getopt.config.passThrough,
397     "foo", &foo,
398     "bar", &bar);
399 ---------
400 
401 An unrecognized option such as "--baz" will be found untouched in
402 `args` after `getopt` returns.
403 
404 Help_Information_Generation:
405 If an option string is followed by another string, this string serves as a
406 description for this option. The `getopt` function returns a struct of type
407 `GetoptResult`. This return value contains information about all passed options
408 as well a $(D bool GetoptResult.helpWanted) flag indicating whether information
409 about these options was requested. The `getopt` function always adds an option for
410 `--help|-h` to set the flag if the option is seen on the command line.
411 
412 Options_Terminator:
413 A lone double-dash terminates `getopt` gathering. It is used to
414 separate program options from other parameters (e.g., options to be passed
415 to another program). Invoking the example above with $(D "--foo -- --bar")
416 parses foo but leaves "--bar" in `args`. The double-dash itself is
417 removed from the argument array unless the `std.getopt.config.keepEndOfOptions`
418 directive is given.
419 */
420 GetoptResult getopt(T...)(ref string[] args, T opts)
421 {
422     import std.exception : enforce;
423     enforce(args.length,
424             "Invalid arguments string passed: program name missing");
425     configuration cfg;
426     GetoptResult rslt;
427 
428     GetOptException excep;
429     void[][string] visitedLongOpts, visitedShortOpts;
430     getoptImpl(args, cfg, rslt, excep, visitedLongOpts, visitedShortOpts, opts);
431 
432     if (!rslt.helpWanted && excep !is null)
433     {
434         throw excep;
435     }
436 
437     return rslt;
438 }
439 
440 ///
441 @safe unittest
442 {
443     auto args = ["prog", "--foo", "-b"];
444 
445     bool foo;
446     bool bar;
447     auto rslt = getopt(args, "foo|f", "Some information about foo.", &foo, "bar|b",
448         "Some help message about bar.", &bar);
449 
450     if (rslt.helpWanted)
451     {
452         defaultGetoptPrinter("Some information about the program.",
453             rslt.options);
454     }
455 }
456 
457 /**
458    Configuration options for `getopt`.
459 
460    You can pass them to `getopt` in any position, except in between an option
461    string and its bound pointer.
462 */
463 enum config {
464     /// Turn case sensitivity on
465     caseSensitive,
466     /// Turn case sensitivity off (default)
467     caseInsensitive,
468     /// Turn bundling on
469     bundling,
470     /// Turn bundling off (default)
471     noBundling,
472     /// Pass unrecognized arguments through
473     passThrough,
474     /// Signal unrecognized arguments as errors (default)
475     noPassThrough,
476     /// Stop at first argument that does not look like an option
477     stopOnFirstNonOption,
478     /// Do not erase the endOfOptions separator from args
479     keepEndOfOptions,
480     /// Make the next option a required option
481     required
482 }
483 
484 /** The result of the `getopt` function.
485 
486 `helpWanted` is set if the option `--help` or `-h` was passed to the option parser.
487 */
488 struct GetoptResult {
489     bool helpWanted; /// Flag indicating if help was requested
490     Option[] options; /// All possible options
491 }
492 
493 /** Information about an option.
494 */
495 struct Option {
496     string optShort; /// The short symbol for this option
497     string optLong; /// The long symbol for this option
498     string help; /// The description of this option
499     bool required; /// If a option is required, not passing it will result in an error
500 }
501 
502 private pure Option splitAndGet(string opt) @trusted nothrow
503 {
504     import std.array : split;
505     auto sp = split(opt, "|");
506     Option ret;
507     if (sp.length > 1)
508     {
509         ret.optShort = "-" ~ (sp[0].length < sp[1].length ?
510             sp[0] : sp[1]);
511         ret.optLong = "--" ~ (sp[0].length > sp[1].length ?
512             sp[0] : sp[1]);
513     }
514     else if (sp[0].length > 1)
515     {
516         ret.optLong = "--" ~ sp[0];
517     }
518     else
519     {
520         ret.optShort = "-" ~ sp[0];
521     }
522 
523     return ret;
524 }
525 
526 @safe unittest
527 {
528     auto oshort = splitAndGet("f");
529     assert(oshort.optShort == "-f");
530     assert(oshort.optLong == "");
531 
532     auto olong = splitAndGet("foo");
533     assert(olong.optShort == "");
534     assert(olong.optLong == "--foo");
535 
536     auto oshortlong = splitAndGet("f|foo");
537     assert(oshortlong.optShort == "-f");
538     assert(oshortlong.optLong == "--foo");
539 
540     auto olongshort = splitAndGet("foo|f");
541     assert(olongshort.optShort == "-f");
542     assert(olongshort.optLong == "--foo");
543 }
544 
545 /*
546 This function verifies that the variadic parameters passed in getOpt
547 follow this pattern:
548 
549   [config override], option, [description], receiver,
550 
551  - config override: a config value, optional
552  - option:          a string or a char
553  - description:     a string, optional
554  - receiver:        a pointer or a callable
555 */
556 private template optionValidator(A...)
557 {
558     import std.format : format;
559 
560     enum fmt = "getopt validator: %s (at position %d)";
561     enum isReceiver(T) = is(T == U*, U) || (is(T == function)) || (is(T == delegate));
562     enum isOptionStr(T) = isSomeString!T || isSomeChar!T;
563 
564     auto validator()
565     {
566         string msg;
567         static if (A.length > 0)
568         {
569             static if (isReceiver!(A[0]))
570             {
571                 msg = format(fmt, "first argument must be a string or a config", 0);
572             }
573             else static if (!isOptionStr!(A[0]) && !is(A[0] == config))
574             {
575                 msg = format(fmt, "invalid argument type: " ~ A[0].stringof, 0);
576             }
577             else
578             {
579                 static foreach (i; 1 .. A.length)
580                 {
581                     static if (!isReceiver!(A[i]) && !isOptionStr!(A[i]) &&
582                         !(is(A[i] == config)))
583                     {
584                         msg = format(fmt, "invalid argument type: " ~ A[i].stringof, i);
585                         goto end;
586                     }
587                     else static if (isReceiver!(A[i]) && !isOptionStr!(A[i-1]))
588                     {
589                         msg = format(fmt, "a receiver can not be preceeded by a receiver", i);
590                         goto end;
591                     }
592                     else static if (i > 1 && isOptionStr!(A[i]) && isOptionStr!(A[i-1])
593                         && isSomeString!(A[i-2]))
594                     {
595                         msg = format(fmt, "a string can not be preceeded by two strings", i);
596                         goto end;
597                     }
598                 }
599             }
600             static if (!isReceiver!(A[$-1]) && !is(A[$-1] == config))
601             {
602                 msg = format(fmt, "last argument must be a receiver or a config",
603                     A.length -1);
604             }
605         }
606     end:
607         return msg;
608     }
609     enum message = validator;
610     alias optionValidator = message;
611 }
612 
613 private void handleConversion(R)(string option, string value, R* receiver,
614         size_t idx, string file = __FILE__, size_t line = __LINE__)
615 {
616     import std.conv : to, ConvException;
617     import std.format : format;
618     try
619     {
620         *receiver = to!(typeof(*receiver))(value);
621     }
622     catch (ConvException e)
623     {
624         throw new ConvException(format("Argument '%s' at position '%u' could "
625             ~ "not be converted to type '%s' as required by option '%s'.",
626             value, idx, R.stringof, option), e, file, line);
627     }
628 }
629 
630 @safe pure unittest
631 {
632     alias P = void*;
633     alias S = string;
634     alias A = char;
635     alias C = config;
636     alias F = void function();
637 
638     static assert(optionValidator!(S,P) == "");
639     static assert(optionValidator!(S,F) == "");
640     static assert(optionValidator!(A,P) == "");
641     static assert(optionValidator!(A,F) == "");
642 
643     static assert(optionValidator!(C,S,P) == "");
644     static assert(optionValidator!(C,S,F) == "");
645     static assert(optionValidator!(C,A,P) == "");
646     static assert(optionValidator!(C,A,F) == "");
647 
648     static assert(optionValidator!(C,S,S,P) == "");
649     static assert(optionValidator!(C,S,S,F) == "");
650     static assert(optionValidator!(C,A,S,P) == "");
651     static assert(optionValidator!(C,A,S,F) == "");
652 
653     static assert(optionValidator!(C,S,S,P) == "");
654     static assert(optionValidator!(C,S,S,P,C,S,F) == "");
655     static assert(optionValidator!(C,S,P,C,S,S,F) == "");
656 
657     static assert(optionValidator!(C,A,P,A,S,F) == "");
658     static assert(optionValidator!(C,A,P,C,A,S,F) == "");
659 
660     static assert(optionValidator!(P,S,S) != "");
661     static assert(optionValidator!(P,P,S) != "");
662     static assert(optionValidator!(P,F,S,P) != "");
663     static assert(optionValidator!(C,C,S) != "");
664     static assert(optionValidator!(S,S,P,S,S,P,S) != "");
665     static assert(optionValidator!(S,S,P,P) != "");
666     static assert(optionValidator!(S,S,S,P) != "");
667 
668     static assert(optionValidator!(C,A,S,P,C,A,F) == "");
669     static assert(optionValidator!(C,A,P,C,A,S,F) == "");
670 }
671 
672 // https://issues.dlang.org/show_bug.cgi?id=15914
673 @safe unittest
674 {
675     import std.exception : assertThrown;
676     bool opt;
677     string[] args = ["program", "-a"];
678     getopt(args, config.passThrough, 'a', &opt);
679     assert(opt);
680     opt = false;
681     args = ["program", "-a"];
682     getopt(args, 'a', &opt);
683     assert(opt);
684     opt = false;
685     args = ["program", "-a"];
686     getopt(args, 'a', "help string", &opt);
687     assert(opt);
688     opt = false;
689     args = ["program", "-a"];
690     getopt(args, config.caseSensitive, 'a', "help string", &opt);
691     assert(opt);
692 
693     assertThrown(getopt(args, "", "forgot to put a string", &opt));
694 }
695 
696 private void getoptImpl(T...)(ref string[] args, ref configuration cfg,
697     ref GetoptResult rslt, ref GetOptException excep,
698     void[][string] visitedLongOpts, void[][string] visitedShortOpts, T opts)
699 {
700     enum validationMessage = optionValidator!T;
701     static assert(validationMessage == "", validationMessage);
702 
703     import std.algorithm.mutation : remove;
704     import std.conv : to;
705     import std.uni : toLower;
706     static if (opts.length)
707     {
708         static if (is(typeof(opts[0]) : config))
709         {
710             // it's a configuration flag, act on it
711             setConfig(cfg, opts[0]);
712             return getoptImpl(args, cfg, rslt, excep, visitedLongOpts,
713                 visitedShortOpts, opts[1 .. $]);
714         }
715         else
716         {
717             // it's an option string
718             auto option = to!string(opts[0]);
719             if (option.length == 0)
720             {
721                 excep = new GetOptException("An option name may not be an empty string", excep);
722                 return;
723             }
724             Option optionHelp = splitAndGet(option);
725             optionHelp.required = cfg.required;
726 
727             if (optionHelp.optLong.length)
728             {
729                 auto name = optionHelp.optLong;
730                 if (!cfg.caseSensitive)
731                     name = name.toLower();
732                 assert(name !in visitedLongOpts,
733                     "Long option " ~ optionHelp.optLong ~ " is multiply defined");
734 
735                 visitedLongOpts[optionHelp.optLong] = [];
736             }
737 
738             if (optionHelp.optShort.length)
739             {
740                 auto name = optionHelp.optShort;
741                 if (!cfg.caseSensitive)
742                     name = name.toLower();
743                 assert(name !in visitedShortOpts,
744                     "Short option " ~ optionHelp.optShort
745                     ~ " is multiply defined");
746 
747                 visitedShortOpts[optionHelp.optShort] = [];
748             }
749 
750             static if (is(typeof(opts[1]) : string))
751             {
752                 alias receiver = opts[2];
753                 optionHelp.help = opts[1];
754                 immutable lowSliceIdx = 3;
755             }
756             else
757             {
758                 alias receiver = opts[1];
759                 immutable lowSliceIdx = 2;
760             }
761 
762             rslt.options ~= optionHelp;
763 
764             bool incremental;
765             // Handle options of the form --blah+
766             if (option.length && option[$ - 1] == autoIncrementChar)
767             {
768                 option = option[0 .. $ - 1];
769                 incremental = true;
770             }
771 
772             bool optWasHandled = handleOption(option, receiver, args, cfg, incremental);
773 
774             if (cfg.required && !optWasHandled)
775             {
776                 excep = new GetOptException("Required option "
777                     ~ option ~ " was not supplied", excep);
778             }
779             cfg.required = false;
780 
781             getoptImpl(args, cfg, rslt, excep, visitedLongOpts,
782                 visitedShortOpts, opts[lowSliceIdx .. $]);
783         }
784     }
785     else
786     {
787         // no more options to look for, potentially some arguments left
788         for (size_t i = 1; i < args.length;)
789         {
790             auto a = args[i];
791             if (endOfOptions.length && a == endOfOptions)
792             {
793                 // Consume the "--" if keepEndOfOptions is not specified
794                 if (!cfg.keepEndOfOptions)
795                     args = args.remove(i);
796                 break;
797             }
798             if (a.length < 2 || a[0] != optionChar)
799             {
800                 // not an option
801                 if (cfg.stopOnFirstNonOption) break;
802                 ++i;
803                 continue;
804             }
805             if (a == "--help" || a == "-h")
806             {
807                 rslt.helpWanted = true;
808                 args = args.remove(i);
809                 continue;
810             }
811             if (!cfg.passThrough)
812             {
813                 throw new GetOptException("Unrecognized option "~a, excep);
814             }
815             ++i;
816         }
817 
818         Option helpOpt;
819         helpOpt.optShort = "-h";
820         helpOpt.optLong = "--help";
821         helpOpt.help = "This help information.";
822         rslt.options ~= helpOpt;
823     }
824 }
825 
826 private bool handleOption(R)(string option, R receiver, ref string[] args,
827     ref configuration cfg, bool incremental)
828 {
829     import std.algorithm.iteration : map, splitter;
830     import std.ascii : isAlpha;
831     import std.conv : text, to;
832     // Scan arguments looking for a match for this option
833     bool ret = false;
834     for (size_t i = 1; i < args.length; )
835     {
836         auto a = args[i];
837         if (endOfOptions.length && a == endOfOptions) break;
838         if (cfg.stopOnFirstNonOption && (!a.length || a[0] != optionChar))
839         {
840             // first non-option is end of options
841             break;
842         }
843         // Unbundle bundled arguments if necessary
844         if (cfg.bundling && a.length > 2 && a[0] == optionChar &&
845                 a[1] != optionChar)
846         {
847             string[] expanded;
848             foreach (j, dchar c; a[1 .. $])
849             {
850                 // If the character is not alpha, stop right there. This allows
851                 // e.g. -j100 to work as "pass argument 100 to option -j".
852                 if (!isAlpha(c))
853                 {
854                     if (c == '=')
855                         j++;
856                     expanded ~= a[j + 1 .. $];
857                     break;
858                 }
859                 expanded ~= text(optionChar, c);
860             }
861             args = args[0 .. i] ~ expanded ~ args[i + 1 .. $];
862             continue;
863         }
864 
865         string val;
866         if (!optMatch(a, option, val, cfg))
867         {
868             ++i;
869             continue;
870         }
871 
872         ret = true;
873 
874         // found it
875         // from here on, commit to eat args[i]
876         // (and potentially args[i + 1] too, but that comes later)
877         args = args[0 .. i] ~ args[i + 1 .. $];
878 
879         static if (is(typeof(*receiver) == bool))
880         {
881             if (val.length)
882             {
883                 // parse '--b=true/false'
884                 handleConversion(option, val, receiver, i);
885             }
886             else
887             {
888                 // no argument means set it to true
889                 *receiver = true;
890             }
891         }
892         else
893         {
894             import std.exception : enforce;
895             // non-boolean option, which might include an argument
896             enum isCallbackWithLessThanTwoParameters =
897                 (is(typeof(receiver) == delegate) || is(typeof(*receiver) == function)) &&
898                 !is(typeof(receiver("", "")));
899             if (!isCallbackWithLessThanTwoParameters && !(val.length) && !incremental)
900             {
901                 // Eat the next argument too.  Check to make sure there's one
902                 // to be eaten first, though.
903                 enforce!GetOptException(i < args.length,
904                     "Missing value for argument " ~ a ~ ".");
905                 val = args[i];
906                 args = args[0 .. i] ~ args[i + 1 .. $];
907             }
908             static if (is(typeof(*receiver) == enum) ||
909                 is(typeof(*receiver) == string))
910             {
911                 handleConversion(option, val, receiver, i);
912             }
913             else static if (is(typeof(*receiver) : real))
914             {
915                 // numeric receiver
916                 if (incremental)
917                 {
918                     ++*receiver;
919                 }
920                 else
921                 {
922                     handleConversion(option, val, receiver, i);
923                 }
924             }
925             else static if (is(typeof(*receiver) == string))
926             {
927                 // string receiver
928                 *receiver = to!(typeof(*receiver))(val);
929             }
930             else static if (is(typeof(receiver) == delegate) ||
931                             is(typeof(*receiver) == function))
932             {
933                 static if (is(typeof(receiver("", "")) : void))
934                 {
935                     // option with argument
936                     receiver(option, val);
937                 }
938                 else static if (is(typeof(receiver("")) : void))
939                 {
940                     alias RType = typeof(receiver(""));
941                     static assert(is(RType : void),
942                             "Invalid receiver return type " ~ RType.stringof);
943                     // boolean-style receiver
944                     receiver(option);
945                 }
946                 else
947                 {
948                     alias RType = typeof(receiver());
949                     static assert(is(RType : void),
950                             "Invalid receiver return type " ~ RType.stringof);
951                     // boolean-style receiver without argument
952                     receiver();
953                 }
954             }
955             else static if (isArray!(typeof(*receiver)))
956             {
957                 // array receiver
958                 import std.range : ElementEncodingType;
959                 alias E = ElementEncodingType!(typeof(*receiver));
960 
961                 if (arraySep == "")
962                 {
963                     E tmp;
964                     handleConversion(option, val, &tmp, i);
965                     *receiver ~= tmp;
966                 }
967                 else
968                 {
969                     foreach (elem; val.splitter(arraySep))
970                     {
971                         E tmp;
972                         handleConversion(option, elem, &tmp, i);
973                         *receiver ~= tmp;
974                     }
975                 }
976             }
977             else static if (isAssociativeArray!(typeof(*receiver)))
978             {
979                 // hash receiver
980                 alias K = typeof(receiver.keys[0]);
981                 alias V = typeof(receiver.values[0]);
982 
983                 import std.range : only;
984                 import std.string : indexOf;
985                 import std.typecons : Tuple, tuple;
986 
987                 static Tuple!(K, V) getter(string input)
988                 {
989                     auto j = indexOf(input, assignChar);
990                     enforce!GetOptException(j != -1, "Could not find '"
991                         ~ to!string(assignChar) ~ "' in argument '" ~ input ~ "'.");
992                     auto key = input[0 .. j];
993                     auto value = input[j + 1 .. $];
994 
995                     K k;
996                     handleConversion("", key, &k, 0);
997 
998                     V v;
999                     handleConversion("", value, &v, 0);
1000 
1001                     return tuple(k,v);
1002                 }
1003 
1004                 static void setHash(Range)(R receiver, Range range)
1005                 {
1006                     foreach (k, v; range.map!getter)
1007                         (*receiver)[k] = v;
1008                 }
1009 
1010                 if (arraySep == "")
1011                     setHash(receiver, val.only);
1012                 else
1013                     setHash(receiver, val.splitter(arraySep));
1014             }
1015             else
1016                 static assert(false, "getopt does not know how to handle the type " ~ typeof(receiver).stringof);
1017         }
1018     }
1019 
1020     return ret;
1021 }
1022 
1023 // https://issues.dlang.org/show_bug.cgi?id=17574
1024 @safe unittest
1025 {
1026     import std.algorithm.searching : startsWith;
1027 
1028     try
1029     {
1030         string[string] mapping;
1031         immutable as = arraySep;
1032         arraySep = ",";
1033         scope (exit)
1034             arraySep = as;
1035         string[] args = ["testProgram", "-m", "a=b,c=\"d,e,f\""];
1036         args.getopt("m", &mapping);
1037         assert(false, "Exception not thrown");
1038     }
1039     catch (GetOptException goe)
1040         assert(goe.msg.startsWith("Could not find"));
1041 }
1042 
1043 // https://issues.dlang.org/show_bug.cgi?id=5316 - arrays with arraySep
1044 @safe unittest
1045 {
1046     import std.conv;
1047 
1048     arraySep = ",";
1049     scope (exit) arraySep = "";
1050 
1051     string[] names;
1052     auto args = ["program.name", "-nfoo,bar,baz"];
1053     getopt(args, "name|n", &names);
1054     assert(names == ["foo", "bar", "baz"], to!string(names));
1055 
1056     names = names.init;
1057     args = ["program.name", "-n", "foo,bar,baz"];
1058     getopt(args, "name|n", &names);
1059     assert(names == ["foo", "bar", "baz"], to!string(names));
1060 
1061     names = names.init;
1062     args = ["program.name", "--name=foo,bar,baz"];
1063     getopt(args, "name|n", &names);
1064     assert(names == ["foo", "bar", "baz"], to!string(names));
1065 
1066     names = names.init;
1067     args = ["program.name", "--name", "foo,bar,baz"];
1068     getopt(args, "name|n", &names);
1069     assert(names == ["foo", "bar", "baz"], to!string(names));
1070 }
1071 
1072 // https://issues.dlang.org/show_bug.cgi?id=5316 - associative arrays with arraySep
1073 @safe unittest
1074 {
1075     import std.conv;
1076 
1077     arraySep = ",";
1078     scope (exit) arraySep = "";
1079 
1080     int[string] values;
1081     values = values.init;
1082     auto args = ["program.name", "-vfoo=0,bar=1,baz=2"];
1083     getopt(args, "values|v", &values);
1084     assert(values == ["foo":0, "bar":1, "baz":2], to!string(values));
1085 
1086     values = values.init;
1087     args = ["program.name", "-v", "foo=0,bar=1,baz=2"];
1088     getopt(args, "values|v", &values);
1089     assert(values == ["foo":0, "bar":1, "baz":2], to!string(values));
1090 
1091     values = values.init;
1092     args = ["program.name", "--values=foo=0,bar=1,baz=2"];
1093     getopt(args, "values|t", &values);
1094     assert(values == ["foo":0, "bar":1, "baz":2], to!string(values));
1095 
1096     values = values.init;
1097     args = ["program.name", "--values", "foo=0,bar=1,baz=2"];
1098     getopt(args, "values|v", &values);
1099     assert(values == ["foo":0, "bar":1, "baz":2], to!string(values));
1100 }
1101 
1102 /**
1103    The option character (default '-').
1104 
1105    Defaults to '-' but it can be assigned to prior to calling `getopt`.
1106  */
1107 dchar optionChar = '-';
1108 
1109 /**
1110    The string that conventionally marks the end of all options (default '--').
1111 
1112    Defaults to "--" but can be assigned to prior to calling `getopt`. Assigning an
1113    empty string to `endOfOptions` effectively disables it.
1114  */
1115 string endOfOptions = "--";
1116 
1117 /**
1118    The assignment character used in options with parameters (default '=').
1119 
1120    Defaults to '=' but can be assigned to prior to calling `getopt`.
1121  */
1122 dchar assignChar = '=';
1123 
1124 /**
1125    When set to "", parameters to array and associative array receivers are
1126    treated as an individual argument. That is, only one argument is appended or
1127    inserted per appearance of the option switch. If `arraySep` is set to
1128    something else, then each parameter is first split by the separator, and the
1129    individual pieces are treated as arguments to the same option.
1130 
1131    Defaults to "" but can be assigned to prior to calling `getopt`.
1132  */
1133 string arraySep = "";
1134 
1135 private enum autoIncrementChar = '+';
1136 
1137 private struct configuration
1138 {
1139     import std.bitmanip : bitfields;
1140     mixin(bitfields!(
1141                 bool, "caseSensitive",  1,
1142                 bool, "bundling", 1,
1143                 bool, "passThrough", 1,
1144                 bool, "stopOnFirstNonOption", 1,
1145                 bool, "keepEndOfOptions", 1,
1146                 bool, "required", 1,
1147                 ubyte, "", 2));
1148 }
1149 
1150 private bool optMatch(string arg, scope string optPattern, ref string value,
1151     configuration cfg) @safe
1152 {
1153     import std.algorithm.iteration : splitter;
1154     import std.string : indexOf;
1155     import std.uni : icmp;
1156     //writeln("optMatch:\n  ", arg, "\n  ", optPattern, "\n  ", value);
1157     //scope(success) writeln("optMatch result: ", value);
1158     if (arg.length < 2 || arg[0] != optionChar) return false;
1159     // yank the leading '-'
1160     arg = arg[1 .. $];
1161     immutable isLong = arg.length > 1 && arg[0] == optionChar;
1162     //writeln("isLong: ", isLong);
1163     // yank the second '-' if present
1164     if (isLong) arg = arg[1 .. $];
1165     immutable eqPos = indexOf(arg, assignChar);
1166     if (isLong && eqPos >= 0)
1167     {
1168         // argument looks like --opt=value
1169         value = arg[eqPos + 1 .. $];
1170         arg = arg[0 .. eqPos];
1171     }
1172     else
1173     {
1174         if (!isLong && eqPos == 1)
1175         {
1176             // argument looks like -o=value
1177             value = arg[2 .. $];
1178             arg = arg[0 .. 1];
1179         }
1180         else
1181         if (!isLong && !cfg.bundling)
1182         {
1183             // argument looks like -ovalue and there's no bundling
1184             value = arg[1 .. $];
1185             arg = arg[0 .. 1];
1186         }
1187         else
1188         {
1189             // argument looks like --opt, or -oxyz with bundling
1190             value = null;
1191         }
1192     }
1193     //writeln("Arg: ", arg, " pattern: ", optPattern, " value: ", value);
1194     // Split the option
1195     foreach (v; splitter(optPattern, "|"))
1196     {
1197         //writeln("Trying variant: ", v, " against ", arg);
1198         if (arg == v || (!cfg.caseSensitive && icmp(arg, v) == 0))
1199             return true;
1200         if (cfg.bundling && !isLong && v.length == 1
1201                 && indexOf(arg, v) >= 0)
1202         {
1203             //writeln("success");
1204             return true;
1205         }
1206     }
1207     return false;
1208 }
1209 
1210 private void setConfig(ref configuration cfg, config option) @safe pure nothrow @nogc
1211 {
1212     final switch (option)
1213     {
1214     case config.caseSensitive: cfg.caseSensitive = true; break;
1215     case config.caseInsensitive: cfg.caseSensitive = false; break;
1216     case config.bundling: cfg.bundling = true; break;
1217     case config.noBundling: cfg.bundling = false; break;
1218     case config.passThrough: cfg.passThrough = true; break;
1219     case config.noPassThrough: cfg.passThrough = false; break;
1220     case config.required: cfg.required = true; break;
1221     case config.stopOnFirstNonOption:
1222         cfg.stopOnFirstNonOption = true; break;
1223     case config.keepEndOfOptions:
1224         cfg.keepEndOfOptions = true; break;
1225     }
1226 }
1227 
1228 @safe unittest
1229 {
1230     import std.conv;
1231     import std.math.operations : isClose;
1232 
1233     uint paranoid = 2;
1234     string[] args = ["program.name", "--paranoid", "--paranoid", "--paranoid"];
1235     getopt(args, "paranoid+", &paranoid);
1236     assert(paranoid == 5, to!(string)(paranoid));
1237 
1238     enum Color { no, yes }
1239     Color color;
1240     args = ["program.name", "--color=yes",];
1241     getopt(args, "color", &color);
1242     assert(color, to!(string)(color));
1243 
1244     color = Color.no;
1245     args = ["program.name", "--color", "yes",];
1246     getopt(args, "color", &color);
1247     assert(color, to!(string)(color));
1248 
1249     string data = "file.dat";
1250     int length = 24;
1251     bool verbose = false;
1252     args = ["program.name", "--length=5", "--file", "dat.file", "--verbose"];
1253     getopt(
1254         args,
1255         "length",  &length,
1256         "file",    &data,
1257         "verbose", &verbose);
1258     assert(args.length == 1);
1259     assert(data == "dat.file");
1260     assert(length == 5);
1261     assert(verbose);
1262 
1263     //
1264     string[] outputFiles;
1265     args = ["program.name", "--output=myfile.txt", "--output", "yourfile.txt"];
1266     getopt(args, "output", &outputFiles);
1267     assert(outputFiles.length == 2
1268            && outputFiles[0] == "myfile.txt" && outputFiles[1] == "yourfile.txt");
1269 
1270     outputFiles = [];
1271     arraySep = ",";
1272     args = ["program.name", "--output", "myfile.txt,yourfile.txt"];
1273     getopt(args, "output", &outputFiles);
1274     assert(outputFiles.length == 2
1275            && outputFiles[0] == "myfile.txt" && outputFiles[1] == "yourfile.txt");
1276     arraySep = "";
1277 
1278     foreach (testArgs;
1279         [["program.name", "--tune=alpha=0.5", "--tune", "beta=0.6"],
1280          ["program.name", "--tune=alpha=0.5,beta=0.6"],
1281          ["program.name", "--tune", "alpha=0.5,beta=0.6"]])
1282     {
1283         arraySep = ",";
1284         double[string] tuningParms;
1285         getopt(testArgs, "tune", &tuningParms);
1286         assert(testArgs.length == 1);
1287         assert(tuningParms.length == 2);
1288         assert(isClose(tuningParms["alpha"], 0.5));
1289         assert(isClose(tuningParms["beta"], 0.6));
1290         arraySep = "";
1291     }
1292 
1293     uint verbosityLevel = 1;
1294     void myHandler(string option)
1295     {
1296         if (option == "quiet")
1297         {
1298             verbosityLevel = 0;
1299         }
1300         else
1301         {
1302             assert(option == "verbose");
1303             verbosityLevel = 2;
1304         }
1305     }
1306     args = ["program.name", "--quiet"];
1307     getopt(args, "verbose", &myHandler, "quiet", &myHandler);
1308     assert(verbosityLevel == 0);
1309     args = ["program.name", "--verbose"];
1310     getopt(args, "verbose", &myHandler, "quiet", &myHandler);
1311     assert(verbosityLevel == 2);
1312 
1313     verbosityLevel = 1;
1314     void myHandler2(string option, string value)
1315     {
1316         assert(option == "verbose");
1317         verbosityLevel = 2;
1318     }
1319     args = ["program.name", "--verbose", "2"];
1320     getopt(args, "verbose", &myHandler2);
1321     assert(verbosityLevel == 2);
1322 
1323     verbosityLevel = 1;
1324     void myHandler3()
1325     {
1326         verbosityLevel = 2;
1327     }
1328     args = ["program.name", "--verbose"];
1329     getopt(args, "verbose", &myHandler3);
1330     assert(verbosityLevel == 2);
1331 
1332     bool foo, bar;
1333     args = ["program.name", "--foo", "--bAr"];
1334     getopt(args,
1335         std.getopt.config.caseSensitive,
1336         std.getopt.config.passThrough,
1337         "foo", &foo,
1338         "bar", &bar);
1339     assert(args[1] == "--bAr");
1340 
1341     // test stopOnFirstNonOption
1342 
1343     args = ["program.name", "--foo", "nonoption", "--bar"];
1344     foo = bar = false;
1345     getopt(args,
1346         std.getopt.config.stopOnFirstNonOption,
1347         "foo", &foo,
1348         "bar", &bar);
1349     assert(foo && !bar && args[1] == "nonoption" && args[2] == "--bar");
1350 
1351     args = ["program.name", "--foo", "nonoption", "--zab"];
1352     foo = bar = false;
1353     getopt(args,
1354         std.getopt.config.stopOnFirstNonOption,
1355         "foo", &foo,
1356         "bar", &bar);
1357     assert(foo && !bar && args[1] == "nonoption" && args[2] == "--zab");
1358 
1359     args = ["program.name", "--fb1", "--fb2=true", "--tb1=false"];
1360     bool fb1, fb2;
1361     bool tb1 = true;
1362     getopt(args, "fb1", &fb1, "fb2", &fb2, "tb1", &tb1);
1363     assert(fb1 && fb2 && !tb1);
1364 
1365     // test keepEndOfOptions
1366 
1367     args = ["program.name", "--foo", "nonoption", "--bar", "--", "--baz"];
1368     getopt(args,
1369         std.getopt.config.keepEndOfOptions,
1370         "foo", &foo,
1371         "bar", &bar);
1372     assert(args == ["program.name", "nonoption", "--", "--baz"]);
1373 
1374     // Ensure old behavior without the keepEndOfOptions
1375 
1376     args = ["program.name", "--foo", "nonoption", "--bar", "--", "--baz"];
1377     getopt(args,
1378         "foo", &foo,
1379         "bar", &bar);
1380     assert(args == ["program.name", "nonoption", "--baz"]);
1381 
1382     // test function callbacks
1383 
1384     static class MyEx : Exception
1385     {
1386         this() { super(""); }
1387         this(string option) { this(); this.option = option; }
1388         this(string option, string value) { this(option); this.value = value; }
1389 
1390         string option;
1391         string value;
1392     }
1393 
1394     static void myStaticHandler1() { throw new MyEx(); }
1395     args = ["program.name", "--verbose"];
1396     try { getopt(args, "verbose", &myStaticHandler1); assert(0); }
1397     catch (MyEx ex) { assert(ex.option is null && ex.value is null); }
1398 
1399     static void myStaticHandler2(string option) { throw new MyEx(option); }
1400     args = ["program.name", "--verbose"];
1401     try { getopt(args, "verbose", &myStaticHandler2); assert(0); }
1402     catch (MyEx ex) { assert(ex.option == "verbose" && ex.value is null); }
1403 
1404     static void myStaticHandler3(string option, string value) { throw new MyEx(option, value); }
1405     args = ["program.name", "--verbose", "2"];
1406     try { getopt(args, "verbose", &myStaticHandler3); assert(0); }
1407     catch (MyEx ex) { assert(ex.option == "verbose" && ex.value == "2"); }
1408 
1409     // check that GetOptException is thrown if the value is missing
1410     args = ["program.name", "--verbose"];
1411     try { getopt(args, "verbose", &myStaticHandler3); assert(0); }
1412     catch (GetOptException e) {}
1413     catch (Exception e) { assert(0); }
1414 }
1415 
1416 @safe unittest // @safe std.getopt.config option use
1417 {
1418     long x = 0;
1419     string[] args = ["program", "--inc-x", "--inc-x"];
1420     getopt(args,
1421            std.getopt.config.caseSensitive,
1422            "inc-x", "Add one to x", delegate void() { x++; });
1423     assert(x == 2);
1424 }
1425 
1426 // https://issues.dlang.org/show_bug.cgi?id=2142
1427 @safe unittest
1428 {
1429     bool f_linenum, f_filename;
1430     string[] args = [ "", "-nl" ];
1431     getopt
1432         (
1433             args,
1434             std.getopt.config.bundling,
1435             //std.getopt.config.caseSensitive,
1436             "linenum|l", &f_linenum,
1437             "filename|n", &f_filename
1438         );
1439     assert(f_linenum);
1440     assert(f_filename);
1441 }
1442 
1443 // https://issues.dlang.org/show_bug.cgi?id=6887
1444 @safe unittest
1445 {
1446     string[] p;
1447     string[] args = ["", "-pa"];
1448     getopt(args, "p", &p);
1449     assert(p.length == 1);
1450     assert(p[0] == "a");
1451 }
1452 
1453 // https://issues.dlang.org/show_bug.cgi?id=6888
1454 @safe unittest
1455 {
1456     int[string] foo;
1457     auto args = ["", "-t", "a=1"];
1458     getopt(args, "t", &foo);
1459     assert(foo == ["a":1]);
1460 }
1461 
1462 // https://issues.dlang.org/show_bug.cgi?id=9583
1463 @safe unittest
1464 {
1465     int opt;
1466     auto args = ["prog", "--opt=123", "--", "--a", "--b", "--c"];
1467     getopt(args, "opt", &opt);
1468     assert(args == ["prog", "--a", "--b", "--c"]);
1469 }
1470 
1471 @safe unittest
1472 {
1473     string foo, bar;
1474     auto args = ["prog", "-thello", "-dbar=baz"];
1475     getopt(args, "t", &foo, "d", &bar);
1476     assert(foo == "hello");
1477     assert(bar == "bar=baz");
1478 
1479     // From https://issues.dlang.org/show_bug.cgi?id=5762
1480     string a;
1481     args = ["prog", "-a-0x12"];
1482     getopt(args, config.bundling, "a|addr", &a);
1483     assert(a == "-0x12", a);
1484     args = ["prog", "--addr=-0x12"];
1485     getopt(args, config.bundling, "a|addr", &a);
1486     assert(a == "-0x12");
1487 
1488     // From https://issues.dlang.org/show_bug.cgi?id=11764
1489     args = ["main", "-test"];
1490     bool opt;
1491     args.getopt(config.passThrough, "opt", &opt);
1492     assert(args == ["main", "-test"]);
1493 
1494     // From https://issues.dlang.org/show_bug.cgi?id=15220
1495     args = ["main", "-o=str"];
1496     string o;
1497     args.getopt("o", &o);
1498     assert(o == "str");
1499 
1500     args = ["main", "-o=str"];
1501     o = null;
1502     args.getopt(config.bundling, "o", &o);
1503     assert(o == "str");
1504 }
1505 
1506 // https://issues.dlang.org/show_bug.cgi?id=5228
1507 @safe unittest
1508 {
1509     import std.conv;
1510     import std.exception;
1511 
1512     auto args = ["prog", "--foo=bar"];
1513     int abc;
1514     assertThrown!GetOptException(getopt(args, "abc", &abc));
1515 
1516     args = ["prog", "--abc=string"];
1517     assertThrown!ConvException(getopt(args, "abc", &abc));
1518 }
1519 
1520 // https://issues.dlang.org/show_bug.cgi?id=7693
1521 @safe unittest
1522 {
1523     import std.exception;
1524 
1525     enum Foo {
1526         bar,
1527         baz
1528     }
1529 
1530     auto args = ["prog", "--foo=barZZZ"];
1531     Foo foo;
1532     assertThrown(getopt(args, "foo", &foo));
1533     args = ["prog", "--foo=bar"];
1534     assertNotThrown(getopt(args, "foo", &foo));
1535     args = ["prog", "--foo", "barZZZ"];
1536     assertThrown(getopt(args, "foo", &foo));
1537     args = ["prog", "--foo", "baz"];
1538     assertNotThrown(getopt(args, "foo", &foo));
1539 }
1540 
1541 // Same as https://issues.dlang.org/show_bug.cgi?id=7693 only for `bool`
1542 @safe unittest
1543 {
1544     import std.exception;
1545 
1546     auto args = ["prog", "--foo=truefoobar"];
1547     bool foo;
1548     assertThrown(getopt(args, "foo", &foo));
1549     args = ["prog", "--foo"];
1550     getopt(args, "foo", &foo);
1551     assert(foo);
1552 }
1553 
1554 @safe unittest
1555 {
1556     bool foo;
1557     auto args = ["prog", "--foo"];
1558     getopt(args, "foo", &foo);
1559     assert(foo);
1560 }
1561 
1562 @safe unittest
1563 {
1564     bool foo;
1565     bool bar;
1566     auto args = ["prog", "--foo", "-b"];
1567     getopt(args, config.caseInsensitive,"foo|f", "Some foo", &foo,
1568         config.caseSensitive, "bar|b", "Some bar", &bar);
1569     assert(foo);
1570     assert(bar);
1571 }
1572 
1573 @safe unittest
1574 {
1575     bool foo;
1576     bool bar;
1577     auto args = ["prog", "-b", "--foo", "-z"];
1578     getopt(args, config.caseInsensitive, config.required, "foo|f", "Some foo",
1579         &foo, config.caseSensitive, "bar|b", "Some bar", &bar,
1580         config.passThrough);
1581     assert(foo);
1582     assert(bar);
1583 }
1584 
1585 @safe unittest
1586 {
1587     import std.exception;
1588 
1589     bool foo;
1590     bool bar;
1591     auto args = ["prog", "-b", "-z"];
1592     assertThrown(getopt(args, config.caseInsensitive, config.required, "foo|f",
1593         "Some foo", &foo, config.caseSensitive, "bar|b", "Some bar", &bar,
1594         config.passThrough));
1595 }
1596 
1597 @safe unittest
1598 {
1599     import std.exception;
1600 
1601     bool foo;
1602     bool bar;
1603     auto args = ["prog", "--foo", "-z"];
1604     assertNotThrown(getopt(args, config.caseInsensitive, config.required,
1605         "foo|f", "Some foo", &foo, config.caseSensitive, "bar|b", "Some bar",
1606         &bar, config.passThrough));
1607     assert(foo);
1608     assert(!bar);
1609 }
1610 
1611 @safe unittest
1612 {
1613     bool foo;
1614     auto args = ["prog", "-f"];
1615     auto r = getopt(args, config.caseInsensitive, "help|f", "Some foo", &foo);
1616     assert(foo);
1617     assert(!r.helpWanted);
1618 }
1619 
1620 @safe unittest // implicit help option without config.passThrough
1621 {
1622     string[] args = ["program", "--help"];
1623     auto r = getopt(args);
1624     assert(r.helpWanted);
1625 }
1626 
1627 // std.getopt: implicit help option breaks the next argument
1628 // https://issues.dlang.org/show_bug.cgi?id=13316
1629 @safe unittest
1630 {
1631     string[] args = ["program", "--help", "--", "something"];
1632     getopt(args);
1633     assert(args == ["program", "something"]);
1634 
1635     args = ["program", "--help", "--"];
1636     getopt(args);
1637     assert(args == ["program"]);
1638 
1639     bool b;
1640     args = ["program", "--help", "nonoption", "--option"];
1641     getopt(args, config.stopOnFirstNonOption, "option", &b);
1642     assert(args == ["program", "nonoption", "--option"]);
1643 }
1644 
1645 // std.getopt: endOfOptions broken when it doesn't look like an option
1646 // https://issues.dlang.org/show_bug.cgi?id=13317
1647 @safe unittest
1648 {
1649     auto endOfOptionsBackup = endOfOptions;
1650     scope(exit) endOfOptions = endOfOptionsBackup;
1651     endOfOptions = "endofoptions";
1652     string[] args = ["program", "endofoptions", "--option"];
1653     bool b = false;
1654     getopt(args, "option", &b);
1655     assert(!b);
1656     assert(args == ["program", "--option"]);
1657 }
1658 
1659 // make std.getopt ready for DIP 1000
1660 // https://issues.dlang.org/show_bug.cgi?id=20480
1661 @safe unittest
1662 {
1663     string[] args = ["test", "--foo", "42", "--bar", "BAR"];
1664     int foo;
1665     string bar;
1666     getopt(args, "foo", &foo, "bar", "bar help", &bar);
1667     assert(foo == 42);
1668     assert(bar == "BAR");
1669 }
1670 
1671 /** This function prints the passed `Option`s and text in an aligned manner on `stdout`.
1672 
1673 The passed text will be printed first, followed by a newline, then the short
1674 and long version of every option will be printed. The short and long version
1675 will be aligned to the longest option of every `Option` passed. If the option
1676 is required, then "Required:" will be printed after the long version of the
1677 `Option`. If a help message is present it will be printed next. The format is
1678 illustrated by this code:
1679 
1680 ------------
1681 foreach (it; opt)
1682 {
1683     writefln("%*s %*s%s%s", lengthOfLongestShortOption, it.optShort,
1684         lengthOfLongestLongOption, it.optLong,
1685         it.required ? " Required: " : " ", it.help);
1686 }
1687 ------------
1688 
1689 Params:
1690     text = The text to printed at the beginning of the help output.
1691     opt = The `Option` extracted from the `getopt` parameter.
1692 */
1693 void defaultGetoptPrinter(string text, Option[] opt) @safe
1694 {
1695     import std.stdio : stdout;
1696     // stdout global __gshared is trusted with a locked text writer
1697     auto w = (() @trusted => stdout.lockingTextWriter())();
1698 
1699     defaultGetoptFormatter(w, text, opt);
1700 }
1701 
1702 /** This function writes the passed text and `Option` into an output range
1703 in the manner described in the documentation of function
1704 `defaultGetoptPrinter`, unless the style option is used.
1705 
1706 Params:
1707     output = The output range used to write the help information.
1708     text = The text to print at the beginning of the help output.
1709     opt = The `Option` extracted from the `getopt` parameter.
1710     style = The manner in which to display the output of each `Option.`
1711 */
1712 void defaultGetoptFormatter(Output)(Output output, string text, Option[] opt, string style = "%*s %*s%*s%s\n")
1713 {
1714     import std.algorithm.comparison : min, max;
1715     import std.format.write : formattedWrite;
1716 
1717     output.formattedWrite("%s\n", text);
1718 
1719     size_t ls, ll;
1720     bool hasRequired = false;
1721     foreach (it; opt)
1722     {
1723         ls = max(ls, it.optShort.length);
1724         ll = max(ll, it.optLong.length);
1725 
1726         hasRequired = hasRequired || it.required;
1727     }
1728 
1729     string re = " Required: ";
1730 
1731     foreach (it; opt)
1732     {
1733         output.formattedWrite(style, ls, it.optShort, ll, it.optLong,
1734             hasRequired ? re.length : 1, it.required ? re : " ", it.help);
1735     }
1736 }
1737 
1738 @safe unittest
1739 {
1740     import std.conv;
1741 
1742     import std.array;
1743     import std.string;
1744     bool a;
1745     auto args = ["prog", "--foo"];
1746     auto t = getopt(args, "foo|f", "Help", &a);
1747     string s;
1748     auto app = appender!string();
1749     defaultGetoptFormatter(app, "Some Text", t.options);
1750 
1751     string helpMsg = app.data;
1752     //writeln(helpMsg);
1753     assert(helpMsg.length);
1754     assert(helpMsg.count("\n") == 3, to!string(helpMsg.count("\n")) ~ " "
1755         ~ helpMsg);
1756     assert(helpMsg.indexOf("--foo") != -1);
1757     assert(helpMsg.indexOf("-f") != -1);
1758     assert(helpMsg.indexOf("-h") != -1);
1759     assert(helpMsg.indexOf("--help") != -1);
1760     assert(helpMsg.indexOf("Help") != -1);
1761 
1762     string wanted = "Some Text\n-f  --foo Help\n-h --help This help "
1763         ~ "information.\n";
1764     assert(wanted == helpMsg);
1765 }
1766 
1767 @safe unittest
1768 {
1769     import std.array ;
1770     import std.conv;
1771     import std.string;
1772     bool a;
1773     auto args = ["prog", "--foo"];
1774     auto t = getopt(args, config.required, "foo|f", "Help", &a);
1775     string s;
1776     auto app = appender!string();
1777     defaultGetoptFormatter(app, "Some Text", t.options);
1778 
1779     string helpMsg = app.data;
1780     //writeln(helpMsg);
1781     assert(helpMsg.length);
1782     assert(helpMsg.count("\n") == 3, to!string(helpMsg.count("\n")) ~ " "
1783         ~ helpMsg);
1784     assert(helpMsg.indexOf("Required:") != -1);
1785     assert(helpMsg.indexOf("--foo") != -1);
1786     assert(helpMsg.indexOf("-f") != -1);
1787     assert(helpMsg.indexOf("-h") != -1);
1788     assert(helpMsg.indexOf("--help") != -1);
1789     assert(helpMsg.indexOf("Help") != -1);
1790 
1791     string wanted = "Some Text\n-f  --foo Required: Help\n-h --help "
1792         ~ "          This help information.\n";
1793     assert(wanted == helpMsg, helpMsg ~ wanted);
1794 }
1795 
1796 // https://issues.dlang.org/show_bug.cgi?id=14724
1797 @safe unittest
1798 {
1799     bool a;
1800     auto args = ["prog", "--help"];
1801     GetoptResult rslt;
1802     try
1803     {
1804         rslt = getopt(args, config.required, "foo|f", "bool a", &a);
1805     }
1806     catch (Exception e)
1807     {
1808         enum errorMsg = "If the request for help was passed required options" ~
1809                 "must not be set.";
1810         assert(false, errorMsg);
1811     }
1812 
1813     assert(rslt.helpWanted);
1814 }
1815 
1816 // throw on duplicate options
1817 @system unittest
1818 {
1819     import core.exception : AssertError;
1820     import std.exception : assertNotThrown, assertThrown;
1821     auto args = ["prog", "--abc", "1"];
1822     int abc, def;
1823     assertThrown!AssertError(getopt(args, "abc", &abc, "abc", &abc));
1824     assertThrown!AssertError(getopt(args, "abc|a", &abc, "def|a", &def));
1825     assertNotThrown!AssertError(getopt(args, "abc", &abc, "def", &def));
1826 
1827     // https://issues.dlang.org/show_bug.cgi?id=23940
1828     assertThrown!AssertError(getopt(args,
1829             "abc", &abc, "ABC", &def));
1830     assertThrown!AssertError(getopt(args, config.caseInsensitive,
1831             "abc", &abc, "ABC", &def));
1832     assertNotThrown!AssertError(getopt(args, config.caseSensitive,
1833             "abc", &abc, "ABC", &def));
1834 }
1835 
1836 // https://issues.dlang.org/show_bug.cgi?id=17327 repeated option use
1837 @safe unittest
1838 {
1839     long num = 0;
1840 
1841     string[] args = ["program", "--num", "3"];
1842     getopt(args, "n|num", &num);
1843     assert(num == 3);
1844 
1845     args = ["program", "--num", "3", "--num", "5"];
1846     getopt(args, "n|num", &num);
1847     assert(num == 5);
1848 
1849     args = ["program", "--n", "3", "--num", "5", "-n", "-7"];
1850     getopt(args, "n|num", &num);
1851     assert(num == -7);
1852 
1853     void add1() { num++; }
1854     void add2(string option) { num += 2; }
1855     void addN(string option, string value)
1856     {
1857         import std.conv : to;
1858         num += value.to!long;
1859     }
1860 
1861     num = 0;
1862     args = ["program", "--add1", "--add2", "--add1", "--add", "5", "--add2", "--add", "10"];
1863     getopt(args,
1864            "add1", "Add 1 to num", &add1,
1865            "add2", "Add 2 to num", &add2,
1866            "add", "Add N to num", &addN,);
1867     assert(num == 21);
1868 
1869     bool flag = false;
1870     args = ["program", "--flag"];
1871     getopt(args, "f|flag", "Boolean", &flag);
1872     assert(flag);
1873 
1874     flag = false;
1875     args = ["program", "-f", "-f"];
1876     getopt(args, "f|flag", "Boolean", &flag);
1877     assert(flag);
1878 
1879     flag = false;
1880     args = ["program", "--flag=true", "--flag=false"];
1881     getopt(args, "f|flag", "Boolean", &flag);
1882     assert(!flag);
1883 
1884     flag = false;
1885     args = ["program", "--flag=true", "--flag=false", "-f"];
1886     getopt(args, "f|flag", "Boolean", &flag);
1887     assert(flag);
1888 }
1889 
1890 @system unittest  // Delegates as callbacks
1891 {
1892     alias TwoArgOptionHandler = void delegate(string option, string value) @safe;
1893 
1894     TwoArgOptionHandler makeAddNHandler(ref long dest)
1895     {
1896         void addN(ref long dest, string n)
1897         {
1898             import std.conv : to;
1899             dest += n.to!long;
1900         }
1901 
1902         return (option, value) => addN(dest, value);
1903     }
1904 
1905     long x = 0;
1906     long y = 0;
1907 
1908     string[] args =
1909         ["program", "--x-plus-1", "--x-plus-1", "--x-plus-5", "--x-plus-n", "10",
1910          "--y-plus-n", "25", "--y-plus-7", "--y-plus-n", "15", "--y-plus-3"];
1911 
1912     getopt(args,
1913            "x-plus-1", "Add one to x", delegate void() { x += 1; },
1914            "x-plus-5", "Add five to x", delegate void(string option) { x += 5; },
1915            "x-plus-n", "Add NUM to x", makeAddNHandler(x),
1916            "y-plus-7", "Add seven to y", delegate void() { y += 7; },
1917            "y-plus-3", "Add three to y", delegate void(string option) { y += 3; },
1918            "y-plus-n", "Add NUM to x", makeAddNHandler(y),);
1919 
1920     assert(x == 17);
1921     assert(y == 50);
1922 }
1923 
1924 // Hyphens at the start of option values;
1925 // https://issues.dlang.org/show_bug.cgi?id=17650
1926 @safe unittest
1927 {
1928     auto args = ["program", "-m", "-5", "-n", "-50", "-c", "-", "-f", "-"];
1929 
1930     int m;
1931     int n;
1932     char c;
1933     string f;
1934 
1935     getopt(args,
1936            "m|mm", "integer", &m,
1937            "n|nn", "integer", &n,
1938            "c|cc", "character", &c,
1939            "f|file", "filename or hyphen for stdin", &f);
1940 
1941     assert(m == -5);
1942     assert(n == -50);
1943     assert(c == '-');
1944     assert(f == "-");
1945 }
1946 
1947 // Hyphen at the option value;
1948 // https://issues.dlang.org/show_bug.cgi?id=22394
1949 @safe unittest
1950 {
1951     auto args = ["program", "-"];
1952 
1953     getopt(args);
1954 
1955     assert(args == ["program", "-"]);
1956 }
1957 
1958 @safe unittest
1959 {
1960     import std.conv;
1961 
1962     import std.array;
1963     import std.string;
1964     bool a;
1965     auto args = ["prog", "--foo"];
1966     auto t = getopt(args, "foo|f", "Help", &a);
1967     string s;
1968     auto app = appender!string();
1969     defaultGetoptFormatter(app, "Some Text", t.options, "\t\t%*s %*s%*s\n%s\n");
1970 
1971     string helpMsg = app.data;
1972     //writeln(helpMsg);
1973     assert(helpMsg.length);
1974     assert(helpMsg.count("\n") == 5, to!string(helpMsg.count("\n")) ~ " "
1975         ~ helpMsg);
1976     assert(helpMsg.indexOf("--foo") != -1);
1977     assert(helpMsg.indexOf("-f") != -1);
1978     assert(helpMsg.indexOf("-h") != -1);
1979     assert(helpMsg.indexOf("--help") != -1);
1980     assert(helpMsg.indexOf("Help") != -1);
1981 
1982     string wanted = "Some Text\n\t\t-f  --foo \nHelp\n\t\t-h --help \nThis help "
1983         ~ "information.\n";
1984     assert(wanted == helpMsg);
1985 }
1986 
1987 
1988 @safe unittest
1989 {
1990     import std.conv : ConvException;
1991     import std.string : indexOf;
1992 
1993     enum UniqueIdentifer {
1994         a,
1995         b
1996     }
1997 
1998     UniqueIdentifer a;
1999 
2000     auto args = ["prog", "--foo", "HELLO"];
2001     try
2002     {
2003         auto t = getopt(args, "foo|f", &a);
2004         assert(false, "Must not be reached, as \"HELLO\" cannot be converted"
2005             ~ " to enum A.");
2006     }
2007     catch (ConvException e)
2008     {
2009         string str = () @trusted { return e.toString(); }();
2010         assert(str.indexOf("HELLO") != -1);
2011         assert(str.indexOf("UniqueIdentifer") != -1);
2012         assert(str.indexOf("foo") != -1);
2013     }
2014 }
2015 
2016 @safe unittest
2017 {
2018     import std.conv : ConvException;
2019     import std.string : indexOf;
2020 
2021     int a;
2022 
2023     auto args = ["prog", "--foo", "HELLO"];
2024     try
2025     {
2026         auto t = getopt(args, "foo|f", &a);
2027         assert(false, "Must not be reached, as \"HELLO\" cannot be converted"
2028             ~ " to an int");
2029     }
2030     catch (ConvException e)
2031     {
2032         string str = () @trusted { return e.toString(); }();
2033         assert(str.indexOf("HELLO") != -1);
2034         assert(str.indexOf("int") != -1);
2035         assert(str.indexOf("foo") != -1);
2036     }
2037 
2038     args = ["prog", "--foo", "1337"];
2039     getopt(args, "foo|f", &a);
2040     assert(a == 1337);
2041 }