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