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 }