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