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