1 // Written in the D programming language. 2 3 /** 4 Processing of command line options. 5 6 The getopt module implements a $(D getopt) function, which adheres to 7 the POSIX syntax for command line options. GNU extensions are 8 supported in the form of long options introduced by a double dash 9 ("--"). Support for bundling of command line options, as was the case 10 with the more traditional single-letter approach, is provided but not 11 enabled by default. 12 13 Copyright: Copyright Andrei Alexandrescu 2008 - 2015. 14 License: $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0). 15 Authors: $(HTTP erdani.org, Andrei Alexandrescu) 16 Credits: This module and its documentation are inspired by Perl's $(HTTP 17 perldoc.perl.org/Getopt/Long.html, Getopt::Long) module. The syntax of 18 D's $(D getopt) is simpler than its Perl counterpart because $(D 19 getopt) infers the expected parameter types from the static types of 20 the passed-in pointers. 21 Source: $(PHOBOSSRC std/_getopt.d) 22 */ 23 /* 24 Copyright Andrei Alexandrescu 2008 - 2015. 25 Distributed under the Boost Software License, Version 1.0. 26 (See accompanying file LICENSE_1_0.txt or copy at 27 http://www.boost.org/LICENSE_1_0.txt) 28 */ 29 module std.getopt; 30 31 import std.exception; // basicExceptionCtors 32 import std.traits; 33 34 /** 35 Thrown on one of the following conditions: 36 $(UL 37 $(LI An unrecognized command-line argument is passed, and 38 $(D std.getopt.config.passThrough) was not present.) 39 $(LI A command-line option was not found, and 40 $(D std.getopt.config.required) was present.) 41 ) 42 */ 43 class GetOptException : Exception 44 { 45 mixin basicExceptionCtors; 46 } 47 48 static assert(is(typeof(new GetOptException("message")))); 49 static assert(is(typeof(new GetOptException("message", Exception.init)))); 50 51 /** 52 Parse and remove command line options from a string array. 53 54 Synopsis: 55 56 --------- 57 import std.getopt; 58 59 string data = "file.dat"; 60 int length = 24; 61 bool verbose; 62 enum Color { no, yes }; 63 Color color; 64 65 void main(string[] args) 66 { 67 auto helpInformation = getopt( 68 args, 69 "length", &length, // numeric 70 "file", &data, // string 71 "verbose", &verbose, // flag 72 "color", "Information about this color", &color); // enum 73 ... 74 75 if (helpInformation.helpWanted) 76 { 77 defaultGetoptPrinter("Some information about the program.", 78 helpInformation.options); 79 } 80 } 81 --------- 82 83 The $(D getopt) function takes a reference to the command line 84 (as received by $(D main)) as its first argument, and an 85 unbounded number of pairs of strings and pointers. Each string is an 86 option meant to "fill" the value referenced by the pointer to its 87 right (the "bound" pointer). The option string in the call to 88 $(D getopt) should not start with a dash. 89 90 In all cases, the command-line options that were parsed and used by 91 $(D getopt) are removed from $(D args). Whatever in the 92 arguments did not look like an option is left in $(D args) for 93 further processing by the program. Values that were unaffected by the 94 options are not touched, so a common idiom is to initialize options 95 to their defaults and then invoke $(D getopt). If a 96 command-line argument is recognized as an option with a parameter and 97 the parameter cannot be parsed properly (e.g., a number is expected 98 but not present), a $(D ConvException) exception is thrown. 99 If $(D std.getopt.config.passThrough) was not passed to $(D getopt) 100 and an unrecognized command-line argument is found, a $(D GetOptException) 101 is thrown. 102 103 Depending on the type of the pointer being bound, $(D getopt) 104 recognizes the following kinds of options: 105 106 $(OL 107 $(LI $(I Boolean options). A lone argument sets the option to $(D true). 108 Additionally $(B true) or $(B false) can be set within the option separated 109 with an "=" sign: 110 111 --------- 112 bool verbose = false, debugging = true; 113 getopt(args, "verbose", &verbose, "debug", &debugging); 114 --------- 115 116 To set $(D verbose) to $(D true), invoke the program with either 117 $(D --verbose) or $(D --verbose=true). 118 119 To set $(D debugging) to $(D false), invoke the program with 120 $(D --debugging=false). 121 ) 122 123 $(LI $(I Numeric options.) If an option is bound to a numeric type, a 124 number is expected as the next option, or right within the option separated 125 with an "=" sign: 126 127 --------- 128 uint timeout; 129 getopt(args, "timeout", &timeout); 130 --------- 131 132 To set $(D timeout) to $(D 5), invoke the program with either 133 $(D --timeout=5) or $(D --timeout 5). 134 ) 135 136 $(LI $(I Incremental options.) If an option name has a "+" suffix and is 137 bound to a numeric type, then the option's value tracks the number of times 138 the option occurred on the command line: 139 140 --------- 141 uint paranoid; 142 getopt(args, "paranoid+", ¶noid); 143 --------- 144 145 Invoking the program with "--paranoid --paranoid --paranoid" will set $(D 146 paranoid) to 3. Note that an incremental option never expects a parameter, 147 e.g., in the command line "--paranoid 42 --paranoid", the "42" does not set 148 $(D paranoid) to 42; instead, $(D paranoid) is set to 2 and "42" is not 149 considered as part of the normal program arguments. 150 ) 151 152 $(LI $(I Enum options.) If an option is bound to an enum, an enum symbol as 153 a string is expected as the next option, or right within the option 154 separated with an "=" sign: 155 156 --------- 157 enum Color { no, yes }; 158 Color color; // default initialized to Color.no 159 getopt(args, "color", &color); 160 --------- 161 162 To set $(D color) to $(D Color.yes), invoke the program with either 163 $(D --color=yes) or $(D --color yes). 164 ) 165 166 $(LI $(I String options.) If an option is bound to a string, a string is 167 expected as the next option, or right within the option separated with an 168 "=" sign: 169 170 --------- 171 string outputFile; 172 getopt(args, "output", &outputFile); 173 --------- 174 175 Invoking the program with "--output=myfile.txt" or "--output myfile.txt" 176 will set $(D outputFile) to "myfile.txt". If you want to pass a string 177 containing spaces, you need to use the quoting that is appropriate to your 178 shell, e.g. --output='my file.txt'. 179 ) 180 181 $(LI $(I Array options.) If an option is bound to an array, a new element 182 is appended to the array each time the option occurs: 183 184 --------- 185 string[] outputFiles; 186 getopt(args, "output", &outputFiles); 187 --------- 188 189 Invoking the program with "--output=myfile.txt --output=yourfile.txt" or 190 "--output myfile.txt --output yourfile.txt" will set $(D outputFiles) to 191 $(D [ "myfile.txt", "yourfile.txt" ]). 192 193 Alternatively you can set $(LREF arraySep) as the element separator: 194 195 --------- 196 string[] outputFiles; 197 arraySep = ","; // defaults to "", separation by whitespace 198 getopt(args, "output", &outputFiles); 199 --------- 200 201 With the above code you can invoke the program with 202 "--output=myfile.txt,yourfile.txt", or "--output myfile.txt,yourfile.txt".) 203 204 $(LI $(I Hash options.) If an option is bound to an associative array, a 205 string of the form "name=value" is expected as the next option, or right 206 within the option separated with an "=" sign: 207 208 --------- 209 double[string] tuningParms; 210 getopt(args, "tune", &tuningParms); 211 --------- 212 213 Invoking the program with e.g. "--tune=alpha=0.5 --tune beta=0.6" will set 214 $(D tuningParms) to [ "alpha" : 0.5, "beta" : 0.6 ]. 215 216 Alternatively you can set $(LREF arraySep) as the element separator: 217 218 --------- 219 double[string] tuningParms; 220 arraySep = ","; // defaults to "", separation by whitespace 221 getopt(args, "tune", &tuningParms); 222 --------- 223 224 With the above code you can invoke the program with 225 "--tune=alpha=0.5,beta=0.6", or "--tune alpha=0.5,beta=0.6". 226 227 In general, the keys and values can be of any parsable types. 228 ) 229 230 $(LI $(I Callback options.) An option can be bound to a function or 231 delegate with the signature $(D void function()), $(D void function(string 232 option)), $(D void function(string option, string value)), or their 233 delegate equivalents. 234 235 $(UL 236 $(LI If the callback doesn't take any arguments, the callback is 237 invoked whenever the option is seen. 238 ) 239 240 $(LI If the callback takes one string argument, the option string 241 (without the leading dash(es)) is passed to the callback. After that, 242 the option string is considered handled and removed from the options 243 array. 244 245 --------- 246 void main(string[] args) 247 { 248 uint verbosityLevel = 1; 249 void myHandler(string option) 250 { 251 if (option == "quiet") 252 { 253 verbosityLevel = 0; 254 } 255 else 256 { 257 assert(option == "verbose"); 258 verbosityLevel = 2; 259 } 260 } 261 getopt(args, "verbose", &myHandler, "quiet", &myHandler); 262 } 263 --------- 264 265 ) 266 267 $(LI If the callback takes two string arguments, the option string is 268 handled as an option with one argument, and parsed accordingly. The 269 option and its value are passed to the callback. After that, whatever 270 was passed to the callback is considered handled and removed from the 271 list. 272 273 --------- 274 int main(string[] args) 275 { 276 uint verbosityLevel = 1; 277 bool handlerFailed = false; 278 void myHandler(string option, string value) 279 { 280 switch (value) 281 { 282 case "quiet": verbosityLevel = 0; break; 283 case "verbose": verbosityLevel = 2; break; 284 case "shouting": verbosityLevel = verbosityLevel.max; break; 285 default : 286 stderr.writeln("Unknown verbosity level ", value); 287 handlerFailed = true; 288 break; 289 } 290 } 291 getopt(args, "verbosity", &myHandler); 292 return handlerFailed ? 1 : 0; 293 } 294 --------- 295 ) 296 )) 297 ) 298 299 Options_with_multiple_names: 300 Sometimes option synonyms are desirable, e.g. "--verbose", 301 "--loquacious", and "--garrulous" should have the same effect. Such 302 alternate option names can be included in the option specification, 303 using "|" as a separator: 304 305 --------- 306 bool verbose; 307 getopt(args, "verbose|loquacious|garrulous", &verbose); 308 --------- 309 310 Case: 311 By default options are case-insensitive. You can change that behavior 312 by passing $(D getopt) the $(D caseSensitive) directive like this: 313 314 --------- 315 bool foo, bar; 316 getopt(args, 317 std.getopt.config.caseSensitive, 318 "foo", &foo, 319 "bar", &bar); 320 --------- 321 322 In the example above, "--foo" and "--bar" are recognized, but "--Foo", "--Bar", 323 "--FOo", "--bAr", etc. are rejected. 324 The directive is active until the end of $(D getopt), or until the 325 converse directive $(D caseInsensitive) is encountered: 326 327 --------- 328 bool foo, bar; 329 getopt(args, 330 std.getopt.config.caseSensitive, 331 "foo", &foo, 332 std.getopt.config.caseInsensitive, 333 "bar", &bar); 334 --------- 335 336 The option "--Foo" is rejected due to $(D 337 std.getopt.config.caseSensitive), but not "--Bar", "--bAr" 338 etc. because the directive $(D 339 std.getopt.config.caseInsensitive) turned sensitivity off before 340 option "bar" was parsed. 341 342 Short_versus_long_options: 343 Traditionally, programs accepted single-letter options preceded by 344 only one dash (e.g. $(D -t)). $(D getopt) accepts such parameters 345 seamlessly. When used with a double-dash (e.g. $(D --t)), a 346 single-letter option behaves the same as a multi-letter option. When 347 used with a single dash, a single-letter option is accepted. If the 348 option has a parameter, that must be "stuck" to the option without 349 any intervening space or "=": 350 351 --------- 352 uint timeout; 353 getopt(args, "timeout|t", &timeout); 354 --------- 355 356 To set $(D timeout) to $(D 5), use either of the following: $(D --timeout=5), 357 $(D --timeout 5), $(D --t=5), $(D --t 5), or $(D -t5). Forms such as $(D -t 5) 358 and $(D -timeout=5) will be not accepted. 359 360 For more details about short options, refer also to the next section. 361 362 Bundling: 363 Single-letter options can be bundled together, i.e. "-abc" is the same as 364 $(D "-a -b -c"). By default, this option is turned off. You can turn it on 365 with the $(D std.getopt.config.bundling) directive: 366 367 --------- 368 bool foo, bar; 369 getopt(args, 370 std.getopt.config.bundling, 371 "foo|f", &foo, 372 "bar|b", &bar); 373 --------- 374 375 In case you want to only enable bundling for some of the parameters, 376 bundling can be turned off with $(D std.getopt.config.noBundling). 377 378 Required: 379 An option can be marked as required. If that option is not present in the 380 arguments an exception will be thrown. 381 382 --------- 383 bool foo, bar; 384 getopt(args, 385 std.getopt.config.required, 386 "foo|f", &foo, 387 "bar|b", &bar); 388 --------- 389 390 Only the option directly following $(D std.getopt.config.required) is 391 required. 392 393 Passing_unrecognized_options_through: 394 If an application needs to do its own processing of whichever arguments 395 $(D getopt) did not understand, it can pass the 396 $(D std.getopt.config.passThrough) directive to $(D getopt): 397 398 --------- 399 bool foo, bar; 400 getopt(args, 401 std.getopt.config.passThrough, 402 "foo", &foo, 403 "bar", &bar); 404 --------- 405 406 An unrecognized option such as "--baz" will be found untouched in 407 $(D args) after $(D getopt) returns. 408 409 Help_Information_Generation: 410 If an option string is followed by another string, this string serves as a 411 description for this option. The $(D getopt) function returns a struct of type 412 $(D GetoptResult). This return value contains information about all passed options 413 as well a $(D bool GetoptResult.helpWanted) flag indicating whether information 414 about these options was requested. The $(D getopt) function always adds an option for 415 `--help|-h` to set the flag if the option is seen on the command line. 416 417 Options_Terminator: 418 A lone double-dash terminates $(D getopt) gathering. It is used to 419 separate program options from other parameters (e.g., options to be passed 420 to another program). Invoking the example above with $(D "--foo -- --bar") 421 parses foo but leaves "--bar" in $(D args). The double-dash itself is 422 removed from the argument array unless the $(D std.getopt.config.keepEndOfOptions) 423 directive is given. 424 */ 425 GetoptResult getopt(T...)(ref string[] args, T opts) 426 { 427 import std.exception : enforce; 428 enforce(args.length, 429 "Invalid arguments string passed: program name missing"); 430 configuration cfg; 431 GetoptResult rslt; 432 433 GetOptException excep; 434 void[][string] visitedLongOpts, visitedShortOpts; 435 getoptImpl(args, cfg, rslt, excep, visitedLongOpts, visitedShortOpts, opts); 436 437 if (!rslt.helpWanted && excep !is null) 438 { 439 throw excep; 440 } 441 442 return rslt; 443 } 444 445 /// 446 @system unittest 447 { 448 auto args = ["prog", "--foo", "-b"]; 449 450 bool foo; 451 bool bar; 452 auto rslt = getopt(args, "foo|f", "Some information about foo.", &foo, "bar|b", 453 "Some help message about bar.", &bar); 454 455 if (rslt.helpWanted) 456 { 457 defaultGetoptPrinter("Some information about the program.", 458 rslt.options); 459 } 460 } 461 462 /** 463 Configuration options for $(D getopt). 464 465 You can pass them to $(D getopt) in any position, except in between an option 466 string and its bound pointer. 467 */ 468 enum config { 469 /// Turn case sensitivity on 470 caseSensitive, 471 /// Turn case sensitivity off (default) 472 caseInsensitive, 473 /// Turn bundling on 474 bundling, 475 /// Turn bundling off (default) 476 noBundling, 477 /// Pass unrecognized arguments through 478 passThrough, 479 /// Signal unrecognized arguments as errors (default) 480 noPassThrough, 481 /// Stop at first argument that does not look like an option 482 stopOnFirstNonOption, 483 /// Do not erase the endOfOptions separator from args 484 keepEndOfOptions, 485 /// Make the next option a required option 486 required 487 } 488 489 /** The result of the $(D getopt) function. 490 491 $(D helpWanted) is set if the option `--help` or `-h` was passed to the option parser. 492 */ 493 struct GetoptResult { 494 bool helpWanted; /// Flag indicating if help was requested 495 Option[] options; /// All possible options 496 } 497 498 /** Information about an option. 499 */ 500 struct Option { 501 string optShort; /// The short symbol for this option 502 string optLong; /// The long symbol for this option 503 string help; /// The description of this option 504 bool required; /// If a option is required, not passing it will result in an error 505 } 506 507 private pure Option splitAndGet(string opt) @trusted nothrow 508 { 509 import std.array : split; 510 auto sp = split(opt, "|"); 511 Option ret; 512 if (sp.length > 1) 513 { 514 ret.optShort = "-" ~ (sp[0].length < sp[1].length ? 515 sp[0] : sp[1]); 516 ret.optLong = "--" ~ (sp[0].length > sp[1].length ? 517 sp[0] : sp[1]); 518 } 519 else if (sp[0].length > 1) 520 { 521 ret.optLong = "--" ~ sp[0]; 522 } 523 else 524 { 525 ret.optShort = "-" ~ sp[0]; 526 } 527 528 return ret; 529 } 530 531 @safe unittest 532 { 533 auto oshort = splitAndGet("f"); 534 assert(oshort.optShort == "-f"); 535 assert(oshort.optLong == ""); 536 537 auto olong = splitAndGet("foo"); 538 assert(olong.optShort == ""); 539 assert(olong.optLong == "--foo"); 540 541 auto oshortlong = splitAndGet("f|foo"); 542 assert(oshortlong.optShort == "-f"); 543 assert(oshortlong.optLong == "--foo"); 544 545 auto olongshort = splitAndGet("foo|f"); 546 assert(olongshort.optShort == "-f"); 547 assert(olongshort.optLong == "--foo"); 548 } 549 550 /* 551 This function verifies that the variadic parameters passed in getOpt 552 follow this pattern: 553 554 [config override], option, [description], receiver, 555 556 - config override: a config value, optional 557 - option: a string or a char 558 - description: a string, optional 559 - receiver: a pointer or a callable 560 */ 561 private template optionValidator(A...) 562 { 563 import std.format : format; 564 import std.typecons : staticIota; 565 566 enum fmt = "getopt validator: %s (at position %d)"; 567 enum isReceiver(T) = isPointer!T || (is(T == function)) || (is(T == delegate)); 568 enum isOptionStr(T) = isSomeString!T || isSomeChar!T; 569 570 auto validator() 571 { 572 string msg; 573 static if (A.length > 0) 574 { 575 static if (isReceiver!(A[0])) 576 { 577 msg = format(fmt, "first argument must be a string or a config", 0); 578 } 579 else static if (!isOptionStr!(A[0]) && !is(A[0] == config)) 580 { 581 msg = format(fmt, "invalid argument type: " ~ A[0].stringof, 0); 582 } 583 else foreach (i; staticIota!(1, A.length)) 584 { 585 static if (!isReceiver!(A[i]) && !isOptionStr!(A[i]) && 586 !(is(A[i] == config))) 587 { 588 msg = format(fmt, "invalid argument type: " ~ A[i].stringof, i); 589 break; 590 } 591 else static if (isReceiver!(A[i]) && !isOptionStr!(A[i-1])) 592 { 593 msg = format(fmt, "a receiver can not be preceeded by a receiver", i); 594 break; 595 } 596 else static if (i > 1 && isOptionStr!(A[i]) && isOptionStr!(A[i-1]) 597 && isSomeString!(A[i-2])) 598 { 599 msg = format(fmt, "a string can not be preceeded by two strings", i); 600 break; 601 } 602 } 603 static if (!isReceiver!(A[$-1]) && !is(A[$-1] == config)) 604 { 605 msg = format(fmt, "last argument must be a receiver or a config", 606 A.length -1); 607 } 608 } 609 return msg; 610 } 611 enum message = validator; 612 alias optionValidator = message; 613 } 614 615 @safe pure unittest 616 { 617 alias P = void*; 618 alias S = string; 619 alias A = char; 620 alias C = config; 621 alias F = void function(); 622 623 static assert(optionValidator!(S,P) == ""); 624 static assert(optionValidator!(S,F) == ""); 625 static assert(optionValidator!(A,P) == ""); 626 static assert(optionValidator!(A,F) == ""); 627 628 static assert(optionValidator!(C,S,P) == ""); 629 static assert(optionValidator!(C,S,F) == ""); 630 static assert(optionValidator!(C,A,P) == ""); 631 static assert(optionValidator!(C,A,F) == ""); 632 633 static assert(optionValidator!(C,S,S,P) == ""); 634 static assert(optionValidator!(C,S,S,F) == ""); 635 static assert(optionValidator!(C,A,S,P) == ""); 636 static assert(optionValidator!(C,A,S,F) == ""); 637 638 static assert(optionValidator!(C,S,S,P) == ""); 639 static assert(optionValidator!(C,S,S,P,C,S,F) == ""); 640 static assert(optionValidator!(C,S,P,C,S,S,F) == ""); 641 642 static assert(optionValidator!(C,A,P,A,S,F) == ""); 643 static assert(optionValidator!(C,A,P,C,A,S,F) == ""); 644 645 static assert(optionValidator!(P,S,S) != ""); 646 static assert(optionValidator!(P,P,S) != ""); 647 static assert(optionValidator!(P,F,S,P) != ""); 648 static assert(optionValidator!(C,C,S) != ""); 649 static assert(optionValidator!(S,S,P,S,S,P,S) != ""); 650 static assert(optionValidator!(S,S,P,P) != ""); 651 static assert(optionValidator!(S,S,S,P) != ""); 652 653 static assert(optionValidator!(C,A,S,P,C,A,F) == ""); 654 static assert(optionValidator!(C,A,P,C,A,S,F) == ""); 655 } 656 657 @system unittest // bugzilla 15914 658 { 659 bool opt; 660 string[] args = ["program", "-a"]; 661 getopt(args, config.passThrough, 'a', &opt); 662 assert(opt); 663 opt = false; 664 args = ["program", "-a"]; 665 getopt(args, 'a', &opt); 666 assert(opt); 667 opt = false; 668 args = ["program", "-a"]; 669 getopt(args, 'a', "help string", &opt); 670 assert(opt); 671 opt = false; 672 args = ["program", "-a"]; 673 getopt(args, config.caseSensitive, 'a', "help string", &opt); 674 assert(opt); 675 676 assertThrown(getopt(args, "", "forgot to put a string", &opt)); 677 } 678 679 private void getoptImpl(T...)(ref string[] args, ref configuration cfg, 680 ref GetoptResult rslt, ref GetOptException excep, 681 void[][string] visitedLongOpts, void[][string] visitedShortOpts, T opts) 682 { 683 enum validationMessage = optionValidator!T; 684 static assert(validationMessage == "", validationMessage); 685 686 import std.algorithm.mutation : remove; 687 import std.conv : to; 688 static if (opts.length) 689 { 690 static if (is(typeof(opts[0]) : config)) 691 { 692 // it's a configuration flag, act on it 693 setConfig(cfg, opts[0]); 694 return getoptImpl(args, cfg, rslt, excep, visitedLongOpts, 695 visitedShortOpts, opts[1 .. $]); 696 } 697 else 698 { 699 // it's an option string 700 auto option = to!string(opts[0]); 701 if (option.length == 0) 702 { 703 excep = new GetOptException("An option name may not be an empty string", excep); 704 return; 705 } 706 Option optionHelp = splitAndGet(option); 707 optionHelp.required = cfg.required; 708 709 if (optionHelp.optLong.length) 710 { 711 assert(optionHelp.optLong !in visitedLongOpts, 712 "Long option " ~ optionHelp.optLong ~ " is multiply defined"); 713 714 visitedLongOpts[optionHelp.optLong] = []; 715 } 716 717 if (optionHelp.optShort.length) 718 { 719 assert(optionHelp.optShort !in visitedShortOpts, 720 "Short option " ~ optionHelp.optShort 721 ~ " is multiply defined"); 722 723 visitedShortOpts[optionHelp.optShort] = []; 724 } 725 726 static if (is(typeof(opts[1]) : string)) 727 { 728 auto receiver = opts[2]; 729 optionHelp.help = opts[1]; 730 immutable lowSliceIdx = 3; 731 } 732 else 733 { 734 auto receiver = opts[1]; 735 immutable lowSliceIdx = 2; 736 } 737 738 rslt.options ~= optionHelp; 739 740 bool incremental; 741 // Handle options of the form --blah+ 742 if (option.length && option[$ - 1] == autoIncrementChar) 743 { 744 option = option[0 .. $ - 1]; 745 incremental = true; 746 } 747 748 bool optWasHandled = handleOption(option, receiver, args, cfg, incremental); 749 750 if (cfg.required && !optWasHandled) 751 { 752 excep = new GetOptException("Required option " 753 ~ option ~ " was not supplied", excep); 754 } 755 cfg.required = false; 756 757 getoptImpl(args, cfg, rslt, excep, visitedLongOpts, 758 visitedShortOpts, opts[lowSliceIdx .. $]); 759 } 760 } 761 else 762 { 763 // no more options to look for, potentially some arguments left 764 for (size_t i = 1; i < args.length;) 765 { 766 auto a = args[i]; 767 if (endOfOptions.length && a == endOfOptions) 768 { 769 // Consume the "--" if keepEndOfOptions is not specified 770 if (!cfg.keepEndOfOptions) 771 args = args.remove(i); 772 break; 773 } 774 if (!a.length || a[0] != optionChar) 775 { 776 // not an option 777 if (cfg.stopOnFirstNonOption) break; 778 ++i; 779 continue; 780 } 781 if (a == "--help" || a == "-h") 782 { 783 rslt.helpWanted = true; 784 args = args.remove(i); 785 continue; 786 } 787 if (!cfg.passThrough) 788 { 789 throw new GetOptException("Unrecognized option "~a, excep); 790 } 791 ++i; 792 } 793 794 Option helpOpt; 795 helpOpt.optShort = "-h"; 796 helpOpt.optLong = "--help"; 797 helpOpt.help = "This help information."; 798 rslt.options ~= helpOpt; 799 } 800 } 801 802 private bool handleOption(R)(string option, R receiver, ref string[] args, 803 ref configuration cfg, bool incremental) 804 { 805 import std.algorithm.iteration : map, splitter; 806 import std.ascii : isAlpha; 807 import std.conv : text, to; 808 // Scan arguments looking for a match for this option 809 bool ret = false; 810 for (size_t i = 1; i < args.length; ) 811 { 812 auto a = args[i]; 813 if (endOfOptions.length && a == endOfOptions) break; 814 if (cfg.stopOnFirstNonOption && (!a.length || a[0] != optionChar)) 815 { 816 // first non-option is end of options 817 break; 818 } 819 // Unbundle bundled arguments if necessary 820 if (cfg.bundling && a.length > 2 && a[0] == optionChar && 821 a[1] != optionChar) 822 { 823 string[] expanded; 824 foreach (j, dchar c; a[1 .. $]) 825 { 826 // If the character is not alpha, stop right there. This allows 827 // e.g. -j100 to work as "pass argument 100 to option -j". 828 if (!isAlpha(c)) 829 { 830 if (c == '=') 831 j++; 832 expanded ~= a[j + 1 .. $]; 833 break; 834 } 835 expanded ~= text(optionChar, c); 836 } 837 args = args[0 .. i] ~ expanded ~ args[i + 1 .. $]; 838 continue; 839 } 840 841 string val; 842 if (!optMatch(a, option, val, cfg)) 843 { 844 ++i; 845 continue; 846 } 847 848 ret = true; 849 850 // found it 851 // from here on, commit to eat args[i] 852 // (and potentially args[i + 1] too, but that comes later) 853 args = args[0 .. i] ~ args[i + 1 .. $]; 854 855 static if (is(typeof(*receiver) == bool)) 856 { 857 if (val.length) 858 { 859 // parse '--b=true/false' 860 *receiver = to!(typeof(*receiver))(val); 861 } 862 else 863 { 864 // no argument means set it to true 865 *receiver = true; 866 } 867 } 868 else 869 { 870 import std.exception : enforce; 871 // non-boolean option, which might include an argument 872 //enum isCallbackWithOneParameter = is(typeof(receiver("")) : void); 873 enum isCallbackWithLessThanTwoParameters = 874 (is(typeof(receiver) == delegate) || is(typeof(*receiver) == function)) && 875 !is(typeof(receiver("", ""))); 876 if (!isCallbackWithLessThanTwoParameters && !(val.length) && !incremental) 877 { 878 // Eat the next argument too. Check to make sure there's one 879 // to be eaten first, though. 880 enforce(i < args.length, 881 "Missing value for argument " ~ a ~ "."); 882 val = args[i]; 883 args = args[0 .. i] ~ args[i + 1 .. $]; 884 } 885 static if (is(typeof(*receiver) == enum)) 886 { 887 *receiver = to!(typeof(*receiver))(val); 888 } 889 else static if (is(typeof(*receiver) : real)) 890 { 891 // numeric receiver 892 if (incremental) ++*receiver; 893 else *receiver = to!(typeof(*receiver))(val); 894 } 895 else static if (is(typeof(*receiver) == string)) 896 { 897 // string receiver 898 *receiver = to!(typeof(*receiver))(val); 899 } 900 else static if (is(typeof(receiver) == delegate) || 901 is(typeof(*receiver) == function)) 902 { 903 static if (is(typeof(receiver("", "")) : void)) 904 { 905 // option with argument 906 receiver(option, val); 907 } 908 else static if (is(typeof(receiver("")) : void)) 909 { 910 static assert(is(typeof(receiver("")) : void)); 911 // boolean-style receiver 912 receiver(option); 913 } 914 else 915 { 916 static assert(is(typeof(receiver()) : void)); 917 // boolean-style receiver without argument 918 receiver(); 919 } 920 } 921 else static if (isArray!(typeof(*receiver))) 922 { 923 // array receiver 924 import std.range : ElementEncodingType; 925 alias E = ElementEncodingType!(typeof(*receiver)); 926 927 if (arraySep == "") 928 { 929 *receiver ~= to!E(val); 930 } 931 else 932 { 933 foreach (elem; val.splitter(arraySep).map!(a => to!E(a))()) 934 *receiver ~= elem; 935 } 936 } 937 else static if (isAssociativeArray!(typeof(*receiver))) 938 { 939 // hash receiver 940 alias K = typeof(receiver.keys[0]); 941 alias V = typeof(receiver.values[0]); 942 943 import std.range : only; 944 import std.string : indexOf; 945 import std.typecons : Tuple, tuple; 946 947 static Tuple!(K, V) getter(string input) 948 { 949 auto j = indexOf(input, assignChar); 950 enforce!GetOptException(j != -1, "Could not find '" 951 ~ to!string(assignChar) ~ "' in argument '" ~ input ~ "'."); 952 auto key = input[0 .. j]; 953 auto value = input[j + 1 .. $]; 954 return tuple(to!K(key), to!V(value)); 955 } 956 957 static void setHash(Range)(R receiver, Range range) 958 { 959 foreach (k, v; range.map!getter) 960 (*receiver)[k] = v; 961 } 962 963 if (arraySep == "") 964 setHash(receiver, val.only); 965 else 966 setHash(receiver, val.splitter(arraySep)); 967 } 968 else 969 static assert(false, "getopt does not know how to handle the type " ~ typeof(receiver).stringof); 970 } 971 } 972 973 return ret; 974 } 975 976 // 17574 977 @system unittest 978 { 979 import std.algorithm.searching : startsWith; 980 981 try 982 { 983 string[string] mapping; 984 immutable as = arraySep; 985 arraySep = ","; 986 scope (exit) 987 arraySep = as; 988 string[] args = ["testProgram", "-m", "a=b,c=\"d,e,f\""]; 989 args.getopt("m", &mapping); 990 assert(false, "Exception not thrown"); 991 } 992 catch (GetOptException goe) 993 assert(goe.msg.startsWith("Could not find")); 994 } 995 996 // 5316 - arrays with arraySep 997 @system unittest 998 { 999 import std.conv; 1000 1001 arraySep = ","; 1002 scope (exit) arraySep = ""; 1003 1004 string[] names; 1005 auto args = ["program.name", "-nfoo,bar,baz"]; 1006 getopt(args, "name|n", &names); 1007 assert(names == ["foo", "bar", "baz"], to!string(names)); 1008 1009 names = names.init; 1010 args = ["program.name", "-n", "foo,bar,baz"]; 1011 getopt(args, "name|n", &names); 1012 assert(names == ["foo", "bar", "baz"], to!string(names)); 1013 1014 names = names.init; 1015 args = ["program.name", "--name=foo,bar,baz"]; 1016 getopt(args, "name|n", &names); 1017 assert(names == ["foo", "bar", "baz"], to!string(names)); 1018 1019 names = names.init; 1020 args = ["program.name", "--name", "foo,bar,baz"]; 1021 getopt(args, "name|n", &names); 1022 assert(names == ["foo", "bar", "baz"], to!string(names)); 1023 } 1024 1025 // 5316 - associative arrays with arraySep 1026 @system unittest 1027 { 1028 import std.conv; 1029 1030 arraySep = ","; 1031 scope (exit) arraySep = ""; 1032 1033 int[string] values; 1034 values = values.init; 1035 auto args = ["program.name", "-vfoo=0,bar=1,baz=2"]; 1036 getopt(args, "values|v", &values); 1037 assert(values == ["foo":0, "bar":1, "baz":2], to!string(values)); 1038 1039 values = values.init; 1040 args = ["program.name", "-v", "foo=0,bar=1,baz=2"]; 1041 getopt(args, "values|v", &values); 1042 assert(values == ["foo":0, "bar":1, "baz":2], to!string(values)); 1043 1044 values = values.init; 1045 args = ["program.name", "--values=foo=0,bar=1,baz=2"]; 1046 getopt(args, "values|t", &values); 1047 assert(values == ["foo":0, "bar":1, "baz":2], to!string(values)); 1048 1049 values = values.init; 1050 args = ["program.name", "--values", "foo=0,bar=1,baz=2"]; 1051 getopt(args, "values|v", &values); 1052 assert(values == ["foo":0, "bar":1, "baz":2], to!string(values)); 1053 } 1054 1055 /** 1056 The option character (default '-'). 1057 1058 Defaults to '-' but it can be assigned to prior to calling $(D getopt). 1059 */ 1060 dchar optionChar = '-'; 1061 1062 /** 1063 The string that conventionally marks the end of all options (default '--'). 1064 1065 Defaults to "--" but can be assigned to prior to calling $(D getopt). Assigning an 1066 empty string to $(D endOfOptions) effectively disables it. 1067 */ 1068 string endOfOptions = "--"; 1069 1070 /** 1071 The assignment character used in options with parameters (default '='). 1072 1073 Defaults to '=' but can be assigned to prior to calling $(D getopt). 1074 */ 1075 dchar assignChar = '='; 1076 1077 /** 1078 The string used to separate the elements of an array or associative array 1079 (default is "" which means the elements are separated by whitespace). 1080 1081 Defaults to "" but can be assigned to prior to calling $(D getopt). 1082 */ 1083 string arraySep = ""; 1084 1085 private enum autoIncrementChar = '+'; 1086 1087 private struct configuration 1088 { 1089 import std.bitmanip : bitfields; 1090 mixin(bitfields!( 1091 bool, "caseSensitive", 1, 1092 bool, "bundling", 1, 1093 bool, "passThrough", 1, 1094 bool, "stopOnFirstNonOption", 1, 1095 bool, "keepEndOfOptions", 1, 1096 bool, "required", 1, 1097 ubyte, "", 2)); 1098 } 1099 1100 private bool optMatch(string arg, string optPattern, ref string value, 1101 configuration cfg) @safe 1102 { 1103 import std.array : split; 1104 import std.string : indexOf; 1105 import std.uni : toUpper; 1106 //writeln("optMatch:\n ", arg, "\n ", optPattern, "\n ", value); 1107 //scope(success) writeln("optMatch result: ", value); 1108 if (arg.length < 2 || arg[0] != optionChar) return false; 1109 // yank the leading '-' 1110 arg = arg[1 .. $]; 1111 immutable isLong = arg.length > 1 && arg[0] == optionChar; 1112 //writeln("isLong: ", isLong); 1113 // yank the second '-' if present 1114 if (isLong) arg = arg[1 .. $]; 1115 immutable eqPos = indexOf(arg, assignChar); 1116 if (isLong && eqPos >= 0) 1117 { 1118 // argument looks like --opt=value 1119 value = arg[eqPos + 1 .. $]; 1120 arg = arg[0 .. eqPos]; 1121 } 1122 else 1123 { 1124 if (!isLong && eqPos == 1) 1125 { 1126 // argument looks like -o=value 1127 value = arg[2 .. $]; 1128 arg = arg[0 .. 1]; 1129 } 1130 else 1131 if (!isLong && !cfg.bundling) 1132 { 1133 // argument looks like -ovalue and there's no bundling 1134 value = arg[1 .. $]; 1135 arg = arg[0 .. 1]; 1136 } 1137 else 1138 { 1139 // argument looks like --opt, or -oxyz with bundling 1140 value = null; 1141 } 1142 } 1143 //writeln("Arg: ", arg, " pattern: ", optPattern, " value: ", value); 1144 // Split the option 1145 const variants = split(optPattern, "|"); 1146 foreach (v ; variants) 1147 { 1148 //writeln("Trying variant: ", v, " against ", arg); 1149 if (arg == v || !cfg.caseSensitive && toUpper(arg) == toUpper(v)) 1150 return true; 1151 if (cfg.bundling && !isLong && v.length == 1 1152 && indexOf(arg, v) >= 0) 1153 { 1154 //writeln("success"); 1155 return true; 1156 } 1157 } 1158 return false; 1159 } 1160 1161 private void setConfig(ref configuration cfg, config option) @safe pure nothrow @nogc 1162 { 1163 final switch (option) 1164 { 1165 case config.caseSensitive: cfg.caseSensitive = true; break; 1166 case config.caseInsensitive: cfg.caseSensitive = false; break; 1167 case config.bundling: cfg.bundling = true; break; 1168 case config.noBundling: cfg.bundling = false; break; 1169 case config.passThrough: cfg.passThrough = true; break; 1170 case config.noPassThrough: cfg.passThrough = false; break; 1171 case config.required: cfg.required = true; break; 1172 case config.stopOnFirstNonOption: 1173 cfg.stopOnFirstNonOption = true; break; 1174 case config.keepEndOfOptions: 1175 cfg.keepEndOfOptions = true; break; 1176 } 1177 } 1178 1179 @system unittest 1180 { 1181 import std.conv; 1182 import std.math; 1183 1184 uint paranoid = 2; 1185 string[] args = ["program.name", "--paranoid", "--paranoid", "--paranoid"]; 1186 getopt(args, "paranoid+", ¶noid); 1187 assert(paranoid == 5, to!(string)(paranoid)); 1188 1189 enum Color { no, yes } 1190 Color color; 1191 args = ["program.name", "--color=yes",]; 1192 getopt(args, "color", &color); 1193 assert(color, to!(string)(color)); 1194 1195 color = Color.no; 1196 args = ["program.name", "--color", "yes",]; 1197 getopt(args, "color", &color); 1198 assert(color, to!(string)(color)); 1199 1200 string data = "file.dat"; 1201 int length = 24; 1202 bool verbose = false; 1203 args = ["program.name", "--length=5", "--file", "dat.file", "--verbose"]; 1204 getopt( 1205 args, 1206 "length", &length, 1207 "file", &data, 1208 "verbose", &verbose); 1209 assert(args.length == 1); 1210 assert(data == "dat.file"); 1211 assert(length == 5); 1212 assert(verbose); 1213 1214 // 1215 string[] outputFiles; 1216 args = ["program.name", "--output=myfile.txt", "--output", "yourfile.txt"]; 1217 getopt(args, "output", &outputFiles); 1218 assert(outputFiles.length == 2 1219 && outputFiles[0] == "myfile.txt" && outputFiles[1] == "yourfile.txt"); 1220 1221 outputFiles = []; 1222 arraySep = ","; 1223 args = ["program.name", "--output", "myfile.txt,yourfile.txt"]; 1224 getopt(args, "output", &outputFiles); 1225 assert(outputFiles.length == 2 1226 && outputFiles[0] == "myfile.txt" && outputFiles[1] == "yourfile.txt"); 1227 arraySep = ""; 1228 1229 foreach (testArgs; 1230 [["program.name", "--tune=alpha=0.5", "--tune", "beta=0.6"], 1231 ["program.name", "--tune=alpha=0.5,beta=0.6"], 1232 ["program.name", "--tune", "alpha=0.5,beta=0.6"]]) 1233 { 1234 arraySep = ","; 1235 double[string] tuningParms; 1236 getopt(testArgs, "tune", &tuningParms); 1237 assert(testArgs.length == 1); 1238 assert(tuningParms.length == 2); 1239 assert(approxEqual(tuningParms["alpha"], 0.5)); 1240 assert(approxEqual(tuningParms["beta"], 0.6)); 1241 arraySep = ""; 1242 } 1243 1244 uint verbosityLevel = 1; 1245 void myHandler(string option) 1246 { 1247 if (option == "quiet") 1248 { 1249 verbosityLevel = 0; 1250 } 1251 else 1252 { 1253 assert(option == "verbose"); 1254 verbosityLevel = 2; 1255 } 1256 } 1257 args = ["program.name", "--quiet"]; 1258 getopt(args, "verbose", &myHandler, "quiet", &myHandler); 1259 assert(verbosityLevel == 0); 1260 args = ["program.name", "--verbose"]; 1261 getopt(args, "verbose", &myHandler, "quiet", &myHandler); 1262 assert(verbosityLevel == 2); 1263 1264 verbosityLevel = 1; 1265 void myHandler2(string option, string value) 1266 { 1267 assert(option == "verbose"); 1268 verbosityLevel = 2; 1269 } 1270 args = ["program.name", "--verbose", "2"]; 1271 getopt(args, "verbose", &myHandler2); 1272 assert(verbosityLevel == 2); 1273 1274 verbosityLevel = 1; 1275 void myHandler3() 1276 { 1277 verbosityLevel = 2; 1278 } 1279 args = ["program.name", "--verbose"]; 1280 getopt(args, "verbose", &myHandler3); 1281 assert(verbosityLevel == 2); 1282 1283 bool foo, bar; 1284 args = ["program.name", "--foo", "--bAr"]; 1285 getopt(args, 1286 std.getopt.config.caseSensitive, 1287 std.getopt.config.passThrough, 1288 "foo", &foo, 1289 "bar", &bar); 1290 assert(args[1] == "--bAr"); 1291 1292 // test stopOnFirstNonOption 1293 1294 args = ["program.name", "--foo", "nonoption", "--bar"]; 1295 foo = bar = false; 1296 getopt(args, 1297 std.getopt.config.stopOnFirstNonOption, 1298 "foo", &foo, 1299 "bar", &bar); 1300 assert(foo && !bar && args[1] == "nonoption" && args[2] == "--bar"); 1301 1302 args = ["program.name", "--foo", "nonoption", "--zab"]; 1303 foo = bar = false; 1304 getopt(args, 1305 std.getopt.config.stopOnFirstNonOption, 1306 "foo", &foo, 1307 "bar", &bar); 1308 assert(foo && !bar && args[1] == "nonoption" && args[2] == "--zab"); 1309 1310 args = ["program.name", "--fb1", "--fb2=true", "--tb1=false"]; 1311 bool fb1, fb2; 1312 bool tb1 = true; 1313 getopt(args, "fb1", &fb1, "fb2", &fb2, "tb1", &tb1); 1314 assert(fb1 && fb2 && !tb1); 1315 1316 // test keepEndOfOptions 1317 1318 args = ["program.name", "--foo", "nonoption", "--bar", "--", "--baz"]; 1319 getopt(args, 1320 std.getopt.config.keepEndOfOptions, 1321 "foo", &foo, 1322 "bar", &bar); 1323 assert(args == ["program.name", "nonoption", "--", "--baz"]); 1324 1325 // Ensure old behavior without the keepEndOfOptions 1326 1327 args = ["program.name", "--foo", "nonoption", "--bar", "--", "--baz"]; 1328 getopt(args, 1329 "foo", &foo, 1330 "bar", &bar); 1331 assert(args == ["program.name", "nonoption", "--baz"]); 1332 1333 // test function callbacks 1334 1335 static class MyEx : Exception 1336 { 1337 this() { super(""); } 1338 this(string option) { this(); this.option = option; } 1339 this(string option, string value) { this(option); this.value = value; } 1340 1341 string option; 1342 string value; 1343 } 1344 1345 static void myStaticHandler1() { throw new MyEx(); } 1346 args = ["program.name", "--verbose"]; 1347 try { getopt(args, "verbose", &myStaticHandler1); assert(0); } 1348 catch (MyEx ex) { assert(ex.option is null && ex.value is null); } 1349 1350 static void myStaticHandler2(string option) { throw new MyEx(option); } 1351 args = ["program.name", "--verbose"]; 1352 try { getopt(args, "verbose", &myStaticHandler2); assert(0); } 1353 catch (MyEx ex) { assert(ex.option == "verbose" && ex.value is null); } 1354 1355 static void myStaticHandler3(string option, string value) { throw new MyEx(option, value); } 1356 args = ["program.name", "--verbose", "2"]; 1357 try { getopt(args, "verbose", &myStaticHandler3); assert(0); } 1358 catch (MyEx ex) { assert(ex.option == "verbose" && ex.value == "2"); } 1359 } 1360 1361 @safe unittest // @safe std.getopt.config option use 1362 { 1363 long x = 0; 1364 string[] args = ["program", "--inc-x", "--inc-x"]; 1365 getopt(args, 1366 std.getopt.config.caseSensitive, 1367 "inc-x", "Add one to x", delegate void() { x++; }); 1368 assert(x == 2); 1369 } 1370 1371 @system unittest 1372 { 1373 // From bugzilla 2142 1374 bool f_linenum, f_filename; 1375 string[] args = [ "", "-nl" ]; 1376 getopt 1377 ( 1378 args, 1379 std.getopt.config.bundling, 1380 //std.getopt.config.caseSensitive, 1381 "linenum|l", &f_linenum, 1382 "filename|n", &f_filename 1383 ); 1384 assert(f_linenum); 1385 assert(f_filename); 1386 } 1387 1388 @system unittest 1389 { 1390 // From bugzilla 6887 1391 string[] p; 1392 string[] args = ["", "-pa"]; 1393 getopt(args, "p", &p); 1394 assert(p.length == 1); 1395 assert(p[0] == "a"); 1396 } 1397 1398 @system unittest 1399 { 1400 // From bugzilla 6888 1401 int[string] foo; 1402 auto args = ["", "-t", "a=1"]; 1403 getopt(args, "t", &foo); 1404 assert(foo == ["a":1]); 1405 } 1406 1407 @system unittest 1408 { 1409 // From bugzilla 9583 1410 int opt; 1411 auto args = ["prog", "--opt=123", "--", "--a", "--b", "--c"]; 1412 getopt(args, "opt", &opt); 1413 assert(args == ["prog", "--a", "--b", "--c"]); 1414 } 1415 1416 @system unittest 1417 { 1418 string foo, bar; 1419 auto args = ["prog", "-thello", "-dbar=baz"]; 1420 getopt(args, "t", &foo, "d", &bar); 1421 assert(foo == "hello"); 1422 assert(bar == "bar=baz"); 1423 1424 // From bugzilla 5762 1425 string a; 1426 args = ["prog", "-a-0x12"]; 1427 getopt(args, config.bundling, "a|addr", &a); 1428 assert(a == "-0x12", a); 1429 args = ["prog", "--addr=-0x12"]; 1430 getopt(args, config.bundling, "a|addr", &a); 1431 assert(a == "-0x12"); 1432 1433 // From https://d.puremagic.com/issues/show_bug.cgi?id=11764 1434 args = ["main", "-test"]; 1435 bool opt; 1436 args.getopt(config.passThrough, "opt", &opt); 1437 assert(args == ["main", "-test"]); 1438 1439 // From https://issues.dlang.org/show_bug.cgi?id=15220 1440 args = ["main", "-o=str"]; 1441 string o; 1442 args.getopt("o", &o); 1443 assert(o == "str"); 1444 1445 args = ["main", "-o=str"]; 1446 o = null; 1447 args.getopt(config.bundling, "o", &o); 1448 assert(o == "str"); 1449 } 1450 1451 @system unittest // 5228 1452 { 1453 import std.conv; 1454 import std.exception; 1455 1456 auto args = ["prog", "--foo=bar"]; 1457 int abc; 1458 assertThrown!GetOptException(getopt(args, "abc", &abc)); 1459 1460 args = ["prog", "--abc=string"]; 1461 assertThrown!ConvException(getopt(args, "abc", &abc)); 1462 } 1463 1464 @system unittest // From bugzilla 7693 1465 { 1466 import std.exception; 1467 1468 enum Foo { 1469 bar, 1470 baz 1471 } 1472 1473 auto args = ["prog", "--foo=barZZZ"]; 1474 Foo foo; 1475 assertThrown(getopt(args, "foo", &foo)); 1476 args = ["prog", "--foo=bar"]; 1477 assertNotThrown(getopt(args, "foo", &foo)); 1478 args = ["prog", "--foo", "barZZZ"]; 1479 assertThrown(getopt(args, "foo", &foo)); 1480 args = ["prog", "--foo", "baz"]; 1481 assertNotThrown(getopt(args, "foo", &foo)); 1482 } 1483 1484 @system unittest // same bug as 7693 only for bool 1485 { 1486 import std.exception; 1487 1488 auto args = ["prog", "--foo=truefoobar"]; 1489 bool foo; 1490 assertThrown(getopt(args, "foo", &foo)); 1491 args = ["prog", "--foo"]; 1492 getopt(args, "foo", &foo); 1493 assert(foo); 1494 } 1495 1496 @system unittest 1497 { 1498 bool foo; 1499 auto args = ["prog", "--foo"]; 1500 getopt(args, "foo", &foo); 1501 assert(foo); 1502 } 1503 1504 @system unittest 1505 { 1506 bool foo; 1507 bool bar; 1508 auto args = ["prog", "--foo", "-b"]; 1509 getopt(args, config.caseInsensitive,"foo|f", "Some foo", &foo, 1510 config.caseSensitive, "bar|b", "Some bar", &bar); 1511 assert(foo); 1512 assert(bar); 1513 } 1514 1515 @system unittest 1516 { 1517 bool foo; 1518 bool bar; 1519 auto args = ["prog", "-b", "--foo", "-z"]; 1520 getopt(args, config.caseInsensitive, config.required, "foo|f", "Some foo", 1521 &foo, config.caseSensitive, "bar|b", "Some bar", &bar, 1522 config.passThrough); 1523 assert(foo); 1524 assert(bar); 1525 } 1526 1527 @system unittest 1528 { 1529 import std.exception; 1530 1531 bool foo; 1532 bool bar; 1533 auto args = ["prog", "-b", "-z"]; 1534 assertThrown(getopt(args, config.caseInsensitive, config.required, "foo|f", 1535 "Some foo", &foo, config.caseSensitive, "bar|b", "Some bar", &bar, 1536 config.passThrough)); 1537 } 1538 1539 @system unittest 1540 { 1541 import std.exception; 1542 1543 bool foo; 1544 bool bar; 1545 auto args = ["prog", "--foo", "-z"]; 1546 assertNotThrown(getopt(args, config.caseInsensitive, config.required, 1547 "foo|f", "Some foo", &foo, config.caseSensitive, "bar|b", "Some bar", 1548 &bar, config.passThrough)); 1549 assert(foo); 1550 assert(!bar); 1551 } 1552 1553 @system unittest 1554 { 1555 bool foo; 1556 auto args = ["prog", "-f"]; 1557 auto r = getopt(args, config.caseInsensitive, "help|f", "Some foo", &foo); 1558 assert(foo); 1559 assert(!r.helpWanted); 1560 } 1561 1562 @safe unittest // implicit help option without config.passThrough 1563 { 1564 string[] args = ["program", "--help"]; 1565 auto r = getopt(args); 1566 assert(r.helpWanted); 1567 } 1568 1569 // Issue 13316 - std.getopt: implicit help option breaks the next argument 1570 @system unittest 1571 { 1572 string[] args = ["program", "--help", "--", "something"]; 1573 getopt(args); 1574 assert(args == ["program", "something"]); 1575 1576 args = ["program", "--help", "--"]; 1577 getopt(args); 1578 assert(args == ["program"]); 1579 1580 bool b; 1581 args = ["program", "--help", "nonoption", "--option"]; 1582 getopt(args, config.stopOnFirstNonOption, "option", &b); 1583 assert(args == ["program", "nonoption", "--option"]); 1584 } 1585 1586 // Issue 13317 - std.getopt: endOfOptions broken when it doesn't look like an option 1587 @system unittest 1588 { 1589 auto endOfOptionsBackup = endOfOptions; 1590 scope(exit) endOfOptions = endOfOptionsBackup; 1591 endOfOptions = "endofoptions"; 1592 string[] args = ["program", "endofoptions", "--option"]; 1593 bool b = false; 1594 getopt(args, "option", &b); 1595 assert(!b); 1596 assert(args == ["program", "--option"]); 1597 } 1598 1599 /** This function prints the passed $(D Option)s and text in an aligned manner on $(D stdout). 1600 1601 The passed text will be printed first, followed by a newline, then the short 1602 and long version of every option will be printed. The short and long version 1603 will be aligned to the longest option of every $(D Option) passed. If the option 1604 is required, then "Required:" will be printed after the long version of the 1605 $(D Option). If a help message is present it will be printed next. The format is 1606 illustrated by this code: 1607 1608 ------------ 1609 foreach (it; opt) 1610 { 1611 writefln("%*s %*s%s%s", lengthOfLongestShortOption, it.optShort, 1612 lengthOfLongestLongOption, it.optLong, 1613 it.required ? " Required: " : " ", it.help); 1614 } 1615 ------------ 1616 1617 Params: 1618 text = The text to printed at the beginning of the help output. 1619 opt = The $(D Option) extracted from the $(D getopt) parameter. 1620 */ 1621 void defaultGetoptPrinter(string text, Option[] opt) 1622 { 1623 import std.stdio : stdout; 1624 1625 defaultGetoptFormatter(stdout.lockingTextWriter(), text, opt); 1626 } 1627 1628 /** This function writes the passed text and $(D Option) into an output range 1629 in the manner described in the documentation of function 1630 $(D defaultGetoptPrinter). 1631 1632 Params: 1633 output = The output range used to write the help information. 1634 text = The text to print at the beginning of the help output. 1635 opt = The $(D Option) extracted from the $(D getopt) parameter. 1636 */ 1637 void defaultGetoptFormatter(Output)(Output output, string text, Option[] opt) 1638 { 1639 import std.algorithm.comparison : min, max; 1640 import std.format : formattedWrite; 1641 1642 output.formattedWrite("%s\n", text); 1643 1644 size_t ls, ll; 1645 bool hasRequired = false; 1646 foreach (it; opt) 1647 { 1648 ls = max(ls, it.optShort.length); 1649 ll = max(ll, it.optLong.length); 1650 1651 hasRequired = hasRequired || it.required; 1652 } 1653 1654 string re = " Required: "; 1655 1656 foreach (it; opt) 1657 { 1658 output.formattedWrite("%*s %*s%*s%s\n", ls, it.optShort, ll, it.optLong, 1659 hasRequired ? re.length : 1, it.required ? re : " ", it.help); 1660 } 1661 } 1662 1663 @system unittest 1664 { 1665 import std.conv; 1666 1667 import std.array; 1668 import std.string; 1669 bool a; 1670 auto args = ["prog", "--foo"]; 1671 auto t = getopt(args, "foo|f", "Help", &a); 1672 string s; 1673 auto app = appender!string(); 1674 defaultGetoptFormatter(app, "Some Text", t.options); 1675 1676 string helpMsg = app.data; 1677 //writeln(helpMsg); 1678 assert(helpMsg.length); 1679 assert(helpMsg.count("\n") == 3, to!string(helpMsg.count("\n")) ~ " " 1680 ~ helpMsg); 1681 assert(helpMsg.indexOf("--foo") != -1); 1682 assert(helpMsg.indexOf("-f") != -1); 1683 assert(helpMsg.indexOf("-h") != -1); 1684 assert(helpMsg.indexOf("--help") != -1); 1685 assert(helpMsg.indexOf("Help") != -1); 1686 1687 string wanted = "Some Text\n-f --foo Help\n-h --help This help " 1688 ~ "information.\n"; 1689 assert(wanted == helpMsg); 1690 } 1691 1692 @system unittest 1693 { 1694 import std.array ; 1695 import std.conv; 1696 import std.string; 1697 bool a; 1698 auto args = ["prog", "--foo"]; 1699 auto t = getopt(args, config.required, "foo|f", "Help", &a); 1700 string s; 1701 auto app = appender!string(); 1702 defaultGetoptFormatter(app, "Some Text", t.options); 1703 1704 string helpMsg = app.data; 1705 //writeln(helpMsg); 1706 assert(helpMsg.length); 1707 assert(helpMsg.count("\n") == 3, to!string(helpMsg.count("\n")) ~ " " 1708 ~ helpMsg); 1709 assert(helpMsg.indexOf("Required:") != -1); 1710 assert(helpMsg.indexOf("--foo") != -1); 1711 assert(helpMsg.indexOf("-f") != -1); 1712 assert(helpMsg.indexOf("-h") != -1); 1713 assert(helpMsg.indexOf("--help") != -1); 1714 assert(helpMsg.indexOf("Help") != -1); 1715 1716 string wanted = "Some Text\n-f --foo Required: Help\n-h --help " 1717 ~ " This help information.\n"; 1718 assert(wanted == helpMsg, helpMsg ~ wanted); 1719 } 1720 1721 @system unittest // Issue 14724 1722 { 1723 bool a; 1724 auto args = ["prog", "--help"]; 1725 GetoptResult rslt; 1726 try 1727 { 1728 rslt = getopt(args, config.required, "foo|f", "bool a", &a); 1729 } 1730 catch (Exception e) 1731 { 1732 enum errorMsg = "If the request for help was passed required options" ~ 1733 "must not be set."; 1734 assert(false, errorMsg); 1735 } 1736 1737 assert(rslt.helpWanted); 1738 } 1739 1740 // throw on duplicate options 1741 @system unittest 1742 { 1743 import core.exception; 1744 auto args = ["prog", "--abc", "1"]; 1745 int abc, def; 1746 assertThrown!AssertError(getopt(args, "abc", &abc, "abc", &abc)); 1747 assertThrown!AssertError(getopt(args, "abc|a", &abc, "def|a", &def)); 1748 assertNotThrown!AssertError(getopt(args, "abc", &abc, "def", &def)); 1749 } 1750 1751 @system unittest // Issue 17327 repeated option use 1752 { 1753 long num = 0; 1754 1755 string[] args = ["program", "--num", "3"]; 1756 getopt(args, "n|num", &num); 1757 assert(num == 3); 1758 1759 args = ["program", "--num", "3", "--num", "5"]; 1760 getopt(args, "n|num", &num); 1761 assert(num == 5); 1762 1763 args = ["program", "--n", "3", "--num", "5", "-n", "-7"]; 1764 getopt(args, "n|num", &num); 1765 assert(num == -7); 1766 1767 void add1() { num++; } 1768 void add2(string option) { num += 2; } 1769 void addN(string option, string value) 1770 { 1771 import std.conv : to; 1772 num += value.to!long; 1773 } 1774 1775 num = 0; 1776 args = ["program", "--add1", "--add2", "--add1", "--add", "5", "--add2", "--add", "10"]; 1777 getopt(args, 1778 "add1", "Add 1 to num", &add1, 1779 "add2", "Add 2 to num", &add2, 1780 "add", "Add N to num", &addN,); 1781 assert(num == 21); 1782 1783 bool flag = false; 1784 args = ["program", "--flag"]; 1785 getopt(args, "f|flag", "Boolean", &flag); 1786 assert(flag); 1787 1788 flag = false; 1789 args = ["program", "-f", "-f"]; 1790 getopt(args, "f|flag", "Boolean", &flag); 1791 assert(flag); 1792 1793 flag = false; 1794 args = ["program", "--flag=true", "--flag=false"]; 1795 getopt(args, "f|flag", "Boolean", &flag); 1796 assert(!flag); 1797 1798 flag = false; 1799 args = ["program", "--flag=true", "--flag=false", "-f"]; 1800 getopt(args, "f|flag", "Boolean", &flag); 1801 assert(flag); 1802 } 1803 1804 @safe unittest // Delegates as callbacks 1805 { 1806 alias TwoArgOptionHandler = void delegate(string option, string value) @safe; 1807 1808 TwoArgOptionHandler makeAddNHandler(ref long dest) 1809 { 1810 void addN(ref long dest, string n) 1811 { 1812 import std.conv : to; 1813 dest += n.to!long; 1814 } 1815 1816 return (option, value) => addN(dest, value); 1817 } 1818 1819 long x = 0; 1820 long y = 0; 1821 1822 string[] args = 1823 ["program", "--x-plus-1", "--x-plus-1", "--x-plus-5", "--x-plus-n", "10", 1824 "--y-plus-n", "25", "--y-plus-7", "--y-plus-n", "15", "--y-plus-3"]; 1825 1826 getopt(args, 1827 "x-plus-1", "Add one to x", delegate void() { x += 1; }, 1828 "x-plus-5", "Add five to x", delegate void(string option) { x += 5; }, 1829 "x-plus-n", "Add NUM to x", makeAddNHandler(x), 1830 "y-plus-7", "Add seven to y", delegate void() { y += 7; }, 1831 "y-plus-3", "Add three to y", delegate void(string option) { y += 3; }, 1832 "y-plus-n", "Add NUM to x", makeAddNHandler(y),); 1833 1834 assert(x == 17); 1835 assert(y == 50); 1836 } 1837 1838 @system unittest // Hyphens at the start of option values; Issue 17650 1839 { 1840 auto args = ["program", "-m", "-5", "-n", "-50", "-c", "-", "-f", "-"]; 1841 1842 int m; 1843 int n; 1844 char c; 1845 string f; 1846 1847 getopt(args, 1848 "m|mm", "integer", &m, 1849 "n|nn", "integer", &n, 1850 "c|cc", "character", &c, 1851 "f|file", "filename or hyphen for stdin", &f); 1852 1853 assert(m == -5); 1854 assert(n == -50); 1855 assert(c == '-'); 1856 assert(f == "-"); 1857 } 1858