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