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 /**
1066    The option character (default '-').
1067 
1068    Defaults to '-' but it can be assigned to prior to calling `getopt`.
1069  */
1070 dchar optionChar = '-';
1071 
1072 /**
1073    The string that conventionally marks the end of all options (default '--').
1074 
1075    Defaults to "--" but can be assigned to prior to calling `getopt`. Assigning an
1076    empty string to `endOfOptions` effectively disables it.
1077  */
1078 string endOfOptions = "--";
1079 
1080 /**
1081    The assignment character used in options with parameters (default '=').
1082 
1083    Defaults to '=' but can be assigned to prior to calling `getopt`.
1084  */
1085 dchar assignChar = '=';
1086 
1087 /**
1088    When set to "", parameters to array and associative array receivers are
1089    treated as an individual argument. That is, only one argument is appended or
1090    inserted per appearance of the option switch. If `arraySep` is set to
1091    something else, then each parameter is first split by the separator, and the
1092    individual pieces are treated as arguments to the same option.
1093 
1094    Defaults to "" but can be assigned to prior to calling `getopt`.
1095  */
1096 string arraySep = "";
1097 
1098 private enum autoIncrementChar = '+';
1099 
1100 private struct configuration
1101 {
1102     import std.bitmanip : bitfields;
1103     mixin(bitfields!(
1104                 bool, "caseSensitive",  1,
1105                 bool, "bundling", 1,
1106                 bool, "passThrough", 1,
1107                 bool, "stopOnFirstNonOption", 1,
1108                 bool, "keepEndOfOptions", 1,
1109                 bool, "required", 1,
1110                 ubyte, "", 2));
1111 }
1112 
1113 private bool optMatch(string arg, scope string optPattern, ref string value,
1114     configuration cfg) @safe
1115 {
1116     import std.algorithm.iteration : splitter;
1117     import std.string : indexOf;
1118     import std.uni : icmp;
1119     //writeln("optMatch:\n  ", arg, "\n  ", optPattern, "\n  ", value);
1120     //scope(success) writeln("optMatch result: ", value);
1121     if (arg.length < 2 || arg[0] != optionChar) return false;
1122     // yank the leading '-'
1123     arg = arg[1 .. $];
1124     immutable isLong = arg.length > 1 && arg[0] == optionChar;
1125     //writeln("isLong: ", isLong);
1126     // yank the second '-' if present
1127     if (isLong) arg = arg[1 .. $];
1128     immutable eqPos = indexOf(arg, assignChar);
1129     if (isLong && eqPos >= 0)
1130     {
1131         // argument looks like --opt=value
1132         value = arg[eqPos + 1 .. $];
1133         arg = arg[0 .. eqPos];
1134     }
1135     else
1136     {
1137         if (!isLong && eqPos == 1)
1138         {
1139             // argument looks like -o=value
1140             value = arg[2 .. $];
1141             arg = arg[0 .. 1];
1142         }
1143         else
1144         if (!isLong && !cfg.bundling)
1145         {
1146             // argument looks like -ovalue and there's no bundling
1147             value = arg[1 .. $];
1148             arg = arg[0 .. 1];
1149         }
1150         else
1151         {
1152             // argument looks like --opt, or -oxyz with bundling
1153             value = null;
1154         }
1155     }
1156     //writeln("Arg: ", arg, " pattern: ", optPattern, " value: ", value);
1157     // Split the option
1158     foreach (v; splitter(optPattern, "|"))
1159     {
1160         //writeln("Trying variant: ", v, " against ", arg);
1161         if (arg == v || (!cfg.caseSensitive && icmp(arg, v) == 0))
1162             return true;
1163         if (cfg.bundling && !isLong && v.length == 1
1164                 && indexOf(arg, v) >= 0)
1165         {
1166             //writeln("success");
1167             return true;
1168         }
1169     }
1170     return false;
1171 }
1172 
1173 private void setConfig(ref configuration cfg, config option) @safe pure nothrow @nogc
1174 {
1175     final switch (option)
1176     {
1177     case config.caseSensitive: cfg.caseSensitive = true; break;
1178     case config.caseInsensitive: cfg.caseSensitive = false; break;
1179     case config.bundling: cfg.bundling = true; break;
1180     case config.noBundling: cfg.bundling = false; break;
1181     case config.passThrough: cfg.passThrough = true; break;
1182     case config.noPassThrough: cfg.passThrough = false; break;
1183     case config.required: cfg.required = true; break;
1184     case config.stopOnFirstNonOption:
1185         cfg.stopOnFirstNonOption = true; break;
1186     case config.keepEndOfOptions:
1187         cfg.keepEndOfOptions = true; break;
1188     }
1189 }
1190 
1191 @safe unittest
1192 {
1193     import std.conv;
1194     import std.math.operations : isClose;
1195 
1196     uint paranoid = 2;
1197     string[] args = ["program.name", "--paranoid", "--paranoid", "--paranoid"];
1198     getopt(args, "paranoid+", &paranoid);
1199     assert(paranoid == 5, to!(string)(paranoid));
1200 
1201     enum Color { no, yes }
1202     Color color;
1203     args = ["program.name", "--color=yes",];
1204     getopt(args, "color", &color);
1205     assert(color, to!(string)(color));
1206 
1207     color = Color.no;
1208     args = ["program.name", "--color", "yes",];
1209     getopt(args, "color", &color);
1210     assert(color, to!(string)(color));
1211 
1212     string data = "file.dat";
1213     int length = 24;
1214     bool verbose = false;
1215     args = ["program.name", "--length=5", "--file", "dat.file", "--verbose"];
1216     getopt(
1217         args,
1218         "length",  &length,
1219         "file",    &data,
1220         "verbose", &verbose);
1221     assert(args.length == 1);
1222     assert(data == "dat.file");
1223     assert(length == 5);
1224     assert(verbose);
1225 
1226     //
1227     string[] outputFiles;
1228     args = ["program.name", "--output=myfile.txt", "--output", "yourfile.txt"];
1229     getopt(args, "output", &outputFiles);
1230     assert(outputFiles.length == 2
1231            && outputFiles[0] == "myfile.txt" && outputFiles[1] == "yourfile.txt");
1232 
1233     outputFiles = [];
1234     arraySep = ",";
1235     args = ["program.name", "--output", "myfile.txt,yourfile.txt"];
1236     getopt(args, "output", &outputFiles);
1237     assert(outputFiles.length == 2
1238            && outputFiles[0] == "myfile.txt" && outputFiles[1] == "yourfile.txt");
1239     arraySep = "";
1240 
1241     foreach (testArgs;
1242         [["program.name", "--tune=alpha=0.5", "--tune", "beta=0.6"],
1243          ["program.name", "--tune=alpha=0.5,beta=0.6"],
1244          ["program.name", "--tune", "alpha=0.5,beta=0.6"]])
1245     {
1246         arraySep = ",";
1247         double[string] tuningParms;
1248         getopt(testArgs, "tune", &tuningParms);
1249         assert(testArgs.length == 1);
1250         assert(tuningParms.length == 2);
1251         assert(isClose(tuningParms["alpha"], 0.5));
1252         assert(isClose(tuningParms["beta"], 0.6));
1253         arraySep = "";
1254     }
1255 
1256     uint verbosityLevel = 1;
1257     void myHandler(string option)
1258     {
1259         if (option == "quiet")
1260         {
1261             verbosityLevel = 0;
1262         }
1263         else
1264         {
1265             assert(option == "verbose");
1266             verbosityLevel = 2;
1267         }
1268     }
1269     args = ["program.name", "--quiet"];
1270     getopt(args, "verbose", &myHandler, "quiet", &myHandler);
1271     assert(verbosityLevel == 0);
1272     args = ["program.name", "--verbose"];
1273     getopt(args, "verbose", &myHandler, "quiet", &myHandler);
1274     assert(verbosityLevel == 2);
1275 
1276     verbosityLevel = 1;
1277     void myHandler2(string option, string value)
1278     {
1279         assert(option == "verbose");
1280         verbosityLevel = 2;
1281     }
1282     args = ["program.name", "--verbose", "2"];
1283     getopt(args, "verbose", &myHandler2);
1284     assert(verbosityLevel == 2);
1285 
1286     verbosityLevel = 1;
1287     void myHandler3()
1288     {
1289         verbosityLevel = 2;
1290     }
1291     args = ["program.name", "--verbose"];
1292     getopt(args, "verbose", &myHandler3);
1293     assert(verbosityLevel == 2);
1294 
1295     bool foo, bar;
1296     args = ["program.name", "--foo", "--bAr"];
1297     getopt(args,
1298         std.getopt.config.caseSensitive,
1299         std.getopt.config.passThrough,
1300         "foo", &foo,
1301         "bar", &bar);
1302     assert(args[1] == "--bAr");
1303 
1304     // test stopOnFirstNonOption
1305 
1306     args = ["program.name", "--foo", "nonoption", "--bar"];
1307     foo = bar = false;
1308     getopt(args,
1309         std.getopt.config.stopOnFirstNonOption,
1310         "foo", &foo,
1311         "bar", &bar);
1312     assert(foo && !bar && args[1] == "nonoption" && args[2] == "--bar");
1313 
1314     args = ["program.name", "--foo", "nonoption", "--zab"];
1315     foo = bar = false;
1316     getopt(args,
1317         std.getopt.config.stopOnFirstNonOption,
1318         "foo", &foo,
1319         "bar", &bar);
1320     assert(foo && !bar && args[1] == "nonoption" && args[2] == "--zab");
1321 
1322     args = ["program.name", "--fb1", "--fb2=true", "--tb1=false"];
1323     bool fb1, fb2;
1324     bool tb1 = true;
1325     getopt(args, "fb1", &fb1, "fb2", &fb2, "tb1", &tb1);
1326     assert(fb1 && fb2 && !tb1);
1327 
1328     // test keepEndOfOptions
1329 
1330     args = ["program.name", "--foo", "nonoption", "--bar", "--", "--baz"];
1331     getopt(args,
1332         std.getopt.config.keepEndOfOptions,
1333         "foo", &foo,
1334         "bar", &bar);
1335     assert(args == ["program.name", "nonoption", "--", "--baz"]);
1336 
1337     // Ensure old behavior without the keepEndOfOptions
1338 
1339     args = ["program.name", "--foo", "nonoption", "--bar", "--", "--baz"];
1340     getopt(args,
1341         "foo", &foo,
1342         "bar", &bar);
1343     assert(args == ["program.name", "nonoption", "--baz"]);
1344 
1345     // test function callbacks
1346 
1347     static class MyEx : Exception
1348     {
1349         this() { super(""); }
1350         this(string option) { this(); this.option = option; }
1351         this(string option, string value) { this(option); this.value = value; }
1352 
1353         string option;
1354         string value;
1355     }
1356 
1357     static void myStaticHandler1() { throw new MyEx(); }
1358     args = ["program.name", "--verbose"];
1359     try { getopt(args, "verbose", &myStaticHandler1); assert(0); }
1360     catch (MyEx ex) { assert(ex.option is null && ex.value is null); }
1361 
1362     static void myStaticHandler2(string option) { throw new MyEx(option); }
1363     args = ["program.name", "--verbose"];
1364     try { getopt(args, "verbose", &myStaticHandler2); assert(0); }
1365     catch (MyEx ex) { assert(ex.option == "verbose" && ex.value is null); }
1366 
1367     static void myStaticHandler3(string option, string value) { throw new MyEx(option, value); }
1368     args = ["program.name", "--verbose", "2"];
1369     try { getopt(args, "verbose", &myStaticHandler3); assert(0); }
1370     catch (MyEx ex) { assert(ex.option == "verbose" && ex.value == "2"); }
1371 
1372     // check that GetOptException is thrown if the value is missing
1373     args = ["program.name", "--verbose"];
1374     try { getopt(args, "verbose", &myStaticHandler3); assert(0); }
1375     catch (GetOptException e) {}
1376     catch (Exception e) { assert(0); }
1377 }
1378 
1379 @safe unittest // @safe std.getopt.config option use
1380 {
1381     long x = 0;
1382     string[] args = ["program", "--inc-x", "--inc-x"];
1383     getopt(args,
1384            std.getopt.config.caseSensitive,
1385            "inc-x", "Add one to x", delegate void() { x++; });
1386     assert(x == 2);
1387 }
1388 
1389 // https://issues.dlang.org/show_bug.cgi?id=2142
1390 @safe unittest
1391 {
1392     bool f_linenum, f_filename;
1393     string[] args = [ "", "-nl" ];
1394     getopt
1395         (
1396             args,
1397             std.getopt.config.bundling,
1398             //std.getopt.config.caseSensitive,
1399             "linenum|l", &f_linenum,
1400             "filename|n", &f_filename
1401         );
1402     assert(f_linenum);
1403     assert(f_filename);
1404 }
1405 
1406 // https://issues.dlang.org/show_bug.cgi?id=6887
1407 @safe unittest
1408 {
1409     string[] p;
1410     string[] args = ["", "-pa"];
1411     getopt(args, "p", &p);
1412     assert(p.length == 1);
1413     assert(p[0] == "a");
1414 }
1415 
1416 // https://issues.dlang.org/show_bug.cgi?id=6888
1417 @safe unittest
1418 {
1419     int[string] foo;
1420     auto args = ["", "-t", "a=1"];
1421     getopt(args, "t", &foo);
1422     assert(foo == ["a":1]);
1423 }
1424 
1425 // https://issues.dlang.org/show_bug.cgi?id=9583
1426 @safe unittest
1427 {
1428     int opt;
1429     auto args = ["prog", "--opt=123", "--", "--a", "--b", "--c"];
1430     getopt(args, "opt", &opt);
1431     assert(args == ["prog", "--a", "--b", "--c"]);
1432 }
1433 
1434 @safe unittest
1435 {
1436     string foo, bar;
1437     auto args = ["prog", "-thello", "-dbar=baz"];
1438     getopt(args, "t", &foo, "d", &bar);
1439     assert(foo == "hello");
1440     assert(bar == "bar=baz");
1441 
1442     // From https://issues.dlang.org/show_bug.cgi?id=5762
1443     string a;
1444     args = ["prog", "-a-0x12"];
1445     getopt(args, config.bundling, "a|addr", &a);
1446     assert(a == "-0x12", a);
1447     args = ["prog", "--addr=-0x12"];
1448     getopt(args, config.bundling, "a|addr", &a);
1449     assert(a == "-0x12");
1450 
1451     // From https://issues.dlang.org/show_bug.cgi?id=11764
1452     args = ["main", "-test"];
1453     bool opt;
1454     args.getopt(config.passThrough, "opt", &opt);
1455     assert(args == ["main", "-test"]);
1456 
1457     // From https://issues.dlang.org/show_bug.cgi?id=15220
1458     args = ["main", "-o=str"];
1459     string o;
1460     args.getopt("o", &o);
1461     assert(o == "str");
1462 
1463     args = ["main", "-o=str"];
1464     o = null;
1465     args.getopt(config.bundling, "o", &o);
1466     assert(o == "str");
1467 }
1468 
1469 // https://issues.dlang.org/show_bug.cgi?id=5228
1470 @safe unittest
1471 {
1472     import std.conv;
1473     import std.exception;
1474 
1475     auto args = ["prog", "--foo=bar"];
1476     int abc;
1477     assertThrown!GetOptException(getopt(args, "abc", &abc));
1478 
1479     args = ["prog", "--abc=string"];
1480     assertThrown!ConvException(getopt(args, "abc", &abc));
1481 }
1482 
1483 // https://issues.dlang.org/show_bug.cgi?id=7693
1484 @safe unittest
1485 {
1486     import std.exception;
1487 
1488     enum Foo {
1489         bar,
1490         baz
1491     }
1492 
1493     auto args = ["prog", "--foo=barZZZ"];
1494     Foo foo;
1495     assertThrown(getopt(args, "foo", &foo));
1496     args = ["prog", "--foo=bar"];
1497     assertNotThrown(getopt(args, "foo", &foo));
1498     args = ["prog", "--foo", "barZZZ"];
1499     assertThrown(getopt(args, "foo", &foo));
1500     args = ["prog", "--foo", "baz"];
1501     assertNotThrown(getopt(args, "foo", &foo));
1502 }
1503 
1504 // Same as https://issues.dlang.org/show_bug.cgi?id=7693 only for `bool`
1505 @safe unittest
1506 {
1507     import std.exception;
1508 
1509     auto args = ["prog", "--foo=truefoobar"];
1510     bool foo;
1511     assertThrown(getopt(args, "foo", &foo));
1512     args = ["prog", "--foo"];
1513     getopt(args, "foo", &foo);
1514     assert(foo);
1515 }
1516 
1517 @safe unittest
1518 {
1519     bool foo;
1520     auto args = ["prog", "--foo"];
1521     getopt(args, "foo", &foo);
1522     assert(foo);
1523 }
1524 
1525 @safe unittest
1526 {
1527     bool foo;
1528     bool bar;
1529     auto args = ["prog", "--foo", "-b"];
1530     getopt(args, config.caseInsensitive,"foo|f", "Some foo", &foo,
1531         config.caseSensitive, "bar|b", "Some bar", &bar);
1532     assert(foo);
1533     assert(bar);
1534 }
1535 
1536 @safe unittest
1537 {
1538     bool foo;
1539     bool bar;
1540     auto args = ["prog", "-b", "--foo", "-z"];
1541     getopt(args, config.caseInsensitive, config.required, "foo|f", "Some foo",
1542         &foo, config.caseSensitive, "bar|b", "Some bar", &bar,
1543         config.passThrough);
1544     assert(foo);
1545     assert(bar);
1546 }
1547 
1548 @safe unittest
1549 {
1550     import std.exception;
1551 
1552     bool foo;
1553     bool bar;
1554     auto args = ["prog", "-b", "-z"];
1555     assertThrown(getopt(args, config.caseInsensitive, config.required, "foo|f",
1556         "Some foo", &foo, config.caseSensitive, "bar|b", "Some bar", &bar,
1557         config.passThrough));
1558 }
1559 
1560 @safe unittest
1561 {
1562     import std.exception;
1563 
1564     bool foo;
1565     bool bar;
1566     auto args = ["prog", "--foo", "-z"];
1567     assertNotThrown(getopt(args, config.caseInsensitive, config.required,
1568         "foo|f", "Some foo", &foo, config.caseSensitive, "bar|b", "Some bar",
1569         &bar, config.passThrough));
1570     assert(foo);
1571     assert(!bar);
1572 }
1573 
1574 @safe unittest
1575 {
1576     bool foo;
1577     auto args = ["prog", "-f"];
1578     auto r = getopt(args, config.caseInsensitive, "help|f", "Some foo", &foo);
1579     assert(foo);
1580     assert(!r.helpWanted);
1581 }
1582 
1583 @safe unittest // implicit help option without config.passThrough
1584 {
1585     string[] args = ["program", "--help"];
1586     auto r = getopt(args);
1587     assert(r.helpWanted);
1588 }
1589 
1590 // std.getopt: implicit help option breaks the next argument
1591 // https://issues.dlang.org/show_bug.cgi?id=13316
1592 @safe unittest
1593 {
1594     string[] args = ["program", "--help", "--", "something"];
1595     getopt(args);
1596     assert(args == ["program", "something"]);
1597 
1598     args = ["program", "--help", "--"];
1599     getopt(args);
1600     assert(args == ["program"]);
1601 
1602     bool b;
1603     args = ["program", "--help", "nonoption", "--option"];
1604     getopt(args, config.stopOnFirstNonOption, "option", &b);
1605     assert(args == ["program", "nonoption", "--option"]);
1606 }
1607 
1608 // std.getopt: endOfOptions broken when it doesn't look like an option
1609 // https://issues.dlang.org/show_bug.cgi?id=13317
1610 @safe unittest
1611 {
1612     auto endOfOptionsBackup = endOfOptions;
1613     scope(exit) endOfOptions = endOfOptionsBackup;
1614     endOfOptions = "endofoptions";
1615     string[] args = ["program", "endofoptions", "--option"];
1616     bool b = false;
1617     getopt(args, "option", &b);
1618     assert(!b);
1619     assert(args == ["program", "--option"]);
1620 }
1621 
1622 // make std.getopt ready for DIP 1000
1623 // https://issues.dlang.org/show_bug.cgi?id=20480
1624 @safe unittest
1625 {
1626     string[] args = ["test", "--foo", "42", "--bar", "BAR"];
1627     int foo;
1628     string bar;
1629     getopt(args, "foo", &foo, "bar", "bar help", &bar);
1630     assert(foo == 42);
1631     assert(bar == "BAR");
1632 }
1633 
1634 /** This function prints the passed `Option`s and text in an aligned manner on `stdout`.
1635 
1636 The passed text will be printed first, followed by a newline, then the short
1637 and long version of every option will be printed. The short and long version
1638 will be aligned to the longest option of every `Option` passed. If the option
1639 is required, then "Required:" will be printed after the long version of the
1640 `Option`. If a help message is present it will be printed next. The format is
1641 illustrated by this code:
1642 
1643 ------------
1644 foreach (it; opt)
1645 {
1646     writefln("%*s %*s%s%s", lengthOfLongestShortOption, it.optShort,
1647         lengthOfLongestLongOption, it.optLong,
1648         it.required ? " Required: " : " ", it.help);
1649 }
1650 ------------
1651 
1652 Params:
1653     text = The text to printed at the beginning of the help output.
1654     opt = The `Option` extracted from the `getopt` parameter.
1655 */
1656 void defaultGetoptPrinter(string text, Option[] opt) @safe
1657 {
1658     import std.stdio : stdout;
1659     // stdout global __gshared is trusted with a locked text writer
1660     auto w = (() @trusted => stdout.lockingTextWriter())();
1661 
1662     defaultGetoptFormatter(w, text, opt);
1663 }
1664 
1665 /** This function writes the passed text and `Option` into an output range
1666 in the manner described in the documentation of function
1667 `defaultGetoptPrinter`, unless the style option is used.
1668 
1669 Params:
1670     output = The output range used to write the help information.
1671     text = The text to print at the beginning of the help output.
1672     opt = The `Option` extracted from the `getopt` parameter.
1673     style = The manner in which to display the output of each `Option.`
1674 */
1675 void defaultGetoptFormatter(Output)(Output output, string text, Option[] opt, string style = "%*s %*s%*s%s\n")
1676 {
1677     import std.algorithm.comparison : min, max;
1678     import std.format.write : formattedWrite;
1679 
1680     output.formattedWrite("%s\n", text);
1681 
1682     size_t ls, ll;
1683     bool hasRequired = false;
1684     foreach (it; opt)
1685     {
1686         ls = max(ls, it.optShort.length);
1687         ll = max(ll, it.optLong.length);
1688 
1689         hasRequired = hasRequired || it.required;
1690     }
1691 
1692     string re = " Required: ";
1693 
1694     foreach (it; opt)
1695     {
1696         output.formattedWrite(style, ls, it.optShort, ll, it.optLong,
1697             hasRequired ? re.length : 1, it.required ? re : " ", it.help);
1698     }
1699 }
1700 
1701 @safe unittest
1702 {
1703     import std.conv;
1704 
1705     import std.array;
1706     import std.string;
1707     bool a;
1708     auto args = ["prog", "--foo"];
1709     auto t = getopt(args, "foo|f", "Help", &a);
1710     string s;
1711     auto app = appender!string();
1712     defaultGetoptFormatter(app, "Some Text", t.options);
1713 
1714     string helpMsg = app.data;
1715     //writeln(helpMsg);
1716     assert(helpMsg.length);
1717     assert(helpMsg.count("\n") == 3, to!string(helpMsg.count("\n")) ~ " "
1718         ~ helpMsg);
1719     assert(helpMsg.indexOf("--foo") != -1);
1720     assert(helpMsg.indexOf("-f") != -1);
1721     assert(helpMsg.indexOf("-h") != -1);
1722     assert(helpMsg.indexOf("--help") != -1);
1723     assert(helpMsg.indexOf("Help") != -1);
1724 
1725     string wanted = "Some Text\n-f  --foo Help\n-h --help This help "
1726         ~ "information.\n";
1727     assert(wanted == helpMsg);
1728 }
1729 
1730 @safe unittest
1731 {
1732     import std.array ;
1733     import std.conv;
1734     import std.string;
1735     bool a;
1736     auto args = ["prog", "--foo"];
1737     auto t = getopt(args, config.required, "foo|f", "Help", &a);
1738     string s;
1739     auto app = appender!string();
1740     defaultGetoptFormatter(app, "Some Text", t.options);
1741 
1742     string helpMsg = app.data;
1743     //writeln(helpMsg);
1744     assert(helpMsg.length);
1745     assert(helpMsg.count("\n") == 3, to!string(helpMsg.count("\n")) ~ " "
1746         ~ helpMsg);
1747     assert(helpMsg.indexOf("Required:") != -1);
1748     assert(helpMsg.indexOf("--foo") != -1);
1749     assert(helpMsg.indexOf("-f") != -1);
1750     assert(helpMsg.indexOf("-h") != -1);
1751     assert(helpMsg.indexOf("--help") != -1);
1752     assert(helpMsg.indexOf("Help") != -1);
1753 
1754     string wanted = "Some Text\n-f  --foo Required: Help\n-h --help "
1755         ~ "          This help information.\n";
1756     assert(wanted == helpMsg, helpMsg ~ wanted);
1757 }
1758 
1759 // https://issues.dlang.org/show_bug.cgi?id=14724
1760 @safe unittest
1761 {
1762     bool a;
1763     auto args = ["prog", "--help"];
1764     GetoptResult rslt;
1765     try
1766     {
1767         rslt = getopt(args, config.required, "foo|f", "bool a", &a);
1768     }
1769     catch (Exception e)
1770     {
1771         enum errorMsg = "If the request for help was passed required options" ~
1772                 "must not be set.";
1773         assert(false, errorMsg);
1774     }
1775 
1776     assert(rslt.helpWanted);
1777 }
1778 
1779 // throw on duplicate options
1780 @system unittest
1781 {
1782     import core.exception : AssertError;
1783     import std.exception : assertNotThrown, assertThrown;
1784     auto args = ["prog", "--abc", "1"];
1785     int abc, def;
1786     assertThrown!AssertError(getopt(args, "abc", &abc, "abc", &abc));
1787     assertThrown!AssertError(getopt(args, "abc|a", &abc, "def|a", &def));
1788     assertNotThrown!AssertError(getopt(args, "abc", &abc, "def", &def));
1789 
1790     // https://issues.dlang.org/show_bug.cgi?id=23940
1791     assertThrown!AssertError(getopt(args,
1792             "abc", &abc, "ABC", &def));
1793     assertThrown!AssertError(getopt(args, config.caseInsensitive,
1794             "abc", &abc, "ABC", &def));
1795     assertNotThrown!AssertError(getopt(args, config.caseSensitive,
1796             "abc", &abc, "ABC", &def));
1797 }
1798 
1799 // https://issues.dlang.org/show_bug.cgi?id=17327 repeated option use
1800 @safe unittest
1801 {
1802     long num = 0;
1803 
1804     string[] args = ["program", "--num", "3"];
1805     getopt(args, "n|num", &num);
1806     assert(num == 3);
1807 
1808     args = ["program", "--num", "3", "--num", "5"];
1809     getopt(args, "n|num", &num);
1810     assert(num == 5);
1811 
1812     args = ["program", "--n", "3", "--num", "5", "-n", "-7"];
1813     getopt(args, "n|num", &num);
1814     assert(num == -7);
1815 
1816     void add1() { num++; }
1817     void add2(string option) { num += 2; }
1818     void addN(string option, string value)
1819     {
1820         import std.conv : to;
1821         num += value.to!long;
1822     }
1823 
1824     num = 0;
1825     args = ["program", "--add1", "--add2", "--add1", "--add", "5", "--add2", "--add", "10"];
1826     getopt(args,
1827            "add1", "Add 1 to num", &add1,
1828            "add2", "Add 2 to num", &add2,
1829            "add", "Add N to num", &addN,);
1830     assert(num == 21);
1831 
1832     bool flag = false;
1833     args = ["program", "--flag"];
1834     getopt(args, "f|flag", "Boolean", &flag);
1835     assert(flag);
1836 
1837     flag = false;
1838     args = ["program", "-f", "-f"];
1839     getopt(args, "f|flag", "Boolean", &flag);
1840     assert(flag);
1841 
1842     flag = false;
1843     args = ["program", "--flag=true", "--flag=false"];
1844     getopt(args, "f|flag", "Boolean", &flag);
1845     assert(!flag);
1846 
1847     flag = false;
1848     args = ["program", "--flag=true", "--flag=false", "-f"];
1849     getopt(args, "f|flag", "Boolean", &flag);
1850     assert(flag);
1851 }
1852 
1853 @system unittest  // Delegates as callbacks
1854 {
1855     alias TwoArgOptionHandler = void delegate(string option, string value) @safe;
1856 
1857     TwoArgOptionHandler makeAddNHandler(ref long dest)
1858     {
1859         void addN(ref long dest, string n)
1860         {
1861             import std.conv : to;
1862             dest += n.to!long;
1863         }
1864 
1865         return (option, value) => addN(dest, value);
1866     }
1867 
1868     long x = 0;
1869     long y = 0;
1870 
1871     string[] args =
1872         ["program", "--x-plus-1", "--x-plus-1", "--x-plus-5", "--x-plus-n", "10",
1873          "--y-plus-n", "25", "--y-plus-7", "--y-plus-n", "15", "--y-plus-3"];
1874 
1875     getopt(args,
1876            "x-plus-1", "Add one to x", delegate void() { x += 1; },
1877            "x-plus-5", "Add five to x", delegate void(string option) { x += 5; },
1878            "x-plus-n", "Add NUM to x", makeAddNHandler(x),
1879            "y-plus-7", "Add seven to y", delegate void() { y += 7; },
1880            "y-plus-3", "Add three to y", delegate void(string option) { y += 3; },
1881            "y-plus-n", "Add NUM to x", makeAddNHandler(y),);
1882 
1883     assert(x == 17);
1884     assert(y == 50);
1885 }
1886 
1887 // Hyphens at the start of option values;
1888 // https://issues.dlang.org/show_bug.cgi?id=17650
1889 @safe unittest
1890 {
1891     auto args = ["program", "-m", "-5", "-n", "-50", "-c", "-", "-f", "-"];
1892 
1893     int m;
1894     int n;
1895     char c;
1896     string f;
1897 
1898     getopt(args,
1899            "m|mm", "integer", &m,
1900            "n|nn", "integer", &n,
1901            "c|cc", "character", &c,
1902            "f|file", "filename or hyphen for stdin", &f);
1903 
1904     assert(m == -5);
1905     assert(n == -50);
1906     assert(c == '-');
1907     assert(f == "-");
1908 }
1909 
1910 // Hyphen at the option value;
1911 // https://issues.dlang.org/show_bug.cgi?id=22394
1912 @safe unittest
1913 {
1914     auto args = ["program", "-"];
1915 
1916     getopt(args);
1917 
1918     assert(args == ["program", "-"]);
1919 }
1920 
1921 @safe unittest
1922 {
1923     import std.conv;
1924 
1925     import std.array;
1926     import std.string;
1927     bool a;
1928     auto args = ["prog", "--foo"];
1929     auto t = getopt(args, "foo|f", "Help", &a);
1930     string s;
1931     auto app = appender!string();
1932     defaultGetoptFormatter(app, "Some Text", t.options, "\t\t%*s %*s%*s\n%s\n");
1933 
1934     string helpMsg = app.data;
1935     //writeln(helpMsg);
1936     assert(helpMsg.length);
1937     assert(helpMsg.count("\n") == 5, to!string(helpMsg.count("\n")) ~ " "
1938         ~ helpMsg);
1939     assert(helpMsg.indexOf("--foo") != -1);
1940     assert(helpMsg.indexOf("-f") != -1);
1941     assert(helpMsg.indexOf("-h") != -1);
1942     assert(helpMsg.indexOf("--help") != -1);
1943     assert(helpMsg.indexOf("Help") != -1);
1944 
1945     string wanted = "Some Text\n\t\t-f  --foo \nHelp\n\t\t-h --help \nThis help "
1946         ~ "information.\n";
1947     assert(wanted == helpMsg);
1948 }