The OpenD Programming Language

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 @safe pure unittest
614 {
615     alias P = void*;
616     alias S = string;
617     alias A = char;
618     alias C = config;
619     alias F = void function();
620 
621     static assert(optionValidator!(S,P) == "");
622     static assert(optionValidator!(S,F) == "");
623     static assert(optionValidator!(A,P) == "");
624     static assert(optionValidator!(A,F) == "");
625 
626     static assert(optionValidator!(C,S,P) == "");
627     static assert(optionValidator!(C,S,F) == "");
628     static assert(optionValidator!(C,A,P) == "");
629     static assert(optionValidator!(C,A,F) == "");
630 
631     static assert(optionValidator!(C,S,S,P) == "");
632     static assert(optionValidator!(C,S,S,F) == "");
633     static assert(optionValidator!(C,A,S,P) == "");
634     static assert(optionValidator!(C,A,S,F) == "");
635 
636     static assert(optionValidator!(C,S,S,P) == "");
637     static assert(optionValidator!(C,S,S,P,C,S,F) == "");
638     static assert(optionValidator!(C,S,P,C,S,S,F) == "");
639 
640     static assert(optionValidator!(C,A,P,A,S,F) == "");
641     static assert(optionValidator!(C,A,P,C,A,S,F) == "");
642 
643     static assert(optionValidator!(P,S,S) != "");
644     static assert(optionValidator!(P,P,S) != "");
645     static assert(optionValidator!(P,F,S,P) != "");
646     static assert(optionValidator!(C,C,S) != "");
647     static assert(optionValidator!(S,S,P,S,S,P,S) != "");
648     static assert(optionValidator!(S,S,P,P) != "");
649     static assert(optionValidator!(S,S,S,P) != "");
650 
651     static assert(optionValidator!(C,A,S,P,C,A,F) == "");
652     static assert(optionValidator!(C,A,P,C,A,S,F) == "");
653 }
654 
655 // https://issues.dlang.org/show_bug.cgi?id=15914
656 @safe unittest
657 {
658     import std.exception : assertThrown;
659     bool opt;
660     string[] args = ["program", "-a"];
661     getopt(args, config.passThrough, 'a', &opt);
662     assert(opt);
663     opt = false;
664     args = ["program", "-a"];
665     getopt(args, 'a', &opt);
666     assert(opt);
667     opt = false;
668     args = ["program", "-a"];
669     getopt(args, 'a', "help string", &opt);
670     assert(opt);
671     opt = false;
672     args = ["program", "-a"];
673     getopt(args, config.caseSensitive, 'a', "help string", &opt);
674     assert(opt);
675 
676     assertThrown(getopt(args, "", "forgot to put a string", &opt));
677 }
678 
679 private void getoptImpl(T...)(ref string[] args, ref configuration cfg,
680     ref GetoptResult rslt, ref GetOptException excep,
681     void[][string] visitedLongOpts, void[][string] visitedShortOpts, T opts)
682 {
683     enum validationMessage = optionValidator!T;
684     static assert(validationMessage == "", validationMessage);
685 
686     import std.algorithm.mutation : remove;
687     import std.conv : to;
688     import std.uni : toLower;
689     static if (opts.length)
690     {
691         static if (is(typeof(opts[0]) : config))
692         {
693             // it's a configuration flag, act on it
694             setConfig(cfg, opts[0]);
695             return getoptImpl(args, cfg, rslt, excep, visitedLongOpts,
696                 visitedShortOpts, opts[1 .. $]);
697         }
698         else
699         {
700             // it's an option string
701             auto option = to!string(opts[0]);
702             if (option.length == 0)
703             {
704                 excep = new GetOptException("An option name may not be an empty string", excep);
705                 return;
706             }
707             Option optionHelp = splitAndGet(option);
708             optionHelp.required = cfg.required;
709 
710             if (optionHelp.optLong.length)
711             {
712                 auto name = optionHelp.optLong;
713                 if (!cfg.caseSensitive)
714                     name = name.toLower();
715                 assert(name !in visitedLongOpts,
716                     "Long option " ~ optionHelp.optLong ~ " is multiply defined");
717 
718                 visitedLongOpts[optionHelp.optLong] = [];
719             }
720 
721             if (optionHelp.optShort.length)
722             {
723                 auto name = optionHelp.optShort;
724                 if (!cfg.caseSensitive)
725                     name = name.toLower();
726                 assert(name !in visitedShortOpts,
727                     "Short option " ~ optionHelp.optShort
728                     ~ " is multiply defined");
729 
730                 visitedShortOpts[optionHelp.optShort] = [];
731             }
732 
733             static if (is(typeof(opts[1]) : string))
734             {
735                 alias receiver = opts[2];
736                 optionHelp.help = opts[1];
737                 immutable lowSliceIdx = 3;
738             }
739             else
740             {
741                 alias receiver = opts[1];
742                 immutable lowSliceIdx = 2;
743             }
744 
745             rslt.options ~= optionHelp;
746 
747             bool incremental;
748             // Handle options of the form --blah+
749             if (option.length && option[$ - 1] == autoIncrementChar)
750             {
751                 option = option[0 .. $ - 1];
752                 incremental = true;
753             }
754 
755             bool optWasHandled = handleOption(option, receiver, args, cfg, incremental);
756 
757             if (cfg.required && !optWasHandled)
758             {
759                 excep = new GetOptException("Required option "
760                     ~ option ~ " was not supplied", excep);
761             }
762             cfg.required = false;
763 
764             getoptImpl(args, cfg, rslt, excep, visitedLongOpts,
765                 visitedShortOpts, opts[lowSliceIdx .. $]);
766         }
767     }
768     else
769     {
770         // no more options to look for, potentially some arguments left
771         for (size_t i = 1; i < args.length;)
772         {
773             auto a = args[i];
774             if (endOfOptions.length && a == endOfOptions)
775             {
776                 // Consume the "--" if keepEndOfOptions is not specified
777                 if (!cfg.keepEndOfOptions)
778                     args = args.remove(i);
779                 break;
780             }
781             if (a.length < 2 || a[0] != optionChar)
782             {
783                 // not an option
784                 if (cfg.stopOnFirstNonOption) break;
785                 ++i;
786                 continue;
787             }
788             if (a == "--help" || a == "-h")
789             {
790                 rslt.helpWanted = true;
791                 args = args.remove(i);
792                 continue;
793             }
794             if (!cfg.passThrough)
795             {
796                 throw new GetOptException("Unrecognized option "~a, excep);
797             }
798             ++i;
799         }
800 
801         Option helpOpt;
802         helpOpt.optShort = "-h";
803         helpOpt.optLong = "--help";
804         helpOpt.help = "This help information.";
805         rslt.options ~= helpOpt;
806     }
807 }
808 
809 private bool handleOption(R)(string option, R receiver, ref string[] args,
810     ref configuration cfg, bool incremental)
811 {
812     import std.algorithm.iteration : map, splitter;
813     import std.ascii : isAlpha;
814     import std.conv : text, to;
815     // Scan arguments looking for a match for this option
816     bool ret = false;
817     for (size_t i = 1; i < args.length; )
818     {
819         auto a = args[i];
820         if (endOfOptions.length && a == endOfOptions) break;
821         if (cfg.stopOnFirstNonOption && (!a.length || a[0] != optionChar))
822         {
823             // first non-option is end of options
824             break;
825         }
826         // Unbundle bundled arguments if necessary
827         if (cfg.bundling && a.length > 2 && a[0] == optionChar &&
828                 a[1] != optionChar)
829         {
830             string[] expanded;
831             foreach (j, dchar c; a[1 .. $])
832             {
833                 // If the character is not alpha, stop right there. This allows
834                 // e.g. -j100 to work as "pass argument 100 to option -j".
835                 if (!isAlpha(c))
836                 {
837                     if (c == '=')
838                         j++;
839                     expanded ~= a[j + 1 .. $];
840                     break;
841                 }
842                 expanded ~= text(optionChar, c);
843             }
844             args = args[0 .. i] ~ expanded ~ args[i + 1 .. $];
845             continue;
846         }
847 
848         string val;
849         if (!optMatch(a, option, val, cfg))
850         {
851             ++i;
852             continue;
853         }
854 
855         ret = true;
856 
857         // found it
858         // from here on, commit to eat args[i]
859         // (and potentially args[i + 1] too, but that comes later)
860         args = args[0 .. i] ~ args[i + 1 .. $];
861 
862         static if (is(typeof(*receiver) == bool))
863         {
864             if (val.length)
865             {
866                 // parse '--b=true/false'
867                 *receiver = to!(typeof(*receiver))(val);
868             }
869             else
870             {
871                 // no argument means set it to true
872                 *receiver = true;
873             }
874         }
875         else
876         {
877             import std.exception : enforce;
878             // non-boolean option, which might include an argument
879             enum isCallbackWithLessThanTwoParameters =
880                 (is(typeof(receiver) == delegate) || is(typeof(*receiver) == function)) &&
881                 !is(typeof(receiver("", "")));
882             if (!isCallbackWithLessThanTwoParameters && !(val.length) && !incremental)
883             {
884                 // Eat the next argument too.  Check to make sure there's one
885                 // to be eaten first, though.
886                 enforce!GetOptException(i < args.length,
887                     "Missing value for argument " ~ a ~ ".");
888                 val = args[i];
889                 args = args[0 .. i] ~ args[i + 1 .. $];
890             }
891             static if (is(typeof(*receiver) == enum))
892             {
893                 *receiver = to!(typeof(*receiver))(val);
894             }
895             else static if (is(typeof(*receiver) : real))
896             {
897                 // numeric receiver
898                 if (incremental) ++*receiver;
899                 else *receiver = to!(typeof(*receiver))(val);
900             }
901             else static if (is(typeof(*receiver) == string))
902             {
903                 // string receiver
904                 *receiver = to!(typeof(*receiver))(val);
905             }
906             else static if (is(typeof(receiver) == delegate) ||
907                             is(typeof(*receiver) == function))
908             {
909                 static if (is(typeof(receiver("", "")) : void))
910                 {
911                     // option with argument
912                     receiver(option, val);
913                 }
914                 else static if (is(typeof(receiver("")) : void))
915                 {
916                     alias RType = typeof(receiver(""));
917                     static assert(is(RType : void),
918                             "Invalid receiver return type " ~ RType.stringof);
919                     // boolean-style receiver
920                     receiver(option);
921                 }
922                 else
923                 {
924                     alias RType = typeof(receiver());
925                     static assert(is(RType : void),
926                             "Invalid receiver return type " ~ RType.stringof);
927                     // boolean-style receiver without argument
928                     receiver();
929                 }
930             }
931             else static if (isArray!(typeof(*receiver)))
932             {
933                 // array receiver
934                 import std.range : ElementEncodingType;
935                 alias E = ElementEncodingType!(typeof(*receiver));
936 
937                 if (arraySep == "")
938                 {
939                     *receiver ~= to!E(val);
940                 }
941                 else
942                 {
943                     foreach (elem; val.splitter(arraySep).map!(a => to!E(a))())
944                         *receiver ~= elem;
945                 }
946             }
947             else static if (isAssociativeArray!(typeof(*receiver)))
948             {
949                 // hash receiver
950                 alias K = typeof(receiver.keys[0]);
951                 alias V = typeof(receiver.values[0]);
952 
953                 import std.range : only;
954                 import std.string : indexOf;
955                 import std.typecons : Tuple, tuple;
956 
957                 static Tuple!(K, V) getter(string input)
958                 {
959                     auto j = indexOf(input, assignChar);
960                     enforce!GetOptException(j != -1, "Could not find '"
961                         ~ to!string(assignChar) ~ "' in argument '" ~ input ~ "'.");
962                     auto key = input[0 .. j];
963                     auto value = input[j + 1 .. $];
964                     return tuple(to!K(key), to!V(value));
965                 }
966 
967                 static void setHash(Range)(R receiver, Range range)
968                 {
969                     foreach (k, v; range.map!getter)
970                         (*receiver)[k] = v;
971                 }
972 
973                 if (arraySep == "")
974                     setHash(receiver, val.only);
975                 else
976                     setHash(receiver, val.splitter(arraySep));
977             }
978             else
979                 static assert(false, "getopt does not know how to handle the type " ~ typeof(receiver).stringof);
980         }
981     }
982 
983     return ret;
984 }
985 
986 // https://issues.dlang.org/show_bug.cgi?id=17574
987 @safe unittest
988 {
989     import std.algorithm.searching : startsWith;
990 
991     try
992     {
993         string[string] mapping;
994         immutable as = arraySep;
995         arraySep = ",";
996         scope (exit)
997             arraySep = as;
998         string[] args = ["testProgram", "-m", "a=b,c=\"d,e,f\""];
999         args.getopt("m", &mapping);
1000         assert(false, "Exception not thrown");
1001     }
1002     catch (GetOptException goe)
1003         assert(goe.msg.startsWith("Could not find"));
1004 }
1005 
1006 // https://issues.dlang.org/show_bug.cgi?id=5316 - arrays with arraySep
1007 @safe unittest
1008 {
1009     import std.conv;
1010 
1011     arraySep = ",";
1012     scope (exit) arraySep = "";
1013 
1014     string[] names;
1015     auto args = ["program.name", "-nfoo,bar,baz"];
1016     getopt(args, "name|n", &names);
1017     assert(names == ["foo", "bar", "baz"], to!string(names));
1018 
1019     names = names.init;
1020     args = ["program.name", "-n", "foo,bar,baz"];
1021     getopt(args, "name|n", &names);
1022     assert(names == ["foo", "bar", "baz"], to!string(names));
1023 
1024     names = names.init;
1025     args = ["program.name", "--name=foo,bar,baz"];
1026     getopt(args, "name|n", &names);
1027     assert(names == ["foo", "bar", "baz"], to!string(names));
1028 
1029     names = names.init;
1030     args = ["program.name", "--name", "foo,bar,baz"];
1031     getopt(args, "name|n", &names);
1032     assert(names == ["foo", "bar", "baz"], to!string(names));
1033 }
1034 
1035 // https://issues.dlang.org/show_bug.cgi?id=5316 - associative arrays with arraySep
1036 @safe unittest
1037 {
1038     import std.conv;
1039 
1040     arraySep = ",";
1041     scope (exit) arraySep = "";
1042 
1043     int[string] values;
1044     values = values.init;
1045     auto args = ["program.name", "-vfoo=0,bar=1,baz=2"];
1046     getopt(args, "values|v", &values);
1047     assert(values == ["foo":0, "bar":1, "baz":2], to!string(values));
1048 
1049     values = values.init;
1050     args = ["program.name", "-v", "foo=0,bar=1,baz=2"];
1051     getopt(args, "values|v", &values);
1052     assert(values == ["foo":0, "bar":1, "baz":2], to!string(values));
1053 
1054     values = values.init;
1055     args = ["program.name", "--values=foo=0,bar=1,baz=2"];
1056     getopt(args, "values|t", &values);
1057     assert(values == ["foo":0, "bar":1, "baz":2], to!string(values));
1058 
1059     values = values.init;
1060     args = ["program.name", "--values", "foo=0,bar=1,baz=2"];
1061     getopt(args, "values|v", &values);
1062     assert(values == ["foo":0, "bar":1, "baz":2], to!string(values));
1063 }
1064 
1065 version (LDC) version (Windows) version = LDC_Windows;
1066 
1067 version (LDC_Windows)
1068 {
1069     // cannot access TLS globals directly across DLL boundaries
1070 
1071     private
1072     {
1073         dchar _optionChar = '-';
1074         string _endOfOptions = "--";
1075         dchar _assignChar = '=';
1076         string _arraySep = "";
1077     }
1078 
1079     @property @safe @nogc nothrow
1080     pragma(inline, false) // could be safely inlined in the binary containing Phobos only
1081     {
1082         ref dchar optionChar() { return _optionChar; }
1083         ref string endOfOptions() { return _endOfOptions; }
1084         ref dchar assignChar() { return _assignChar; }
1085         ref string arraySep() { return _arraySep; }
1086     }
1087 }
1088 else
1089 {
1090     /**
1091        The option character (default '-').
1092 
1093        Defaults to '-' but it can be assigned to prior to calling `getopt`.
1094      */
1095     dchar optionChar = '-';
1096 
1097     /**
1098        The string that conventionally marks the end of all options (default '--').
1099 
1100        Defaults to "--" but can be assigned to prior to calling `getopt`. Assigning an
1101        empty string to `endOfOptions` effectively disables it.
1102      */
1103     string endOfOptions = "--";
1104 
1105     /**
1106        The assignment character used in options with parameters (default '=').
1107 
1108        Defaults to '=' but can be assigned to prior to calling `getopt`.
1109      */
1110     dchar assignChar = '=';
1111 
1112     /**
1113        When set to "", parameters to array and associative array receivers are
1114        treated as an individual argument. That is, only one argument is appended or
1115        inserted per appearance of the option switch. If `arraySep` is set to
1116        something else, then each parameter is first split by the separator, and the
1117        individual pieces are treated as arguments to the same option.
1118 
1119        Defaults to "" but can be assigned to prior to calling `getopt`.
1120      */
1121     string arraySep = "";
1122 } // !LDC_Windows
1123 
1124 private enum autoIncrementChar = '+';
1125 
1126 private struct configuration
1127 {
1128     import std.bitmanip : bitfields;
1129     mixin(bitfields!(
1130                 bool, "caseSensitive",  1,
1131                 bool, "bundling", 1,
1132                 bool, "passThrough", 1,
1133                 bool, "stopOnFirstNonOption", 1,
1134                 bool, "keepEndOfOptions", 1,
1135                 bool, "required", 1,
1136                 ubyte, "", 2));
1137 }
1138 
1139 private bool optMatch(string arg, scope string optPattern, ref string value,
1140     configuration cfg) @safe
1141 {
1142     import std.algorithm.iteration : splitter;
1143     import std.string : indexOf;
1144     import std.uni : icmp;
1145     //writeln("optMatch:\n  ", arg, "\n  ", optPattern, "\n  ", value);
1146     //scope(success) writeln("optMatch result: ", value);
1147     if (arg.length < 2 || arg[0] != optionChar) return false;
1148     // yank the leading '-'
1149     arg = arg[1 .. $];
1150     immutable isLong = arg.length > 1 && arg[0] == optionChar;
1151     //writeln("isLong: ", isLong);
1152     // yank the second '-' if present
1153     if (isLong) arg = arg[1 .. $];
1154     immutable eqPos = indexOf(arg, assignChar);
1155     if (isLong && eqPos >= 0)
1156     {
1157         // argument looks like --opt=value
1158         value = arg[eqPos + 1 .. $];
1159         arg = arg[0 .. eqPos];
1160     }
1161     else
1162     {
1163         if (!isLong && eqPos == 1)
1164         {
1165             // argument looks like -o=value
1166             value = arg[2 .. $];
1167             arg = arg[0 .. 1];
1168         }
1169         else
1170         if (!isLong && !cfg.bundling)
1171         {
1172             // argument looks like -ovalue and there's no bundling
1173             value = arg[1 .. $];
1174             arg = arg[0 .. 1];
1175         }
1176         else
1177         {
1178             // argument looks like --opt, or -oxyz with bundling
1179             value = null;
1180         }
1181     }
1182     //writeln("Arg: ", arg, " pattern: ", optPattern, " value: ", value);
1183     // Split the option
1184     foreach (v; splitter(optPattern, "|"))
1185     {
1186         //writeln("Trying variant: ", v, " against ", arg);
1187         if (arg == v || (!cfg.caseSensitive && icmp(arg, v) == 0))
1188             return true;
1189         if (cfg.bundling && !isLong && v.length == 1
1190                 && indexOf(arg, v) >= 0)
1191         {
1192             //writeln("success");
1193             return true;
1194         }
1195     }
1196     return false;
1197 }
1198 
1199 private void setConfig(ref configuration cfg, config option) @safe pure nothrow @nogc
1200 {
1201     final switch (option)
1202     {
1203     case config.caseSensitive: cfg.caseSensitive = true; break;
1204     case config.caseInsensitive: cfg.caseSensitive = false; break;
1205     case config.bundling: cfg.bundling = true; break;
1206     case config.noBundling: cfg.bundling = false; break;
1207     case config.passThrough: cfg.passThrough = true; break;
1208     case config.noPassThrough: cfg.passThrough = false; break;
1209     case config.required: cfg.required = true; break;
1210     case config.stopOnFirstNonOption:
1211         cfg.stopOnFirstNonOption = true; break;
1212     case config.keepEndOfOptions:
1213         cfg.keepEndOfOptions = true; break;
1214     }
1215 }
1216 
1217 @safe unittest
1218 {
1219     import std.conv;
1220     import std.math.operations : isClose;
1221 
1222     uint paranoid = 2;
1223     string[] args = ["program.name", "--paranoid", "--paranoid", "--paranoid"];
1224     getopt(args, "paranoid+", &paranoid);
1225     assert(paranoid == 5, to!(string)(paranoid));
1226 
1227     enum Color { no, yes }
1228     Color color;
1229     args = ["program.name", "--color=yes",];
1230     getopt(args, "color", &color);
1231     assert(color, to!(string)(color));
1232 
1233     color = Color.no;
1234     args = ["program.name", "--color", "yes",];
1235     getopt(args, "color", &color);
1236     assert(color, to!(string)(color));
1237 
1238     string data = "file.dat";
1239     int length = 24;
1240     bool verbose = false;
1241     args = ["program.name", "--length=5", "--file", "dat.file", "--verbose"];
1242     getopt(
1243         args,
1244         "length",  &length,
1245         "file",    &data,
1246         "verbose", &verbose);
1247     assert(args.length == 1);
1248     assert(data == "dat.file");
1249     assert(length == 5);
1250     assert(verbose);
1251 
1252     //
1253     string[] outputFiles;
1254     args = ["program.name", "--output=myfile.txt", "--output", "yourfile.txt"];
1255     getopt(args, "output", &outputFiles);
1256     assert(outputFiles.length == 2
1257            && outputFiles[0] == "myfile.txt" && outputFiles[1] == "yourfile.txt");
1258 
1259     outputFiles = [];
1260     arraySep = ",";
1261     args = ["program.name", "--output", "myfile.txt,yourfile.txt"];
1262     getopt(args, "output", &outputFiles);
1263     assert(outputFiles.length == 2
1264            && outputFiles[0] == "myfile.txt" && outputFiles[1] == "yourfile.txt");
1265     arraySep = "";
1266 
1267     foreach (testArgs;
1268         [["program.name", "--tune=alpha=0.5", "--tune", "beta=0.6"],
1269          ["program.name", "--tune=alpha=0.5,beta=0.6"],
1270          ["program.name", "--tune", "alpha=0.5,beta=0.6"]])
1271     {
1272         arraySep = ",";
1273         double[string] tuningParms;
1274         getopt(testArgs, "tune", &tuningParms);
1275         assert(testArgs.length == 1);
1276         assert(tuningParms.length == 2);
1277         assert(isClose(tuningParms["alpha"], 0.5));
1278         assert(isClose(tuningParms["beta"], 0.6));
1279         arraySep = "";
1280     }
1281 
1282     uint verbosityLevel = 1;
1283     void myHandler(string option)
1284     {
1285         if (option == "quiet")
1286         {
1287             verbosityLevel = 0;
1288         }
1289         else
1290         {
1291             assert(option == "verbose");
1292             verbosityLevel = 2;
1293         }
1294     }
1295     args = ["program.name", "--quiet"];
1296     getopt(args, "verbose", &myHandler, "quiet", &myHandler);
1297     assert(verbosityLevel == 0);
1298     args = ["program.name", "--verbose"];
1299     getopt(args, "verbose", &myHandler, "quiet", &myHandler);
1300     assert(verbosityLevel == 2);
1301 
1302     verbosityLevel = 1;
1303     void myHandler2(string option, string value)
1304     {
1305         assert(option == "verbose");
1306         verbosityLevel = 2;
1307     }
1308     args = ["program.name", "--verbose", "2"];
1309     getopt(args, "verbose", &myHandler2);
1310     assert(verbosityLevel == 2);
1311 
1312     verbosityLevel = 1;
1313     void myHandler3()
1314     {
1315         verbosityLevel = 2;
1316     }
1317     args = ["program.name", "--verbose"];
1318     getopt(args, "verbose", &myHandler3);
1319     assert(verbosityLevel == 2);
1320 
1321     bool foo, bar;
1322     args = ["program.name", "--foo", "--bAr"];
1323     getopt(args,
1324         std.getopt.config.caseSensitive,
1325         std.getopt.config.passThrough,
1326         "foo", &foo,
1327         "bar", &bar);
1328     assert(args[1] == "--bAr");
1329 
1330     // test stopOnFirstNonOption
1331 
1332     args = ["program.name", "--foo", "nonoption", "--bar"];
1333     foo = bar = false;
1334     getopt(args,
1335         std.getopt.config.stopOnFirstNonOption,
1336         "foo", &foo,
1337         "bar", &bar);
1338     assert(foo && !bar && args[1] == "nonoption" && args[2] == "--bar");
1339 
1340     args = ["program.name", "--foo", "nonoption", "--zab"];
1341     foo = bar = false;
1342     getopt(args,
1343         std.getopt.config.stopOnFirstNonOption,
1344         "foo", &foo,
1345         "bar", &bar);
1346     assert(foo && !bar && args[1] == "nonoption" && args[2] == "--zab");
1347 
1348     args = ["program.name", "--fb1", "--fb2=true", "--tb1=false"];
1349     bool fb1, fb2;
1350     bool tb1 = true;
1351     getopt(args, "fb1", &fb1, "fb2", &fb2, "tb1", &tb1);
1352     assert(fb1 && fb2 && !tb1);
1353 
1354     // test keepEndOfOptions
1355 
1356     args = ["program.name", "--foo", "nonoption", "--bar", "--", "--baz"];
1357     getopt(args,
1358         std.getopt.config.keepEndOfOptions,
1359         "foo", &foo,
1360         "bar", &bar);
1361     assert(args == ["program.name", "nonoption", "--", "--baz"]);
1362 
1363     // Ensure old behavior without the keepEndOfOptions
1364 
1365     args = ["program.name", "--foo", "nonoption", "--bar", "--", "--baz"];
1366     getopt(args,
1367         "foo", &foo,
1368         "bar", &bar);
1369     assert(args == ["program.name", "nonoption", "--baz"]);
1370 
1371     // test function callbacks
1372 
1373     static class MyEx : Exception
1374     {
1375         this() { super(""); }
1376         this(string option) { this(); this.option = option; }
1377         this(string option, string value) { this(option); this.value = value; }
1378 
1379         string option;
1380         string value;
1381     }
1382 
1383     static void myStaticHandler1() { throw new MyEx(); }
1384     args = ["program.name", "--verbose"];
1385     try { getopt(args, "verbose", &myStaticHandler1); assert(0); }
1386     catch (MyEx ex) { assert(ex.option is null && ex.value is null); }
1387 
1388     static void myStaticHandler2(string option) { throw new MyEx(option); }
1389     args = ["program.name", "--verbose"];
1390     try { getopt(args, "verbose", &myStaticHandler2); assert(0); }
1391     catch (MyEx ex) { assert(ex.option == "verbose" && ex.value is null); }
1392 
1393     static void myStaticHandler3(string option, string value) { throw new MyEx(option, value); }
1394     args = ["program.name", "--verbose", "2"];
1395     try { getopt(args, "verbose", &myStaticHandler3); assert(0); }
1396     catch (MyEx ex) { assert(ex.option == "verbose" && ex.value == "2"); }
1397 
1398     // check that GetOptException is thrown if the value is missing
1399     args = ["program.name", "--verbose"];
1400     try { getopt(args, "verbose", &myStaticHandler3); assert(0); }
1401     catch (GetOptException e) {}
1402     catch (Exception e) { assert(0); }
1403 }
1404 
1405 @safe unittest // @safe std.getopt.config option use
1406 {
1407     long x = 0;
1408     string[] args = ["program", "--inc-x", "--inc-x"];
1409     getopt(args,
1410            std.getopt.config.caseSensitive,
1411            "inc-x", "Add one to x", delegate void() { x++; });
1412     assert(x == 2);
1413 }
1414 
1415 // https://issues.dlang.org/show_bug.cgi?id=2142
1416 @safe unittest
1417 {
1418     bool f_linenum, f_filename;
1419     string[] args = [ "", "-nl" ];
1420     getopt
1421         (
1422             args,
1423             std.getopt.config.bundling,
1424             //std.getopt.config.caseSensitive,
1425             "linenum|l", &f_linenum,
1426             "filename|n", &f_filename
1427         );
1428     assert(f_linenum);
1429     assert(f_filename);
1430 }
1431 
1432 // https://issues.dlang.org/show_bug.cgi?id=6887
1433 @safe unittest
1434 {
1435     string[] p;
1436     string[] args = ["", "-pa"];
1437     getopt(args, "p", &p);
1438     assert(p.length == 1);
1439     assert(p[0] == "a");
1440 }
1441 
1442 // https://issues.dlang.org/show_bug.cgi?id=6888
1443 @safe unittest
1444 {
1445     int[string] foo;
1446     auto args = ["", "-t", "a=1"];
1447     getopt(args, "t", &foo);
1448     assert(foo == ["a":1]);
1449 }
1450 
1451 // https://issues.dlang.org/show_bug.cgi?id=9583
1452 @safe unittest
1453 {
1454     int opt;
1455     auto args = ["prog", "--opt=123", "--", "--a", "--b", "--c"];
1456     getopt(args, "opt", &opt);
1457     assert(args == ["prog", "--a", "--b", "--c"]);
1458 }
1459 
1460 @safe unittest
1461 {
1462     string foo, bar;
1463     auto args = ["prog", "-thello", "-dbar=baz"];
1464     getopt(args, "t", &foo, "d", &bar);
1465     assert(foo == "hello");
1466     assert(bar == "bar=baz");
1467 
1468     // From https://issues.dlang.org/show_bug.cgi?id=5762
1469     string a;
1470     args = ["prog", "-a-0x12"];
1471     getopt(args, config.bundling, "a|addr", &a);
1472     assert(a == "-0x12", a);
1473     args = ["prog", "--addr=-0x12"];
1474     getopt(args, config.bundling, "a|addr", &a);
1475     assert(a == "-0x12");
1476 
1477     // From https://issues.dlang.org/show_bug.cgi?id=11764
1478     args = ["main", "-test"];
1479     bool opt;
1480     args.getopt(config.passThrough, "opt", &opt);
1481     assert(args == ["main", "-test"]);
1482 
1483     // From https://issues.dlang.org/show_bug.cgi?id=15220
1484     args = ["main", "-o=str"];
1485     string o;
1486     args.getopt("o", &o);
1487     assert(o == "str");
1488 
1489     args = ["main", "-o=str"];
1490     o = null;
1491     args.getopt(config.bundling, "o", &o);
1492     assert(o == "str");
1493 }
1494 
1495 // https://issues.dlang.org/show_bug.cgi?id=5228
1496 @safe unittest
1497 {
1498     import std.conv;
1499     import std.exception;
1500 
1501     auto args = ["prog", "--foo=bar"];
1502     int abc;
1503     assertThrown!GetOptException(getopt(args, "abc", &abc));
1504 
1505     args = ["prog", "--abc=string"];
1506     assertThrown!ConvException(getopt(args, "abc", &abc));
1507 }
1508 
1509 // https://issues.dlang.org/show_bug.cgi?id=7693
1510 @safe unittest
1511 {
1512     import std.exception;
1513 
1514     enum Foo {
1515         bar,
1516         baz
1517     }
1518 
1519     auto args = ["prog", "--foo=barZZZ"];
1520     Foo foo;
1521     assertThrown(getopt(args, "foo", &foo));
1522     args = ["prog", "--foo=bar"];
1523     assertNotThrown(getopt(args, "foo", &foo));
1524     args = ["prog", "--foo", "barZZZ"];
1525     assertThrown(getopt(args, "foo", &foo));
1526     args = ["prog", "--foo", "baz"];
1527     assertNotThrown(getopt(args, "foo", &foo));
1528 }
1529 
1530 // Same as https://issues.dlang.org/show_bug.cgi?id=7693 only for `bool`
1531 @safe unittest
1532 {
1533     import std.exception;
1534 
1535     auto args = ["prog", "--foo=truefoobar"];
1536     bool foo;
1537     assertThrown(getopt(args, "foo", &foo));
1538     args = ["prog", "--foo"];
1539     getopt(args, "foo", &foo);
1540     assert(foo);
1541 }
1542 
1543 @safe unittest
1544 {
1545     bool foo;
1546     auto args = ["prog", "--foo"];
1547     getopt(args, "foo", &foo);
1548     assert(foo);
1549 }
1550 
1551 @safe unittest
1552 {
1553     bool foo;
1554     bool bar;
1555     auto args = ["prog", "--foo", "-b"];
1556     getopt(args, config.caseInsensitive,"foo|f", "Some foo", &foo,
1557         config.caseSensitive, "bar|b", "Some bar", &bar);
1558     assert(foo);
1559     assert(bar);
1560 }
1561 
1562 @safe unittest
1563 {
1564     bool foo;
1565     bool bar;
1566     auto args = ["prog", "-b", "--foo", "-z"];
1567     getopt(args, config.caseInsensitive, config.required, "foo|f", "Some foo",
1568         &foo, config.caseSensitive, "bar|b", "Some bar", &bar,
1569         config.passThrough);
1570     assert(foo);
1571     assert(bar);
1572 }
1573 
1574 @safe unittest
1575 {
1576     import std.exception;
1577 
1578     bool foo;
1579     bool bar;
1580     auto args = ["prog", "-b", "-z"];
1581     assertThrown(getopt(args, config.caseInsensitive, config.required, "foo|f",
1582         "Some foo", &foo, config.caseSensitive, "bar|b", "Some bar", &bar,
1583         config.passThrough));
1584 }
1585 
1586 @safe unittest
1587 {
1588     import std.exception;
1589 
1590     bool foo;
1591     bool bar;
1592     auto args = ["prog", "--foo", "-z"];
1593     assertNotThrown(getopt(args, config.caseInsensitive, config.required,
1594         "foo|f", "Some foo", &foo, config.caseSensitive, "bar|b", "Some bar",
1595         &bar, config.passThrough));
1596     assert(foo);
1597     assert(!bar);
1598 }
1599 
1600 @safe unittest
1601 {
1602     bool foo;
1603     auto args = ["prog", "-f"];
1604     auto r = getopt(args, config.caseInsensitive, "help|f", "Some foo", &foo);
1605     assert(foo);
1606     assert(!r.helpWanted);
1607 }
1608 
1609 @safe unittest // implicit help option without config.passThrough
1610 {
1611     string[] args = ["program", "--help"];
1612     auto r = getopt(args);
1613     assert(r.helpWanted);
1614 }
1615 
1616 // std.getopt: implicit help option breaks the next argument
1617 // https://issues.dlang.org/show_bug.cgi?id=13316
1618 @safe unittest
1619 {
1620     string[] args = ["program", "--help", "--", "something"];
1621     getopt(args);
1622     assert(args == ["program", "something"]);
1623 
1624     args = ["program", "--help", "--"];
1625     getopt(args);
1626     assert(args == ["program"]);
1627 
1628     bool b;
1629     args = ["program", "--help", "nonoption", "--option"];
1630     getopt(args, config.stopOnFirstNonOption, "option", &b);
1631     assert(args == ["program", "nonoption", "--option"]);
1632 }
1633 
1634 // std.getopt: endOfOptions broken when it doesn't look like an option
1635 // https://issues.dlang.org/show_bug.cgi?id=13317
1636 @safe unittest
1637 {
1638     auto endOfOptionsBackup = endOfOptions;
1639     scope(exit) endOfOptions = endOfOptionsBackup;
1640     endOfOptions = "endofoptions";
1641     string[] args = ["program", "endofoptions", "--option"];
1642     bool b = false;
1643     getopt(args, "option", &b);
1644     assert(!b);
1645     assert(args == ["program", "--option"]);
1646 }
1647 
1648 // make std.getopt ready for DIP 1000
1649 // https://issues.dlang.org/show_bug.cgi?id=20480
1650 @safe unittest
1651 {
1652     string[] args = ["test", "--foo", "42", "--bar", "BAR"];
1653     int foo;
1654     string bar;
1655     getopt(args, "foo", &foo, "bar", "bar help", &bar);
1656     assert(foo == 42);
1657     assert(bar == "BAR");
1658 }
1659 
1660 /** This function prints the passed `Option`s and text in an aligned manner on `stdout`.
1661 
1662 The passed text will be printed first, followed by a newline, then the short
1663 and long version of every option will be printed. The short and long version
1664 will be aligned to the longest option of every `Option` passed. If the option
1665 is required, then "Required:" will be printed after the long version of the
1666 `Option`. If a help message is present it will be printed next. The format is
1667 illustrated by this code:
1668 
1669 ------------
1670 foreach (it; opt)
1671 {
1672     writefln("%*s %*s%s%s", lengthOfLongestShortOption, it.optShort,
1673         lengthOfLongestLongOption, it.optLong,
1674         it.required ? " Required: " : " ", it.help);
1675 }
1676 ------------
1677 
1678 Params:
1679     text = The text to printed at the beginning of the help output.
1680     opt = The `Option` extracted from the `getopt` parameter.
1681 */
1682 void defaultGetoptPrinter(string text, Option[] opt) @safe
1683 {
1684     import std.stdio : stdout;
1685     // stdout global __gshared is trusted with a locked text writer
1686     auto w = (() @trusted => stdout.lockingTextWriter())();
1687 
1688     defaultGetoptFormatter(w, text, opt);
1689 }
1690 
1691 /** This function writes the passed text and `Option` into an output range
1692 in the manner described in the documentation of function
1693 `defaultGetoptPrinter`, unless the style option is used.
1694 
1695 Params:
1696     output = The output range used to write the help information.
1697     text = The text to print at the beginning of the help output.
1698     opt = The `Option` extracted from the `getopt` parameter.
1699     style = The manner in which to display the output of each `Option.`
1700 */
1701 void defaultGetoptFormatter(Output)(Output output, string text, Option[] opt, string style = "%*s %*s%*s%s\n")
1702 {
1703     import std.algorithm.comparison : min, max;
1704     import std.format.write : formattedWrite;
1705 
1706     output.formattedWrite("%s\n", text);
1707 
1708     size_t ls, ll;
1709     bool hasRequired = false;
1710     foreach (it; opt)
1711     {
1712         ls = max(ls, it.optShort.length);
1713         ll = max(ll, it.optLong.length);
1714 
1715         hasRequired = hasRequired || it.required;
1716     }
1717 
1718     string re = " Required: ";
1719 
1720     foreach (it; opt)
1721     {
1722         output.formattedWrite(style, ls, it.optShort, ll, it.optLong,
1723             hasRequired ? re.length : 1, it.required ? re : " ", it.help);
1724     }
1725 }
1726 
1727 @safe unittest
1728 {
1729     import std.conv;
1730 
1731     import std.array;
1732     import std.string;
1733     bool a;
1734     auto args = ["prog", "--foo"];
1735     auto t = getopt(args, "foo|f", "Help", &a);
1736     string s;
1737     auto app = appender!string();
1738     defaultGetoptFormatter(app, "Some Text", t.options);
1739 
1740     string helpMsg = app.data;
1741     //writeln(helpMsg);
1742     assert(helpMsg.length);
1743     assert(helpMsg.count("\n") == 3, to!string(helpMsg.count("\n")) ~ " "
1744         ~ helpMsg);
1745     assert(helpMsg.indexOf("--foo") != -1);
1746     assert(helpMsg.indexOf("-f") != -1);
1747     assert(helpMsg.indexOf("-h") != -1);
1748     assert(helpMsg.indexOf("--help") != -1);
1749     assert(helpMsg.indexOf("Help") != -1);
1750 
1751     string wanted = "Some Text\n-f  --foo Help\n-h --help This help "
1752         ~ "information.\n";
1753     assert(wanted == helpMsg);
1754 }
1755 
1756 @safe unittest
1757 {
1758     import std.array ;
1759     import std.conv;
1760     import std.string;
1761     bool a;
1762     auto args = ["prog", "--foo"];
1763     auto t = getopt(args, config.required, "foo|f", "Help", &a);
1764     string s;
1765     auto app = appender!string();
1766     defaultGetoptFormatter(app, "Some Text", t.options);
1767 
1768     string helpMsg = app.data;
1769     //writeln(helpMsg);
1770     assert(helpMsg.length);
1771     assert(helpMsg.count("\n") == 3, to!string(helpMsg.count("\n")) ~ " "
1772         ~ helpMsg);
1773     assert(helpMsg.indexOf("Required:") != -1);
1774     assert(helpMsg.indexOf("--foo") != -1);
1775     assert(helpMsg.indexOf("-f") != -1);
1776     assert(helpMsg.indexOf("-h") != -1);
1777     assert(helpMsg.indexOf("--help") != -1);
1778     assert(helpMsg.indexOf("Help") != -1);
1779 
1780     string wanted = "Some Text\n-f  --foo Required: Help\n-h --help "
1781         ~ "          This help information.\n";
1782     assert(wanted == helpMsg, helpMsg ~ wanted);
1783 }
1784 
1785 // https://issues.dlang.org/show_bug.cgi?id=14724
1786 @safe unittest
1787 {
1788     bool a;
1789     auto args = ["prog", "--help"];
1790     GetoptResult rslt;
1791     try
1792     {
1793         rslt = getopt(args, config.required, "foo|f", "bool a", &a);
1794     }
1795     catch (Exception e)
1796     {
1797         enum errorMsg = "If the request for help was passed required options" ~
1798                 "must not be set.";
1799         assert(false, errorMsg);
1800     }
1801 
1802     assert(rslt.helpWanted);
1803 }
1804 
1805 // throw on duplicate options
1806 @system unittest
1807 {
1808     import core.exception : AssertError;
1809     import std.exception : assertNotThrown, assertThrown;
1810     auto args = ["prog", "--abc", "1"];
1811     int abc, def;
1812     assertThrown!AssertError(getopt(args, "abc", &abc, "abc", &abc));
1813     assertThrown!AssertError(getopt(args, "abc|a", &abc, "def|a", &def));
1814     assertNotThrown!AssertError(getopt(args, "abc", &abc, "def", &def));
1815 
1816     // https://issues.dlang.org/show_bug.cgi?id=23940
1817     assertThrown!AssertError(getopt(args,
1818             "abc", &abc, "ABC", &def));
1819     assertThrown!AssertError(getopt(args, config.caseInsensitive,
1820             "abc", &abc, "ABC", &def));
1821     assertNotThrown!AssertError(getopt(args, config.caseSensitive,
1822             "abc", &abc, "ABC", &def));
1823 }
1824 
1825 // https://issues.dlang.org/show_bug.cgi?id=17327 repeated option use
1826 @safe unittest
1827 {
1828     long num = 0;
1829 
1830     string[] args = ["program", "--num", "3"];
1831     getopt(args, "n|num", &num);
1832     assert(num == 3);
1833 
1834     args = ["program", "--num", "3", "--num", "5"];
1835     getopt(args, "n|num", &num);
1836     assert(num == 5);
1837 
1838     args = ["program", "--n", "3", "--num", "5", "-n", "-7"];
1839     getopt(args, "n|num", &num);
1840     assert(num == -7);
1841 
1842     void add1() { num++; }
1843     void add2(string option) { num += 2; }
1844     void addN(string option, string value)
1845     {
1846         import std.conv : to;
1847         num += value.to!long;
1848     }
1849 
1850     num = 0;
1851     args = ["program", "--add1", "--add2", "--add1", "--add", "5", "--add2", "--add", "10"];
1852     getopt(args,
1853            "add1", "Add 1 to num", &add1,
1854            "add2", "Add 2 to num", &add2,
1855            "add", "Add N to num", &addN,);
1856     assert(num == 21);
1857 
1858     bool flag = false;
1859     args = ["program", "--flag"];
1860     getopt(args, "f|flag", "Boolean", &flag);
1861     assert(flag);
1862 
1863     flag = false;
1864     args = ["program", "-f", "-f"];
1865     getopt(args, "f|flag", "Boolean", &flag);
1866     assert(flag);
1867 
1868     flag = false;
1869     args = ["program", "--flag=true", "--flag=false"];
1870     getopt(args, "f|flag", "Boolean", &flag);
1871     assert(!flag);
1872 
1873     flag = false;
1874     args = ["program", "--flag=true", "--flag=false", "-f"];
1875     getopt(args, "f|flag", "Boolean", &flag);
1876     assert(flag);
1877 }
1878 
1879 @system unittest  // Delegates as callbacks
1880 {
1881     alias TwoArgOptionHandler = void delegate(string option, string value) @safe;
1882 
1883     TwoArgOptionHandler makeAddNHandler(ref long dest)
1884     {
1885         void addN(ref long dest, string n)
1886         {
1887             import std.conv : to;
1888             dest += n.to!long;
1889         }
1890 
1891         return (option, value) => addN(dest, value);
1892     }
1893 
1894     long x = 0;
1895     long y = 0;
1896 
1897     string[] args =
1898         ["program", "--x-plus-1", "--x-plus-1", "--x-plus-5", "--x-plus-n", "10",
1899          "--y-plus-n", "25", "--y-plus-7", "--y-plus-n", "15", "--y-plus-3"];
1900 
1901     getopt(args,
1902            "x-plus-1", "Add one to x", delegate void() { x += 1; },
1903            "x-plus-5", "Add five to x", delegate void(string option) { x += 5; },
1904            "x-plus-n", "Add NUM to x", makeAddNHandler(x),
1905            "y-plus-7", "Add seven to y", delegate void() { y += 7; },
1906            "y-plus-3", "Add three to y", delegate void(string option) { y += 3; },
1907            "y-plus-n", "Add NUM to x", makeAddNHandler(y),);
1908 
1909     assert(x == 17);
1910     assert(y == 50);
1911 }
1912 
1913 // Hyphens at the start of option values;
1914 // https://issues.dlang.org/show_bug.cgi?id=17650
1915 @safe unittest
1916 {
1917     auto args = ["program", "-m", "-5", "-n", "-50", "-c", "-", "-f", "-"];
1918 
1919     int m;
1920     int n;
1921     char c;
1922     string f;
1923 
1924     getopt(args,
1925            "m|mm", "integer", &m,
1926            "n|nn", "integer", &n,
1927            "c|cc", "character", &c,
1928            "f|file", "filename or hyphen for stdin", &f);
1929 
1930     assert(m == -5);
1931     assert(n == -50);
1932     assert(c == '-');
1933     assert(f == "-");
1934 }
1935 
1936 // Hyphen at the option value;
1937 // https://issues.dlang.org/show_bug.cgi?id=22394
1938 @safe unittest
1939 {
1940     auto args = ["program", "-"];
1941 
1942     getopt(args);
1943 
1944     assert(args == ["program", "-"]);
1945 }
1946 
1947 @safe unittest
1948 {
1949     import std.conv;
1950 
1951     import std.array;
1952     import std.string;
1953     bool a;
1954     auto args = ["prog", "--foo"];
1955     auto t = getopt(args, "foo|f", "Help", &a);
1956     string s;
1957     auto app = appender!string();
1958     defaultGetoptFormatter(app, "Some Text", t.options, "\t\t%*s %*s%*s\n%s\n");
1959 
1960     string helpMsg = app.data;
1961     //writeln(helpMsg);
1962     assert(helpMsg.length);
1963     assert(helpMsg.count("\n") == 5, to!string(helpMsg.count("\n")) ~ " "
1964         ~ helpMsg);
1965     assert(helpMsg.indexOf("--foo") != -1);
1966     assert(helpMsg.indexOf("-f") != -1);
1967     assert(helpMsg.indexOf("-h") != -1);
1968     assert(helpMsg.indexOf("--help") != -1);
1969     assert(helpMsg.indexOf("Help") != -1);
1970 
1971     string wanted = "Some Text\n\t\t-f  --foo \nHelp\n\t\t-h --help \nThis help "
1972         ~ "information.\n";
1973     assert(wanted == helpMsg);
1974 }