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