1 /**
2 A cover for D standard library 'getopt' routine (std.getopt.getopt) function that preserves
3 command line argument processing order.
4 
5 This is a work-around to a limitation in getopt, in that getopt does not process arguments
6 in command line order. Instead, getopt processes options in the order specified in the call
7 to getopt. That is, the order in the text of the code. This prevents using command line
8 options in ways where order specified by the user is taken into account.
9 
10 More details here: https://issues.dlang.org/show_bug.cgi?id=16539
11 
12 This should only be used when retaining order is important. Though minimized, there are
13 cases that don't work as expected, the most important involving option arguments starting
14 with a dash. See the getoptInorder function comments for specifics.
15 
16 Copyright (c) 2016-2021, eBay Inc.
17 Initially written by Jon Degenhardt
18 
19 License: Boost License 1.0 (http://boost.org/LICENSE_1_0.txt)
20 
21 Acknowledgments:
22 
23 - Unit tests in this file have been adopted from unit tests for the D programming language
24   std.getopt standard library modules (https://dlang.org/phobos/std_getopt.html).
25 
26   License: Boost License 1.0 (http://boost.org/LICENSE_1_0.txt)
27   Copyright: 2008-2015 Andrei Alexandrescu
28 */
29 
30 module tsv_utils.common.getopt_inorder;
31 
32 import std.getopt;
33 
34 /** checkForUnsupportedConfigOptions walks the option list looking for unsupported config
35  * options.
36  *
37  * Currently everything except std.getopt.config.required is supported. An exception is
38  * thrown if an unsupported config parameter is found.
39  *
40  * Note: A compile-time check would be ideal. That does not appear doable, as values of
41  * parameters cannot be read at compile-type, only the data type (template parameter part).
42  * The generated code creates a test against each 'config' parameter in the options list.
43  * (An option list contains both config and non-config parameters.)
44  */
45 private void checkForUnsupportedConfigOptions(T...)(T opts)
46 {
47     static if (opts.length > 0)
48     {
49         /* opts contains a mixture of types (varadic template parameter). Can
50          * only place tests on config option tests.
51          */
52         static if (is(typeof(opts[0]) : std.getopt.config))
53         {
54             if (opts[0] == std.getopt.config.required)
55             {
56                 throw new Exception(
57                     "getoptInorder does not support std.getopt.config.required");
58             }
59         }
60 
61         checkForUnsupportedConfigOptions(opts[1..$]);
62     }
63 }
64 
65 /** hasStopOnFirstNotOption walks the config list returns true if one of the
66  * options in is std.getopt.config.stopOnFirstNonOption.
67  */
68 private bool hasStopOnFirstNonOption(T...)(T opts)
69 {
70     static if (opts.length > 0)
71     {
72         static if (is(typeof(opts[0]) : std.getopt.config))
73         {
74             if (opts[0] == std.getopt.config.stopOnFirstNonOption) return true;
75         }
76 
77         return hasStopOnFirstNonOption(opts[1..$]);
78     }
79     else
80     {
81         return false;
82     }
83 }
84 
85 unittest
86 {
87     int[] vals;
88 
89     assert(!hasStopOnFirstNonOption(
90                "a|aa", "aaa VAL", &vals,
91                "b|bb", "bbb VAL", &vals,
92                "c|cc", "ccc VAL", &vals,
93                ));
94 
95     assert(hasStopOnFirstNonOption(
96                std.getopt.config.stopOnFirstNonOption,
97                "a|aa", "aaa VAL", &vals,
98                "b|bb", "bbb VAL", &vals,
99                "c|cc", "ccc VAL", &vals,
100                ));
101 
102     assert(hasStopOnFirstNonOption(
103                "a|aa", "aaa VAL", &vals,
104                std.getopt.config.stopOnFirstNonOption,
105                "b|bb", "bbb VAL", &vals,
106                "c|cc", "ccc VAL", &vals,
107                ));
108 
109     assert(hasStopOnFirstNonOption(
110                "a|aa", "aaa VAL", &vals,
111                "b|bb", "bbb VAL", &vals,
112                std.getopt.config.stopOnFirstNonOption,
113                "c|cc", "ccc VAL", &vals,
114                ));
115 
116     assert(hasStopOnFirstNonOption(
117                "a|aa", "aaa VAL", &vals,
118                "b|bb", "bbb VAL", &vals,
119                "c|cc", "ccc VAL", &vals,
120                std.getopt.config.stopOnFirstNonOption,
121                ));
122 }
123 
124 /** getoptInorder is a cover to std.getopt that processes command line options in the
125  * order on the command.
126  *
127  * This is intended for command line argument processing where the order of arguments
128  * on the command line is important. The standard library std.getopt routine processes
129  * options in the order listed in call to getopt. Behavioral changes involve order of
130  * callback processing and array filling.
131  *
132  * Other changes from std.getopt:
133  * $(LIST
134  *     * The std.getopt.config.required option is not supported.
135  *     * Single digits cannot be used as short options. e.g. '-1' cannot be an option.
136  *     * Non-numeric option arguments starting with a dash are not interpreted correctly,
137  *       unless it looks like a negative number or is a single dash. Some examples,
138  *       assuming ("--val") takes one argument:
139  *       $(LIST
140  *           * `["--val", "-9"]` - Okay, "-9" is arg
141  *           * `["--val", "-"]`  - Okay, "-" is arg
142  *           * `["--val", "-a"]` - Not okay, "-a" is treated as separate option.
143  *        )
144  *  )
145  */
146 GetoptResult getoptInorder(T...)(ref string[] args, T opts)
147 {
148     import std.algorithm : min, remove;
149     import std.typecons : tuple;
150 
151     debug import std.stdio;
152     debug writeln("\n=========================\n");
153     debug writeln("[getoptInorder] args: ", args, " opts: ", opts);
154 
155     checkForUnsupportedConfigOptions(opts);
156     bool configHasStopOnFirstNonOption = hasStopOnFirstNonOption(opts);
157 
158     bool isOption(string arg)
159     {
160         import std..string : isNumeric;
161         import std.ascii : isDigit;
162 
163         return
164             (arg == std.getopt.endOfOptions) ||
165             (arg.length >= 2 &&
166              arg[0] == std.getopt.optionChar &&
167              !(arg[1].isDigit && arg.isNumeric));
168     }
169 
170     /* Walk input args, passing one command option at a time to getopt.
171      * Example - Assume the args array is:
172      *
173      *    ["program_name", "--foo", "--bar", "1", "--baz", "2", "3", "--goo"]
174      *
175      * The above array is passed to getopt in the following calls:
176      *
177      *   ["program_name", "--foo"]
178      *   ["program_name", "--bar", "1"]
179      *   ["program_name", "--baz", "2", "3"]
180      *   ["program_name", "--goo"]
181      *
182      * The same output variable references are passed each time, with the result that they
183      * are filled in command option order. The result of the last call to getopt is
184      * returned. This works because getopt is returning two pieces of info: the help
185      * options and whether help was wanted. The first is the same on all calls, so the
186      * last call is fine. The 'help wanted' status needs to be tracked, as it could issued
187      * any point in the command line.
188      *
189      * getopt will remove all arguments accounted for by option processing, but others will
190      * be passed through. These are kept as part of the command args as they are encountered.
191      */
192     GetoptResult result;
193     bool helpWanted = false;   // Need to track if help is ever requested.
194     size_t argStart = 1;       // Start at 1, index zero is program name.
195     bool isLastCall = false;
196 
197     while (!isLastCall)
198     {
199         /* This is the last call to getopt if:
200          * - There are zero or one args left
201          * - The next arg is '--' (endOfOptions), which terminates the arg string.
202          */
203         isLastCall = (args.length <= argStart + 1) || (args[argStart] == std.getopt.endOfOptions);
204 
205         size_t argEnd = args.length;
206         if (!isLastCall)
207         {
208             /* Find the next option. */
209             for (size_t i = argStart + 1; i < args.length; i++)
210             {
211                 if (isOption(args[i]))
212                 {
213                     argEnd = i;
214                     break;
215                 }
216             }
217         }
218 
219         auto currArg = args[0..argEnd].dup;
220         size_t currArgLength = currArg.length;
221         debug writeln("[getoptInorder] Calling getopt. args: ", currArg, " opts: ", opts);
222 
223         result = getopt(currArg, opts);
224         helpWanted |= result.helpWanted;
225 
226         debug writeln("[getoptInorder] After getopt call");
227 
228         size_t numRemoved = currArgLength - currArg.length;
229 
230         if (numRemoved > 0)
231         {
232             debug import std.conv;
233             /* Current arg array was modified. Repeat the modification against the full
234              * array. Assumption in this code is that the removal occurs at the start.
235              * e.g. Assume the args passed to getopt are [program --foo abc def ghi]. If
236              * two args are consumed, assumption is the two consumed are [--foo abc] and
237              * [def ghi] are left as pass-through. This code could go be enhanced to
238              * validate the specific args removed, at present does not do this.
239              */
240             debug writefln("[getoptInorder] Arg modified. argStart: %d, argEnd: %d, currArgLength: %d, currArg.length: %d, numRemoved: %d, currArg: %s",
241                      argStart, argEnd, currArgLength, currArg.length, numRemoved, currArg.to!string);
242             args = args.remove(tuple(argStart, argStart + numRemoved));
243             debug writeln("[getoptInorder] Updated args: ", args);
244         }
245 
246         size_t numPassThrough = currArgLength - (argStart + numRemoved);
247 
248         if (numPassThrough > 0)
249         {
250             argStart += numPassThrough;
251             isLastCall |= configHasStopOnFirstNonOption;
252             debug writeln("[getoptInorder] argStart moved forward: ", numPassThrough, " postions.");
253         }
254     }
255 
256     result.helpWanted = helpWanted;
257 
258     return result;
259 }
260 
261 version(unittest)
262 {
263     import std.exception;
264 }
265 
266 unittest // Issue 16539
267 {
268 
269     // Callback order
270     auto args = ["program",
271                  "-a", "1", "-b", "2", "-c", "3",
272                  "--cc", "4", "--bb", "5", "--aa", "6",
273                  "-a", "7", "-b", "8", "-c", "9"];
274 
275     string optionHandlerResult;
276 
277     void optionHandler(string option, string optionVal)
278     {
279         if (optionHandlerResult.length > 0) optionHandlerResult ~= "; ";
280         optionHandlerResult ~= option ~ "=" ~ optionVal;
281     }
282 
283     getoptInorder(
284         args,
285         "a|aa", "aaa VAL", &optionHandler,
286         "b|bb", "bbb VAL", &optionHandler,
287         "c|cc", "ccc VAL", &optionHandler,
288         );
289 
290     assert(optionHandlerResult == "a|aa=1; b|bb=2; c|cc=3; c|cc=4; b|bb=5; a|aa=6; a|aa=7; b|bb=8; c|cc=9");
291 
292     // Array population order
293     string[] cmdvals;
294 
295     args = ["program",
296             "-a", "1", "-b", "2", "-c", "3",
297             "--cc", "4", "--bb", "5", "--aa", "6",
298             "-a", "7", "-b", "8", "-c", "9"];
299 
300     getoptInorder(
301         args,
302         "a|aa", "aaa VAL", &cmdvals,
303         "b|bb", "bbb VAL", &cmdvals,
304         "c|cc", "ccc VAL", &cmdvals,
305         );
306 
307     assert(cmdvals == ["1", "2", "3", "4", "5", "6", "7", "8", "9"]);
308 }
309 
310 unittest // Dashes
311 {
312     auto args = ["program", "-m", "-5", "-n", "-50", "-c", "-"];
313 
314     int m;
315     int n;
316     char c;
317 
318     getoptInorder(
319         args,
320         "m|mm", "integer", &m,
321         "n|nn", "integer", &n,
322         "c|cc", "character", &c,
323         );
324 
325     assert(m == -5);
326     assert(n == -50);
327     assert(c == '-');
328 }
329 
330 
331 /* NOTE: The following unit tests have been adapted from unit tests in std.getopt.d
332  * See https://github.com/dlang/phobos/blob/master/std/getopt.d and
333  * https://dlang.org/phobos/std_getopt.html.
334  */
335 
336 @system unittest
337 {
338     auto args = ["prog", "--foo", "-b"];
339 
340     bool foo;
341     bool bar;
342     auto rslt = getoptInorder(args, "foo|f", "Some information about foo.", &foo, "bar|b",
343         "Some help message about bar.", &bar);
344 
345     if (rslt.helpWanted)
346     {
347         defaultGetoptPrinter("Some information about the program.",
348             rslt.options);
349     }
350 }
351 
352 @system unittest // bugzilla 15914
353 {
354     bool opt;
355     string[] args = ["program", "-a"];
356     getoptInorder(args, config.passThrough, 'a', &opt);
357     assert(opt);
358     opt = false;
359     args = ["program", "-a"];
360     getoptInorder(args, 'a', &opt);
361     assert(opt);
362     opt = false;
363     args = ["program", "-a"];
364     getoptInorder(args, 'a', "help string", &opt);
365     assert(opt);
366     opt = false;
367     args = ["program", "-a"];
368     getoptInorder(args, config.caseSensitive, 'a', "help string", &opt);
369     assert(opt);
370 
371     version(none)
372     {
373         /* About version(none) - This case crashes, whether calling getoptInorder or simply
374          * getopt. Not clear why. Even converting the whole test case to getopt still results
375          * in failure at this line. (Implies getoptInorder is not itself the cause, but could
376          * involve context in which the test is run.)
377          */
378         assertThrown(getoptInorder(args, "", "forgot to put a string", &opt));
379     }
380 }
381 
382 // 5316 - arrays with arraySep
383 @system unittest
384 {
385     import std.conv;
386 
387     arraySep = ",";
388     scope (exit) arraySep = "";
389 
390     string[] names;
391     auto args = ["program.name", "-nfoo,bar,baz"];
392     getoptInorder(args, "name|n", &names);
393     assert(names == ["foo", "bar", "baz"], to!string(names));
394 
395     names = names.init;
396     args = ["program.name", "-n", "foo,bar,baz"];
397     getoptInorder(args, "name|n", &names);
398     assert(names == ["foo", "bar", "baz"], to!string(names));
399 
400     names = names.init;
401     args = ["program.name", "--name=foo,bar,baz"];
402     getoptInorder(args, "name|n", &names);
403     assert(names == ["foo", "bar", "baz"], to!string(names));
404 
405     names = names.init;
406     args = ["program.name", "--name", "foo,bar,baz"];
407     getoptInorder(args, "name|n", &names);
408     assert(names == ["foo", "bar", "baz"], to!string(names));
409 }
410 
411 // 5316 - associative arrays with arraySep
412 @system unittest
413 {
414     import std.conv;
415 
416     arraySep = ",";
417     scope (exit) arraySep = "";
418 
419     int[string] values;
420     values = values.init;
421     auto args = ["program.name", "-vfoo=0,bar=1,baz=2"];
422     getoptInorder(args, "values|v", &values);
423     assert(values == ["foo":0, "bar":1, "baz":2], to!string(values));
424 
425     values = values.init;
426     args = ["program.name", "-v", "foo=0,bar=1,baz=2"];
427     getoptInorder(args, "values|v", &values);
428     assert(values == ["foo":0, "bar":1, "baz":2], to!string(values));
429 
430     values = values.init;
431     args = ["program.name", "--values=foo=0,bar=1,baz=2"];
432     getoptInorder(args, "values|t", &values);
433     assert(values == ["foo":0, "bar":1, "baz":2], to!string(values));
434 
435     values = values.init;
436     args = ["program.name", "--values", "foo=0,bar=1,baz=2"];
437     getoptInorder(args, "values|v", &values);
438     assert(values == ["foo":0, "bar":1, "baz":2], to!string(values));
439 }
440 
441 @system unittest
442 {
443     import std.conv;
444     import std.math;
445 
446     bool closeEnough(T)(T x, T y)
447     {
448         static if (__VERSION__ >= 2096) return isClose(x, y);
449         else return approxEqual(x, y);
450     }
451 
452     uint paranoid = 2;
453     string[] args = ["program.name", "--paranoid", "--paranoid", "--paranoid"];
454     getoptInorder(args, "paranoid+", &paranoid);
455     assert(paranoid == 5, to!(string)(paranoid));
456 
457     enum Color { no, yes }
458     Color color;
459     args = ["program.name", "--color=yes",];
460     getoptInorder(args, "color", &color);
461     assert(color, to!(string)(color));
462 
463     color = Color.no;
464     args = ["program.name", "--color", "yes",];
465     getoptInorder(args, "color", &color);
466     assert(color, to!(string)(color));
467 
468     string data = "file.dat";
469     int length = 24;
470     bool verbose = false;
471     args = ["program.name", "--length=5", "--file", "dat.file", "--verbose"];
472     getoptInorder(
473         args,
474         "length",  &length,
475         "file",    &data,
476         "verbose", &verbose);
477     assert(args.length == 1);
478     assert(data == "dat.file");
479     assert(length == 5);
480     assert(verbose);
481 
482     //
483     string[] outputFiles;
484     args = ["program.name", "--output=myfile.txt", "--output", "yourfile.txt"];
485     getoptInorder(args, "output", &outputFiles);
486     assert(outputFiles.length == 2
487            && outputFiles[0] == "myfile.txt" && outputFiles[1] == "yourfile.txt");
488 
489     outputFiles = [];
490     arraySep = ",";
491     args = ["program.name", "--output", "myfile.txt,yourfile.txt"];
492     getoptInorder(args, "output", &outputFiles);
493     assert(outputFiles.length == 2
494            && outputFiles[0] == "myfile.txt" && outputFiles[1] == "yourfile.txt");
495     arraySep = "";
496 
497     foreach (testArgs;
498         [["program.name", "--tune=alpha=0.5", "--tune", "beta=0.6"],
499          ["program.name", "--tune=alpha=0.5,beta=0.6"],
500          ["program.name", "--tune", "alpha=0.5,beta=0.6"]])
501     {
502         arraySep = ",";
503         double[string] tuningParms;
504         getoptInorder(testArgs, "tune", &tuningParms);
505         assert(testArgs.length == 1);
506         assert(tuningParms.length == 2);
507         assert(closeEnough(tuningParms["alpha"], 0.5));
508         assert(closeEnough(tuningParms["beta"], 0.6));
509         arraySep = "";
510     }
511 
512     uint verbosityLevel = 1;
513     void myHandler(string option)
514     {
515         if (option == "quiet")
516         {
517             verbosityLevel = 0;
518         }
519         else
520         {
521             assert(option == "verbose");
522             verbosityLevel = 2;
523         }
524     }
525     args = ["program.name", "--quiet"];
526     getoptInorder(args, "verbose", &myHandler, "quiet", &myHandler);
527     assert(verbosityLevel == 0);
528     args = ["program.name", "--verbose"];
529     getoptInorder(args, "verbose", &myHandler, "quiet", &myHandler);
530     assert(verbosityLevel == 2);
531 
532     verbosityLevel = 1;
533     void myHandler2(string option, string value)
534     {
535         assert(option == "verbose");
536         verbosityLevel = 2;
537     }
538     args = ["program.name", "--verbose", "2"];
539     getoptInorder(args, "verbose", &myHandler2);
540     assert(verbosityLevel == 2);
541 
542     verbosityLevel = 1;
543     void myHandler3()
544     {
545         verbosityLevel = 2;
546     }
547     args = ["program.name", "--verbose"];
548     getoptInorder(args, "verbose", &myHandler3);
549     assert(verbosityLevel == 2);
550 
551     bool foo, bar;
552     args = ["program.name", "--foo", "--bAr"];
553     getoptInorder(args,
554         std.getopt.config.caseSensitive,
555         std.getopt.config.passThrough,
556         "foo", &foo,
557         "bar", &bar);
558     assert(args[1] == "--bAr");
559 
560     // test stopOnFirstNonOption
561 
562     args = ["program.name", "--foo", "nonoption", "--bar"];
563     foo = bar = false;
564     getoptInorder(args,
565                   std.getopt.config.stopOnFirstNonOption,
566                   "foo", &foo,
567                   "bar", &bar);
568     assert(foo && !bar && args[1] == "nonoption" && args[2] == "--bar");
569 
570     args = ["program.name", "--foo", "nonoption", "--zab"];
571     foo = bar = false;
572     getoptInorder(args,
573                   std.getopt.config.stopOnFirstNonOption,
574                   "foo", &foo,
575                   "bar", &bar);
576     assert(foo && !bar && args[1] == "nonoption" && args[2] == "--zab");
577 
578     args = ["program.name", "--fb1", "--fb2=true", "--tb1=false"];
579     bool fb1, fb2;
580     bool tb1 = true;
581     getoptInorder(args, "fb1", &fb1, "fb2", &fb2, "tb1", &tb1);
582     assert(fb1 && fb2 && !tb1);
583 
584     // test keepEndOfOptions
585 
586     args = ["program.name", "--foo", "nonoption", "--bar", "--", "--baz"];
587     getoptInorder(args,
588         std.getopt.config.keepEndOfOptions,
589         "foo", &foo,
590         "bar", &bar);
591     assert(args == ["program.name", "nonoption", "--", "--baz"]);
592 
593     // Ensure old behavior without the keepEndOfOptions
594 
595     args = ["program.name", "--foo", "nonoption", "--bar", "--", "--baz"];
596     getoptInorder(args,
597         "foo", &foo,
598         "bar", &bar);
599     assert(args == ["program.name", "nonoption", "--baz"]);
600 
601     // test function callbacks
602 
603     static class MyEx : Exception
604     {
605         this() { super(""); }
606         this(string option) { this(); this.option = option; }
607         this(string option, string value) { this(option); this.value = value; }
608 
609         string option;
610         string value;
611     }
612 
613     static void myStaticHandler1() { throw new MyEx(); }
614     args = ["program.name", "--verbose"];
615     try { getoptInorder(args, "verbose", &myStaticHandler1); assert(0); }
616     catch (MyEx ex) { assert(ex.option is null && ex.value is null); }
617 
618     static void myStaticHandler2(string option) { throw new MyEx(option); }
619     args = ["program.name", "--verbose"];
620     try { getoptInorder(args, "verbose", &myStaticHandler2); assert(0); }
621     catch (MyEx ex) { assert(ex.option == "verbose" && ex.value is null); }
622 
623     static void myStaticHandler3(string option, string value) { throw new MyEx(option, value); }
624     args = ["program.name", "--verbose", "2"];
625     try { getoptInorder(args, "verbose", &myStaticHandler3); assert(0); }
626     catch (MyEx ex) { assert(ex.option == "verbose" && ex.value == "2"); }
627 }
628 
629 @system unittest
630 {
631     // From bugzilla 2142
632     bool f_linenum, f_filename;
633     string[] args = [ "", "-nl" ];
634     getoptInorder
635         (
636             args,
637             std.getopt.config.bundling,
638             //std.getopt.config.caseSensitive,
639             "linenum|l", &f_linenum,
640             "filename|n", &f_filename
641         );
642     assert(f_linenum);
643     assert(f_filename);
644 }
645 
646 @system unittest
647 {
648     // From bugzilla 6887
649     string[] p;
650     string[] args = ["", "-pa"];
651     getoptInorder(args, "p", &p);
652     assert(p.length == 1);
653     assert(p[0] == "a");
654 }
655 
656 @system unittest
657 {
658     // From bugzilla 6888
659     int[string] foo;
660     auto args = ["", "-t", "a=1"];
661     getoptInorder(args, "t", &foo);
662     assert(foo == ["a":1]);
663 }
664 
665 @system unittest
666 {
667     // From bugzilla 9583
668     int opt;
669     auto args = ["prog", "--opt=123", "--", "--a", "--b", "--c"];
670     getoptInorder(args, "opt", &opt);
671     assert(args == ["prog", "--a", "--b", "--c"]);
672 }
673 
674 @system unittest
675 {
676     string foo, bar;
677     auto args = ["prog", "-thello", "-dbar=baz"];
678     getoptInorder(args, "t", &foo, "d", &bar);
679     assert(foo == "hello");
680     assert(bar == "bar=baz");
681 
682     // From bugzilla 5762
683     string a;
684     args = ["prog", "-a-0x12"];
685     getoptInorder(args, config.bundling, "a|addr", &a);
686     assert(a == "-0x12", a);
687     args = ["prog", "--addr=-0x12"];
688     getoptInorder(args, config.bundling, "a|addr", &a);
689     assert(a == "-0x12");
690 
691     // From https://d.puremagic.com/issues/show_bug.cgi?id=11764
692     args = ["main", "-test"];
693     bool opt;
694     args.getoptInorder(config.passThrough, "opt", &opt);
695     assert(args == ["main", "-test"]);
696 
697     // From https://issues.dlang.org/show_bug.cgi?id=15220
698     args = ["main", "-o=str"];
699     string o;
700     args.getoptInorder("o", &o);
701     assert(o == "str");
702 
703     args = ["main", "-o=str"];
704     o = null;
705     args.getoptInorder(config.bundling, "o", &o);
706     assert(o == "str");
707 }
708 
709 @system unittest // 5228
710 {
711     import std.exception;
712     import std.conv;
713 
714     auto args = ["prog", "--foo=bar"];
715     int abc;
716     assertThrown!GetOptException(getoptInorder(args, "abc", &abc));
717 
718     args = ["prog", "--abc=string"];
719     assertThrown!ConvException(getoptInorder(args, "abc", &abc));
720 }
721 
722 @system unittest // From bugzilla 7693
723 {
724     import std.exception;
725 
726     enum Foo {
727         bar,
728         baz
729     }
730 
731     auto args = ["prog", "--foo=barZZZ"];
732     Foo foo;
733     assertThrown(getoptInorder(args, "foo", &foo));
734     args = ["prog", "--foo=bar"];
735     assertNotThrown(getoptInorder(args, "foo", &foo));
736     args = ["prog", "--foo", "barZZZ"];
737     assertThrown(getoptInorder(args, "foo", &foo));
738     args = ["prog", "--foo", "baz"];
739     assertNotThrown(getoptInorder(args, "foo", &foo));
740 }
741 
742 @system unittest // same bug as 7693 only for bool
743 {
744     import std.exception;
745 
746     auto args = ["prog", "--foo=truefoobar"];
747     bool foo;
748     assertThrown(getoptInorder(args, "foo", &foo));
749     args = ["prog", "--foo"];
750     getoptInorder(args, "foo", &foo);
751     assert(foo);
752 }
753 
754 @system unittest
755 {
756     bool foo;
757     auto args = ["prog", "--foo"];
758     getoptInorder(args, "foo", &foo);
759     assert(foo);
760 }
761 
762 @system unittest
763 {
764     bool foo;
765     bool bar;
766     auto args = ["prog", "--foo", "-b"];
767     getoptInorder(args, config.caseInsensitive,"foo|f", "Some foo", &foo,
768         config.caseSensitive, "bar|b", "Some bar", &bar);
769     assert(foo);
770     assert(bar);
771 }
772 
773 @system unittest
774 {
775     bool foo;
776     bool bar;
777     auto args = ["prog", "-b", "--foo", "-z"];
778     assertThrown(
779         getoptInorder(args, config.caseInsensitive, config.required, "foo|f", "Some foo",
780                       &foo, config.caseSensitive, "bar|b", "Some bar", &bar,
781                       config.passThrough));
782     version(none) // These tests only appy if config.required is supported.
783     {
784         assert(foo);
785         assert(bar);
786     }
787 }
788 
789 @system unittest
790 {
791     import std.exception;
792 
793     bool foo;
794     bool bar;
795     auto args = ["prog", "-b", "-z"];
796     assertThrown(getoptInorder(args, config.caseInsensitive, config.required, "foo|f",
797                                "Some foo", &foo, config.caseSensitive, "bar|b", "Some bar", &bar,
798                                config.passThrough));
799 }
800 
801 @system unittest
802 {
803     version(none)  // No point running this test without config.required support.
804     {
805         import std.exception;
806 
807         bool foo;
808         bool bar;
809         auto args = ["prog", "--foo", "-z"];
810         assertNotThrown(getoptInorder(args, config.caseInsensitive, config.required,
811                                       "foo|f", "Some foo", &foo, config.caseSensitive, "bar|b", "Some bar",
812                                       &bar, config.passThrough));
813         assert(foo);
814         assert(!bar);
815     }
816 }
817 
818 @system unittest
819 {
820     bool foo;
821     auto args = ["prog", "-f"];
822     auto r = getoptInorder(args, config.caseInsensitive, "help|f", "Some foo", &foo);
823     assert(foo);
824     assert(!r.helpWanted);
825 }
826 
827 @safe unittest // implicit help option without config.passThrough
828 {
829     string[] args = ["program", "--help"];
830     auto r = getoptInorder(args);
831     assert(r.helpWanted);
832 }
833 
834 // Issue 13316 - std.getopt: implicit help option breaks the next argument
835 @system unittest
836 {
837     string[] args = ["program", "--help", "--", "something"];
838     getoptInorder(args);
839     assert(args == ["program", "something"]);
840 
841     args = ["program", "--help", "--"];
842     getoptInorder(args);
843     assert(args == ["program"]);
844 
845     bool b;
846     args = ["program", "--help", "nonoption", "--option"];
847     getoptInorder(args, config.stopOnFirstNonOption, "option", &b);
848     assert(args == ["program", "nonoption", "--option"]);
849 }
850 
851 // Issue 13317 - std.getopt: endOfOptions broken when it doesn't look like an option
852 @system unittest
853 {
854     auto endOfOptionsBackup = endOfOptions;
855     scope(exit) endOfOptions = endOfOptionsBackup;
856     endOfOptions = "endofoptions";
857     string[] args = ["program", "endofoptions", "--option"];
858     bool b = false;
859     getoptInorder(args, "option", &b);
860     assert(!b);
861     assert(args == ["program", "--option"]);
862 }
863 
864 @system unittest
865 {
866     import std.conv;
867 
868     import std.array;
869     import std..string;
870     bool a;
871     auto args = ["prog", "--foo"];
872     auto t = getoptInorder(args, "foo|f", "Help", &a);
873     string s;
874     auto app = appender!string();
875     defaultGetoptFormatter(app, "Some Text", t.options);
876 
877     string helpMsg = app.data;
878     //writeln(helpMsg);
879     assert(helpMsg.length);
880     assert(helpMsg.count("\n") == 3, to!string(helpMsg.count("\n")) ~ " "
881         ~ helpMsg);
882     assert(helpMsg.indexOf("--foo") != -1);
883     assert(helpMsg.indexOf("-f") != -1);
884     assert(helpMsg.indexOf("-h") != -1);
885     assert(helpMsg.indexOf("--help") != -1);
886     assert(helpMsg.indexOf("Help") != -1);
887 
888     string wanted = "Some Text\n-f  --foo Help\n-h --help This help "
889         ~ "information.\n";
890     assert(wanted == helpMsg);
891 }
892 
893 @system unittest
894 {
895     version(none)  // No point in running this unit test without config.required support
896     {
897         import std.conv;
898         import std..string;
899         import std.array ;
900         bool a;
901         auto args = ["prog", "--foo"];
902         auto t = getoptInorder(args, config.required, "foo|f", "Help", &a);
903         string s;
904         auto app = appender!string();
905         defaultGetoptFormatter(app, "Some Text", t.options);
906 
907         string helpMsg = app.data;
908         //writeln(helpMsg);
909         assert(helpMsg.length);
910         assert(helpMsg.count("\n") == 3, to!string(helpMsg.count("\n")) ~ " "
911                ~ helpMsg);
912         assert(helpMsg.indexOf("Required:") != -1);
913         assert(helpMsg.indexOf("--foo") != -1);
914         assert(helpMsg.indexOf("-f") != -1);
915         assert(helpMsg.indexOf("-h") != -1);
916         assert(helpMsg.indexOf("--help") != -1);
917         assert(helpMsg.indexOf("Help") != -1);
918 
919         string wanted = "Some Text\n-f  --foo Required: Help\n-h --help "
920             ~ "          This help information.\n";
921         assert(wanted == helpMsg, helpMsg ~ wanted);
922     }
923 }
924 
925 @system unittest // Issue 14724
926 {
927     version(none)  // No point running this unit test without config.required support
928     {
929         bool a;
930         auto args = ["prog", "--help"];
931         GetoptResult rslt;
932         try
933         {
934             rslt = getoptInorder(args, config.required, "foo|f", "bool a", &a);
935         }
936         catch (Exception e)
937         {
938             enum errorMsg = "If the request for help was passed required options" ~
939                 "must not be set.";
940             assert(false, errorMsg);
941         }
942 
943         assert(rslt.helpWanted);
944     }
945 }