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