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-2020, 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     uint paranoid = 2;
447     string[] args = ["program.name", "--paranoid", "--paranoid", "--paranoid"];
448     getoptInorder(args, "paranoid+", &paranoid);
449     assert(paranoid == 5, to!(string)(paranoid));
450 
451     enum Color { no, yes }
452     Color color;
453     args = ["program.name", "--color=yes",];
454     getoptInorder(args, "color", &color);
455     assert(color, to!(string)(color));
456 
457     color = Color.no;
458     args = ["program.name", "--color", "yes",];
459     getoptInorder(args, "color", &color);
460     assert(color, to!(string)(color));
461 
462     string data = "file.dat";
463     int length = 24;
464     bool verbose = false;
465     args = ["program.name", "--length=5", "--file", "dat.file", "--verbose"];
466     getoptInorder(
467         args,
468         "length",  &length,
469         "file",    &data,
470         "verbose", &verbose);
471     assert(args.length == 1);
472     assert(data == "dat.file");
473     assert(length == 5);
474     assert(verbose);
475 
476     //
477     string[] outputFiles;
478     args = ["program.name", "--output=myfile.txt", "--output", "yourfile.txt"];
479     getoptInorder(args, "output", &outputFiles);
480     assert(outputFiles.length == 2
481            && outputFiles[0] == "myfile.txt" && outputFiles[1] == "yourfile.txt");
482 
483     outputFiles = [];
484     arraySep = ",";
485     args = ["program.name", "--output", "myfile.txt,yourfile.txt"];
486     getoptInorder(args, "output", &outputFiles);
487     assert(outputFiles.length == 2
488            && outputFiles[0] == "myfile.txt" && outputFiles[1] == "yourfile.txt");
489     arraySep = "";
490 
491     foreach (testArgs;
492         [["program.name", "--tune=alpha=0.5", "--tune", "beta=0.6"],
493          ["program.name", "--tune=alpha=0.5,beta=0.6"],
494          ["program.name", "--tune", "alpha=0.5,beta=0.6"]])
495     {
496         arraySep = ",";
497         double[string] tuningParms;
498         getoptInorder(testArgs, "tune", &tuningParms);
499         assert(testArgs.length == 1);
500         assert(tuningParms.length == 2);
501         assert(approxEqual(tuningParms["alpha"], 0.5));
502         assert(approxEqual(tuningParms["beta"], 0.6));
503         arraySep = "";
504     }
505 
506     uint verbosityLevel = 1;
507     void myHandler(string option)
508     {
509         if (option == "quiet")
510         {
511             verbosityLevel = 0;
512         }
513         else
514         {
515             assert(option == "verbose");
516             verbosityLevel = 2;
517         }
518     }
519     args = ["program.name", "--quiet"];
520     getoptInorder(args, "verbose", &myHandler, "quiet", &myHandler);
521     assert(verbosityLevel == 0);
522     args = ["program.name", "--verbose"];
523     getoptInorder(args, "verbose", &myHandler, "quiet", &myHandler);
524     assert(verbosityLevel == 2);
525 
526     verbosityLevel = 1;
527     void myHandler2(string option, string value)
528     {
529         assert(option == "verbose");
530         verbosityLevel = 2;
531     }
532     args = ["program.name", "--verbose", "2"];
533     getoptInorder(args, "verbose", &myHandler2);
534     assert(verbosityLevel == 2);
535 
536     verbosityLevel = 1;
537     void myHandler3()
538     {
539         verbosityLevel = 2;
540     }
541     args = ["program.name", "--verbose"];
542     getoptInorder(args, "verbose", &myHandler3);
543     assert(verbosityLevel == 2);
544 
545     bool foo, bar;
546     args = ["program.name", "--foo", "--bAr"];
547     getoptInorder(args,
548         std.getopt.config.caseSensitive,
549         std.getopt.config.passThrough,
550         "foo", &foo,
551         "bar", &bar);
552     assert(args[1] == "--bAr");
553 
554     // test stopOnFirstNonOption
555 
556     args = ["program.name", "--foo", "nonoption", "--bar"];
557     foo = bar = false;
558     getoptInorder(args,
559                   std.getopt.config.stopOnFirstNonOption,
560                   "foo", &foo,
561                   "bar", &bar);
562     assert(foo && !bar && args[1] == "nonoption" && args[2] == "--bar");
563 
564     args = ["program.name", "--foo", "nonoption", "--zab"];
565     foo = bar = false;
566     getoptInorder(args,
567                   std.getopt.config.stopOnFirstNonOption,
568                   "foo", &foo,
569                   "bar", &bar);
570     assert(foo && !bar && args[1] == "nonoption" && args[2] == "--zab");
571 
572     args = ["program.name", "--fb1", "--fb2=true", "--tb1=false"];
573     bool fb1, fb2;
574     bool tb1 = true;
575     getoptInorder(args, "fb1", &fb1, "fb2", &fb2, "tb1", &tb1);
576     assert(fb1 && fb2 && !tb1);
577 
578     // test keepEndOfOptions
579 
580     args = ["program.name", "--foo", "nonoption", "--bar", "--", "--baz"];
581     getoptInorder(args,
582         std.getopt.config.keepEndOfOptions,
583         "foo", &foo,
584         "bar", &bar);
585     assert(args == ["program.name", "nonoption", "--", "--baz"]);
586 
587     // Ensure old behavior without the keepEndOfOptions
588 
589     args = ["program.name", "--foo", "nonoption", "--bar", "--", "--baz"];
590     getoptInorder(args,
591         "foo", &foo,
592         "bar", &bar);
593     assert(args == ["program.name", "nonoption", "--baz"]);
594 
595     // test function callbacks
596 
597     static class MyEx : Exception
598     {
599         this() { super(""); }
600         this(string option) { this(); this.option = option; }
601         this(string option, string value) { this(option); this.value = value; }
602 
603         string option;
604         string value;
605     }
606 
607     static void myStaticHandler1() { throw new MyEx(); }
608     args = ["program.name", "--verbose"];
609     try { getoptInorder(args, "verbose", &myStaticHandler1); assert(0); }
610     catch (MyEx ex) { assert(ex.option is null && ex.value is null); }
611 
612     static void myStaticHandler2(string option) { throw new MyEx(option); }
613     args = ["program.name", "--verbose"];
614     try { getoptInorder(args, "verbose", &myStaticHandler2); assert(0); }
615     catch (MyEx ex) { assert(ex.option == "verbose" && ex.value is null); }
616 
617     static void myStaticHandler3(string option, string value) { throw new MyEx(option, value); }
618     args = ["program.name", "--verbose", "2"];
619     try { getoptInorder(args, "verbose", &myStaticHandler3); assert(0); }
620     catch (MyEx ex) { assert(ex.option == "verbose" && ex.value == "2"); }
621 }
622 
623 @system unittest
624 {
625     // From bugzilla 2142
626     bool f_linenum, f_filename;
627     string[] args = [ "", "-nl" ];
628     getoptInorder
629         (
630             args,
631             std.getopt.config.bundling,
632             //std.getopt.config.caseSensitive,
633             "linenum|l", &f_linenum,
634             "filename|n", &f_filename
635         );
636     assert(f_linenum);
637     assert(f_filename);
638 }
639 
640 @system unittest
641 {
642     // From bugzilla 6887
643     string[] p;
644     string[] args = ["", "-pa"];
645     getoptInorder(args, "p", &p);
646     assert(p.length == 1);
647     assert(p[0] == "a");
648 }
649 
650 @system unittest
651 {
652     // From bugzilla 6888
653     int[string] foo;
654     auto args = ["", "-t", "a=1"];
655     getoptInorder(args, "t", &foo);
656     assert(foo == ["a":1]);
657 }
658 
659 @system unittest
660 {
661     // From bugzilla 9583
662     int opt;
663     auto args = ["prog", "--opt=123", "--", "--a", "--b", "--c"];
664     getoptInorder(args, "opt", &opt);
665     assert(args == ["prog", "--a", "--b", "--c"]);
666 }
667 
668 @system unittest
669 {
670     string foo, bar;
671     auto args = ["prog", "-thello", "-dbar=baz"];
672     getoptInorder(args, "t", &foo, "d", &bar);
673     assert(foo == "hello");
674     assert(bar == "bar=baz");
675 
676     // From bugzilla 5762
677     string a;
678     args = ["prog", "-a-0x12"];
679     getoptInorder(args, config.bundling, "a|addr", &a);
680     assert(a == "-0x12", a);
681     args = ["prog", "--addr=-0x12"];
682     getoptInorder(args, config.bundling, "a|addr", &a);
683     assert(a == "-0x12");
684 
685     // From https://d.puremagic.com/issues/show_bug.cgi?id=11764
686     args = ["main", "-test"];
687     bool opt;
688     args.getoptInorder(config.passThrough, "opt", &opt);
689     assert(args == ["main", "-test"]);
690 
691     // From https://issues.dlang.org/show_bug.cgi?id=15220
692     args = ["main", "-o=str"];
693     string o;
694     args.getoptInorder("o", &o);
695     assert(o == "str");
696 
697     args = ["main", "-o=str"];
698     o = null;
699     args.getoptInorder(config.bundling, "o", &o);
700     assert(o == "str");
701 }
702 
703 @system unittest // 5228
704 {
705     import std.exception;
706     import std.conv;
707 
708     auto args = ["prog", "--foo=bar"];
709     int abc;
710     assertThrown!GetOptException(getoptInorder(args, "abc", &abc));
711 
712     args = ["prog", "--abc=string"];
713     assertThrown!ConvException(getoptInorder(args, "abc", &abc));
714 }
715 
716 @system unittest // From bugzilla 7693
717 {
718     import std.exception;
719 
720     enum Foo {
721         bar,
722         baz
723     }
724 
725     auto args = ["prog", "--foo=barZZZ"];
726     Foo foo;
727     assertThrown(getoptInorder(args, "foo", &foo));
728     args = ["prog", "--foo=bar"];
729     assertNotThrown(getoptInorder(args, "foo", &foo));
730     args = ["prog", "--foo", "barZZZ"];
731     assertThrown(getoptInorder(args, "foo", &foo));
732     args = ["prog", "--foo", "baz"];
733     assertNotThrown(getoptInorder(args, "foo", &foo));
734 }
735 
736 @system unittest // same bug as 7693 only for bool
737 {
738     import std.exception;
739 
740     auto args = ["prog", "--foo=truefoobar"];
741     bool foo;
742     assertThrown(getoptInorder(args, "foo", &foo));
743     args = ["prog", "--foo"];
744     getoptInorder(args, "foo", &foo);
745     assert(foo);
746 }
747 
748 @system unittest
749 {
750     bool foo;
751     auto args = ["prog", "--foo"];
752     getoptInorder(args, "foo", &foo);
753     assert(foo);
754 }
755 
756 @system unittest
757 {
758     bool foo;
759     bool bar;
760     auto args = ["prog", "--foo", "-b"];
761     getoptInorder(args, config.caseInsensitive,"foo|f", "Some foo", &foo,
762         config.caseSensitive, "bar|b", "Some bar", &bar);
763     assert(foo);
764     assert(bar);
765 }
766 
767 @system unittest
768 {
769     bool foo;
770     bool bar;
771     auto args = ["prog", "-b", "--foo", "-z"];
772     assertThrown(
773         getoptInorder(args, config.caseInsensitive, config.required, "foo|f", "Some foo",
774                       &foo, config.caseSensitive, "bar|b", "Some bar", &bar,
775                       config.passThrough));
776     version(none) // These tests only appy if config.required is supported.
777     {
778         assert(foo);
779         assert(bar);
780     }
781 }
782 
783 @system unittest
784 {
785     import std.exception;
786 
787     bool foo;
788     bool bar;
789     auto args = ["prog", "-b", "-z"];
790     assertThrown(getoptInorder(args, config.caseInsensitive, config.required, "foo|f",
791                                "Some foo", &foo, config.caseSensitive, "bar|b", "Some bar", &bar,
792                                config.passThrough));
793 }
794 
795 @system unittest
796 {
797     version(none)  // No point running this test without config.required support.
798     {
799         import std.exception;
800 
801         bool foo;
802         bool bar;
803         auto args = ["prog", "--foo", "-z"];
804         assertNotThrown(getoptInorder(args, config.caseInsensitive, config.required,
805                                       "foo|f", "Some foo", &foo, config.caseSensitive, "bar|b", "Some bar",
806                                       &bar, config.passThrough));
807         assert(foo);
808         assert(!bar);
809     }
810 }
811 
812 @system unittest
813 {
814     bool foo;
815     auto args = ["prog", "-f"];
816     auto r = getoptInorder(args, config.caseInsensitive, "help|f", "Some foo", &foo);
817     assert(foo);
818     assert(!r.helpWanted);
819 }
820 
821 @safe unittest // implicit help option without config.passThrough
822 {
823     string[] args = ["program", "--help"];
824     auto r = getoptInorder(args);
825     assert(r.helpWanted);
826 }
827 
828 // Issue 13316 - std.getopt: implicit help option breaks the next argument
829 @system unittest
830 {
831     string[] args = ["program", "--help", "--", "something"];
832     getoptInorder(args);
833     assert(args == ["program", "something"]);
834 
835     args = ["program", "--help", "--"];
836     getoptInorder(args);
837     assert(args == ["program"]);
838 
839     bool b;
840     args = ["program", "--help", "nonoption", "--option"];
841     getoptInorder(args, config.stopOnFirstNonOption, "option", &b);
842     assert(args == ["program", "nonoption", "--option"]);
843 }
844 
845 // Issue 13317 - std.getopt: endOfOptions broken when it doesn't look like an option
846 @system unittest
847 {
848     auto endOfOptionsBackup = endOfOptions;
849     scope(exit) endOfOptions = endOfOptionsBackup;
850     endOfOptions = "endofoptions";
851     string[] args = ["program", "endofoptions", "--option"];
852     bool b = false;
853     getoptInorder(args, "option", &b);
854     assert(!b);
855     assert(args == ["program", "--option"]);
856 }
857 
858 @system unittest
859 {
860     import std.conv;
861 
862     import std.array;
863     import std.string;
864     bool a;
865     auto args = ["prog", "--foo"];
866     auto t = getoptInorder(args, "foo|f", "Help", &a);
867     string s;
868     auto app = appender!string();
869     defaultGetoptFormatter(app, "Some Text", t.options);
870 
871     string helpMsg = app.data;
872     //writeln(helpMsg);
873     assert(helpMsg.length);
874     assert(helpMsg.count("\n") == 3, to!string(helpMsg.count("\n")) ~ " "
875         ~ helpMsg);
876     assert(helpMsg.indexOf("--foo") != -1);
877     assert(helpMsg.indexOf("-f") != -1);
878     assert(helpMsg.indexOf("-h") != -1);
879     assert(helpMsg.indexOf("--help") != -1);
880     assert(helpMsg.indexOf("Help") != -1);
881 
882     string wanted = "Some Text\n-f  --foo Help\n-h --help This help "
883         ~ "information.\n";
884     assert(wanted == helpMsg);
885 }
886 
887 @system unittest
888 {
889     version(none)  // No point in running this unit test without config.required support
890     {
891         import std.conv;
892         import std.string;
893         import std.array ;
894         bool a;
895         auto args = ["prog", "--foo"];
896         auto t = getoptInorder(args, config.required, "foo|f", "Help", &a);
897         string s;
898         auto app = appender!string();
899         defaultGetoptFormatter(app, "Some Text", t.options);
900 
901         string helpMsg = app.data;
902         //writeln(helpMsg);
903         assert(helpMsg.length);
904         assert(helpMsg.count("\n") == 3, to!string(helpMsg.count("\n")) ~ " "
905                ~ helpMsg);
906         assert(helpMsg.indexOf("Required:") != -1);
907         assert(helpMsg.indexOf("--foo") != -1);
908         assert(helpMsg.indexOf("-f") != -1);
909         assert(helpMsg.indexOf("-h") != -1);
910         assert(helpMsg.indexOf("--help") != -1);
911         assert(helpMsg.indexOf("Help") != -1);
912 
913         string wanted = "Some Text\n-f  --foo Required: Help\n-h --help "
914             ~ "          This help information.\n";
915         assert(wanted == helpMsg, helpMsg ~ wanted);
916     }
917 }
918 
919 @system unittest // Issue 14724
920 {
921     version(none)  // No point running this unit test without config.required support
922     {
923         bool a;
924         auto args = ["prog", "--help"];
925         GetoptResult rslt;
926         try
927         {
928             rslt = getoptInorder(args, config.required, "foo|f", "bool a", &a);
929         }
930         catch (Exception e)
931         {
932             enum errorMsg = "If the request for help was passed required options" ~
933                 "must not be set.";
934             assert(false, errorMsg);
935         }
936 
937         assert(rslt.helpWanted);
938     }
939 }