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