PageRenderTime 84ms CodeModel.GetById 2ms app.highlight 74ms RepoModel.GetById 1ms app.codeStats 1ms

/std/getopt.d

http://github.com/jcd/phobos
D | 874 lines | 420 code | 44 blank | 410 comment | 127 complexity | 45b6688467d9a8e35065a6a2b7230d71 MD5 | raw file
  1// Written in the D programming language.
  2
  3/**
  4Processing of command line options.
  5
  6The getopt module implements a $(D getopt) function, which adheres to
  7the POSIX syntax for command line options. GNU extensions are
  8supported in the form of long options introduced by a double dash
  9("--"). Support for bundling of command line options, as was the case
 10with the more traditional single-letter approach, is provided but not
 11enabled by default.
 12
 13Macros:
 14
 15WIKI = Phobos/StdGetopt
 16
 17Copyright: Copyright Andrei Alexandrescu 2008 - 2009.
 18License:   <a href="http://www.boost.org/LICENSE_1_0.txt">Boost License 1.0</a>.
 19Authors:   $(WEB erdani.org, Andrei Alexandrescu)
 20Credits:   This module and its documentation are inspired by Perl's $(WEB
 21                   perldoc.perl.org/Getopt/Long.html, Getopt::Long) module. The syntax of
 22                   D's $(D getopt) is simpler than its Perl counterpart because $(D
 23                   getopt) infers the expected parameter types from the static types of
 24                   the passed-in pointers.
 25Source:    $(PHOBOSSRC std/_getopt.d)
 26*/
 27/*
 28         Copyright Andrei Alexandrescu 2008 - 2009.
 29Distributed under the Boost Software License, Version 1.0.
 30   (See accompanying file LICENSE_1_0.txt or copy at
 31         http://www.boost.org/LICENSE_1_0.txt)
 32*/
 33module std.getopt;
 34
 35private import std.array, std.string, std.conv, std.traits, std.bitmanip,
 36    std.algorithm, std.ascii, std.exception;
 37
 38version (unittest)
 39{
 40    import std.stdio; // for testing only
 41}
 42
 43/**
 44 Synopsis:
 45
 46---------
 47import std.getopt;
 48
 49string data = "file.dat";
 50int length = 24;
 51bool verbose;
 52enum Color { no, yes };
 53Color color;
 54
 55void main(string[] args)
 56{
 57  getopt(
 58    args,
 59    "length",  &length,    // numeric
 60    "file",    &data,      // string
 61    "verbose", &verbose,   // flag
 62    "color",   &color);    // enum
 63  ...
 64}
 65---------
 66
 67 The $(D getopt) function takes a reference to the command line
 68 (as received by $(D main)) as its first argument, and an
 69 unbounded number of pairs of strings and pointers. Each string is an
 70 option meant to "fill" the value pointed-to by the pointer to its
 71 right (the "bound" pointer). The option string in the call to
 72 $(D getopt) should not start with a dash.
 73
 74 In all cases, the command-line options that were parsed and used by
 75 $(D getopt) are removed from $(D args). Whatever in the
 76 arguments did not look like an option is left in $(D args) for
 77 further processing by the program. Values that were unaffected by the
 78 options are not touched, so a common idiom is to initialize options
 79 to their defaults and then invoke $(D getopt). If a
 80 command-line argument is recognized as an option with a parameter and
 81 the parameter cannot be parsed properly (e.g. a number is expected
 82 but not present), a $(D Exception) exception is thrown.
 83
 84 Depending on the type of the pointer being bound, $(D getopt)
 85 recognizes the following kinds of options:
 86
 87 $(OL $(LI $(I Boolean options). A lone argument sets the option to $(D true).
 88 Additionally $(B true) or $(B false) can be set within the option separated with
 89 an "=" sign:
 90
 91---------
 92  bool verbose = false, debugging = true;
 93  getopt(args, "verbose", &verbose, "debug", &debugging);
 94---------
 95
 96 To set $(D verbose) to $(D true), invoke the program with either $(D
 97 --verbose) or $(D --verbose=true).
 98
 99 To set $(D debugging) to $(D false), invoke the program with $(D --debugging=false).
100
101 )$(LI $(I Numeric options.) If an option is bound to a numeric type, a
102 number is expected as the next option, or right within the option
103 separated with an "=" sign:
104
105---------
106  uint timeout;
107  getopt(args, "timeout", &timeout);
108---------
109
110To set $(D timeout) to $(D 5), invoke the program with either $(D
111--timeout=5) or $(D --timeout 5).
112
113 $(UL $(LI $(I Incremental options.) If an option name has a "+" suffix and
114 is bound to a numeric type, then the option's value tracks the number
115 of times the option occurred on the command line:
116
117---------
118  uint paranoid;
119  getopt(args, "paranoid+", &paranoid);
120---------
121
122 Invoking the program with "--paranoid --paranoid --paranoid" will set
123 $(D paranoid) to 3. Note that an incremental option never
124 expects a parameter, e.g. in the command line "--paranoid 42
125 --paranoid", the "42" does not set $(D paranoid) to 42;
126 instead, $(D paranoid) is set to 2 and "42" is not considered
127 as part of the normal program arguments.)))
128
129 $(LI $(I Enum options.) If an option is bound to an enum, an enum symbol as a
130 string is expected as the next option, or right within the option separated
131 with an "=" sign:
132
133---------
134  enum Color { no, yes };
135  Color color; // default initialized to Color.no
136  getopt(args, "color", &color);
137---------
138
139To set $(D color) to $(D Color.yes), invoke the program with either $(D
140--color=yes) or $(D --color yes).)
141
142 $(LI $(I String options.) If an option is bound to a string, a string
143 is expected as the next option, or right within the option separated
144 with an "=" sign:
145
146---------
147string outputFile;
148getopt(args, "output", &outputFile);
149---------
150
151 Invoking the program with "--output=myfile.txt" or "--output
152 myfile.txt" will set $(D outputFile) to "myfile.txt". If you want to
153 pass a string containing spaces, you need to use the quoting that is
154 appropriate to your shell, e.g. --output='my file.txt'.)
155
156 $(LI $(I Array options.) If an option is bound to an array, a new
157 element is appended to the array each time the option occurs:
158
159---------
160string[] outputFiles;
161getopt(args, "output", &outputFiles);
162---------
163
164 Invoking the program with "--output=myfile.txt --output=yourfile.txt"
165 or "--output myfile.txt --output yourfile.txt" will set $(D
166 outputFiles) to [ "myfile.txt", "yourfile.txt" ] .)
167
168 $(LI $(I Hash options.) If an option is bound to an associative
169 array, a string of the form "name=value" is expected as the next
170 option, or right within the option separated with an "=" sign:
171
172---------
173double[string] tuningParms;
174getopt(args, "tune", &tuningParms);
175---------
176
177Invoking the program with e.g. "--tune=alpha=0.5 --tune beta=0.6" will
178set $(D tuningParms) to [ "alpha" : 0.5, "beta" : 0.6 ]. In general,
179keys and values can be of any parsable types.)
180
181$(LI $(I Callback options.) An option can be bound to a function or
182delegate with the signature $(D void function()), $(D void function(string option)),
183$(D void function(string option, string value)), or their delegate equivalents.
184
185$(UL $(LI If the callback doesn't take any arguments, the callback is invoked
186whenever the option is seen.) $(LI If the callback takes one string argument,
187the option string (without the leading dash(es)) is passed to the callback.
188After that, the option string is considered handled and removed from the
189options array.
190
191---------
192void main(string[] args)
193{
194  uint verbosityLevel = 1;
195  void myHandler(string option)
196  {
197    if (option == "quiet")
198    {
199      verbosityLevel = 0;
200    }
201    else
202    {
203      assert(option == "verbose");
204      verbosityLevel = 2;
205    }
206  }
207  getopt(args, "verbose", &myHandler, "quiet", &myHandler);
208}
209---------
210
211)$(LI If the callback takes two string arguments, the
212option string is handled as an option with one argument, and parsed
213accordingly. The option and its value are passed to the
214callback. After that, whatever was passed to the callback is
215considered handled and removed from the list.
216
217---------
218void main(string[] args)
219{
220  uint verbosityLevel = 1;
221  void myHandler(string option, string value)
222  {
223    switch (value)
224    {
225      case "quiet": verbosityLevel = 0; break;
226      case "verbose": verbosityLevel = 2; break;
227      case "shouting": verbosityLevel = verbosityLevel.max; break;
228      default :
229        stderr.writeln("Dunno how verbose you want me to be by saying ",
230          value);
231        exit(1);
232    }
233  }
234  getopt(args, "verbosity", &myHandler);
235}
236---------
237))))
238
239$(B Options with multiple names)
240
241Sometimes option synonyms are desirable, e.g. "--verbose",
242"--loquacious", and "--garrulous" should have the same effect. Such
243alternate option names can be included in the option specification,
244using "|" as a separator:
245
246---------
247bool verbose;
248getopt(args, "verbose|loquacious|garrulous", &verbose);
249---------
250
251$(B Case)
252
253By default options are case-insensitive. You can change that behavior
254by passing $(D getopt) the $(D caseSensitive) directive like this:
255
256---------
257bool foo, bar;
258getopt(args,
259    std.getopt.config.caseSensitive,
260    "foo", &foo,
261    "bar", &bar);
262---------
263
264In the example above, "--foo", "--bar", "--FOo", "--bAr" etc. are recognized.
265The directive is active til the end of $(D getopt), or until the
266converse directive $(D caseInsensitive) is encountered:
267
268---------
269bool foo, bar;
270getopt(args,
271    std.getopt.config.caseSensitive,
272    "foo", &foo,
273    std.getopt.config.caseInsensitive,
274    "bar", &bar);
275---------
276
277The option "--Foo" is rejected due to $(D
278std.getopt.config.caseSensitive), but not "--Bar", "--bAr"
279etc. because the directive $(D
280std.getopt.config.caseInsensitive) turned sensitivity off before
281option "bar" was parsed.
282
283$(B "Short" versus "long" options)
284
285Traditionally, programs accepted single-letter options preceded by
286only one dash (e.g. $(D -t)). $(D getopt) accepts such parameters
287seamlessly. When used with a double-dash (e.g. $(D --t)), a
288single-letter option behaves the same as a multi-letter option. When
289used with a single dash, a single-letter option is accepted. If the
290option has a parameter, that must be "stuck" to the option without
291any intervening space or "=":
292
293---------
294uint timeout;
295getopt(args, "timeout|t", &timeout);
296---------
297
298To set $(D timeout) to $(D 5), use either of the following: $(D --timeout=5),
299$(D --timeout 5), $(D --t=5), $(D --t 5), or $(D -t5). Forms such as $(D -t 5)
300and $(D -timeout=5) will be not accepted.
301
302For more details about short options, refer also to the next section.
303
304$(B Bundling)
305
306Single-letter options can be bundled together, i.e. "-abc" is the same as
307$(D "-a -b -c"). By default, this confusing option is turned off. You can
308turn it on with the $(D std.getopt.config.bundling) directive:
309
310---------
311bool foo, bar;
312getopt(args,
313    std.getopt.config.bundling,
314    "foo|f", &foo,
315    "bar|b", &bar);
316---------
317
318In case you want to only enable bundling for some of the parameters,
319bundling can be turned off with $(D std.getopt.config.noBundling).
320
321$(B Passing unrecognized options through)
322
323If an application needs to do its own processing of whichever arguments
324$(D getopt) did not understand, it can pass the
325$(D std.getopt.config.passThrough) directive to $(D getopt):
326
327---------
328bool foo, bar;
329getopt(args,
330    std.getopt.config.passThrough,
331    "foo", &foo,
332    "bar", &bar);
333---------
334
335An unrecognized option such as "--baz" will be found untouched in
336$(D args) after $(D getopt) returns.
337
338$(B Options Terminator)
339
340A lonesome double-dash terminates $(D getopt) gathering. It is used to
341separate program options from other parameters (e.g. options to be passed
342to another program). Invoking the example above with $(D "--foo -- --bar")
343parses foo but leaves "--bar" in $(D args). The double-dash itself is
344removed from the argument array.
345*/
346
347void getopt(T...)(ref string[] args, T opts) {
348    enforce(args.length,
349            "Invalid arguments string passed: program name missing");
350    configuration cfg;
351    return getoptImpl(args, cfg, opts);
352}
353
354/**
355 * Configuration options for $(D getopt). You can pass them to $(D
356 * getopt) in any position, except in between an option string and its
357 * bound pointer.
358 */
359
360enum config {
361    /// Turns case sensitivity on
362    caseSensitive,
363    /// Turns case sensitivity off
364    caseInsensitive,
365    /// Turns bundling on
366    bundling,
367    /// Turns bundling off
368    noBundling,
369    /// Pass unrecognized arguments through
370    passThrough,
371    /// Signal unrecognized arguments as errors
372    noPassThrough,
373    /// Stop at first argument that does not look like an option
374    stopOnFirstNonOption,
375}
376
377private void getoptImpl(T...)(ref string[] args,
378    ref configuration cfg, T opts)
379{
380    static if (opts.length)
381    {
382        static if (is(typeof(opts[0]) : config))
383        {
384            // it's a configuration flag, act on it
385            setConfig(cfg, opts[0]);
386            return getoptImpl(args, cfg, opts[1 .. $]);
387        }
388        else
389        {
390            // it's an option string
391            auto option = to!string(opts[0]);
392            auto receiver = opts[1];
393            bool incremental;
394            // Handle options of the form --blah+
395            if (option.length && option[$ - 1] == autoIncrementChar)
396            {
397                option = option[0 .. $ - 1];
398                incremental = true;
399            }
400            handleOption(option, receiver, args, cfg, incremental);
401            return getoptImpl(args, cfg, opts[2 .. $]);
402        }
403    }
404    else
405    {
406        // no more options to look for, potentially some arguments left
407        foreach (i, a ; args[1 .. $]) {
408            if (!a.length || a[0] != optionChar)
409            {
410                // not an option
411                if (cfg.stopOnFirstNonOption) break;
412                continue;
413            }
414            if (endOfOptions.length && a == endOfOptions)
415            {
416                // Consume the "--"
417                args = args.remove(i + 1);
418                break;
419            }
420            if (!cfg.passThrough)
421            {
422                throw new Exception("Unrecognized option "~a);
423            }
424        }
425    }
426}
427
428void handleOption(R)(string option, R receiver, ref string[] args,
429        ref configuration cfg, bool incremental)
430{
431    // Scan arguments looking for a match for this option
432    for (size_t i = 1; i < args.length; ) {
433        auto a = args[i];
434        if (endOfOptions.length && a == endOfOptions) break;
435        if (cfg.stopOnFirstNonOption && (!a.length || a[0] != optionChar))
436        {
437            // first non-option is end of options
438            break;
439        }
440        // Unbundle bundled arguments if necessary
441        if (cfg.bundling && a.length > 2 && a[0] == optionChar &&
442                a[1] != optionChar)
443        {
444            string[] expanded;
445            foreach (j, dchar c; a[1 .. $])
446            {
447                // If the character is not alpha, stop right there. This allows
448                // e.g. -j100 to work as "pass argument 100 to option -j".
449                if (!isAlpha(c))
450                {
451                    expanded ~= a[j + 1 .. $];
452                    break;
453                }
454                expanded ~= text(optionChar, c);
455            }
456            args = args[0 .. i] ~ expanded ~ args[i + 1 .. $];
457            continue;
458        }
459
460        string val;
461        if (!optMatch(a, option, val, cfg))
462        {
463            ++i;
464            continue;
465        }
466        // found it
467        // from here on, commit to eat args[i]
468        // (and potentially args[i + 1] too, but that comes later)
469        args = args[0 .. i] ~ args[i + 1 .. $];
470
471        static if (is(typeof(*receiver) == bool))
472        {
473            // parse '--b=true/false'
474            if (val.length)
475            {
476                *receiver = parse!(typeof(*receiver))(val);
477                break;
478            }
479
480            // no argument means set it to true
481            *receiver = true;
482            break;
483        }
484        else
485        {
486            // non-boolean option, which might include an argument
487            //enum isCallbackWithOneParameter = is(typeof(receiver("")) : void);
488            enum isCallbackWithLessThanTwoParameters =
489                (is(typeof(receiver) == delegate) || is(typeof(*receiver) == function)) &&
490                !is(typeof(receiver("", "")));
491            if (!isCallbackWithLessThanTwoParameters && !(val.length) && !incremental) {
492                // Eat the next argument too.  Check to make sure there's one
493                // to be eaten first, though.
494                enforce(i < args.length,
495                    "Missing value for argument " ~ a ~ ".");
496                val = args[i];
497                args = args[0 .. i] ~ args[i + 1 .. $];
498            }
499            static if (is(typeof(*receiver) == enum))
500            {
501                // enum receiver
502                *receiver = parse!(typeof(*receiver))(val);
503            }
504            else static if (is(typeof(*receiver) : real))
505            {
506                // numeric receiver
507                if (incremental) ++*receiver;
508                else *receiver = to!(typeof(*receiver))(val);
509            }
510            else static if (is(typeof(*receiver) == string))
511            {
512                // string receiver
513                *receiver = to!(typeof(*receiver))(val);
514            }
515            else static if (is(typeof(receiver) == delegate) ||
516                            is(typeof(*receiver) == function))
517            {
518                static if (is(typeof(receiver("", "")) : void))
519                {
520                    // option with argument
521                    receiver(option, val);
522                }
523                else static if (is(typeof(receiver("")) : void))
524                {
525                    static assert(is(typeof(receiver("")) : void));
526                    // boolean-style receiver
527                    receiver(option);
528                }
529                else
530                {
531                    static assert(is(typeof(receiver()) : void));
532                    // boolean-style receiver without argument
533                    receiver();
534                }
535            }
536            else static if (isArray!(typeof(*receiver)))
537            {
538                // array receiver
539                *receiver ~= [ to!(typeof((*receiver)[0]))(val) ];
540            }
541            else static if (isAssociativeArray!(typeof(*receiver)))
542            {
543                // hash receiver
544                alias typeof(receiver.keys[0]) K;
545                alias typeof(receiver.values[0]) V;
546                auto j = std.string.indexOf(val, assignChar);
547                auto key = val[0 .. j], value = val[j + 1 .. $];
548                (*receiver)[to!(K)(key)] = to!(V)(value);
549            }
550            else
551            {
552                static assert(false, "Dunno how to deal with type " ~
553                        typeof(receiver).stringof);
554            }
555        }
556    }
557}
558
559/**
560   The option character. Defaults to '-' but it can be assigned to
561   prior to calling $(D getopt).
562 */
563dchar optionChar = '-';
564
565/**
566   The string that conventionally marks the end of all
567   options. Defaults to "--" but can be assigned to prior to calling
568   $(D getopt). Assigning an empty string to $(D endOfOptions)
569   effectively disables it.
570 */
571string endOfOptions = "--";
572
573/**
574   The assignment character used in options with parameters. Defaults
575   to '=' but can be assigned to prior to calling $(D getopt).
576 */
577dchar assignChar = '=';
578
579enum autoIncrementChar = '+';
580
581private struct configuration
582{
583    mixin(bitfields!(
584                bool, "caseSensitive",  1,
585                bool, "bundling", 1,
586                bool, "passThrough", 1,
587                bool, "stopOnFirstNonOption", 1,
588                ubyte, "", 4));
589}
590
591private bool optMatch(string arg, string optPattern, ref string value,
592    configuration cfg)
593{
594    //writeln("optMatch:\n  ", arg, "\n  ", optPattern, "\n  ", value);
595    //scope(success) writeln("optMatch result: ", value);
596    if (!arg.length || arg[0] != optionChar) return false;
597    // yank the leading '-'
598    arg = arg[1 .. $];
599    immutable isLong = arg.length > 1 && arg[0] == optionChar;
600    //writeln("isLong: ", isLong);
601    // yank the second '-' if present
602    if (isLong) arg = arg[1 .. $];
603    immutable eqPos = std.string.indexOf(arg, assignChar);
604    if (isLong && eqPos >= 0)
605    {
606        // argument looks like --opt=value
607        value = arg[eqPos + 1 .. $];
608        arg = arg[0 .. eqPos];
609    }
610    else
611    {
612        if (!isLong && !cfg.bundling)
613        {
614            // argument looks like -ovalue and there's no bundling
615            value = arg[1 .. $];
616            arg = arg[0 .. 1];
617        }
618        else
619        {
620            // argument looks like --opt, or -oxyz with bundling
621            value = null;
622        }
623    }
624    //writeln("Arg: ", arg, " pattern: ", optPattern, " value: ", value);
625    // Split the option
626    const variants = split(optPattern, "|");
627    foreach (v ; variants)
628    {
629        //writeln("Trying variant: ", v, " against ", arg);
630        if (arg == v || !cfg.caseSensitive && toUpper(arg) == toUpper(v))
631            return true;
632        if (cfg.bundling && !isLong && v.length == 1
633                && std.string.indexOf(arg, v) >= 0)
634        {
635            //writeln("success");
636            return true;
637        }
638    }
639    return false;
640}
641
642private void setConfig(ref configuration cfg, config option)
643{
644    switch (option)
645    {
646    case config.caseSensitive: cfg.caseSensitive = true; break;
647    case config.caseInsensitive: cfg.caseSensitive = false; break;
648    case config.bundling: cfg.bundling = true; break;
649    case config.noBundling: cfg.bundling = false; break;
650    case config.passThrough: cfg.passThrough = true; break;
651    case config.noPassThrough: cfg.passThrough = false; break;
652    case config.stopOnFirstNonOption:
653        cfg.stopOnFirstNonOption = true; break;
654    default: assert(false);
655    }
656}
657
658unittest
659{
660    uint paranoid = 2;
661    string[] args = (["program.name",
662                      "--paranoid", "--paranoid", "--paranoid"]).dup;
663    getopt(args, "paranoid+", &paranoid);
664    assert(paranoid == 5, to!(string)(paranoid));
665
666    enum Color { no, yes }
667    Color color;
668    args = (["program.name", "--color=yes",]).dup;
669    getopt(args, "color", &color);
670    assert(color, to!(string)(color));
671
672    color = Color.no;
673    args = (["program.name", "--color", "yes",]).dup;
674    getopt(args, "color", &color);
675    assert(color, to!(string)(color));
676
677    string data = "file.dat";
678    int length = 24;
679    bool verbose = false;
680    args = (["program.name", "--length=5",
681                      "--file", "dat.file", "--verbose"]).dup;
682    getopt(
683        args,
684        "length",  &length,
685        "file",    &data,
686        "verbose", &verbose);
687    assert(args.length == 1);
688    assert(data == "dat.file");
689    assert(length == 5);
690    assert(verbose);
691
692    //
693    string[] outputFiles;
694    args = (["program.name", "--output=myfile.txt",
695             "--output", "yourfile.txt"]).dup;
696    getopt(args, "output", &outputFiles);
697    assert(outputFiles.length == 2
698           && outputFiles[0] == "myfile.txt" && outputFiles[0] == "myfile.txt");
699
700    args = (["program.name", "--tune=alpha=0.5",
701             "--tune", "beta=0.6"]).dup;
702    double[string] tuningParms;
703    getopt(args, "tune", &tuningParms);
704    assert(args.length == 1);
705    assert(tuningParms.length == 2);
706    assert(tuningParms["alpha"] == 0.5);
707    assert(tuningParms["beta"] == 0.6);
708
709    uint verbosityLevel = 1;
710    void myHandler(string option)
711    {
712        if (option == "quiet")
713        {
714            verbosityLevel = 0;
715        }
716        else
717        {
718            assert(option == "verbose");
719            verbosityLevel = 2;
720        }
721    }
722    args = (["program.name", "--quiet"]).dup;
723    getopt(args, "verbose", &myHandler, "quiet", &myHandler);
724    assert(verbosityLevel == 0);
725    args = (["program.name", "--verbose"]).dup;
726    getopt(args, "verbose", &myHandler, "quiet", &myHandler);
727    assert(verbosityLevel == 2);
728
729    verbosityLevel = 1;
730    void myHandler2(string option, string value)
731    {
732        assert(option == "verbose");
733        verbosityLevel = 2;
734    }
735    args = (["program.name", "--verbose", "2"]).dup;
736    getopt(args, "verbose", &myHandler2);
737    assert(verbosityLevel == 2);
738
739    verbosityLevel = 1;
740    void myHandler3()
741    {
742        verbosityLevel = 2;
743    }
744    args = (["program.name", "--verbose"]).dup;
745    getopt(args, "verbose", &myHandler3);
746    assert(verbosityLevel == 2);
747
748    bool foo, bar;
749    args = (["program.name", "--foo", "--bAr"]).dup;
750    getopt(args,
751        std.getopt.config.caseSensitive,
752        std.getopt.config.passThrough,
753        "foo", &foo,
754        "bar", &bar);
755    assert(args[1] == "--bAr");
756
757    // test stopOnFirstNonOption
758
759    args = (["program.name", "--foo", "nonoption", "--bar"]).dup;
760    foo = bar = false;
761    getopt(args,
762        std.getopt.config.stopOnFirstNonOption,
763        "foo", &foo,
764        "bar", &bar);
765    assert(foo && !bar && args[1] == "nonoption" && args[2] == "--bar");
766
767    args = (["program.name", "--foo", "nonoption", "--zab"]).dup;
768    foo = bar = false;
769    getopt(args,
770        std.getopt.config.stopOnFirstNonOption,
771        "foo", &foo,
772        "bar", &bar);
773    assert(foo && !bar && args[1] == "nonoption" && args[2] == "--zab");
774
775    args = (["program.name", "--fb1", "--fb2=true", "--tb1=false"]).dup;
776    bool fb1, fb2;
777    bool tb1 = true;
778    getopt(args, "fb1", &fb1, "fb2", &fb2, "tb1", &tb1);
779    assert(fb1 && fb2 && !tb1);
780
781    // test function callbacks
782
783    static class MyEx : Exception
784    {
785        this() { super(""); }
786        this(string option) { this(); this.option = option; }
787        this(string option, string value) { this(option); this.value = value; }
788
789        string option;
790        string value;
791    }
792
793    static void myStaticHandler1() { throw new MyEx(); }
794    args = (["program.name", "--verbose"]).dup;
795    try { getopt(args, "verbose", &myStaticHandler1); assert(0); }
796    catch (MyEx ex) { assert(ex.option is null && ex.value is null); }
797
798    static void myStaticHandler2(string option) { throw new MyEx(option); }
799    args = (["program.name", "--verbose"]).dup;
800    try { getopt(args, "verbose", &myStaticHandler2); assert(0); }
801    catch (MyEx ex) { assert(ex.option == "verbose" && ex.value is null); }
802
803    static void myStaticHandler3(string option, string value) { throw new MyEx(option, value); }
804    args = (["program.name", "--verbose", "2"]).dup;
805    try { getopt(args, "verbose", &myStaticHandler3); assert(0); }
806    catch (MyEx ex) { assert(ex.option == "verbose" && ex.value == "2"); }
807}
808
809unittest
810{
811    // From bugzilla 2142
812    bool f_linenum, f_filename;
813    string[] args = [ "", "-nl" ];
814    getopt
815        (
816            args,
817            std.getopt.config.bundling,
818            //std.getopt.config.caseSensitive,
819            "linenum|l", &f_linenum,
820            "filename|n", &f_filename
821        );
822    assert(f_linenum);
823    assert(f_filename);
824}
825
826unittest
827{
828    // From bugzilla 6887
829    string[] p;
830    string[] args = ["", "-pa"];
831    getopt(args, "p", &p);
832    assert(p.length == 1);
833    assert(p[0] == "a");
834}
835
836unittest
837{
838    // From bugzilla 6888
839    int[string] foo;
840    auto args = ["", "-t", "a=1"];
841    getopt(args, "t", &foo);
842    assert(foo == ["a":1]);
843}
844
845unittest
846{
847    // From bugzilla 9583
848    int opt;
849    auto args = ["prog", "--opt=123", "--", "--a", "--b", "--c"];
850    getopt(args, "opt", &opt);
851    assert(args == ["prog", "--a", "--b", "--c"]);
852}
853
854unittest
855{
856    string foo, bar;
857    auto args = ["prog", "-thello", "-dbar=baz"];
858    getopt(args, "t", &foo, "d", &bar);
859    assert(foo == "hello");
860    assert(bar == "bar=baz");
861    // From bugzilla 5762
862    string a;
863    args = ["prog", "-a-0x12"];
864    getopt(args, config.bundling, "a|addr", &a);
865    assert(a == "-0x12", a);
866    args = ["prog", "--addr=-0x12"];
867    getopt(args, config.bundling, "a|addr", &a);
868    assert(a == "-0x12");
869    // From https://d.puremagic.com/issues/show_bug.cgi?id=11764
870    args = ["main", "-test"];
871    bool opt;
872    args.getopt(config.passThrough, "opt", &opt);
873    assert(args == ["main", "-test"]);
874}