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