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