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