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 */
getopt(T...)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 
splitAndGet(string opt)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 */
optionValidator(A...)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) = isPointer!T || (is(T == function)) || (is(T == delegate));
562     enum isOptionStr(T) = isSomeString!T || isSomeChar!T;
563 
564     auto validator()
565     {
566         string msg;
567         static if (A.length > 0)
568         {
569             static if (isReceiver!(A[0]))
570             {
571                 msg = format(fmt, "first argument must be a string or a config", 0);
572             }
573             else static if (!isOptionStr!(A[0]) && !is(A[0] == config))
574             {
575                 msg = format(fmt, "invalid argument type: " ~ A[0].stringof, 0);
576             }
577             else
578             {
579                 static foreach (i; 1 .. A.length)
580                 {
581                     static if (!isReceiver!(A[i]) && !isOptionStr!(A[i]) &&
582                         !(is(A[i] == config)))
583                     {
584                         msg = format(fmt, "invalid argument type: " ~ A[i].stringof, i);
585                         goto end;
586                     }
587                     else static if (isReceiver!(A[i]) && !isOptionStr!(A[i-1]))
588                     {
589                         msg = format(fmt, "a receiver can not be preceeded by a receiver", i);
590                         goto end;
591                     }
592                     else static if (i > 1 && isOptionStr!(A[i]) && isOptionStr!(A[i-1])
593                         && isSomeString!(A[i-2]))
594                     {
595                         msg = format(fmt, "a string can not be preceeded by two strings", i);
596                         goto end;
597                     }
598                 }
599             }
600             static if (!isReceiver!(A[$-1]) && !is(A[$-1] == config))
601             {
602                 msg = format(fmt, "last argument must be a receiver or a config",
603                     A.length -1);
604             }
605         }
606     end:
607         return msg;
608     }
609     enum message = validator;
610     alias optionValidator = message;
611 }
612 
613 @safe pure unittest
614 {
615     alias P = void*;
616     alias S = string;
617     alias A = char;
618     alias C = config;
619     alias F = void function();
620 
621     static assert(optionValidator!(S,P) == "");
622     static assert(optionValidator!(S,F) == "");
623     static assert(optionValidator!(A,P) == "");
624     static assert(optionValidator!(A,F) == "");
625 
626     static assert(optionValidator!(C,S,P) == "");
627     static assert(optionValidator!(C,S,F) == "");
628     static assert(optionValidator!(C,A,P) == "");
629     static assert(optionValidator!(C,A,F) == "");
630 
631     static assert(optionValidator!(C,S,S,P) == "");
632     static assert(optionValidator!(C,S,S,F) == "");
633     static assert(optionValidator!(C,A,S,P) == "");
634     static assert(optionValidator!(C,A,S,F) == "");
635 
636     static assert(optionValidator!(C,S,S,P) == "");
637     static assert(optionValidator!(C,S,S,P,C,S,F) == "");
638     static assert(optionValidator!(C,S,P,C,S,S,F) == "");
639 
640     static assert(optionValidator!(C,A,P,A,S,F) == "");
641     static assert(optionValidator!(C,A,P,C,A,S,F) == "");
642 
643     static assert(optionValidator!(P,S,S) != "");
644     static assert(optionValidator!(P,P,S) != "");
645     static assert(optionValidator!(P,F,S,P) != "");
646     static assert(optionValidator!(C,C,S) != "");
647     static assert(optionValidator!(S,S,P,S,S,P,S) != "");
648     static assert(optionValidator!(S,S,P,P) != "");
649     static assert(optionValidator!(S,S,S,P) != "");
650 
651     static assert(optionValidator!(C,A,S,P,C,A,F) == "");
652     static assert(optionValidator!(C,A,P,C,A,S,F) == "");
653 }
654 
655 // https://issues.dlang.org/show_bug.cgi?id=15914
656 @safe unittest
657 {
658     import std.exception : assertThrown;
659     bool opt;
660     string[] args = ["program", "-a"];
661     getopt(args, config.passThrough, 'a', &opt);
662     assert(opt);
663     opt = false;
664     args = ["program", "-a"];
665     getopt(args, 'a', &opt);
666     assert(opt);
667     opt = false;
668     args = ["program", "-a"];
669     getopt(args, 'a', "help string", &opt);
670     assert(opt);
671     opt = false;
672     args = ["program", "-a"];
673     getopt(args, config.caseSensitive, 'a', "help string", &opt);
674     assert(opt);
675 
676     assertThrown(getopt(args, "", "forgot to put a string", &opt));
677 }
678 
getoptImpl(T...)679 private void getoptImpl(T...)(ref string[] args, ref configuration cfg,
680     ref GetoptResult rslt, ref GetOptException excep,
681     void[][string] visitedLongOpts, void[][string] visitedShortOpts, T opts)
682 {
683     enum validationMessage = optionValidator!T;
684     static assert(validationMessage == "", validationMessage);
685 
686     import std.algorithm.mutation : remove;
687     import std.conv : to;
688     static if (opts.length)
689     {
690         static if (is(typeof(opts[0]) : config))
691         {
692             // it's a configuration flag, act on it
693             setConfig(cfg, opts[0]);
694             return getoptImpl(args, cfg, rslt, excep, visitedLongOpts,
695                 visitedShortOpts, opts[1 .. $]);
696         }
697         else
698         {
699             // it's an option string
700             auto option = to!string(opts[0]);
701             if (option.length == 0)
702             {
703                 excep = new GetOptException("An option name may not be an empty string", excep);
704                 return;
705             }
706             Option optionHelp = splitAndGet(option);
707             optionHelp.required = cfg.required;
708 
709             if (optionHelp.optLong.length)
710             {
711                 assert(optionHelp.optLong !in visitedLongOpts,
712                     "Long option " ~ optionHelp.optLong ~ " is multiply defined");
713 
714                 visitedLongOpts[optionHelp.optLong] = [];
715             }
716 
717             if (optionHelp.optShort.length)
718             {
719                 assert(optionHelp.optShort !in visitedShortOpts,
720                     "Short option " ~ optionHelp.optShort
721                     ~ " is multiply defined");
722 
723                 visitedShortOpts[optionHelp.optShort] = [];
724             }
725 
726             static if (is(typeof(opts[1]) : string))
727             {
728                 alias receiver = opts[2];
729                 optionHelp.help = opts[1];
730                 immutable lowSliceIdx = 3;
731             }
732             else
733             {
734                 alias receiver = opts[1];
735                 immutable lowSliceIdx = 2;
736             }
737 
738             rslt.options ~= optionHelp;
739 
740             bool incremental;
741             // Handle options of the form --blah+
742             if (option.length && option[$ - 1] == autoIncrementChar)
743             {
744                 option = option[0 .. $ - 1];
745                 incremental = true;
746             }
747 
748             bool optWasHandled = handleOption(option, receiver, args, cfg, incremental);
749 
750             if (cfg.required && !optWasHandled)
751             {
752                 excep = new GetOptException("Required option "
753                     ~ option ~ " was not supplied", excep);
754             }
755             cfg.required = false;
756 
757             getoptImpl(args, cfg, rslt, excep, visitedLongOpts,
758                 visitedShortOpts, opts[lowSliceIdx .. $]);
759         }
760     }
761     else
762     {
763         // no more options to look for, potentially some arguments left
764         for (size_t i = 1; i < args.length;)
765         {
766             auto a = args[i];
767             if (endOfOptions.length && a == endOfOptions)
768             {
769                 // Consume the "--" if keepEndOfOptions is not specified
770                 if (!cfg.keepEndOfOptions)
771                     args = args.remove(i);
772                 break;
773             }
774             if (a.length < 2 || a[0] != optionChar)
775             {
776                 // not an option
777                 if (cfg.stopOnFirstNonOption) break;
778                 ++i;
779                 continue;
780             }
781             if (a == "--help" || a == "-h")
782             {
783                 rslt.helpWanted = true;
784                 args = args.remove(i);
785                 continue;
786             }
787             if (!cfg.passThrough)
788             {
789                 throw new GetOptException("Unrecognized option "~a, excep);
790             }
791             ++i;
792         }
793 
794         Option helpOpt;
795         helpOpt.optShort = "-h";
796         helpOpt.optLong = "--help";
797         helpOpt.help = "This help information.";
798         rslt.options ~= helpOpt;
799     }
800 }
801 
handleOption(R)802 private bool handleOption(R)(string option, R receiver, ref string[] args,
803     ref configuration cfg, bool incremental)
804 {
805     import std.algorithm.iteration : map, splitter;
806     import std.ascii : isAlpha;
807     import std.conv : text, to;
808     // Scan arguments looking for a match for this option
809     bool ret = false;
810     for (size_t i = 1; i < args.length; )
811     {
812         auto a = args[i];
813         if (endOfOptions.length && a == endOfOptions) break;
814         if (cfg.stopOnFirstNonOption && (!a.length || a[0] != optionChar))
815         {
816             // first non-option is end of options
817             break;
818         }
819         // Unbundle bundled arguments if necessary
820         if (cfg.bundling && a.length > 2 && a[0] == optionChar &&
821                 a[1] != optionChar)
822         {
823             string[] expanded;
824             foreach (j, dchar c; a[1 .. $])
825             {
826                 // If the character is not alpha, stop right there. This allows
827                 // e.g. -j100 to work as "pass argument 100 to option -j".
828                 if (!isAlpha(c))
829                 {
830                     if (c == '=')
831                         j++;
832                     expanded ~= a[j + 1 .. $];
833                     break;
834                 }
835                 expanded ~= text(optionChar, c);
836             }
837             args = args[0 .. i] ~ expanded ~ args[i + 1 .. $];
838             continue;
839         }
840 
841         string val;
842         if (!optMatch(a, option, val, cfg))
843         {
844             ++i;
845             continue;
846         }
847 
848         ret = true;
849 
850         // found it
851         // from here on, commit to eat args[i]
852         // (and potentially args[i + 1] too, but that comes later)
853         args = args[0 .. i] ~ args[i + 1 .. $];
854 
855         static if (is(typeof(*receiver) == bool))
856         {
857             if (val.length)
858             {
859                 // parse '--b=true/false'
860                 *receiver = to!(typeof(*receiver))(val);
861             }
862             else
863             {
864                 // no argument means set it to true
865                 *receiver = true;
866             }
867         }
868         else
869         {
870             import std.exception : enforce;
871             // non-boolean option, which might include an argument
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!GetOptException(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 
optMatch(string arg,scope string optPattern,ref string value,configuration cfg)1106 private bool optMatch(string arg, scope string optPattern, ref string value,
1107     configuration cfg) @safe
1108 {
1109     import std.algorithm.iteration : splitter;
1110     import std.string : indexOf;
1111     import std.uni : icmp;
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     foreach (v; splitter(optPattern, "|"))
1152     {
1153         //writeln("Trying variant: ", v, " against ", arg);
1154         if (arg == v || (!cfg.caseSensitive && icmp(arg, v) == 0))
1155             return true;
1156         if (cfg.bundling && !isLong && v.length == 1
1157                 && indexOf(arg, v) >= 0)
1158         {
1159             //writeln("success");
1160             return true;
1161         }
1162     }
1163     return false;
1164 }
1165 
setConfig(ref configuration cfg,config option)1166 private void setConfig(ref configuration cfg, config option) @safe pure nothrow @nogc
1167 {
1168     final switch (option)
1169     {
1170     case config.caseSensitive: cfg.caseSensitive = true; break;
1171     case config.caseInsensitive: cfg.caseSensitive = false; break;
1172     case config.bundling: cfg.bundling = true; break;
1173     case config.noBundling: cfg.bundling = false; break;
1174     case config.passThrough: cfg.passThrough = true; break;
1175     case config.noPassThrough: cfg.passThrough = false; break;
1176     case config.required: cfg.required = true; break;
1177     case config.stopOnFirstNonOption:
1178         cfg.stopOnFirstNonOption = true; break;
1179     case config.keepEndOfOptions:
1180         cfg.keepEndOfOptions = true; break;
1181     }
1182 }
1183 
1184 @safe unittest
1185 {
1186     import std.conv;
1187     import std.math.operations : isClose;
1188 
1189     uint paranoid = 2;
1190     string[] args = ["program.name", "--paranoid", "--paranoid", "--paranoid"];
1191     getopt(args, "paranoid+", &paranoid);
1192     assert(paranoid == 5, to!(string)(paranoid));
1193 
1194     enum Color { no, yes }
1195     Color color;
1196     args = ["program.name", "--color=yes",];
1197     getopt(args, "color", &color);
1198     assert(color, to!(string)(color));
1199 
1200     color = Color.no;
1201     args = ["program.name", "--color", "yes",];
1202     getopt(args, "color", &color);
1203     assert(color, to!(string)(color));
1204 
1205     string data = "file.dat";
1206     int length = 24;
1207     bool verbose = false;
1208     args = ["program.name", "--length=5", "--file", "dat.file", "--verbose"];
1209     getopt(
1210         args,
1211         "length",  &length,
1212         "file",    &data,
1213         "verbose", &verbose);
1214     assert(args.length == 1);
1215     assert(data == "dat.file");
1216     assert(length == 5);
1217     assert(verbose);
1218 
1219     //
1220     string[] outputFiles;
1221     args = ["program.name", "--output=myfile.txt", "--output", "yourfile.txt"];
1222     getopt(args, "output", &outputFiles);
1223     assert(outputFiles.length == 2
1224            && outputFiles[0] == "myfile.txt" && outputFiles[1] == "yourfile.txt");
1225 
1226     outputFiles = [];
1227     arraySep = ",";
1228     args = ["program.name", "--output", "myfile.txt,yourfile.txt"];
1229     getopt(args, "output", &outputFiles);
1230     assert(outputFiles.length == 2
1231            && outputFiles[0] == "myfile.txt" && outputFiles[1] == "yourfile.txt");
1232     arraySep = "";
1233 
foreach(testArgs;)1234     foreach (testArgs;
1235         [["program.name", "--tune=alpha=0.5", "--tune", "beta=0.6"],
1236          ["program.name", "--tune=alpha=0.5,beta=0.6"],
1237          ["program.name", "--tune", "alpha=0.5,beta=0.6"]])
1238     {
1239         arraySep = ",";
1240         double[string] tuningParms;
1241         getopt(testArgs, "tune", &tuningParms);
1242         assert(testArgs.length == 1);
1243         assert(tuningParms.length == 2);
1244         assert(isClose(tuningParms["alpha"], 0.5));
1245         assert(isClose(tuningParms["beta"], 0.6));
1246         arraySep = "";
1247     }
1248 
1249     uint verbosityLevel = 1;
myHandler(string option)1250     void myHandler(string option)
1251     {
1252         if (option == "quiet")
1253         {
1254             verbosityLevel = 0;
1255         }
1256         else
1257         {
1258             assert(option == "verbose");
1259             verbosityLevel = 2;
1260         }
1261     }
1262     args = ["program.name", "--quiet"];
1263     getopt(args, "verbose", &myHandler, "quiet", &myHandler);
1264     assert(verbosityLevel == 0);
1265     args = ["program.name", "--verbose"];
1266     getopt(args, "verbose", &myHandler, "quiet", &myHandler);
1267     assert(verbosityLevel == 2);
1268 
1269     verbosityLevel = 1;
myHandler2(string option,string value)1270     void myHandler2(string option, string value)
1271     {
1272         assert(option == "verbose");
1273         verbosityLevel = 2;
1274     }
1275     args = ["program.name", "--verbose", "2"];
1276     getopt(args, "verbose", &myHandler2);
1277     assert(verbosityLevel == 2);
1278 
1279     verbosityLevel = 1;
myHandler3()1280     void myHandler3()
1281     {
1282         verbosityLevel = 2;
1283     }
1284     args = ["program.name", "--verbose"];
1285     getopt(args, "verbose", &myHandler3);
1286     assert(verbosityLevel == 2);
1287 
1288     bool foo, bar;
1289     args = ["program.name", "--foo", "--bAr"];
1290     getopt(args,
1291         std.getopt.config.caseSensitive,
1292         std.getopt.config.passThrough,
1293         "foo", &foo,
1294         "bar", &bar);
1295     assert(args[1] == "--bAr");
1296 
1297     // test stopOnFirstNonOption
1298 
1299     args = ["program.name", "--foo", "nonoption", "--bar"];
1300     foo = bar = false;
1301     getopt(args,
1302         std.getopt.config.stopOnFirstNonOption,
1303         "foo", &foo,
1304         "bar", &bar);
1305     assert(foo && !bar && args[1] == "nonoption" && args[2] == "--bar");
1306 
1307     args = ["program.name", "--foo", "nonoption", "--zab"];
1308     foo = bar = false;
1309     getopt(args,
1310         std.getopt.config.stopOnFirstNonOption,
1311         "foo", &foo,
1312         "bar", &bar);
1313     assert(foo && !bar && args[1] == "nonoption" && args[2] == "--zab");
1314 
1315     args = ["program.name", "--fb1", "--fb2=true", "--tb1=false"];
1316     bool fb1, fb2;
1317     bool tb1 = true;
1318     getopt(args, "fb1", &fb1, "fb2", &fb2, "tb1", &tb1);
1319     assert(fb1 && fb2 && !tb1);
1320 
1321     // test keepEndOfOptions
1322 
1323     args = ["program.name", "--foo", "nonoption", "--bar", "--", "--baz"];
1324     getopt(args,
1325         std.getopt.config.keepEndOfOptions,
1326         "foo", &foo,
1327         "bar", &bar);
1328     assert(args == ["program.name", "nonoption", "--", "--baz"]);
1329 
1330     // Ensure old behavior without the keepEndOfOptions
1331 
1332     args = ["program.name", "--foo", "nonoption", "--bar", "--", "--baz"];
1333     getopt(args,
1334         "foo", &foo,
1335         "bar", &bar);
1336     assert(args == ["program.name", "nonoption", "--baz"]);
1337 
1338     // test function callbacks
1339 
1340     static class MyEx : Exception
1341     {
this()1342         this() { super(""); }
this(string option)1343         this(string option) { this(); this.option = option; }
this(string option,string value)1344         this(string option, string value) { this(option); this.value = value; }
1345 
1346         string option;
1347         string value;
1348     }
1349 
myStaticHandler1()1350     static void myStaticHandler1() { throw new MyEx(); }
1351     args = ["program.name", "--verbose"];
1352     try { getopt(args, "verbose", &myStaticHandler1); assert(0); }
catch(MyEx ex)1353     catch (MyEx ex) { assert(ex.option is null && ex.value is null); }
1354 
myStaticHandler2(string option)1355     static void myStaticHandler2(string option) { throw new MyEx(option); }
1356     args = ["program.name", "--verbose"];
1357     try { getopt(args, "verbose", &myStaticHandler2); assert(0); }
catch(MyEx ex)1358     catch (MyEx ex) { assert(ex.option == "verbose" && ex.value is null); }
1359 
myStaticHandler3(string option,string value)1360     static void myStaticHandler3(string option, string value) { throw new MyEx(option, value); }
1361     args = ["program.name", "--verbose", "2"];
1362     try { getopt(args, "verbose", &myStaticHandler3); assert(0); }
catch(MyEx ex)1363     catch (MyEx ex) { assert(ex.option == "verbose" && ex.value == "2"); }
1364 
1365     // check that GetOptException is thrown if the value is missing
1366     args = ["program.name", "--verbose"];
1367     try { getopt(args, "verbose", &myStaticHandler3); assert(0); }
catch(GetOptException e)1368     catch (GetOptException e) {}
catch(Exception e)1369     catch (Exception e) { assert(0); }
1370 }
1371 
1372 @safe unittest // @safe std.getopt.config option use
1373 {
1374     long x = 0;
1375     string[] args = ["program", "--inc-x", "--inc-x"];
1376     getopt(args,
1377            std.getopt.config.caseSensitive,
1378            "inc-x", "Add one to x", delegate void() { x++; });
1379     assert(x == 2);
1380 }
1381 
1382 // https://issues.dlang.org/show_bug.cgi?id=2142
1383 @safe unittest
1384 {
1385     bool f_linenum, f_filename;
1386     string[] args = [ "", "-nl" ];
1387     getopt
1388         (
1389             args,
1390             std.getopt.config.bundling,
1391             //std.getopt.config.caseSensitive,
1392             "linenum|l", &f_linenum,
1393             "filename|n", &f_filename
1394         );
1395     assert(f_linenum);
1396     assert(f_filename);
1397 }
1398 
1399 // https://issues.dlang.org/show_bug.cgi?id=6887
1400 @safe unittest
1401 {
1402     string[] p;
1403     string[] args = ["", "-pa"];
1404     getopt(args, "p", &p);
1405     assert(p.length == 1);
1406     assert(p[0] == "a");
1407 }
1408 
1409 // https://issues.dlang.org/show_bug.cgi?id=6888
1410 @safe unittest
1411 {
1412     int[string] foo;
1413     auto args = ["", "-t", "a=1"];
1414     getopt(args, "t", &foo);
1415     assert(foo == ["a":1]);
1416 }
1417 
1418 // https://issues.dlang.org/show_bug.cgi?id=9583
1419 @safe unittest
1420 {
1421     int opt;
1422     auto args = ["prog", "--opt=123", "--", "--a", "--b", "--c"];
1423     getopt(args, "opt", &opt);
1424     assert(args == ["prog", "--a", "--b", "--c"]);
1425 }
1426 
1427 @safe unittest
1428 {
1429     string foo, bar;
1430     auto args = ["prog", "-thello", "-dbar=baz"];
1431     getopt(args, "t", &foo, "d", &bar);
1432     assert(foo == "hello");
1433     assert(bar == "bar=baz");
1434 
1435     // From https://issues.dlang.org/show_bug.cgi?id=5762
1436     string a;
1437     args = ["prog", "-a-0x12"];
1438     getopt(args, config.bundling, "a|addr", &a);
1439     assert(a == "-0x12", a);
1440     args = ["prog", "--addr=-0x12"];
1441     getopt(args, config.bundling, "a|addr", &a);
1442     assert(a == "-0x12");
1443 
1444     // From https://issues.dlang.org/show_bug.cgi?id=11764
1445     args = ["main", "-test"];
1446     bool opt;
1447     args.getopt(config.passThrough, "opt", &opt);
1448     assert(args == ["main", "-test"]);
1449 
1450     // From https://issues.dlang.org/show_bug.cgi?id=15220
1451     args = ["main", "-o=str"];
1452     string o;
1453     args.getopt("o", &o);
1454     assert(o == "str");
1455 
1456     args = ["main", "-o=str"];
1457     o = null;
1458     args.getopt(config.bundling, "o", &o);
1459     assert(o == "str");
1460 }
1461 
1462 // https://issues.dlang.org/show_bug.cgi?id=5228
1463 @safe unittest
1464 {
1465     import std.conv;
1466     import std.exception;
1467 
1468     auto args = ["prog", "--foo=bar"];
1469     int abc;
1470     assertThrown!GetOptException(getopt(args, "abc", &abc));
1471 
1472     args = ["prog", "--abc=string"];
1473     assertThrown!ConvException(getopt(args, "abc", &abc));
1474 }
1475 
1476 // https://issues.dlang.org/show_bug.cgi?id=7693
1477 @safe unittest
1478 {
1479     import std.exception;
1480 
1481     enum Foo {
1482         bar,
1483         baz
1484     }
1485 
1486     auto args = ["prog", "--foo=barZZZ"];
1487     Foo foo;
1488     assertThrown(getopt(args, "foo", &foo));
1489     args = ["prog", "--foo=bar"];
1490     assertNotThrown(getopt(args, "foo", &foo));
1491     args = ["prog", "--foo", "barZZZ"];
1492     assertThrown(getopt(args, "foo", &foo));
1493     args = ["prog", "--foo", "baz"];
1494     assertNotThrown(getopt(args, "foo", &foo));
1495 }
1496 
1497 // Same as https://issues.dlang.org/show_bug.cgi?id=7693 only for `bool`
1498 @safe unittest
1499 {
1500     import std.exception;
1501 
1502     auto args = ["prog", "--foo=truefoobar"];
1503     bool foo;
1504     assertThrown(getopt(args, "foo", &foo));
1505     args = ["prog", "--foo"];
1506     getopt(args, "foo", &foo);
1507     assert(foo);
1508 }
1509 
1510 @safe unittest
1511 {
1512     bool foo;
1513     auto args = ["prog", "--foo"];
1514     getopt(args, "foo", &foo);
1515     assert(foo);
1516 }
1517 
1518 @safe unittest
1519 {
1520     bool foo;
1521     bool bar;
1522     auto args = ["prog", "--foo", "-b"];
1523     getopt(args, config.caseInsensitive,"foo|f", "Some foo", &foo,
1524         config.caseSensitive, "bar|b", "Some bar", &bar);
1525     assert(foo);
1526     assert(bar);
1527 }
1528 
1529 @safe unittest
1530 {
1531     bool foo;
1532     bool bar;
1533     auto args = ["prog", "-b", "--foo", "-z"];
1534     getopt(args, config.caseInsensitive, config.required, "foo|f", "Some foo",
1535         &foo, config.caseSensitive, "bar|b", "Some bar", &bar,
1536         config.passThrough);
1537     assert(foo);
1538     assert(bar);
1539 }
1540 
1541 @safe unittest
1542 {
1543     import std.exception;
1544 
1545     bool foo;
1546     bool bar;
1547     auto args = ["prog", "-b", "-z"];
1548     assertThrown(getopt(args, config.caseInsensitive, config.required, "foo|f",
1549         "Some foo", &foo, config.caseSensitive, "bar|b", "Some bar", &bar,
1550         config.passThrough));
1551 }
1552 
1553 @safe unittest
1554 {
1555     import std.exception;
1556 
1557     bool foo;
1558     bool bar;
1559     auto args = ["prog", "--foo", "-z"];
1560     assertNotThrown(getopt(args, config.caseInsensitive, config.required,
1561         "foo|f", "Some foo", &foo, config.caseSensitive, "bar|b", "Some bar",
1562         &bar, config.passThrough));
1563     assert(foo);
1564     assert(!bar);
1565 }
1566 
1567 @safe unittest
1568 {
1569     bool foo;
1570     auto args = ["prog", "-f"];
1571     auto r = getopt(args, config.caseInsensitive, "help|f", "Some foo", &foo);
1572     assert(foo);
1573     assert(!r.helpWanted);
1574 }
1575 
1576 @safe unittest // implicit help option without config.passThrough
1577 {
1578     string[] args = ["program", "--help"];
1579     auto r = getopt(args);
1580     assert(r.helpWanted);
1581 }
1582 
1583 // std.getopt: implicit help option breaks the next argument
1584 // https://issues.dlang.org/show_bug.cgi?id=13316
1585 @safe unittest
1586 {
1587     string[] args = ["program", "--help", "--", "something"];
1588     getopt(args);
1589     assert(args == ["program", "something"]);
1590 
1591     args = ["program", "--help", "--"];
1592     getopt(args);
1593     assert(args == ["program"]);
1594 
1595     bool b;
1596     args = ["program", "--help", "nonoption", "--option"];
1597     getopt(args, config.stopOnFirstNonOption, "option", &b);
1598     assert(args == ["program", "nonoption", "--option"]);
1599 }
1600 
1601 // std.getopt: endOfOptions broken when it doesn't look like an option
1602 // https://issues.dlang.org/show_bug.cgi?id=13317
1603 @safe unittest
1604 {
1605     auto endOfOptionsBackup = endOfOptions;
1606     scope(exit) endOfOptions = endOfOptionsBackup;
1607     endOfOptions = "endofoptions";
1608     string[] args = ["program", "endofoptions", "--option"];
1609     bool b = false;
1610     getopt(args, "option", &b);
1611     assert(!b);
1612     assert(args == ["program", "--option"]);
1613 }
1614 
1615 // make std.getopt ready for DIP 1000
1616 // https://issues.dlang.org/show_bug.cgi?id=20480
1617 @safe unittest
1618 {
1619     string[] args = ["test", "--foo", "42", "--bar", "BAR"];
1620     int foo;
1621     string bar;
1622     getopt(args, "foo", &foo, "bar", "bar help", &bar);
1623     assert(foo == 42);
1624     assert(bar == "BAR");
1625 }
1626 
1627 /** This function prints the passed `Option`s and text in an aligned manner on `stdout`.
1628 
1629 The passed text will be printed first, followed by a newline, then the short
1630 and long version of every option will be printed. The short and long version
1631 will be aligned to the longest option of every `Option` passed. If the option
1632 is required, then "Required:" will be printed after the long version of the
1633 `Option`. If a help message is present it will be printed next. The format is
1634 illustrated by this code:
1635 
1636 ------------
1637 foreach (it; opt)
1638 {
1639     writefln("%*s %*s%s%s", lengthOfLongestShortOption, it.optShort,
1640         lengthOfLongestLongOption, it.optLong,
1641         it.required ? " Required: " : " ", it.help);
1642 }
1643 ------------
1644 
1645 Params:
1646     text = The text to printed at the beginning of the help output.
1647     opt = The `Option` extracted from the `getopt` parameter.
1648 */
defaultGetoptPrinter(string text,Option[]opt)1649 void defaultGetoptPrinter(string text, Option[] opt) @safe
1650 {
1651     import std.stdio : stdout;
1652     // stdout global __gshared is trusted with a locked text writer
1653     auto w = (() @trusted => stdout.lockingTextWriter())();
1654 
1655     defaultGetoptFormatter(w, text, opt);
1656 }
1657 
1658 /** This function writes the passed text and `Option` into an output range
1659 in the manner described in the documentation of function
1660 `defaultGetoptPrinter`, unless the style option is used.
1661 
1662 Params:
1663     output = The output range used to write the help information.
1664     text = The text to print at the beginning of the help output.
1665     opt = The `Option` extracted from the `getopt` parameter.
1666     style = The manner in which to display the output of each `Option.`
1667 */
defaultGetoptFormatter(Output)1668 void defaultGetoptFormatter(Output)(Output output, string text, Option[] opt, string style = "%*s %*s%*s%s\n")
1669 {
1670     import std.algorithm.comparison : min, max;
1671     import std.format.write : formattedWrite;
1672 
1673     output.formattedWrite("%s\n", text);
1674 
1675     size_t ls, ll;
1676     bool hasRequired = false;
1677     foreach (it; opt)
1678     {
1679         ls = max(ls, it.optShort.length);
1680         ll = max(ll, it.optLong.length);
1681 
1682         hasRequired = hasRequired || it.required;
1683     }
1684 
1685     string re = " Required: ";
1686 
1687     foreach (it; opt)
1688     {
1689         output.formattedWrite(style, ls, it.optShort, ll, it.optLong,
1690             hasRequired ? re.length : 1, it.required ? re : " ", it.help);
1691     }
1692 }
1693 
1694 @safe unittest
1695 {
1696     import std.conv;
1697 
1698     import std.array;
1699     import std.string;
1700     bool a;
1701     auto args = ["prog", "--foo"];
1702     auto t = getopt(args, "foo|f", "Help", &a);
1703     string s;
1704     auto app = appender!string();
1705     defaultGetoptFormatter(app, "Some Text", t.options);
1706 
1707     string helpMsg = app.data;
1708     //writeln(helpMsg);
1709     assert(helpMsg.length);
1710     assert(helpMsg.count("\n") == 3, to!string(helpMsg.count("\n")) ~ " "
1711         ~ helpMsg);
1712     assert(helpMsg.indexOf("--foo") != -1);
1713     assert(helpMsg.indexOf("-f") != -1);
1714     assert(helpMsg.indexOf("-h") != -1);
1715     assert(helpMsg.indexOf("--help") != -1);
1716     assert(helpMsg.indexOf("Help") != -1);
1717 
1718     string wanted = "Some Text\n-f  --foo Help\n-h --help This help "
1719         ~ "information.\n";
1720     assert(wanted == helpMsg);
1721 }
1722 
1723 @safe unittest
1724 {
1725     import std.array ;
1726     import std.conv;
1727     import std.string;
1728     bool a;
1729     auto args = ["prog", "--foo"];
1730     auto t = getopt(args, config.required, "foo|f", "Help", &a);
1731     string s;
1732     auto app = appender!string();
1733     defaultGetoptFormatter(app, "Some Text", t.options);
1734 
1735     string helpMsg = app.data;
1736     //writeln(helpMsg);
1737     assert(helpMsg.length);
1738     assert(helpMsg.count("\n") == 3, to!string(helpMsg.count("\n")) ~ " "
1739         ~ helpMsg);
1740     assert(helpMsg.indexOf("Required:") != -1);
1741     assert(helpMsg.indexOf("--foo") != -1);
1742     assert(helpMsg.indexOf("-f") != -1);
1743     assert(helpMsg.indexOf("-h") != -1);
1744     assert(helpMsg.indexOf("--help") != -1);
1745     assert(helpMsg.indexOf("Help") != -1);
1746 
1747     string wanted = "Some Text\n-f  --foo Required: Help\n-h --help "
1748         ~ "          This help information.\n";
1749     assert(wanted == helpMsg, helpMsg ~ wanted);
1750 }
1751 
1752 // https://issues.dlang.org/show_bug.cgi?id=14724
1753 @safe unittest
1754 {
1755     bool a;
1756     auto args = ["prog", "--help"];
1757     GetoptResult rslt;
1758     try
1759     {
1760         rslt = getopt(args, config.required, "foo|f", "bool a", &a);
1761     }
catch(Exception e)1762     catch (Exception e)
1763     {
1764         enum errorMsg = "If the request for help was passed required options" ~
1765                 "must not be set.";
1766         assert(false, errorMsg);
1767     }
1768 
1769     assert(rslt.helpWanted);
1770 }
1771 
1772 // throw on duplicate options
1773 @system unittest
1774 {
1775     import core.exception : AssertError;
1776     import std.exception : assertNotThrown, assertThrown;
1777     auto args = ["prog", "--abc", "1"];
1778     int abc, def;
1779     assertThrown!AssertError(getopt(args, "abc", &abc, "abc", &abc));
1780     assertThrown!AssertError(getopt(args, "abc|a", &abc, "def|a", &def));
1781     assertNotThrown!AssertError(getopt(args, "abc", &abc, "def", &def));
1782 }
1783 
1784 // https://issues.dlang.org/show_bug.cgi?id=17327 repeated option use
1785 @safe unittest
1786 {
1787     long num = 0;
1788 
1789     string[] args = ["program", "--num", "3"];
1790     getopt(args, "n|num", &num);
1791     assert(num == 3);
1792 
1793     args = ["program", "--num", "3", "--num", "5"];
1794     getopt(args, "n|num", &num);
1795     assert(num == 5);
1796 
1797     args = ["program", "--n", "3", "--num", "5", "-n", "-7"];
1798     getopt(args, "n|num", &num);
1799     assert(num == -7);
1800 
add1()1801     void add1() { num++; }
add2(string option)1802     void add2(string option) { num += 2; }
addN(string option,string value)1803     void addN(string option, string value)
1804     {
1805         import std.conv : to;
1806         num += value.to!long;
1807     }
1808 
1809     num = 0;
1810     args = ["program", "--add1", "--add2", "--add1", "--add", "5", "--add2", "--add", "10"];
1811     getopt(args,
1812            "add1", "Add 1 to num", &add1,
1813            "add2", "Add 2 to num", &add2,
1814            "add", "Add N to num", &addN,);
1815     assert(num == 21);
1816 
1817     bool flag = false;
1818     args = ["program", "--flag"];
1819     getopt(args, "f|flag", "Boolean", &flag);
1820     assert(flag);
1821 
1822     flag = false;
1823     args = ["program", "-f", "-f"];
1824     getopt(args, "f|flag", "Boolean", &flag);
1825     assert(flag);
1826 
1827     flag = false;
1828     args = ["program", "--flag=true", "--flag=false"];
1829     getopt(args, "f|flag", "Boolean", &flag);
1830     assert(!flag);
1831 
1832     flag = false;
1833     args = ["program", "--flag=true", "--flag=false", "-f"];
1834     getopt(args, "f|flag", "Boolean", &flag);
1835     assert(flag);
1836 }
1837 
1838 @safe unittest  // Delegates as callbacks
1839 {
1840     alias TwoArgOptionHandler = void delegate(string option, string value) @safe;
1841 
makeAddNHandler(ref long dest)1842     TwoArgOptionHandler makeAddNHandler(ref long dest)
1843     {
1844         void addN(ref long dest, string n)
1845         {
1846             import std.conv : to;
1847             dest += n.to!long;
1848         }
1849 
1850         return (option, value) => addN(dest, value);
1851     }
1852 
1853     long x = 0;
1854     long y = 0;
1855 
1856     string[] args =
1857         ["program", "--x-plus-1", "--x-plus-1", "--x-plus-5", "--x-plus-n", "10",
1858          "--y-plus-n", "25", "--y-plus-7", "--y-plus-n", "15", "--y-plus-3"];
1859 
1860     getopt(args,
1861            "x-plus-1", "Add one to x", delegate void() { x += 1; },
1862            "x-plus-5", "Add five to x", delegate void(string option) { x += 5; },
1863            "x-plus-n", "Add NUM to x", makeAddNHandler(x),
1864            "y-plus-7", "Add seven to y", delegate void() { y += 7; },
1865            "y-plus-3", "Add three to y", delegate void(string option) { y += 3; },
1866            "y-plus-n", "Add NUM to x", makeAddNHandler(y),);
1867 
1868     assert(x == 17);
1869     assert(y == 50);
1870 }
1871 
1872 // Hyphens at the start of option values;
1873 // https://issues.dlang.org/show_bug.cgi?id=17650
1874 @safe unittest
1875 {
1876     auto args = ["program", "-m", "-5", "-n", "-50", "-c", "-", "-f", "-"];
1877 
1878     int m;
1879     int n;
1880     char c;
1881     string f;
1882 
1883     getopt(args,
1884            "m|mm", "integer", &m,
1885            "n|nn", "integer", &n,
1886            "c|cc", "character", &c,
1887            "f|file", "filename or hyphen for stdin", &f);
1888 
1889     assert(m == -5);
1890     assert(n == -50);
1891     assert(c == '-');
1892     assert(f == "-");
1893 }
1894 
1895 // Hyphen at the option value;
1896 // https://issues.dlang.org/show_bug.cgi?id=22394
1897 @safe unittest
1898 {
1899     auto args = ["program", "-"];
1900 
1901     getopt(args);
1902 
1903     assert(args == ["program", "-"]);
1904 }
1905 
1906 @safe unittest
1907 {
1908     import std.conv;
1909 
1910     import std.array;
1911     import std.string;
1912     bool a;
1913     auto args = ["prog", "--foo"];
1914     auto t = getopt(args, "foo|f", "Help", &a);
1915     string s;
1916     auto app = appender!string();
1917     defaultGetoptFormatter(app, "Some Text", t.options, "\t\t%*s %*s%*s\n%s\n");
1918 
1919     string helpMsg = app.data;
1920     //writeln(helpMsg);
1921     assert(helpMsg.length);
1922     assert(helpMsg.count("\n") == 5, to!string(helpMsg.count("\n")) ~ " "
1923         ~ helpMsg);
1924     assert(helpMsg.indexOf("--foo") != -1);
1925     assert(helpMsg.indexOf("-f") != -1);
1926     assert(helpMsg.indexOf("-h") != -1);
1927     assert(helpMsg.indexOf("--help") != -1);
1928     assert(helpMsg.indexOf("Help") != -1);
1929 
1930     string wanted = "Some Text\n\t\t-f  --foo \nHelp\n\t\t-h --help \nThis help "
1931         ~ "information.\n";
1932     assert(wanted == helpMsg);
1933 }
1934