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