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 private string optionValidatorErrorFormat(string msg, size_t idx) 546 { 547 import std.conv : to; 548 return "getopt validator: " ~ msg ~ " (at position " ~ to!(string)(idx) ~ 549 ")"; 550 } 551 552 /* 553 This function verifies that the variadic parameters passed in getOpt 554 follow this pattern: 555 556 [config override], option, [description], receiver, 557 558 - config override: a config value, optional 559 - option: a string or a char 560 - description: a string, optional 561 - receiver: a pointer or a callable 562 */ 563 private template optionValidator(A...) 564 { 565 enum isReceiver(T) = is(T == U*, U) || (is(T == function)) || (is(T == delegate)); 566 enum isOptionStr(T) = isSomeString!T || isSomeChar!T; 567 568 auto validator() 569 { 570 string msg; 571 static if (A.length > 0) 572 { 573 static if (isReceiver!(A[0])) 574 { 575 msg = optionValidatorErrorFormat("first argument must be a string or a config", 0); 576 } 577 else static if (!isOptionStr!(A[0]) && !is(A[0] == config)) 578 { 579 msg = optionValidatorErrorFormat("invalid argument type: " ~ A[0].stringof, 0); 580 } 581 else 582 { 583 static foreach (i; 1 .. A.length) 584 { 585 static if (!isReceiver!(A[i]) && !isOptionStr!(A[i]) && 586 !(is(A[i] == config))) 587 { 588 msg = optionValidatorErrorFormat("invalid argument type: " ~ A[i].stringof, i); 589 goto end; 590 } 591 else static if (isReceiver!(A[i]) && !isOptionStr!(A[i-1])) 592 { 593 msg = optionValidatorErrorFormat("a receiver can not be preceeded by a receiver", i); 594 goto end; 595 } 596 else static if (i > 1 && isOptionStr!(A[i]) && isOptionStr!(A[i-1]) 597 && isSomeString!(A[i-2])) 598 { 599 msg = optionValidatorErrorFormat("a string can not be preceeded by two strings", i); 600 goto end; 601 } 602 } 603 } 604 static if (!isReceiver!(A[$-1]) && !is(A[$-1] == config)) 605 { 606 msg = optionValidatorErrorFormat("last argument must be a receiver or a config", 607 A.length -1); 608 } 609 } 610 end: 611 return msg; 612 } 613 enum message = validator; 614 alias optionValidator = message; 615 } 616 617 private auto getoptTo(R)(string option, string value, 618 size_t idx, string file = __FILE__, size_t line = __LINE__) 619 { 620 import std.conv : to, ConvException; 621 try 622 { 623 return to!R(value); 624 } 625 catch (ConvException e) 626 { 627 throw new ConvException("Argument '" ~ value ~ "' at position '" ~ 628 to!(string)(idx) ~ "' could not be converted to type '" ~ 629 R.stringof ~ "' as required by option '" ~ option ~ "'.", e, file, 630 line); 631 } 632 } 633 634 @safe pure unittest 635 { 636 alias P = void*; 637 alias S = string; 638 alias A = char; 639 alias C = config; 640 alias F = void function(); 641 642 static assert(optionValidator!(S,P) == ""); 643 static assert(optionValidator!(S,F) == ""); 644 static assert(optionValidator!(A,P) == ""); 645 static assert(optionValidator!(A,F) == ""); 646 647 static assert(optionValidator!(C,S,P) == ""); 648 static assert(optionValidator!(C,S,F) == ""); 649 static assert(optionValidator!(C,A,P) == ""); 650 static assert(optionValidator!(C,A,F) == ""); 651 652 static assert(optionValidator!(C,S,S,P) == ""); 653 static assert(optionValidator!(C,S,S,F) == ""); 654 static assert(optionValidator!(C,A,S,P) == ""); 655 static assert(optionValidator!(C,A,S,F) == ""); 656 657 static assert(optionValidator!(C,S,S,P) == ""); 658 static assert(optionValidator!(C,S,S,P,C,S,F) == ""); 659 static assert(optionValidator!(C,S,P,C,S,S,F) == ""); 660 661 static assert(optionValidator!(C,A,P,A,S,F) == ""); 662 static assert(optionValidator!(C,A,P,C,A,S,F) == ""); 663 664 static assert(optionValidator!(P,S,S) != ""); 665 static assert(optionValidator!(P,P,S) != ""); 666 static assert(optionValidator!(P,F,S,P) != ""); 667 static assert(optionValidator!(C,C,S) != ""); 668 static assert(optionValidator!(S,S,P,S,S,P,S) != ""); 669 static assert(optionValidator!(S,S,P,P) != ""); 670 static assert(optionValidator!(S,S,S,P) != ""); 671 672 static assert(optionValidator!(C,A,S,P,C,A,F) == ""); 673 static assert(optionValidator!(C,A,P,C,A,S,F) == ""); 674 } 675 676 // https://issues.dlang.org/show_bug.cgi?id=15914 677 @safe unittest 678 { 679 import std.exception : assertThrown; 680 bool opt; 681 string[] args = ["program", "-a"]; 682 getopt(args, config.passThrough, 'a', &opt); 683 assert(opt); 684 opt = false; 685 args = ["program", "-a"]; 686 getopt(args, 'a', &opt); 687 assert(opt); 688 opt = false; 689 args = ["program", "-a"]; 690 getopt(args, 'a', "help string", &opt); 691 assert(opt); 692 opt = false; 693 args = ["program", "-a"]; 694 getopt(args, config.caseSensitive, 'a', "help string", &opt); 695 assert(opt); 696 697 assertThrown(getopt(args, "", "forgot to put a string", &opt)); 698 } 699 700 private void getoptImpl(T...)(ref string[] args, ref configuration cfg, 701 ref GetoptResult rslt, ref GetOptException excep, 702 void[][string] visitedLongOpts, void[][string] visitedShortOpts, T opts) 703 { 704 enum validationMessage = optionValidator!T; 705 static assert(validationMessage == "", validationMessage); 706 707 import std.algorithm.mutation : remove; 708 import std.conv : to; 709 import std.uni : toLower; 710 static if (opts.length) 711 { 712 static if (is(typeof(opts[0]) : config)) 713 { 714 // it's a configuration flag, act on it 715 setConfig(cfg, opts[0]); 716 return getoptImpl(args, cfg, rslt, excep, visitedLongOpts, 717 visitedShortOpts, opts[1 .. $]); 718 } 719 else 720 { 721 // it's an option string 722 auto option = to!string(opts[0]); 723 if (option.length == 0) 724 { 725 excep = new GetOptException("An option name may not be an empty string", excep); 726 return; 727 } 728 Option optionHelp = splitAndGet(option); 729 optionHelp.required = cfg.required; 730 731 if (optionHelp.optLong.length) 732 { 733 auto name = optionHelp.optLong; 734 if (!cfg.caseSensitive) 735 name = name.toLower(); 736 assert(name !in visitedLongOpts, 737 "Long option " ~ optionHelp.optLong ~ " is multiply defined"); 738 739 visitedLongOpts[optionHelp.optLong] = []; 740 } 741 742 if (optionHelp.optShort.length) 743 { 744 auto name = optionHelp.optShort; 745 if (!cfg.caseSensitive) 746 name = name.toLower(); 747 assert(name !in visitedShortOpts, 748 "Short option " ~ optionHelp.optShort 749 ~ " is multiply defined"); 750 751 visitedShortOpts[optionHelp.optShort] = []; 752 } 753 754 static if (is(typeof(opts[1]) : string)) 755 { 756 alias receiver = opts[2]; 757 optionHelp.help = opts[1]; 758 immutable lowSliceIdx = 3; 759 } 760 else 761 { 762 alias receiver = opts[1]; 763 immutable lowSliceIdx = 2; 764 } 765 766 rslt.options ~= optionHelp; 767 768 bool incremental; 769 // Handle options of the form --blah+ 770 if (option.length && option[$ - 1] == autoIncrementChar) 771 { 772 option = option[0 .. $ - 1]; 773 incremental = true; 774 } 775 776 bool optWasHandled = handleOption(option, receiver, args, cfg, incremental); 777 778 if (cfg.required && !optWasHandled) 779 { 780 excep = new GetOptException("Required option " 781 ~ option ~ " was not supplied", excep); 782 } 783 cfg.required = false; 784 785 getoptImpl(args, cfg, rslt, excep, visitedLongOpts, 786 visitedShortOpts, opts[lowSliceIdx .. $]); 787 } 788 } 789 else 790 { 791 // no more options to look for, potentially some arguments left 792 for (size_t i = 1; i < args.length;) 793 { 794 auto a = args[i]; 795 if (endOfOptions.length && a == endOfOptions) 796 { 797 // Consume the "--" if keepEndOfOptions is not specified 798 if (!cfg.keepEndOfOptions) 799 args = args.remove(i); 800 break; 801 } 802 if (a.length < 2 || a[0] != optionChar) 803 { 804 // not an option 805 if (cfg.stopOnFirstNonOption) break; 806 ++i; 807 continue; 808 } 809 if (a == "--help" || a == "-h") 810 { 811 rslt.helpWanted = true; 812 args = args.remove(i); 813 continue; 814 } 815 if (!cfg.passThrough) 816 { 817 throw new GetOptException("Unrecognized option "~a, excep); 818 } 819 ++i; 820 } 821 822 Option helpOpt; 823 helpOpt.optShort = "-h"; 824 helpOpt.optLong = "--help"; 825 helpOpt.help = "This help information."; 826 rslt.options ~= helpOpt; 827 } 828 } 829 830 private bool handleOption(R)(string option, R receiver, ref string[] args, 831 ref configuration cfg, bool incremental) 832 { 833 import std.algorithm.iteration : map, splitter; 834 import std.ascii : isAlpha; 835 import std.conv : text, to; 836 // Scan arguments looking for a match for this option 837 bool ret = false; 838 for (size_t i = 1; i < args.length; ) 839 { 840 auto a = args[i]; 841 if (endOfOptions.length && a == endOfOptions) break; 842 if (cfg.stopOnFirstNonOption && (!a.length || a[0] != optionChar)) 843 { 844 // first non-option is end of options 845 break; 846 } 847 // Unbundle bundled arguments if necessary 848 if (cfg.bundling && a.length > 2 && a[0] == optionChar && 849 a[1] != optionChar) 850 { 851 string[] expanded; 852 foreach (j, dchar c; a[1 .. $]) 853 { 854 // If the character is not alpha, stop right there. This allows 855 // e.g. -j100 to work as "pass argument 100 to option -j". 856 if (!isAlpha(c)) 857 { 858 if (c == '=') 859 j++; 860 expanded ~= a[j + 1 .. $]; 861 break; 862 } 863 expanded ~= text(optionChar, c); 864 } 865 args = args[0 .. i] ~ expanded ~ args[i + 1 .. $]; 866 continue; 867 } 868 869 string val; 870 if (!optMatch(a, option, val, cfg)) 871 { 872 ++i; 873 continue; 874 } 875 876 ret = true; 877 878 // found it 879 // from here on, commit to eat args[i] 880 // (and potentially args[i + 1] too, but that comes later) 881 args = args[0 .. i] ~ args[i + 1 .. $]; 882 883 static if (is(typeof(*receiver))) 884 alias Target = typeof(*receiver); 885 else 886 // delegate 887 alias Target = void; 888 889 static if (is(Target == bool)) 890 { 891 if (val.length) 892 { 893 // parse '--b=true/false' 894 *receiver = getoptTo!(Target)(option, val, i); 895 } 896 else 897 { 898 // no argument means set it to true 899 *receiver = true; 900 } 901 } 902 else 903 { 904 import std.exception : enforce; 905 // non-boolean option, which might include an argument 906 enum isCallbackWithLessThanTwoParameters = 907 (is(R == delegate) || is(Target == function)) && 908 !is(typeof(receiver("", ""))); 909 if (!isCallbackWithLessThanTwoParameters && !(val.length) && !incremental) 910 { 911 // Eat the next argument too. Check to make sure there's one 912 // to be eaten first, though. 913 enforce!GetOptException(i < args.length, 914 "Missing value for argument " ~ a ~ "."); 915 val = args[i]; 916 args = args[0 .. i] ~ args[i + 1 .. $]; 917 } 918 static if (is(Target == enum) || 919 is(Target == string)) 920 { 921 *receiver = getoptTo!Target(option, val, i); 922 } 923 else static if (is(Target : real)) 924 { 925 // numeric receiver 926 if (incremental) 927 { 928 ++*receiver; 929 } 930 else 931 { 932 *receiver = getoptTo!Target(option, val, i); 933 } 934 } 935 else static if (is(Target == string)) 936 { 937 // string receiver 938 *receiver = getoptTo!(Target)(option, val, i); 939 } 940 else static if (is(R == delegate) || 941 is(Target == function)) 942 { 943 static if (is(typeof(receiver("", "")) : void)) 944 { 945 // option with argument 946 receiver(option, val); 947 } 948 else static if (is(typeof(receiver("")) : void)) 949 { 950 alias RType = typeof(receiver("")); 951 static assert(is(RType : void), 952 "Invalid receiver return type " ~ RType.stringof); 953 // boolean-style receiver 954 receiver(option); 955 } 956 else 957 { 958 alias RType = typeof(receiver()); 959 static assert(is(RType : void), 960 "Invalid receiver return type " ~ RType.stringof); 961 // boolean-style receiver without argument 962 receiver(); 963 } 964 } 965 else static if (isArray!(Target)) 966 { 967 // array receiver 968 import std.range : ElementEncodingType; 969 alias E = ElementEncodingType!(Target); 970 971 if (arraySep == "") 972 { 973 *receiver ~= getoptTo!E(option, val, i); 974 } 975 else 976 { 977 foreach (elem; val.splitter(arraySep)) 978 { 979 *receiver ~= getoptTo!E(option, elem, i); 980 } 981 } 982 } 983 else static if (isAssociativeArray!(Target)) 984 { 985 // hash receiver 986 alias K = typeof(receiver.keys[0]); 987 alias V = typeof(receiver.values[0]); 988 989 import std.range : only; 990 import std.string : indexOf; 991 import std.typecons : Tuple, tuple; 992 993 static Tuple!(K, V) getter(string input) 994 { 995 auto j = indexOf(input, assignChar); 996 enforce!GetOptException(j != -1, "Could not find '" 997 ~ to!string(assignChar) ~ "' in argument '" ~ input ~ "'."); 998 auto key = input[0 .. j]; 999 auto value = input[j + 1 .. $]; 1000 return tuple(getoptTo!K("", key, 0), getoptTo!V("", value, 0)); 1001 } 1002 1003 static void setHash(Range)(R receiver, Range range) 1004 { 1005 foreach (k, v; range.map!getter) 1006 (*receiver)[k] = v; 1007 } 1008 1009 if (arraySep == "") 1010 setHash(receiver, val.only); 1011 else 1012 setHash(receiver, val.splitter(arraySep)); 1013 } 1014 else 1015 static assert(false, "getopt does not know how to handle the type " ~ R.stringof); 1016 } 1017 } 1018 1019 return ret; 1020 } 1021 1022 // https://issues.dlang.org/show_bug.cgi?id=17574 1023 @safe unittest 1024 { 1025 import std.algorithm.searching : startsWith; 1026 1027 try 1028 { 1029 string[string] mapping; 1030 immutable as = arraySep; 1031 arraySep = ","; 1032 scope (exit) 1033 arraySep = as; 1034 string[] args = ["testProgram", "-m", "a=b,c=\"d,e,f\""]; 1035 args.getopt("m", &mapping); 1036 assert(false, "Exception not thrown"); 1037 } 1038 catch (GetOptException goe) 1039 assert(goe.msg.startsWith("Could not find")); 1040 } 1041 1042 // https://issues.dlang.org/show_bug.cgi?id=5316 - arrays with arraySep 1043 @safe unittest 1044 { 1045 import std.conv; 1046 1047 arraySep = ","; 1048 scope (exit) arraySep = ""; 1049 1050 string[] names; 1051 auto args = ["program.name", "-nfoo,bar,baz"]; 1052 getopt(args, "name|n", &names); 1053 assert(names == ["foo", "bar", "baz"], to!string(names)); 1054 1055 names = names.init; 1056 args = ["program.name", "-n", "foo,bar,baz"]; 1057 getopt(args, "name|n", &names); 1058 assert(names == ["foo", "bar", "baz"], to!string(names)); 1059 1060 names = names.init; 1061 args = ["program.name", "--name=foo,bar,baz"]; 1062 getopt(args, "name|n", &names); 1063 assert(names == ["foo", "bar", "baz"], to!string(names)); 1064 1065 names = names.init; 1066 args = ["program.name", "--name", "foo,bar,baz"]; 1067 getopt(args, "name|n", &names); 1068 assert(names == ["foo", "bar", "baz"], to!string(names)); 1069 } 1070 1071 // https://issues.dlang.org/show_bug.cgi?id=5316 - associative arrays with arraySep 1072 @safe unittest 1073 { 1074 import std.conv; 1075 1076 arraySep = ","; 1077 scope (exit) arraySep = ""; 1078 1079 int[string] values; 1080 values = values.init; 1081 auto args = ["program.name", "-vfoo=0,bar=1,baz=2"]; 1082 getopt(args, "values|v", &values); 1083 assert(values == ["foo":0, "bar":1, "baz":2], to!string(values)); 1084 1085 values = values.init; 1086 args = ["program.name", "-v", "foo=0,bar=1,baz=2"]; 1087 getopt(args, "values|v", &values); 1088 assert(values == ["foo":0, "bar":1, "baz":2], to!string(values)); 1089 1090 values = values.init; 1091 args = ["program.name", "--values=foo=0,bar=1,baz=2"]; 1092 getopt(args, "values|t", &values); 1093 assert(values == ["foo":0, "bar":1, "baz":2], to!string(values)); 1094 1095 values = values.init; 1096 args = ["program.name", "--values", "foo=0,bar=1,baz=2"]; 1097 getopt(args, "values|v", &values); 1098 assert(values == ["foo":0, "bar":1, "baz":2], to!string(values)); 1099 } 1100 1101 // https://github.com/dlang/phobos/issues/10680 1102 @safe unittest 1103 { 1104 arraySep = ","; 1105 scope(exit) arraySep = ""; 1106 const(string)[] s; 1107 string[] args = ["program.name", "-s", "a", "-s", "b", "-s", "c,d,e"]; 1108 getopt(args, "values|s", &s); 1109 assert(s == ["a", "b", "c", "d", "e"]); 1110 } 1111 1112 1113 /** 1114 The option character (default '-'). 1115 1116 Defaults to '-' but it can be assigned to prior to calling `getopt`. 1117 */ 1118 dchar optionChar = '-'; 1119 1120 /** 1121 The string that conventionally marks the end of all options (default '--'). 1122 1123 Defaults to "--" but can be assigned to prior to calling `getopt`. Assigning an 1124 empty string to `endOfOptions` effectively disables it. 1125 */ 1126 string endOfOptions = "--"; 1127 1128 /** 1129 The assignment character used in options with parameters (default '='). 1130 1131 Defaults to '=' but can be assigned to prior to calling `getopt`. 1132 */ 1133 dchar assignChar = '='; 1134 1135 /** 1136 When set to "", parameters to array and associative array receivers are 1137 treated as an individual argument. That is, only one argument is appended or 1138 inserted per appearance of the option switch. If `arraySep` is set to 1139 something else, then each parameter is first split by the separator, and the 1140 individual pieces are treated as arguments to the same option. 1141 1142 Defaults to "" but can be assigned to prior to calling `getopt`. 1143 */ 1144 string arraySep = ""; 1145 1146 private enum autoIncrementChar = '+'; 1147 1148 private struct configuration 1149 { 1150 import std.bitmanip : bitfields; 1151 mixin(bitfields!( 1152 bool, "caseSensitive", 1, 1153 bool, "bundling", 1, 1154 bool, "passThrough", 1, 1155 bool, "stopOnFirstNonOption", 1, 1156 bool, "keepEndOfOptions", 1, 1157 bool, "required", 1, 1158 ubyte, "", 2)); 1159 } 1160 1161 private bool optMatch(string arg, scope string optPattern, ref string value, 1162 configuration cfg) @safe 1163 { 1164 import std.algorithm.iteration : splitter; 1165 import std.string : indexOf; 1166 import std.uni : icmp; 1167 //writeln("optMatch:\n ", arg, "\n ", optPattern, "\n ", value); 1168 //scope(success) writeln("optMatch result: ", value); 1169 if (arg.length < 2 || arg[0] != optionChar) return false; 1170 // yank the leading '-' 1171 arg = arg[1 .. $]; 1172 immutable isLong = arg.length > 1 && arg[0] == optionChar; 1173 //writeln("isLong: ", isLong); 1174 // yank the second '-' if present 1175 if (isLong) arg = arg[1 .. $]; 1176 immutable eqPos = indexOf(arg, assignChar); 1177 if (isLong && eqPos >= 0) 1178 { 1179 // argument looks like --opt=value 1180 value = arg[eqPos + 1 .. $]; 1181 arg = arg[0 .. eqPos]; 1182 } 1183 else 1184 { 1185 if (!isLong && eqPos == 1) 1186 { 1187 // argument looks like -o=value 1188 value = arg[2 .. $]; 1189 arg = arg[0 .. 1]; 1190 } 1191 else 1192 if (!isLong && !cfg.bundling) 1193 { 1194 // argument looks like -ovalue and there's no bundling 1195 value = arg[1 .. $]; 1196 arg = arg[0 .. 1]; 1197 } 1198 else 1199 { 1200 // argument looks like --opt, or -oxyz with bundling 1201 value = null; 1202 } 1203 } 1204 //writeln("Arg: ", arg, " pattern: ", optPattern, " value: ", value); 1205 // Split the option 1206 foreach (v; splitter(optPattern, "|")) 1207 { 1208 //writeln("Trying variant: ", v, " against ", arg); 1209 if (arg == v || (!cfg.caseSensitive && icmp(arg, v) == 0)) 1210 return true; 1211 if (cfg.bundling && !isLong && v.length == 1 1212 && indexOf(arg, v) >= 0) 1213 { 1214 //writeln("success"); 1215 return true; 1216 } 1217 } 1218 return false; 1219 } 1220 1221 private void setConfig(ref configuration cfg, config option) @safe pure nothrow @nogc 1222 { 1223 final switch (option) 1224 { 1225 case config.caseSensitive: cfg.caseSensitive = true; break; 1226 case config.caseInsensitive: cfg.caseSensitive = false; break; 1227 case config.bundling: cfg.bundling = true; break; 1228 case config.noBundling: cfg.bundling = false; break; 1229 case config.passThrough: cfg.passThrough = true; break; 1230 case config.noPassThrough: cfg.passThrough = false; break; 1231 case config.required: cfg.required = true; break; 1232 case config.stopOnFirstNonOption: 1233 cfg.stopOnFirstNonOption = true; break; 1234 case config.keepEndOfOptions: 1235 cfg.keepEndOfOptions = true; break; 1236 } 1237 } 1238 1239 @safe unittest 1240 { 1241 import std.conv; 1242 import std.math.operations : isClose; 1243 1244 uint paranoid = 2; 1245 string[] args = ["program.name", "--paranoid", "--paranoid", "--paranoid"]; 1246 getopt(args, "paranoid+", ¶noid); 1247 assert(paranoid == 5, to!(string)(paranoid)); 1248 1249 enum Color { no, yes } 1250 Color color; 1251 args = ["program.name", "--color=yes",]; 1252 getopt(args, "color", &color); 1253 assert(color, to!(string)(color)); 1254 1255 color = Color.no; 1256 args = ["program.name", "--color", "yes",]; 1257 getopt(args, "color", &color); 1258 assert(color, to!(string)(color)); 1259 1260 string data = "file.dat"; 1261 int length = 24; 1262 bool verbose = false; 1263 args = ["program.name", "--length=5", "--file", "dat.file", "--verbose"]; 1264 getopt( 1265 args, 1266 "length", &length, 1267 "file", &data, 1268 "verbose", &verbose); 1269 assert(args.length == 1); 1270 assert(data == "dat.file"); 1271 assert(length == 5); 1272 assert(verbose); 1273 1274 // 1275 string[] outputFiles; 1276 args = ["program.name", "--output=myfile.txt", "--output", "yourfile.txt"]; 1277 getopt(args, "output", &outputFiles); 1278 assert(outputFiles.length == 2 1279 && outputFiles[0] == "myfile.txt" && outputFiles[1] == "yourfile.txt"); 1280 1281 outputFiles = []; 1282 arraySep = ","; 1283 args = ["program.name", "--output", "myfile.txt,yourfile.txt"]; 1284 getopt(args, "output", &outputFiles); 1285 assert(outputFiles.length == 2 1286 && outputFiles[0] == "myfile.txt" && outputFiles[1] == "yourfile.txt"); 1287 arraySep = ""; 1288 1289 foreach (testArgs; 1290 [["program.name", "--tune=alpha=0.5", "--tune", "beta=0.6"], 1291 ["program.name", "--tune=alpha=0.5,beta=0.6"], 1292 ["program.name", "--tune", "alpha=0.5,beta=0.6"]]) 1293 { 1294 arraySep = ","; 1295 double[string] tuningParms; 1296 getopt(testArgs, "tune", &tuningParms); 1297 assert(testArgs.length == 1); 1298 assert(tuningParms.length == 2); 1299 assert(isClose(tuningParms["alpha"], 0.5)); 1300 assert(isClose(tuningParms["beta"], 0.6)); 1301 arraySep = ""; 1302 } 1303 1304 uint verbosityLevel = 1; 1305 void myHandler(string option) 1306 { 1307 if (option == "quiet") 1308 { 1309 verbosityLevel = 0; 1310 } 1311 else 1312 { 1313 assert(option == "verbose"); 1314 verbosityLevel = 2; 1315 } 1316 } 1317 args = ["program.name", "--quiet"]; 1318 getopt(args, "verbose", &myHandler, "quiet", &myHandler); 1319 assert(verbosityLevel == 0); 1320 args = ["program.name", "--verbose"]; 1321 getopt(args, "verbose", &myHandler, "quiet", &myHandler); 1322 assert(verbosityLevel == 2); 1323 1324 verbosityLevel = 1; 1325 void myHandler2(string option, string value) 1326 { 1327 assert(option == "verbose"); 1328 verbosityLevel = 2; 1329 } 1330 args = ["program.name", "--verbose", "2"]; 1331 getopt(args, "verbose", &myHandler2); 1332 assert(verbosityLevel == 2); 1333 1334 verbosityLevel = 1; 1335 void myHandler3() 1336 { 1337 verbosityLevel = 2; 1338 } 1339 args = ["program.name", "--verbose"]; 1340 getopt(args, "verbose", &myHandler3); 1341 assert(verbosityLevel == 2); 1342 1343 bool foo, bar; 1344 args = ["program.name", "--foo", "--bAr"]; 1345 getopt(args, 1346 std.getopt.config.caseSensitive, 1347 std.getopt.config.passThrough, 1348 "foo", &foo, 1349 "bar", &bar); 1350 assert(args[1] == "--bAr"); 1351 1352 // test stopOnFirstNonOption 1353 1354 args = ["program.name", "--foo", "nonoption", "--bar"]; 1355 foo = bar = false; 1356 getopt(args, 1357 std.getopt.config.stopOnFirstNonOption, 1358 "foo", &foo, 1359 "bar", &bar); 1360 assert(foo && !bar && args[1] == "nonoption" && args[2] == "--bar"); 1361 1362 args = ["program.name", "--foo", "nonoption", "--zab"]; 1363 foo = bar = false; 1364 getopt(args, 1365 std.getopt.config.stopOnFirstNonOption, 1366 "foo", &foo, 1367 "bar", &bar); 1368 assert(foo && !bar && args[1] == "nonoption" && args[2] == "--zab"); 1369 1370 args = ["program.name", "--fb1", "--fb2=true", "--tb1=false"]; 1371 bool fb1, fb2; 1372 bool tb1 = true; 1373 getopt(args, "fb1", &fb1, "fb2", &fb2, "tb1", &tb1); 1374 assert(fb1 && fb2 && !tb1); 1375 1376 // test keepEndOfOptions 1377 1378 args = ["program.name", "--foo", "nonoption", "--bar", "--", "--baz"]; 1379 getopt(args, 1380 std.getopt.config.keepEndOfOptions, 1381 "foo", &foo, 1382 "bar", &bar); 1383 assert(args == ["program.name", "nonoption", "--", "--baz"]); 1384 1385 // Ensure old behavior without the keepEndOfOptions 1386 1387 args = ["program.name", "--foo", "nonoption", "--bar", "--", "--baz"]; 1388 getopt(args, 1389 "foo", &foo, 1390 "bar", &bar); 1391 assert(args == ["program.name", "nonoption", "--baz"]); 1392 1393 // test function callbacks 1394 1395 static class MyEx : Exception 1396 { 1397 this() { super(""); } 1398 this(string option) { this(); this.option = option; } 1399 this(string option, string value) { this(option); this.value = value; } 1400 1401 string option; 1402 string value; 1403 } 1404 1405 static void myStaticHandler1() { throw new MyEx(); } 1406 args = ["program.name", "--verbose"]; 1407 try { getopt(args, "verbose", &myStaticHandler1); assert(0); } 1408 catch (MyEx ex) { assert(ex.option is null && ex.value is null); } 1409 1410 static void myStaticHandler2(string option) { throw new MyEx(option); } 1411 args = ["program.name", "--verbose"]; 1412 try { getopt(args, "verbose", &myStaticHandler2); assert(0); } 1413 catch (MyEx ex) { assert(ex.option == "verbose" && ex.value is null); } 1414 1415 static void myStaticHandler3(string option, string value) { throw new MyEx(option, value); } 1416 args = ["program.name", "--verbose", "2"]; 1417 try { getopt(args, "verbose", &myStaticHandler3); assert(0); } 1418 catch (MyEx ex) { assert(ex.option == "verbose" && ex.value == "2"); } 1419 1420 // check that GetOptException is thrown if the value is missing 1421 args = ["program.name", "--verbose"]; 1422 try { getopt(args, "verbose", &myStaticHandler3); assert(0); } 1423 catch (GetOptException e) {} 1424 catch (Exception e) { assert(0); } 1425 } 1426 1427 @safe unittest // @safe std.getopt.config option use 1428 { 1429 long x = 0; 1430 string[] args = ["program", "--inc-x", "--inc-x"]; 1431 getopt(args, 1432 std.getopt.config.caseSensitive, 1433 "inc-x", "Add one to x", delegate void() { x++; }); 1434 assert(x == 2); 1435 } 1436 1437 // https://issues.dlang.org/show_bug.cgi?id=2142 1438 @safe unittest 1439 { 1440 bool f_linenum, f_filename; 1441 string[] args = [ "", "-nl" ]; 1442 getopt 1443 ( 1444 args, 1445 std.getopt.config.bundling, 1446 //std.getopt.config.caseSensitive, 1447 "linenum|l", &f_linenum, 1448 "filename|n", &f_filename 1449 ); 1450 assert(f_linenum); 1451 assert(f_filename); 1452 } 1453 1454 // https://issues.dlang.org/show_bug.cgi?id=6887 1455 @safe unittest 1456 { 1457 string[] p; 1458 string[] args = ["", "-pa"]; 1459 getopt(args, "p", &p); 1460 assert(p.length == 1); 1461 assert(p[0] == "a"); 1462 } 1463 1464 // https://issues.dlang.org/show_bug.cgi?id=6888 1465 @safe unittest 1466 { 1467 int[string] foo; 1468 auto args = ["", "-t", "a=1"]; 1469 getopt(args, "t", &foo); 1470 assert(foo == ["a":1]); 1471 } 1472 1473 // https://issues.dlang.org/show_bug.cgi?id=9583 1474 @safe unittest 1475 { 1476 int opt; 1477 auto args = ["prog", "--opt=123", "--", "--a", "--b", "--c"]; 1478 getopt(args, "opt", &opt); 1479 assert(args == ["prog", "--a", "--b", "--c"]); 1480 } 1481 1482 @safe unittest 1483 { 1484 string foo, bar; 1485 auto args = ["prog", "-thello", "-dbar=baz"]; 1486 getopt(args, "t", &foo, "d", &bar); 1487 assert(foo == "hello"); 1488 assert(bar == "bar=baz"); 1489 1490 // From https://issues.dlang.org/show_bug.cgi?id=5762 1491 string a; 1492 args = ["prog", "-a-0x12"]; 1493 getopt(args, config.bundling, "a|addr", &a); 1494 assert(a == "-0x12", a); 1495 args = ["prog", "--addr=-0x12"]; 1496 getopt(args, config.bundling, "a|addr", &a); 1497 assert(a == "-0x12"); 1498 1499 // From https://issues.dlang.org/show_bug.cgi?id=11764 1500 args = ["main", "-test"]; 1501 bool opt; 1502 args.getopt(config.passThrough, "opt", &opt); 1503 assert(args == ["main", "-test"]); 1504 1505 // From https://issues.dlang.org/show_bug.cgi?id=15220 1506 args = ["main", "-o=str"]; 1507 string o; 1508 args.getopt("o", &o); 1509 assert(o == "str"); 1510 1511 args = ["main", "-o=str"]; 1512 o = null; 1513 args.getopt(config.bundling, "o", &o); 1514 assert(o == "str"); 1515 } 1516 1517 // https://issues.dlang.org/show_bug.cgi?id=5228 1518 @safe unittest 1519 { 1520 import std.conv; 1521 import std.exception; 1522 1523 auto args = ["prog", "--foo=bar"]; 1524 int abc; 1525 assertThrown!GetOptException(getopt(args, "abc", &abc)); 1526 1527 args = ["prog", "--abc=string"]; 1528 assertThrown!ConvException(getopt(args, "abc", &abc)); 1529 } 1530 1531 // https://issues.dlang.org/show_bug.cgi?id=7693 1532 @safe unittest 1533 { 1534 import std.exception; 1535 1536 enum Foo { 1537 bar, 1538 baz 1539 } 1540 1541 auto args = ["prog", "--foo=barZZZ"]; 1542 Foo foo; 1543 assertThrown(getopt(args, "foo", &foo)); 1544 args = ["prog", "--foo=bar"]; 1545 assertNotThrown(getopt(args, "foo", &foo)); 1546 args = ["prog", "--foo", "barZZZ"]; 1547 assertThrown(getopt(args, "foo", &foo)); 1548 args = ["prog", "--foo", "baz"]; 1549 assertNotThrown(getopt(args, "foo", &foo)); 1550 } 1551 1552 // Same as https://issues.dlang.org/show_bug.cgi?id=7693 only for `bool` 1553 @safe unittest 1554 { 1555 import std.exception; 1556 1557 auto args = ["prog", "--foo=truefoobar"]; 1558 bool foo; 1559 assertThrown(getopt(args, "foo", &foo)); 1560 args = ["prog", "--foo"]; 1561 getopt(args, "foo", &foo); 1562 assert(foo); 1563 } 1564 1565 @safe unittest 1566 { 1567 bool foo; 1568 auto args = ["prog", "--foo"]; 1569 getopt(args, "foo", &foo); 1570 assert(foo); 1571 } 1572 1573 @safe unittest 1574 { 1575 bool foo; 1576 bool bar; 1577 auto args = ["prog", "--foo", "-b"]; 1578 getopt(args, config.caseInsensitive,"foo|f", "Some foo", &foo, 1579 config.caseSensitive, "bar|b", "Some bar", &bar); 1580 assert(foo); 1581 assert(bar); 1582 } 1583 1584 @safe unittest 1585 { 1586 bool foo; 1587 bool bar; 1588 auto args = ["prog", "-b", "--foo", "-z"]; 1589 getopt(args, config.caseInsensitive, config.required, "foo|f", "Some foo", 1590 &foo, config.caseSensitive, "bar|b", "Some bar", &bar, 1591 config.passThrough); 1592 assert(foo); 1593 assert(bar); 1594 } 1595 1596 @safe unittest 1597 { 1598 import std.exception; 1599 1600 bool foo; 1601 bool bar; 1602 auto args = ["prog", "-b", "-z"]; 1603 assertThrown(getopt(args, config.caseInsensitive, config.required, "foo|f", 1604 "Some foo", &foo, config.caseSensitive, "bar|b", "Some bar", &bar, 1605 config.passThrough)); 1606 } 1607 1608 @safe unittest 1609 { 1610 import std.exception; 1611 1612 bool foo; 1613 bool bar; 1614 auto args = ["prog", "--foo", "-z"]; 1615 assertNotThrown(getopt(args, config.caseInsensitive, config.required, 1616 "foo|f", "Some foo", &foo, config.caseSensitive, "bar|b", "Some bar", 1617 &bar, config.passThrough)); 1618 assert(foo); 1619 assert(!bar); 1620 } 1621 1622 @safe unittest 1623 { 1624 bool foo; 1625 auto args = ["prog", "-f"]; 1626 auto r = getopt(args, config.caseInsensitive, "help|f", "Some foo", &foo); 1627 assert(foo); 1628 assert(!r.helpWanted); 1629 } 1630 1631 @safe unittest // implicit help option without config.passThrough 1632 { 1633 string[] args = ["program", "--help"]; 1634 auto r = getopt(args); 1635 assert(r.helpWanted); 1636 } 1637 1638 // std.getopt: implicit help option breaks the next argument 1639 // https://issues.dlang.org/show_bug.cgi?id=13316 1640 @safe unittest 1641 { 1642 string[] args = ["program", "--help", "--", "something"]; 1643 getopt(args); 1644 assert(args == ["program", "something"]); 1645 1646 args = ["program", "--help", "--"]; 1647 getopt(args); 1648 assert(args == ["program"]); 1649 1650 bool b; 1651 args = ["program", "--help", "nonoption", "--option"]; 1652 getopt(args, config.stopOnFirstNonOption, "option", &b); 1653 assert(args == ["program", "nonoption", "--option"]); 1654 } 1655 1656 // std.getopt: endOfOptions broken when it doesn't look like an option 1657 // https://issues.dlang.org/show_bug.cgi?id=13317 1658 @safe unittest 1659 { 1660 auto endOfOptionsBackup = endOfOptions; 1661 scope(exit) endOfOptions = endOfOptionsBackup; 1662 endOfOptions = "endofoptions"; 1663 string[] args = ["program", "endofoptions", "--option"]; 1664 bool b = false; 1665 getopt(args, "option", &b); 1666 assert(!b); 1667 assert(args == ["program", "--option"]); 1668 } 1669 1670 // make std.getopt ready for DIP 1000 1671 // https://issues.dlang.org/show_bug.cgi?id=20480 1672 @safe unittest 1673 { 1674 string[] args = ["test", "--foo", "42", "--bar", "BAR"]; 1675 int foo; 1676 string bar; 1677 getopt(args, "foo", &foo, "bar", "bar help", &bar); 1678 assert(foo == 42); 1679 assert(bar == "BAR"); 1680 } 1681 1682 /** This function prints the passed `Option`s and text in an aligned manner on `stdout`. 1683 1684 The passed text will be printed first, followed by a newline, then the short 1685 and long version of every option will be printed. The short and long version 1686 will be aligned to the longest option of every `Option` passed. If the option 1687 is required, then "Required:" will be printed after the long version of the 1688 `Option`. If a help message is present it will be printed next. The format is 1689 illustrated by this code: 1690 1691 ------------ 1692 foreach (it; opt) 1693 { 1694 writefln("%*s %*s%s%s", lengthOfLongestShortOption, it.optShort, 1695 lengthOfLongestLongOption, it.optLong, 1696 it.required ? " Required: " : " ", it.help); 1697 } 1698 ------------ 1699 1700 Params: 1701 text = The text to printed at the beginning of the help output. 1702 opt = The `Option` extracted from the `getopt` parameter. 1703 */ 1704 void defaultGetoptPrinter(string text, Option[] opt) @safe 1705 { 1706 import std.stdio : stdout; 1707 // stdout global __gshared is trusted with a locked text writer 1708 auto w = (() @trusted => stdout.lockingTextWriter())(); 1709 1710 defaultGetoptFormatter(w, text, opt); 1711 } 1712 1713 /** This function writes the passed text and `Option` into an output range 1714 in the manner described in the documentation of function 1715 `defaultGetoptPrinter`, unless the style option is used. 1716 1717 Params: 1718 output = The output range used to write the help information. 1719 text = The text to print at the beginning of the help output. 1720 opt = The `Option` extracted from the `getopt` parameter. 1721 style = The manner in which to display the output of each `Option.` 1722 */ 1723 void defaultGetoptFormatter(Output)(Output output, string text, Option[] opt, string style = "%*s %*s%*s%s\n") 1724 { 1725 import std.algorithm.comparison : min, max; 1726 import std.format.write : formattedWrite; 1727 1728 output.formattedWrite("%s\n", text); 1729 1730 size_t ls, ll; 1731 bool hasRequired = false; 1732 foreach (it; opt) 1733 { 1734 ls = max(ls, it.optShort.length); 1735 ll = max(ll, it.optLong.length); 1736 1737 hasRequired = hasRequired || it.required; 1738 } 1739 1740 string re = " Required: "; 1741 1742 foreach (it; opt) 1743 { 1744 output.formattedWrite(style, ls, it.optShort, ll, it.optLong, 1745 hasRequired ? re.length : 1, it.required ? re : " ", it.help); 1746 } 1747 } 1748 1749 @safe unittest 1750 { 1751 import std.conv; 1752 1753 import std.array; 1754 import std.string; 1755 bool a; 1756 auto args = ["prog", "--foo"]; 1757 auto t = getopt(args, "foo|f", "Help", &a); 1758 string s; 1759 auto app = appender!string(); 1760 defaultGetoptFormatter(app, "Some Text", t.options); 1761 1762 string helpMsg = app.data; 1763 //writeln(helpMsg); 1764 assert(helpMsg.length); 1765 assert(helpMsg.count("\n") == 3, to!string(helpMsg.count("\n")) ~ " " 1766 ~ helpMsg); 1767 assert(helpMsg.indexOf("--foo") != -1); 1768 assert(helpMsg.indexOf("-f") != -1); 1769 assert(helpMsg.indexOf("-h") != -1); 1770 assert(helpMsg.indexOf("--help") != -1); 1771 assert(helpMsg.indexOf("Help") != -1); 1772 1773 string wanted = "Some Text\n-f --foo Help\n-h --help This help " 1774 ~ "information.\n"; 1775 assert(wanted == helpMsg); 1776 } 1777 1778 @safe unittest 1779 { 1780 import std.array ; 1781 import std.conv; 1782 import std.string; 1783 bool a; 1784 auto args = ["prog", "--foo"]; 1785 auto t = getopt(args, config.required, "foo|f", "Help", &a); 1786 string s; 1787 auto app = appender!string(); 1788 defaultGetoptFormatter(app, "Some Text", t.options); 1789 1790 string helpMsg = app.data; 1791 //writeln(helpMsg); 1792 assert(helpMsg.length); 1793 assert(helpMsg.count("\n") == 3, to!string(helpMsg.count("\n")) ~ " " 1794 ~ helpMsg); 1795 assert(helpMsg.indexOf("Required:") != -1); 1796 assert(helpMsg.indexOf("--foo") != -1); 1797 assert(helpMsg.indexOf("-f") != -1); 1798 assert(helpMsg.indexOf("-h") != -1); 1799 assert(helpMsg.indexOf("--help") != -1); 1800 assert(helpMsg.indexOf("Help") != -1); 1801 1802 string wanted = "Some Text\n-f --foo Required: Help\n-h --help " 1803 ~ " This help information.\n"; 1804 assert(wanted == helpMsg, helpMsg ~ wanted); 1805 } 1806 1807 // https://issues.dlang.org/show_bug.cgi?id=14724 1808 @safe unittest 1809 { 1810 bool a; 1811 auto args = ["prog", "--help"]; 1812 GetoptResult rslt; 1813 try 1814 { 1815 rslt = getopt(args, config.required, "foo|f", "bool a", &a); 1816 } 1817 catch (Exception e) 1818 { 1819 enum errorMsg = "If the request for help was passed required options" ~ 1820 "must not be set."; 1821 assert(false, errorMsg); 1822 } 1823 1824 assert(rslt.helpWanted); 1825 } 1826 1827 // throw on duplicate options 1828 @system unittest 1829 { 1830 import core.exception : AssertError; 1831 import std.exception : assertNotThrown, assertThrown; 1832 auto args = ["prog", "--abc", "1"]; 1833 int abc, def; 1834 assertThrown!AssertError(getopt(args, "abc", &abc, "abc", &abc)); 1835 assertThrown!AssertError(getopt(args, "abc|a", &abc, "def|a", &def)); 1836 assertNotThrown!AssertError(getopt(args, "abc", &abc, "def", &def)); 1837 1838 // https://issues.dlang.org/show_bug.cgi?id=23940 1839 assertThrown!AssertError(getopt(args, 1840 "abc", &abc, "ABC", &def)); 1841 assertThrown!AssertError(getopt(args, config.caseInsensitive, 1842 "abc", &abc, "ABC", &def)); 1843 assertNotThrown!AssertError(getopt(args, config.caseSensitive, 1844 "abc", &abc, "ABC", &def)); 1845 } 1846 1847 // https://issues.dlang.org/show_bug.cgi?id=17327 repeated option use 1848 @safe unittest 1849 { 1850 long num = 0; 1851 1852 string[] args = ["program", "--num", "3"]; 1853 getopt(args, "n|num", &num); 1854 assert(num == 3); 1855 1856 args = ["program", "--num", "3", "--num", "5"]; 1857 getopt(args, "n|num", &num); 1858 assert(num == 5); 1859 1860 args = ["program", "--n", "3", "--num", "5", "-n", "-7"]; 1861 getopt(args, "n|num", &num); 1862 assert(num == -7); 1863 1864 void add1() { num++; } 1865 void add2(string option) { num += 2; } 1866 void addN(string option, string value) 1867 { 1868 import std.conv : to; 1869 num += value.to!long; 1870 } 1871 1872 num = 0; 1873 args = ["program", "--add1", "--add2", "--add1", "--add", "5", "--add2", "--add", "10"]; 1874 getopt(args, 1875 "add1", "Add 1 to num", &add1, 1876 "add2", "Add 2 to num", &add2, 1877 "add", "Add N to num", &addN,); 1878 assert(num == 21); 1879 1880 bool flag = false; 1881 args = ["program", "--flag"]; 1882 getopt(args, "f|flag", "Boolean", &flag); 1883 assert(flag); 1884 1885 flag = false; 1886 args = ["program", "-f", "-f"]; 1887 getopt(args, "f|flag", "Boolean", &flag); 1888 assert(flag); 1889 1890 flag = false; 1891 args = ["program", "--flag=true", "--flag=false"]; 1892 getopt(args, "f|flag", "Boolean", &flag); 1893 assert(!flag); 1894 1895 flag = false; 1896 args = ["program", "--flag=true", "--flag=false", "-f"]; 1897 getopt(args, "f|flag", "Boolean", &flag); 1898 assert(flag); 1899 } 1900 1901 @system unittest // Delegates as callbacks 1902 { 1903 alias TwoArgOptionHandler = void delegate(string option, string value) @safe; 1904 1905 TwoArgOptionHandler makeAddNHandler(ref long dest) 1906 { 1907 void addN(ref long dest, string n) 1908 { 1909 import std.conv : to; 1910 dest += n.to!long; 1911 } 1912 1913 return (option, value) => addN(dest, value); 1914 } 1915 1916 long x = 0; 1917 long y = 0; 1918 1919 string[] args = 1920 ["program", "--x-plus-1", "--x-plus-1", "--x-plus-5", "--x-plus-n", "10", 1921 "--y-plus-n", "25", "--y-plus-7", "--y-plus-n", "15", "--y-plus-3"]; 1922 1923 getopt(args, 1924 "x-plus-1", "Add one to x", delegate void() { x += 1; }, 1925 "x-plus-5", "Add five to x", delegate void(string option) { x += 5; }, 1926 "x-plus-n", "Add NUM to x", makeAddNHandler(x), 1927 "y-plus-7", "Add seven to y", delegate void() { y += 7; }, 1928 "y-plus-3", "Add three to y", delegate void(string option) { y += 3; }, 1929 "y-plus-n", "Add NUM to x", makeAddNHandler(y),); 1930 1931 assert(x == 17); 1932 assert(y == 50); 1933 } 1934 1935 // Hyphens at the start of option values; 1936 // https://issues.dlang.org/show_bug.cgi?id=17650 1937 @safe unittest 1938 { 1939 auto args = ["program", "-m", "-5", "-n", "-50", "-c", "-", "-f", "-"]; 1940 1941 int m; 1942 int n; 1943 char c; 1944 string f; 1945 1946 getopt(args, 1947 "m|mm", "integer", &m, 1948 "n|nn", "integer", &n, 1949 "c|cc", "character", &c, 1950 "f|file", "filename or hyphen for stdin", &f); 1951 1952 assert(m == -5); 1953 assert(n == -50); 1954 assert(c == '-'); 1955 assert(f == "-"); 1956 } 1957 1958 // Hyphen at the option value; 1959 // https://issues.dlang.org/show_bug.cgi?id=22394 1960 @safe unittest 1961 { 1962 auto args = ["program", "-"]; 1963 1964 getopt(args); 1965 1966 assert(args == ["program", "-"]); 1967 } 1968 1969 @safe unittest 1970 { 1971 import std.conv; 1972 1973 import std.array; 1974 import std.string; 1975 bool a; 1976 auto args = ["prog", "--foo"]; 1977 auto t = getopt(args, "foo|f", "Help", &a); 1978 string s; 1979 auto app = appender!string(); 1980 defaultGetoptFormatter(app, "Some Text", t.options, "\t\t%*s %*s%*s\n%s\n"); 1981 1982 string helpMsg = app.data; 1983 //writeln(helpMsg); 1984 assert(helpMsg.length); 1985 assert(helpMsg.count("\n") == 5, to!string(helpMsg.count("\n")) ~ " " 1986 ~ helpMsg); 1987 assert(helpMsg.indexOf("--foo") != -1); 1988 assert(helpMsg.indexOf("-f") != -1); 1989 assert(helpMsg.indexOf("-h") != -1); 1990 assert(helpMsg.indexOf("--help") != -1); 1991 assert(helpMsg.indexOf("Help") != -1); 1992 1993 string wanted = "Some Text\n\t\t-f --foo \nHelp\n\t\t-h --help \nThis help " 1994 ~ "information.\n"; 1995 assert(wanted == helpMsg); 1996 } 1997 1998 1999 @safe unittest 2000 { 2001 import std.conv : ConvException; 2002 import std.string : indexOf; 2003 2004 enum UniqueIdentifer { 2005 a, 2006 b 2007 } 2008 2009 UniqueIdentifer a; 2010 2011 auto args = ["prog", "--foo", "HELLO"]; 2012 try 2013 { 2014 auto t = getopt(args, "foo|f", &a); 2015 assert(false, "Must not be reached, as \"HELLO\" cannot be converted" 2016 ~ " to enum A."); 2017 } 2018 catch (ConvException e) 2019 { 2020 string str = () @trusted { return e.toString(); }(); 2021 assert(str.indexOf("HELLO") != -1); 2022 assert(str.indexOf("UniqueIdentifer") != -1); 2023 assert(str.indexOf("foo") != -1); 2024 } 2025 } 2026 2027 @safe unittest 2028 { 2029 import std.conv : ConvException; 2030 import std.string : indexOf; 2031 2032 int a; 2033 2034 auto args = ["prog", "--foo", "HELLO"]; 2035 try 2036 { 2037 auto t = getopt(args, "foo|f", &a); 2038 assert(false, "Must not be reached, as \"HELLO\" cannot be converted" 2039 ~ " to an int"); 2040 } 2041 catch (ConvException e) 2042 { 2043 string str = () @trusted { return e.toString(); }(); 2044 assert(str.indexOf("HELLO") != -1); 2045 assert(str.indexOf("int") != -1); 2046 assert(str.indexOf("foo") != -1); 2047 } 2048 2049 args = ["prog", "--foo", "1337"]; 2050 getopt(args, "foo|f", &a); 2051 assert(a == 1337); 2052 }