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+", ¶noid);
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 }