1 /******************************************************************************* 2 3 Module to manage command-line arguments. 4 5 ____________________________________________________________________________ 6 7 Simple usage: 8 9 --- 10 11 int main ( istring[] cl_args ) 12 { 13 // Create an object to parse command-line arguments 14 auto args = new Arguments; 15 16 // Setup what arguments are valid 17 // (these can be configured in various ways as will be demonstrated 18 // later in the documentation) 19 args("alpha"); 20 args("bravo"); 21 22 // Parse the actual command-line arguments given to the application 23 // (the first element is the application name, so that should not be 24 // passed to the 'parse()' function) 25 auto args_ok = args.parse(cl_args[1 .. $]); 26 27 if ( args_ok ) 28 { 29 // Proceed with rest of the application 30 ... 31 } 32 else 33 { 34 // Discover what caused the error and handle appropriately 35 } 36 } 37 38 --- 39 40 ____________________________________________________________________________ 41 42 43 For the sake of brevity, the rest of this documentation will not show the 44 'main()' function or the creation of the 'args' object. Also, setting up of 45 arguments will be shown only where necessary. Moreover, the 'args.parse()' 46 function will be called with a custom string representing the command-line 47 arguments. This is as shown in the following example: 48 49 --- 50 51 args.parse("--alpha --bravo"); 52 53 if ( args("alpha").set ) 54 { 55 // This will be reached as '--alpha' was given 56 } 57 58 if ( args("bravo").set ) 59 { 60 // This will be reached as '--bravo' was given 61 } 62 63 if ( args("charlie").set ) 64 { 65 // This will *not* be reached as '--charlie' was not given 66 } 67 68 --- 69 70 ____________________________________________________________________________ 71 72 73 When arguments are being set up, normally all arguments that an application 74 supports are explicitly declared and suitably configured. But sometimes, it 75 may be desirable to use on-the-fly arguments that are not set up but 76 discovered during parsing. Such arguments are called 'sloppy arguments'. 77 Support for sloppy arguments is disabled by default, but can be enabled when 78 calling the 'parse()' function, as shown below: 79 80 --- 81 82 args("alpha"); 83 84 args.parse("--alpha --bravo"); 85 // This will result in an error because only 'alpha' was declared, 86 // but not 'bravo'. 87 88 args.parse("--alpha --bravo", true); 89 // This, on the other hand would work. Space for 'bravo' (and 90 // potentially any of its parameters) would be allocated when 91 // 'bravo' gets discovered during parsing. 92 93 --- 94 95 ____________________________________________________________________________ 96 97 98 Arguments can be configured to have aliases. This is a convenient way to 99 represent arguments with long names. Aliases are always exactly one 100 character long. An argument can have multiple aliases. Aliases are always 101 given on the command-line using the short prefix. 102 103 --- 104 105 args("alpha").aliased('a'); 106 args("help").aliased('?').aliased('h'); // multiple aliases allowed 107 108 args.parse("-a -?"); 109 110 --- 111 112 ____________________________________________________________________________ 113 114 115 Arguments can be configured to be mandatorily present, by calling the 116 'required()' function as follows: 117 118 --- 119 120 args("alpha").required(); 121 122 args.parse("--bravo"); 123 // This will fail because the required argument 'alpha' was not 124 // given. 125 126 --- 127 128 ____________________________________________________________________________ 129 130 131 An argument can be configured to depend upon another, by calling the 132 'requires()' function as follows: 133 134 --- 135 136 args("alpha"); 137 args("bravo").requires("alpha"); 138 139 args.parse("--bravo"); 140 // This will fail because 'bravo' needs 'alpha', but 'alpha' was not 141 // given. 142 143 args.parse("--alpha --bravo"); 144 // This, on the other hand, will succeed. 145 146 --- 147 148 ____________________________________________________________________________ 149 150 151 An argument can be configured to conflict with another, by calling the 152 'conflicts()' function as follows: 153 154 --- 155 156 args("alpha"); 157 args("bravo").conflicts("alpha"); 158 159 args.parse("--alpha --bravo"); 160 // This will fail because 'bravo' conflicts with 'alpha', so both of 161 // them can't be present together. 162 163 --- 164 165 ____________________________________________________________________________ 166 167 168 By default arguments don't have any associated parameters. When setting up 169 arguments, they can be configured to have zero or more associated 170 parameters. Parameters assigned to an argument can be accessed using that 171 argument's 'assigned[]' array at consecutive indices. The number of 172 parameters assigned to an argument must exactly match the number of 173 parameters it has been set up to have, or else parsing will fail. Dealing 174 with parameters is shown in the following example: 175 176 --- 177 178 args("alpha"); 179 args("bravo").params(0); 180 // Doing `params(0)` is redundant 181 args("charlie").params(1); 182 // 'charlie' must have exactly one associated parameter 183 184 args.parse("--alpha --bravo --charlie=chaplin"); 185 // the parameter assigned to 'charlie' (i.e. 'chaplin') can be 186 // accessed using `args("charlie").assigned[0]` 187 188 --- 189 190 ____________________________________________________________________________ 191 192 193 Parameter assignment can be either explicit or implicit. Explicit assignment 194 is done using an assignment symbol (defaults to '=', can be changed), 195 whereas implicit assignment happens when a parameter is found after a 196 whitespace. 197 Implicit assignment always happens to the last known argument target, such 198 that multiple parameters accumulate (until the configured parameters count 199 for that argument is reached). Any extra parameters encountered after that 200 are assigned to a special 'null' argument. The 'null' argument is always 201 defined and acts as an accumulator for parameters left uncaptured by other 202 arguments. 203 204 Please note: 205 * if sloppy arguments are supported, and if a sloppy argument happens to 206 be the last known argument target, then implicit assignment of any 207 extra parameters will happen to that sloppy argument. 208 [example 2 below] 209 210 * explicit assignment to an argument always associates the parameter 211 with that argument even if that argument's parameters count has been 212 reached. In this case, 'parse()' will fail. 213 [example 3 below] 214 215 --- 216 217 args("alpha").params(3); 218 219 // Example 1 220 args.parse("--alpha=one --alpha=two three four"); 221 // In this case, 'alpha' would have 3 parameters assigned to it (so 222 // its 'assigned' array would be `["one", "two", "three"]`), and the 223 // null argument would have 1 parameter (with its 'assigned' array 224 // being `["four"]`). 225 // Here's why: 226 // Two of these parameters ('one' & 'two') were assigned explicitly. 227 // The next parameter ('three') was assigned implicitly since 228 // 'alpha' was the last known argument target. At this point, 229 // alpha's parameters count is reached, so no more implicit 230 // assignment will happen to 'alpha'. 231 // So the last parameter ('four') is assigned to the special 'null' 232 // argument. 233 234 // Example 2 235 // (sloppy arguments supported by passing 'true' as the second parameter 236 // to 'parse()') 237 args.parse("--alpha one two three four --xray five six", true); 238 // In this case, 'alpha' would get its 3 parameters ('one', 'two' & 239 // 'three') by way of implicit assignment. 240 // Parameter 'four' would be assigned to the 'null' argument (since 241 // implicit assignment to the last known argument target 'alpha' is 242 // not possible as alpha's parameter count has been reached). 243 // The sloppy argument 'xray' now becomes the new last known 244 // argument target and hence gets the last two parameters ('five' & 245 // 'six'). 246 247 // Example 3 248 args.parse("--alpha one two three --alpha=four"); 249 // As before, 'alpha' would get its 3 parameters ('one', 'two' & 250 // 'three') by way of implicit assignment. 251 // Since 'four' is being explicitly assigned to 'alpha', parsing 252 // will fail here as 'alpha' has been configured to have at most 3 253 // parameters. 254 255 --- 256 257 ____________________________________________________________________________ 258 259 260 An argument can be configured to have one or more default parameters. This 261 means that if the argument was not given on the command-line, it would still 262 contain the configured parameter(s). 263 It is, of course, possible to have no default parameters configured. But if 264 one or more default parameters have been configured, then their number must 265 exactly match the number of parameters configured. 266 267 Please note: 268 * Irrespective of whether default parameters have been configured or not, 269 if an argument was not given on the command-line, its 'set()' function 270 would return 'false'. 271 [example 1 below] 272 273 * Irrespective of whether default parameters have been configured or not, 274 if an argument is given on the command-line, it must honour its 275 configured number of parameters. 276 [example 2 below] 277 278 --- 279 280 args("alpha").params(1).defaults("one"); 281 282 // Example 1 283 args.parse("--bravo"); 284 // 'alpha' was not given, so `args("alpha").set` would return false 285 // but still `args("alpha").assigned[0]` would contain 'one' 286 287 // Example 2 288 args.parse("--alpha"); 289 // this will fail because 'alpha' expects a parameter and that was 290 // not given. In this case, the configured default parameter will 291 // *not* be picked up. 292 293 --- 294 295 ____________________________________________________________________________ 296 297 298 Parameters of an argument can be restricted to a pre-defined set of 299 acceptable values. In this case, argument parsing will fail on an attempt to 300 assign a value from outside the set: 301 302 --- 303 304 args("greeting").restrict(["hello", "namaste", "ahoj", "hola"]); 305 args("enabled").restrict(["true", "false", "t", "f", "y", "n"]); 306 307 args.parse("--greeting=bye"); 308 // This will fail since 'bye' is not among the acceptable values 309 310 --- 311 312 ____________________________________________________________________________ 313 314 315 The parser makes a distinction between long prefix arguments and short 316 prefix arguments. Long prefix arguments start with two hyphens (--argument), 317 while short prefix arguments start with a single hyphen (-a) [the prefixes 318 themselves are configurable, as shown in later documentation]. Within a 319 short prefix argument, each character represents an individual argument. 320 Long prefix arguments must always be distinct, while short prefix arguments 321 may be combined together. 322 323 --- 324 325 args.parse("--alpha -b"); 326 // The argument 'alpha' will be set. 327 // The argument represented by 'b' will be set (note that 'b' here 328 // could be an alias to another argument, or could be the argument 329 // name itself) 330 331 --- 332 333 ____________________________________________________________________________ 334 335 336 When assigning parameters to an argument using the argument's short prefix 337 version, it is possible to "smush" the parameter with the argument. Smushing 338 refers to omitting the explicit assignment symbol ('=' by default) or 339 whitespace (when relying on implicit assignment) that separates an argument 340 from its parameter. The ability to smush an argument with its parameter in 341 this manner has to be explicitly enabled using the 'smush()' function. 342 343 Please note: 344 * smushing cannot be done with the long prefix version of an argument 345 [example 2 below] 346 347 * smushing is irrelevant if an argument has no parameters 348 [example 3 below] 349 350 * if an argument has more than one parameter, and smushing is desired, 351 then the short prefix version of the argument needs to be repeated as 352 many times as the number of parameters to be assigned (this is because 353 one smush can only assign one parameter at a time) 354 [example 4 below] 355 356 * smushing cannot be used if the parameter contains the explicit 357 assignment symbol ('=' by default). In this case, either explicit or 358 implicit assignment should be used. This limitation is due to how 359 argv/argc values are stripped of original quotes. 360 [example 5 below] 361 362 --- 363 364 // Example 1 365 args("alpha").aliased('a').params(1).smush; 366 args.parse("-aparam"); 367 // OK - this is equivalent to `args.parse("-a param");` 368 369 // Example 2 370 args("bravo").params(1).smush; 371 args.parse("--bravoparam"); 372 // ERROR - 'param' cannot be smushed with 'bravo' 373 374 // Example 3 375 args("charlie").smush; 376 // irrelevant smush as argument has no parameters 377 378 // Example 4 379 args('d').params(2).smush; 380 args.parse("-dfile1 -dfile2"); 381 // smushing multiple parameters requires the short prefix version of 382 // the argument to be repeated. This could have been done without 383 // smushing as `args.parse("-d file1 file2);` 384 385 // Example 5 386 args("e").params(1).smush; 387 args.parse("-e'foo=bar'"); 388 // The parameter 'foo=bar' cannot be smushed with the argument as 389 // the parameter contains '=' within. Be especially careful of this 390 // as the 'parse()' function will not fail in this case, but may 391 // result in unexpected behaviour. 392 // The proper way to assign a parameter containing the explicit 393 // assignment symbol is to use one of the following: 394 // args.parse("-e='foo=bar'"); // explicit assignment 395 // args.parse("-e 'foo=bar'"); // implicit assignment 396 397 --- 398 399 ____________________________________________________________________________ 400 401 402 The prefixes used for the long prefix and the short prefix version of the 403 arguments default to '--' & '-' respectively, but they are configurable. To 404 change these, the desired prefix strings need to be passed to the 405 constructor as shown below: 406 407 --- 408 409 // Change short prefix to '/' & long prefix to '%' 410 auto args = new Arguments(null, null, null, null, "/", "%"); 411 412 args.parse("%alpha=param %bravo /abc"); 413 // arguments 'alpha' & 'bravo' set using the long prefix version 414 // arguments represented by the characters 'a', 'b' & 'c' set using 415 // the short prefix version 416 417 --- 418 419 Note that it is also possible to disable both prefixes by passing 'null' as 420 the constructor parameters. 421 422 ____________________________________________________________________________ 423 424 425 We noted in the documentation earlier that a parameter following a 426 whitespace gets assigned to the last known target (implicit assignment). On 427 the other hand, the symbol used for explicitly assigning a parameter to an 428 argument defaults to '='. This symbol is also configurable, and can be 429 changed by passing the desired symbol character to the constructor as 430 shown below: 431 432 --- 433 434 // Change the parameter assignment symbol to ':' 435 // (the short prefix and long prefix need to be passed as their default 436 // values since we're not changing them) 437 auto args = new Arguments(null, null, null, null, "-", "--", ':'); 438 439 args.parse("--alpha:param"); 440 // argument 'alpha' will be assigned parameter 'param' using 441 // explicit assignment 442 443 --- 444 445 ____________________________________________________________________________ 446 447 448 All text following a "--" token are treated as parameters (even if they 449 start with the long prefix or the short prefix). This notion is applied by 450 unix systems to terminate argument processing in a similar manner. 451 452 These parameters are always assigned to the special 'null' argument. 453 454 --- 455 456 args("alpha").params(1); 457 458 args.parse("--alpha one -- -two --three"); 459 // 'alpha' gets one parameter ('one') 460 // the null argument gets two parameters ('-two' & '--three') 461 // note how 'two' & 'three' are prefixed by the short and long 462 // prefixes respectively, but the prefixes don't play any part as 463 // these are just parameters now 464 465 --- 466 467 ____________________________________________________________________________ 468 469 470 When configuring the command-line arguments, qualifiers can be chained 471 together as shown in the following example: 472 473 --- 474 475 args("alpha") 476 .required 477 .params(1) 478 .aliased('a') 479 .requires("bravo") 480 .conflicts("charlie") 481 .defaults("one"); 482 483 --- 484 485 ____________________________________________________________________________ 486 487 The full help message for the application (which includes the configured 488 usage, long & short descriptions as well as the help text of each of the 489 arguments) can be displayed using the 'displayHelp()' function as follows: 490 491 --- 492 493 auto args = new Arguments( 494 "my_app", 495 "{0} : this is a short description", 496 "this is the usage string", 497 "this is a long description on how to make '{0}' work"); 498 499 args("alpha") 500 .aliased('a') 501 .params(1,3) 502 .help("help for alpha"); 503 args("bravo") 504 .aliased('b') 505 .params(1) 506 .defaults("val") 507 .help("help for bravo"); 508 509 args.displayHelp(); 510 511 --- 512 513 Doing this, would produce the following help message: 514 515 my_app : this is a short description 516 517 Usage: this is the usage string 518 519 this is a long description on how to make 'my_app' work 520 521 Program options: 522 -a, --alpha help for alpha (1-3 params) 523 -b, --bravo help for bravo (1 param, default: [val]) 524 525 ____________________________________________________________________________ 526 527 528 The 'parse()' function will return true only where all conditions are met. 529 If an error occurs, the parser will set an error code and return false. 530 531 The error codes (which indicate the nature of the error) are as follows: 532 533 None : ok (no error) 534 ParamLo : too few parameters were assigned to this argument 535 ParamHi : too many parameters were assigned to this argument 536 Required : this is a required argument, but was not given 537 Requires : this argument depends on another argument which was not given 538 Conflict : this argument conflicts with another given argument 539 Extra : unexpected argument (will not trigger an error if sloppy 540 arguments are enabled) 541 Option : parameter assigned is not one of the acceptable options 542 543 544 A simple way to handle errors is to invoke an internal format routine, which 545 constructs error messages on your behalf. The messages are constructed using 546 a layout handler and the messages themselves may be customized (for i18n 547 purposes). See the two 'errors()' methods for more information on this. The 548 following example shows this way of handling errors: 549 550 --- 551 552 if ( ! args.parse (...) ) 553 { 554 stderr(args.errors(&stderr.layout.sprint)); 555 } 556 557 --- 558 559 560 Another way of handling argument parsing errors, is to traverse the set of 561 arguments, to find out exactly which argument has the error, and what is the 562 error code. This is as shown in the following example: 563 564 --- 565 566 if ( ! args.parse (...) ) 567 { 568 foreach ( arg; args ) 569 { 570 if ( arg.error ) 571 { 572 // 'arg.error' contains one of the above error-codes 573 574 ... 575 } 576 } 577 } 578 579 --- 580 581 ____________________________________________________________________________ 582 583 584 The following two types of callbacks are supported: 585 - a callback called when an argument is parsed 586 - a callback called whenever a parameter gets assigned to an argument 587 (see the 'bind()' methods for the signatures of these delegates). 588 589 ____________________________________________________________________________ 590 591 Copyright: 592 Copyright (c) 2009 Kris. 593 Some parts copyright (c) 2009-2016 dunnhumby Germany GmbH. 594 All rights reserved. 595 596 License: 597 Tango Dual License: 3-Clause BSD License / Academic Free License v3.0. 598 See LICENSE_TANGO.txt for details. 599 600 *******************************************************************************/ 601 602 module ocean.text.Arguments; 603 604 605 606 607 import ocean.transition; 608 609 import ocean.io.Stdout; 610 import ocean.io.stream.Format : FormatOutput; 611 import ocean.math.Math; 612 import ocean.text.Util; 613 import ocean.text.convert.Formatter; 614 import ocean.text.convert.Integer; 615 import ocean.util.container.SortedMap; 616 import ocean.util.container.more.Stack; 617 import ocean.core.Verify; 618 619 version(UnitTest) import ocean.core.Test; 620 621 /******************************************************************************* 622 623 The main arguments container class. 624 625 *******************************************************************************/ 626 627 public class Arguments 628 { 629 import ocean.core.Enforce : enforce; 630 631 /*************************************************************************** 632 633 Convenience aliases to access a specific argument instance 634 635 ***************************************************************************/ 636 637 public alias get opCall; // args("name") 638 public alias get opIndex; // args["name"] 639 640 641 /*************************************************************************** 642 643 Convenience alias to get the value of a boolean argument 644 645 ***************************************************************************/ 646 647 public alias getBool exists; 648 649 650 /*************************************************************************** 651 652 Application's name to use in help messages. 653 654 ***************************************************************************/ 655 656 public istring app_name; 657 658 659 /*************************************************************************** 660 661 Application's short usage description (as a format string). 662 663 This is used as a format string to print the usage. The first parameter 664 to the format string is the application's name. This string should 665 describe how to invoke the application. 666 667 If the usage description spans multiple lines, then it's better to start 668 each line with a tab character (\t). 669 670 Examples: 671 672 --- 673 674 args.usage = "{0} [OPTIONS] SOMETHING FILE"; 675 args.usage = "{0} [OPTIONS] SOMETHING FILE\n" 676 "\t{0} --version"; 677 678 --- 679 680 ***************************************************************************/ 681 682 public istring usage = "{0} [OPTIONS] [ARGS]"; 683 684 685 /*************************************************************************** 686 687 One line description of what the application does (as a format string). 688 689 This is used as a format string to print a short description of what the 690 application does. The first argument is the name of the application (but 691 the name shouldn't normally be used in the description). 692 693 ***************************************************************************/ 694 695 public istring short_desc; 696 697 698 /*************************************************************************** 699 700 Long description about the application and how to use it (as a format 701 string). 702 703 This is used as a format string to print a long description of what the 704 application does and how to use it. The first argument is the name of 705 the application. 706 707 ***************************************************************************/ 708 709 public istring long_desc; 710 711 712 /*************************************************************************** 713 714 Stack used to help in assigning implicitly assigned parameters to 715 arguments during parsing. 716 717 This stack contains argument instances of only those arguments that can 718 have one or more associated parameters. Implicit parameters always get 719 assigned to the topmost argument in the stack. Once the number of 720 parameters of the topmost argument in the stack reaches its maximum 721 configured value, that argument gets popped off the stack. Future 722 implicit assignments will then happen to the new topmost argument in the 723 stack. 724 725 The null argument is always the first one that gets pushed onto the 726 stack. This ensures that it is able to "catch" all unclaimed parameters 727 at the end. 728 729 ***************************************************************************/ 730 731 private Stack!(Argument) stack; 732 733 734 /*************************************************************************** 735 736 All argument instances. A sorted map (indexed by the argument name) is 737 used to store these so that the arguments appear in a sorted manner in 738 the help text output 739 740 ***************************************************************************/ 741 742 private SortedMap!(cstring, Argument) args; 743 744 745 /*************************************************************************** 746 747 Argument instances that have aliases. A sorted map (indexed by the 748 argument aliases) is used to store these so that the arguments appear in 749 a sorted manner in the help text output 750 751 ***************************************************************************/ 752 753 private SortedMap!(cstring, Argument) aliases; 754 755 756 /*************************************************************************** 757 758 Character to be used as the explicit assignment symbol 759 760 ***************************************************************************/ 761 762 private char eq; 763 764 765 /*************************************************************************** 766 767 The short prefix string 768 769 ***************************************************************************/ 770 771 private istring sp; 772 773 774 /*************************************************************************** 775 776 The long prefix string 777 778 ***************************************************************************/ 779 780 private istring lp; 781 782 783 /*************************************************************************** 784 785 Error messages 786 787 ***************************************************************************/ 788 789 private Const!(istring)[] msgs; 790 791 792 /*************************************************************************** 793 794 Format strings of all default errors 795 796 ***************************************************************************/ 797 798 private static immutable istring[] errmsg = [ 799 "argument '{0}' expects {2} parameter(s) but has {1}\n", 800 "argument '{0}' expects {3} parameter(s) but has {1}\n", 801 "argument '{0}' is missing\n", 802 "argument '{0}' requires '{4}'\n", 803 "argument '{0}' conflicts with '{4}'\n", 804 "unexpected argument '{0}'\n", 805 "argument '{0}' expects one of {5}\n", 806 "invalid parameter for argument '{0}': {4}\n", 807 ]; 808 809 810 /*************************************************************************** 811 812 Internal string used for spacing of the full help message 813 814 ***************************************************************************/ 815 816 private mstring spaces; 817 818 819 /*************************************************************************** 820 821 Temporary formatting buffer 822 823 ***************************************************************************/ 824 825 private mstring tmp_buf; 826 827 828 /*************************************************************************** 829 830 Maximum width of the column showing argument aliases in the full help 831 message 832 833 ***************************************************************************/ 834 835 private size_t aliases_width; 836 837 838 /*************************************************************************** 839 840 Maximum width of the column showing argument names in the full help 841 message 842 843 ***************************************************************************/ 844 845 private size_t long_name_width; 846 847 848 /*************************************************************************** 849 850 Constructor. 851 852 Params: 853 app_name = name of the application (to show in the help message) 854 short_desc = short description of what the application does (should 855 be one line only, preferably less than 80 characters long) 856 usage = how the application is supposed to be invoked 857 long_desc = long description of what the application does and how to 858 use it 859 sp = string to use as the short prefix (defaults to '-') 860 lp = string to use as the long prefix (defaults to '--') 861 eq = character to use as the explicit assignment symbol 862 (defaults to '=') 863 864 ***************************************************************************/ 865 866 public this ( istring app_name = null, istring short_desc = null, 867 istring usage = null, istring long_desc = null, istring sp = "-", 868 istring lp = "--", char eq = '=' ) 869 { 870 this.msgs = this.errmsg; 871 872 this.app_name = app_name; 873 this.short_desc = short_desc; 874 this.long_desc = long_desc; 875 this.sp = sp; 876 this.lp = lp; 877 this.eq = eq; 878 879 this.args = new typeof(this.args)(); 880 this.aliases = new typeof(this.aliases)(); 881 882 if ( usage.length > 0 ) 883 { 884 this.usage = usage; 885 } 886 887 this.get(null).params; // set null argument to consume params 888 } 889 890 891 /*************************************************************************** 892 893 Parses the command-line arguments into a set of Argument instances. The 894 command-line arguments are expected to be passed in a string. 895 896 Params: 897 input = string to be parsed (contains command-line arguments) 898 sloppy = true if any unexpected arguments found during parsing 899 should be accepted on-the-fly, false if unexpected arguments 900 should be treated as error 901 902 Returns: 903 true if parsing was successful, false otherwise 904 905 ***************************************************************************/ 906 907 public bool parse ( istring input, bool sloppy = false ) 908 { 909 istring[] tmp; 910 911 foreach ( s; quotes(input, " ") ) 912 { 913 tmp ~= s; 914 } 915 916 return parse(tmp, sloppy); 917 } 918 919 920 /*************************************************************************** 921 922 Parses the command-line arguments into a set of Argument instances. The 923 command-line arguments are expected to be passed in an array of strings. 924 925 Params: 926 input = array of strings to be parsed (contains command-line 927 arguments) 928 sloppy = true if any unexpected arguments found during parsing 929 should be accepted on-the-fly, false if unexpected arguments 930 should be treated as error 931 932 Returns: 933 true if parsing was successful, false otherwise 934 935 ***************************************************************************/ 936 937 public bool parse ( Const!(istring)[] input, bool sloppy = false ) 938 { 939 bool done; 940 int error; 941 942 stack.push(this.get(null)); 943 944 foreach ( s; input ) 945 { 946 if ( done is false ) 947 { 948 if ( s == "--" ) 949 { 950 done = true; 951 952 stack.clear.push(this.get(null)); 953 954 continue; 955 } 956 else 957 { 958 if ( argument(s, lp, sloppy, false) || 959 argument(s, sp, sloppy, true) ) 960 { 961 continue; 962 } 963 } 964 } 965 966 stack.top.append (s); 967 } 968 969 foreach ( arg; args ) 970 { 971 error |= arg.valid; 972 } 973 974 return error is 0; 975 } 976 977 978 /*************************************************************************** 979 980 Unsets all configured arguments (as if they weren't given at all on the 981 command-line), clears all parameters that may have been assigned to 982 arguments and also clears any parsing errors that may have been 983 associated with any argument(s). 984 985 Note that configured arguments are *not* removed. 986 987 Returns: 988 this object for method chaining 989 990 ***************************************************************************/ 991 992 public Arguments clear ( ) 993 { 994 stack.clear; 995 996 foreach ( arg; args ) 997 { 998 arg.set = false; 999 arg.values = null; 1000 arg.error = arg.None; 1001 } 1002 1003 return this; 1004 } 1005 1006 1007 /*************************************************************************** 1008 1009 Gets a reference to an argument, creating a new instance if necessary. 1010 1011 Params: 1012 name = character representing the argument to be retrieved (this is 1013 usually an alias to the argument, but could also be the argument 1014 name if the argument name is exactly one character long) 1015 1016 Returns: 1017 a reference to the argument 1018 1019 ***************************************************************************/ 1020 1021 public Argument get ( char name ) 1022 { 1023 return get(cast(cstring)(&name)[0 .. 1]); 1024 } 1025 1026 1027 /*************************************************************************** 1028 1029 Gets a reference to an argument, creating a new instance if necessary. 1030 1031 Params: 1032 name = string containing the argument name (pass null to access the 1033 special 'null' argument) 1034 1035 Returns: 1036 a reference to the argument 1037 1038 ***************************************************************************/ 1039 1040 public Argument get ( cstring name ) 1041 { 1042 auto a = name in args; 1043 1044 if ( a is null ) 1045 { 1046 auto _name = idup(name); 1047 1048 auto arg = new Argument(_name); 1049 1050 args[_name] = arg; 1051 1052 return arg; 1053 } 1054 1055 return *a; 1056 } 1057 1058 1059 /*************************************************************************** 1060 1061 Enables 'foreach' iteration over the set of configured arguments. 1062 1063 Params: 1064 dg = delegate called for each argument 1065 1066 ***************************************************************************/ 1067 1068 public int opApply ( scope int delegate(ref Argument) dg ) 1069 { 1070 int result; 1071 1072 foreach ( arg; args ) 1073 { 1074 if ( (result = dg(arg)) != 0 ) 1075 { 1076 break; 1077 } 1078 } 1079 1080 return result; 1081 } 1082 1083 /*************************************************************************** 1084 1085 Constructs a string of error messages. 1086 1087 Returns: 1088 formatted error message string 1089 1090 ***************************************************************************/ 1091 1092 public istring errors () 1093 { 1094 mstring result; 1095 1096 foreach (arg; args) 1097 { 1098 if (arg.error) 1099 { 1100 sformat( 1101 result, msgs[arg.error-1], arg.name, 1102 arg.values.length, arg.min, arg.max, arg.bogus, 1103 arg.options); 1104 } 1105 } 1106 1107 return assumeUnique(result); 1108 } 1109 1110 1111 /*************************************************************************** 1112 1113 Replaces the default error messages with the given string. 1114 Note that arguments are passed to the formatter in the following order, 1115 and these should be indexed appropriately by each of the error messages 1116 (see the 'errmsg' variable for the format string): 1117 1118 index 0: the argument name 1119 index 1: number of parameters 1120 index 2: configured minimum parameters 1121 index 3: configured maximum parameters 1122 index 4: conflicting/dependent argument (or invalid param) 1123 index 5: array of configured parameter options 1124 1125 Params: 1126 errors = string to replace the default error messages with 1127 1128 Returns: 1129 this object for method chaining 1130 1131 ***************************************************************************/ 1132 1133 public Arguments errors ( Const!(istring)[] errors ) 1134 { 1135 if ( errors.length is errmsg.length ) 1136 { 1137 msgs = errors; 1138 } 1139 else 1140 { 1141 verify(false); 1142 } 1143 1144 return this; 1145 } 1146 1147 1148 /*************************************************************************** 1149 1150 Exposes the configured help text for each of the configured arguments, 1151 via the given delegate. Note that the delegate will be called only for 1152 those arguments for which a help text has been configured. 1153 1154 Params: 1155 dg = delegate that will be called for each argument having a help 1156 text (the argument name and the help text itself will be sent as 1157 parameters to the delegate) 1158 1159 Returns: 1160 this object for method chaining 1161 1162 ***************************************************************************/ 1163 1164 public Arguments help ( scope void delegate ( istring arg, istring help ) dg ) 1165 { 1166 foreach ( arg; args ) 1167 { 1168 if ( arg.text.ptr ) 1169 { 1170 dg(arg.name, arg.text); 1171 } 1172 } 1173 1174 return this; 1175 } 1176 1177 1178 /*************************************************************************** 1179 1180 Displays the full help message for the application. 1181 1182 Params: 1183 output = stream where to print the help message (Stderr by default) 1184 1185 ***************************************************************************/ 1186 1187 public void displayHelp ( FormatOutput output = Stderr ) 1188 { 1189 if ( this.short_desc.length > 0 ) 1190 { 1191 output.formatln(this.short_desc, this.app_name); 1192 output.newline; 1193 } 1194 1195 output.formatln("Usage:\t" ~ this.usage, this.app_name); 1196 output.newline; 1197 1198 if ( this.long_desc.length > 0 ) 1199 { 1200 output.formatln(this.long_desc, this.app_name); 1201 output.newline; 1202 } 1203 1204 foreach ( arg; this.args ) 1205 { 1206 this.calculateSpacing(arg); 1207 } 1208 1209 output.formatln("Program options:"); 1210 1211 foreach ( arg; this.args ) 1212 { 1213 // Skip the null argument 1214 if ( arg.name.length == 0 ) 1215 { 1216 continue; 1217 } 1218 1219 output.formatln("{}", this.formatArgumentHelp(arg, this.tmp_buf)); 1220 } 1221 1222 output.newline; 1223 } 1224 1225 1226 /*************************************************************************** 1227 1228 Displays any errors that occurred. 1229 1230 Params: 1231 output = stream where to print the errors (Stderr by default) 1232 1233 ***************************************************************************/ 1234 1235 public void displayErrors ( FormatOutput output = Stderr ) 1236 { 1237 output.format("{}", this.errors()); 1238 } 1239 1240 1241 /*************************************************************************** 1242 1243 Convenience method to check whether an argument is set or not (i.e. 1244 whether it was found during parsing of the command-line arguments). 1245 1246 Params: 1247 name = name of the argument 1248 1249 Returns: 1250 true if the argument is set, false otherwise 1251 1252 ***************************************************************************/ 1253 1254 public bool getBool ( cstring name ) 1255 { 1256 auto arg = this.get(name); 1257 1258 if ( arg ) 1259 { 1260 return arg.set; 1261 } 1262 else 1263 { 1264 return false; 1265 } 1266 } 1267 1268 1269 /*************************************************************************** 1270 1271 Convenience method to get the integer value of the parameter assigned to 1272 an argument. This is valid only if the argument has been assigned 1273 exactly one parameter. 1274 1275 Params: 1276 T = type of integer to return 1277 name = name of the argument 1278 1279 Returns: 1280 integer value of the parameter assigned to the argument 1281 0 if value is missing or not a valid integer 1282 1283 ***************************************************************************/ 1284 1285 public T getInt ( T ) ( cstring name ) 1286 { 1287 auto arg = this.get(name); 1288 1289 cstring value; 1290 1291 if ( arg && arg.assigned.length == 1 ) 1292 { 1293 value = arg.assigned[0]; 1294 } 1295 1296 T num; 1297 if (toInteger(value, num)) 1298 return num; 1299 else 1300 return 0; 1301 } 1302 1303 1304 /*************************************************************************** 1305 1306 Convenience method to get the string parameter assigned to an argument. 1307 This is valid only if the argument has been assigned exactly one 1308 parameter. 1309 1310 Params: 1311 name = name of the argument 1312 1313 Returns: 1314 parameter assigned to the argument 1315 1316 ***************************************************************************/ 1317 1318 public istring getString ( cstring name ) 1319 { 1320 auto arg = this.get(name); 1321 1322 istring value; 1323 1324 if ( arg && arg.assigned.length == 1 ) 1325 { 1326 value = arg.assigned[0]; 1327 } 1328 1329 return value; 1330 } 1331 1332 1333 /*************************************************************************** 1334 1335 Tests for the presence of a switch (long/short prefix) and enables the 1336 associated argument if found. Also looks for and handles explicit 1337 parameter assignment. 1338 1339 Params: 1340 s = An individual string from the command-line arguments (includes 1341 the long/short prefix if it is an argument string) 1342 p = the prefix string (whether this is the long prefix or the short 1343 prefix is indicated by the 'flag' parameter) 1344 sloppy = true if any unexpected arguments found during parsing 1345 should be accepted on-the-fly, false if unexpected arguments 1346 should be treated as error 1347 flag = true if the prefix string given is the short prefix, false if 1348 it is the long prefix 1349 1350 Returns: 1351 true if the given string was an argument, false if it was a 1352 parameter 1353 1354 ***************************************************************************/ 1355 1356 private bool argument ( istring s, istring p, bool sloppy, bool flag ) 1357 { 1358 if ( s.length >= p.length && s[0 .. p.length] == p ) 1359 { 1360 s = s[p.length .. $]; 1361 1362 auto i = locate(s, eq); 1363 1364 if ( i < s.length ) 1365 { 1366 enable(s[0 .. i], sloppy, flag).append(s[i + 1 .. $], true); 1367 } 1368 else 1369 { 1370 // trap empty arguments; attach as param to null-arg 1371 if ( s.length ) 1372 { 1373 enable(s, sloppy, flag); 1374 } 1375 else 1376 { 1377 this.get(null).append(p, true); 1378 } 1379 } 1380 1381 return true; 1382 } 1383 1384 return false; 1385 } 1386 1387 1388 /*************************************************************************** 1389 1390 Indicates the existence of an argument, and handles sloppy arguments 1391 along with multiple-flags and smushed parameters. Note that sloppy 1392 arguments are configured with parameters enabled. 1393 1394 Params: 1395 elem = an argument name found during parsing (does not contain the 1396 long/short prefix) 1397 sloppy = true if any unexpected arguments found during parsing 1398 should be accepted on-the-fly, false if unexpected arguments 1399 should be treated as error 1400 flag = true if the argument name was preceded by the short prefix, 1401 false if it was preceded by the long prefix 1402 1403 Returns: 1404 the configured argument instance 1405 1406 ***************************************************************************/ 1407 1408 private Argument enable ( istring elem, bool sloppy, bool flag = false ) 1409 { 1410 if ( flag && elem.length > 1 ) 1411 { 1412 // locate arg for first char 1413 auto arg = enable(elem[0 .. 1], sloppy); 1414 1415 elem = elem[1 .. $]; 1416 1417 // drop further processing of this flag where in error 1418 if ( arg.error is arg.None ) 1419 { 1420 // smush remaining text or treat as additional args 1421 if ( arg.cat ) 1422 { 1423 arg.append(elem, true); 1424 } 1425 else 1426 { 1427 arg = enable(elem, sloppy, true); 1428 } 1429 } 1430 1431 return arg; 1432 } 1433 1434 // if not in args, or in aliases, then create new arg 1435 auto a = elem in args; 1436 1437 if ( a is null ) 1438 { 1439 if ( (a = elem in aliases) is null ) 1440 { 1441 return this.get(elem).params.enable(!sloppy); 1442 } 1443 } 1444 1445 return a.enable; 1446 } 1447 1448 1449 /*************************************************************************** 1450 1451 Calculates the width required to display all the aliases based on the 1452 given number of aliases (in the aliases string, each character is an 1453 individual argument alias). 1454 1455 Params: 1456 aliases = number of argument aliases 1457 1458 Returns: 1459 width required to display all the aliases 1460 1461 ***************************************************************************/ 1462 1463 private size_t aliasesWidth ( size_t aliases ) 1464 { 1465 auto width = aliases * 2; // *2 for a '-' before each alias 1466 1467 if ( aliases > 1 ) 1468 { 1469 width += (aliases - 1) * 2; // ', ' after each alias except the last 1470 } 1471 1472 return width; 1473 } 1474 1475 1476 /*************************************************************************** 1477 1478 Calculates the maximum width required to display the given argument name 1479 and its aliases. 1480 1481 Params: 1482 arg = the argument instance 1483 1484 ***************************************************************************/ 1485 1486 private void calculateSpacing ( Argument arg ) 1487 { 1488 this.long_name_width = max(this.long_name_width, arg.name.length); 1489 1490 this.aliases_width = max(this.aliases_width, 1491 this.aliasesWidth(arg.aliases.length)); 1492 } 1493 1494 1495 /*************************************************************************** 1496 1497 Formats the help text for a single argument. 1498 1499 Params: 1500 arg = argument instance for which the help text is to be formatted 1501 buf = buffer into which to format the help text 1502 1503 Returns: 1504 the formatted help text 1505 1506 ***************************************************************************/ 1507 1508 private mstring formatArgumentHelp ( Argument arg, ref mstring buf ) 1509 { 1510 buf.length = 0; 1511 enableStomping(buf); 1512 1513 sformat(buf, " "); 1514 1515 foreach ( i, al; arg.aliases ) 1516 { 1517 sformat(buf, "-{}", al); 1518 1519 if ( i != arg.aliases.length - 1 || arg.name.length ) 1520 { 1521 sformat(buf, ", "); 1522 } 1523 } 1524 1525 // there is no trailing ", " in this case, so add two spaces instead. 1526 if ( arg.aliases.length == 0 ) 1527 { 1528 sformat(buf, " "); 1529 } 1530 1531 sformat(buf, "{}", 1532 this.space(this.aliases_width - 1533 this.aliasesWidth(arg.aliases.length))); 1534 1535 sformat(buf, "--{}{} ", 1536 arg.name, this.space(this.long_name_width - arg.name.length)); 1537 1538 if ( arg.text.length ) 1539 { 1540 sformat(buf, "{}", arg.text); 1541 } 1542 1543 uint extras; 1544 1545 bool params = arg.min > 0 || arg.max > 0; 1546 1547 if ( params ) extras++; 1548 if ( arg.options.length ) extras++; 1549 if ( arg.deefalts.length ) extras++; 1550 1551 if ( extras ) 1552 { 1553 // comma separate sections if more info to come 1554 void next ( ) 1555 { 1556 extras--; 1557 1558 if ( extras ) 1559 { 1560 sformat(buf, ", "); 1561 } 1562 } 1563 1564 sformat(buf, " ("); 1565 1566 if ( params ) 1567 { 1568 if ( arg.min == arg.max ) 1569 { 1570 sformat(buf, "{} param{}", arg.min, 1571 arg.min == 1 ? "" : "s"); 1572 } 1573 else 1574 { 1575 sformat(buf, "{}-{} params", arg.min, arg.max); 1576 } 1577 1578 next(); 1579 } 1580 1581 if ( arg.options.length ) 1582 { 1583 sformat(buf, "{}", arg.options); 1584 1585 next(); 1586 } 1587 1588 if ( arg.deefalts.length ) 1589 { 1590 sformat(buf, "default: {}", arg.deefalts); 1591 1592 next(); 1593 } 1594 1595 sformat(buf, ")"); 1596 } 1597 1598 return buf; 1599 } 1600 1601 1602 /*************************************************************************** 1603 1604 Creates a string with the specified number of spaces. 1605 1606 Params: 1607 width = desired number of spaces 1608 1609 Returns: 1610 string with desired number of spaces. 1611 1612 ***************************************************************************/ 1613 1614 private cstring space ( size_t width ) 1615 { 1616 if ( width == 0 ) 1617 { 1618 return ""; 1619 } 1620 1621 this.spaces.length = width; 1622 enableStomping(this.spaces); 1623 1624 this.spaces[] = ' '; 1625 1626 return this.spaces; 1627 } 1628 1629 1630 /*************************************************************************** 1631 1632 Class that declares a specific argument instance. 1633 One of these is instantiated using one of the outer class' `get()` 1634 methods. All existing argument instances can be iterated over using the 1635 outer class' `opApply()` method. 1636 1637 ***************************************************************************/ 1638 1639 private class Argument 1640 { 1641 /*********************************************************************** 1642 1643 Enumeration of all error identifiers 1644 1645 ***********************************************************************/ 1646 1647 public enum 1648 { 1649 None, // ok (no error) 1650 1651 ParamLo, // too few parameters were assigned to this argument 1652 1653 ParamHi, // too many parameters were assigned to this argument 1654 1655 Required, // this is a required argument, but was not given 1656 1657 Requires, // this argument depends on another argument which was not 1658 // given 1659 1660 Conflict, // this argument conflicts with another given argument 1661 1662 Extra, // unexpected argument (will not trigger an error if 1663 // sloppy arguments are enabled) 1664 1665 Option, // parameter assigned is not one of the acceptable options 1666 1667 Invalid // invalid error 1668 } 1669 1670 1671 /*********************************************************************** 1672 1673 Convenience aliases 1674 1675 ***********************************************************************/ 1676 1677 public alias void delegate ( ) Invoker; 1678 public alias istring delegate ( istring value ) Inspector; 1679 1680 1681 /*********************************************************************** 1682 1683 Minimum number of parameters for this argument 1684 1685 ***********************************************************************/ 1686 1687 public int min; 1688 1689 1690 /*********************************************************************** 1691 1692 Maximum number of parameters for this argument 1693 1694 ***********************************************************************/ 1695 1696 public int max; 1697 1698 1699 /*********************************************************************** 1700 1701 The error code for this argument (0 => no error) 1702 1703 ***********************************************************************/ 1704 1705 public int error; 1706 1707 1708 /*********************************************************************** 1709 1710 Flag to indicate whether this argument is present or not 1711 1712 ***********************************************************************/ 1713 1714 public bool set; 1715 1716 1717 /*********************************************************************** 1718 1719 String in which each character is an alias for this argument 1720 1721 ***********************************************************************/ 1722 1723 public istring aliases; 1724 1725 1726 /*********************************************************************** 1727 1728 The name of the argument 1729 1730 ***********************************************************************/ 1731 1732 public istring name; 1733 1734 1735 /*********************************************************************** 1736 1737 The help text of the argument 1738 1739 ***********************************************************************/ 1740 1741 public istring text; 1742 1743 1744 /*********************************************************************** 1745 1746 Allowed parameters for this argument (there is no restriction on the 1747 acceptable parameters if this array is empty) 1748 1749 ***********************************************************************/ 1750 1751 public Const!(istring)[] options; 1752 1753 1754 /*********************************************************************** 1755 1756 Default parameters for this argument 1757 1758 ***********************************************************************/ 1759 1760 public Const!(istring)[] deefalts; 1761 1762 1763 /*********************************************************************** 1764 1765 Flag to indicate whether this argument is required or not 1766 1767 ***********************************************************************/ 1768 1769 private bool req; 1770 1771 1772 /*********************************************************************** 1773 1774 Flag to indicate whether this argument is smushable or not 1775 1776 ***********************************************************************/ 1777 1778 private bool cat; 1779 1780 1781 /*********************************************************************** 1782 1783 Flag to indicate whether this argument can accept implicit 1784 parameters or not 1785 1786 ***********************************************************************/ 1787 1788 private bool exp; 1789 1790 1791 /*********************************************************************** 1792 1793 Flag to indicate whether this argument has failed parsing or not 1794 1795 ***********************************************************************/ 1796 1797 private bool fail; 1798 1799 1800 /*********************************************************************** 1801 1802 The name of the argument that conflicts with this argument 1803 1804 ***********************************************************************/ 1805 1806 private istring bogus; 1807 1808 1809 /*********************************************************************** 1810 1811 Parameters assigned to this argument 1812 1813 ***********************************************************************/ 1814 1815 private istring[] values; 1816 1817 1818 /*********************************************************************** 1819 1820 Invocation callback 1821 1822 ***********************************************************************/ 1823 1824 private Invoker invoker; 1825 1826 1827 /*********************************************************************** 1828 1829 Inspection callback 1830 1831 ***********************************************************************/ 1832 1833 private Inspector inspector; 1834 1835 1836 /*********************************************************************** 1837 1838 Argument instances that are required by this argument 1839 1840 ***********************************************************************/ 1841 1842 private Argument[] dependees; 1843 1844 1845 /*********************************************************************** 1846 1847 Argument instances that this argument conflicts with 1848 1849 ***********************************************************************/ 1850 1851 private Argument[] conflictees; 1852 1853 1854 /*********************************************************************** 1855 1856 Constructor. 1857 1858 Params: 1859 name = name of the argument 1860 1861 ***********************************************************************/ 1862 1863 public this ( istring name ) 1864 { 1865 this.name = name; 1866 } 1867 1868 1869 /*********************************************************************** 1870 1871 Returns: 1872 the name of this argument 1873 1874 ***********************************************************************/ 1875 1876 public override istring toString ( ) 1877 { 1878 return name; 1879 } 1880 1881 1882 /*********************************************************************** 1883 1884 Returns: 1885 parameters assigned to this argument, or the default parameters 1886 if this argument was not present on the command-line 1887 1888 ***********************************************************************/ 1889 1890 public Const!(istring)[] assigned ( ) 1891 { 1892 return values.length ? values : deefalts; 1893 } 1894 1895 1896 /*********************************************************************** 1897 1898 Sets an alias for this argument. 1899 1900 Params: 1901 name = character to be used as an alias for this argument 1902 1903 Returns: 1904 this object for method chaining 1905 1906 ***********************************************************************/ 1907 1908 public Argument aliased ( char name ) 1909 { 1910 if ( auto arg = cast(cstring)((&name)[0..1]) in this.outer.aliases ) 1911 { 1912 verify( 1913 false, 1914 "Argument '" ~ this.name ~ "' cannot " ~ 1915 "be assigned alias '" ~ name ~ "' as it has " ~ 1916 "already been assigned to argument '" 1917 ~ arg.name ~ "'." 1918 ); 1919 } 1920 1921 this.outer.aliases[idup((&name)[0 .. 1])] = this; 1922 1923 this.aliases ~= name; 1924 1925 return this; 1926 } 1927 1928 1929 /*********************************************************************** 1930 1931 Makes this a mandatory argument. 1932 1933 Returns: 1934 this object for method chaining 1935 1936 ***********************************************************************/ 1937 1938 public Argument required ( ) 1939 { 1940 this.req = true; 1941 1942 return this; 1943 } 1944 1945 1946 /*********************************************************************** 1947 1948 Sets this argument to depend upon another argument. 1949 1950 Params: 1951 arg = argument instance which is to be set as a dependency 1952 1953 Returns: 1954 this object for method chaining 1955 1956 ***********************************************************************/ 1957 1958 public Argument requires ( Argument arg ) 1959 { 1960 dependees ~= arg; 1961 1962 return this; 1963 } 1964 1965 1966 /*********************************************************************** 1967 1968 Sets this argument to depend upon another argument. 1969 1970 Params: 1971 other = name of the argument which is to be set as a dependency 1972 1973 Returns: 1974 this object for method chaining 1975 1976 ***********************************************************************/ 1977 1978 public Argument requires ( istring other ) 1979 { 1980 return requires(this.outer.get(other)); 1981 } 1982 1983 1984 /*********************************************************************** 1985 1986 Sets this argument to depend upon another argument. 1987 1988 Params: 1989 other = alias of the argument which is to be set as a dependency 1990 1991 Returns: 1992 this object for method chaining 1993 1994 ***********************************************************************/ 1995 1996 public Argument requires ( char other ) 1997 { 1998 return requires(cast(istring)(&other)[0 .. 1]); 1999 } 2000 2001 2002 /*********************************************************************** 2003 2004 Sets this argument to conflict with another argument. 2005 2006 Params: 2007 arg = argument instance with which this argument should conflict 2008 2009 Returns: 2010 this object for method chaining 2011 2012 ***********************************************************************/ 2013 2014 public Argument conflicts ( Argument arg ) 2015 { 2016 conflictees ~= arg; 2017 2018 return this; 2019 } 2020 2021 2022 /*********************************************************************** 2023 2024 Sets this argument to conflict with another argument. 2025 2026 Params: 2027 other = name of the argument with which this argument should 2028 conflict 2029 2030 Returns: 2031 this object for method chaining 2032 2033 ***********************************************************************/ 2034 2035 public Argument conflicts ( istring other ) 2036 { 2037 return conflicts(this.outer.get(other)); 2038 } 2039 2040 2041 /*********************************************************************** 2042 2043 Sets this argument to conflict with another argument. 2044 2045 Params: 2046 other = alias of the argument with which this argument should 2047 conflict 2048 2049 Returns: 2050 this object for method chaining 2051 2052 ***********************************************************************/ 2053 2054 public Argument conflicts ( char other ) 2055 { 2056 return conflicts(cast(istring)(&other)[0 .. 1]); 2057 } 2058 2059 2060 /*********************************************************************** 2061 2062 Enables parameter assignment for this argument. The minimum and 2063 maximum number of parameters are set to 0 and 42 respectively. 2064 2065 Returns: 2066 this object for method chaining 2067 2068 ***********************************************************************/ 2069 2070 public Argument params ( ) 2071 { 2072 return params(0, 42); 2073 } 2074 2075 2076 /*********************************************************************** 2077 2078 Enables parameter assignment for this argument and sets an exact 2079 count for the number of parameters required. 2080 2081 Params: 2082 count = the number of parameters to be set 2083 2084 Returns: 2085 this object for method chaining 2086 2087 ***********************************************************************/ 2088 2089 public Argument params ( int count ) 2090 { 2091 return params(count, count); 2092 } 2093 2094 2095 /*********************************************************************** 2096 2097 Enables parameter assignment for this argument and sets the counts 2098 of both the minimum and maximum parameters required. 2099 2100 Params: 2101 min = minimum number of parameters required 2102 max = maximum number of parameters required 2103 2104 Returns: 2105 this object for method chaining 2106 2107 ***********************************************************************/ 2108 2109 public Argument params ( int min, int max ) 2110 { 2111 this.min = min; 2112 2113 this.max = max; 2114 2115 return this; 2116 } 2117 2118 2119 /*********************************************************************** 2120 2121 Adds a default parameter for this argument. 2122 2123 Params: 2124 values = default parameter to be added 2125 2126 Returns: 2127 this object for method chaining 2128 2129 ***********************************************************************/ 2130 2131 public Argument defaults ( istring values ) 2132 { 2133 this.deefalts ~= values; 2134 2135 return this; 2136 } 2137 2138 2139 /*********************************************************************** 2140 2141 Sets an inspector for this argument. The inspector delegate gets 2142 fired when a parameter is appended to this argument. 2143 The appended parameter gets sent to the delegate as the input 2144 parameter. If the appended parameter is ok, the delegate should 2145 return null. Otherwise, it should return a text string describing 2146 the issue. A non-null return value from the delegate will trigger an 2147 error. 2148 2149 Params: 2150 inspector = delegate to be called when a parameter is appended 2151 to this argument 2152 2153 Returns: 2154 this object for method chaining 2155 2156 ***********************************************************************/ 2157 2158 public Argument bind ( scope Inspector inspector ) 2159 { 2160 this.inspector = inspector; 2161 2162 return this; 2163 } 2164 2165 2166 /*********************************************************************** 2167 2168 Sets an invoker for this argument. The invoker delegate gets 2169 fired when this argument is found during parsing. 2170 2171 Params: 2172 invoker = delegate to be called when this argument's declaration 2173 is seen 2174 2175 Returns: 2176 this object for method chaining 2177 2178 ***********************************************************************/ 2179 2180 public Argument bind ( scope Invoker invoker ) 2181 { 2182 this.invoker = invoker; 2183 2184 return this; 2185 } 2186 2187 2188 /*********************************************************************** 2189 2190 Enables/disables smushing for this argument. 2191 2192 Smushing refers to omitting the explicit assignment symbol ('=' by 2193 default) or whitespace (when relying on implicit assignment) that 2194 separates an argument from its parameter. Note that smushing is 2195 possible only when assigning parameters to an argument using the 2196 argument's short prefix version. 2197 2198 Params: 2199 yes = true to enable smushing, false to disable 2200 2201 Returns: 2202 this object for method chaining 2203 2204 ***********************************************************************/ 2205 2206 public Argument smush ( bool yes = true ) 2207 { 2208 cat = yes; 2209 2210 return this; 2211 } 2212 2213 2214 /*********************************************************************** 2215 2216 Disables implicit parameter assignment to this argument. 2217 2218 Returns: 2219 this object for method chaining 2220 2221 ***********************************************************************/ 2222 2223 public Argument explicit ( ) 2224 { 2225 exp = true; 2226 2227 return this; 2228 } 2229 2230 2231 /*********************************************************************** 2232 2233 Changes the name of this argument (can be useful for naming the 2234 default argument). 2235 2236 Params: 2237 name = new name of this argument 2238 2239 Returns: 2240 this object for method chaining 2241 2242 ***********************************************************************/ 2243 2244 public Argument title ( istring name ) 2245 { 2246 this.name = name; 2247 2248 return this; 2249 } 2250 2251 2252 /*********************************************************************** 2253 2254 Sets the help text for this argument. 2255 2256 Params: 2257 text = the help text to set 2258 2259 Returns: 2260 this object for method chaining 2261 2262 ***********************************************************************/ 2263 2264 public Argument help ( istring text ) 2265 { 2266 this.text = text; 2267 2268 return this; 2269 } 2270 2271 2272 /*********************************************************************** 2273 2274 Fails the parsing immediately upon encountering this argument. This 2275 can be used for managing help text. 2276 2277 Returns: 2278 this object for method chaining 2279 2280 ***********************************************************************/ 2281 2282 public Argument halt ( ) 2283 { 2284 this.fail = true; 2285 2286 return this; 2287 } 2288 2289 2290 /*********************************************************************** 2291 2292 Restricts parameters of this argument to be in the given set. 2293 2294 Allocates copy of `options` 2295 2296 Params: 2297 options = array containing the set of acceptable parameters 2298 2299 Returns: 2300 this object for method chaining 2301 2302 ***********************************************************************/ 2303 2304 public Argument restrict ( Const!(istring)[] options... ) 2305 { 2306 this.options = options.dup; 2307 2308 return this; 2309 } 2310 2311 2312 /*********************************************************************** 2313 2314 Sets the flag that indicates that this argument was found during 2315 parsing. Also calls the invoker delegate, if configured. If the 2316 argument is unexpected (i.e. was not pre-configured), then an 2317 appropriate error condition gets set. 2318 2319 Params: 2320 unexpected = true if this is an unexpected argument, false 2321 otherwise 2322 2323 Returns: 2324 this object for method chaining 2325 2326 ***********************************************************************/ 2327 2328 private Argument enable ( bool unexpected = false ) 2329 { 2330 this.set = true; 2331 2332 if ( max > 0 ) 2333 { 2334 this.outer.stack.push(this); 2335 } 2336 2337 if ( invoker ) 2338 { 2339 invoker(); 2340 } 2341 2342 if ( unexpected ) 2343 { 2344 error = Extra; 2345 } 2346 2347 return this; 2348 } 2349 2350 2351 /*********************************************************************** 2352 2353 Appends the given parameter to this argument. Also calls the 2354 inspector delegate, if configured. 2355 2356 Params: 2357 value = parameter to be appended 2358 explicit = true if the parameter was explicitly assigned to this 2359 argument, false otherwise (defaults to false) 2360 2361 ***********************************************************************/ 2362 2363 private void append ( istring value, bool explicit = false ) 2364 { 2365 // pop to an argument that can accept implicit parameters? 2366 if ( explicit is false ) 2367 { 2368 auto s = &(this.outer.stack); 2369 2370 while ( s.top.exp && s.size > 1 ) 2371 { 2372 s.pop; 2373 } 2374 } 2375 2376 this.set = true; // needed for default assignments 2377 2378 values ~= value; // append new value 2379 2380 if ( error is None ) 2381 { 2382 if ( inspector ) 2383 { 2384 if ( (bogus = inspector(value)).length ) 2385 { 2386 error = Invalid; 2387 } 2388 } 2389 2390 if ( options.length ) 2391 { 2392 error = Option; 2393 2394 foreach ( option; options ) 2395 { 2396 if ( option == value ) 2397 { 2398 error = None; 2399 } 2400 } 2401 } 2402 } 2403 2404 // pop to an argument that can accept parameters 2405 auto s = &(this.outer.stack); 2406 2407 while ( s.top.values.length >= max && s.size > 1 ) 2408 { 2409 s.pop; 2410 } 2411 } 2412 2413 2414 /*********************************************************************** 2415 2416 Tests whether an error condition occurred for this argument during 2417 parsing, and if so the appropriate error code is set. 2418 2419 Returns: 2420 the error code for this argument (0 => no error) 2421 2422 ***********************************************************************/ 2423 2424 private int valid ( ) 2425 { 2426 if ( error is None ) 2427 { 2428 if ( req && !set ) 2429 { 2430 error = Required; 2431 } 2432 else 2433 { 2434 if ( set ) 2435 { 2436 // short circuit? 2437 if ( fail ) 2438 { 2439 return -1; 2440 } 2441 2442 if ( values.length < min ) 2443 { 2444 error = ParamLo; 2445 } 2446 else 2447 { 2448 if ( values.length > max ) 2449 { 2450 error = ParamHi; 2451 } 2452 else 2453 { 2454 foreach ( arg; dependees ) 2455 { 2456 if ( ! arg.set ) 2457 { 2458 error = Requires; 2459 bogus = arg.name; 2460 } 2461 } 2462 2463 foreach ( arg; conflictees ) 2464 { 2465 if ( arg.set ) 2466 { 2467 error = Conflict; 2468 bogus = arg.name; 2469 } 2470 } 2471 } 2472 } 2473 } 2474 } 2475 } 2476 2477 return error; 2478 } 2479 } 2480 } 2481 2482 2483 2484 /******************************************************************************* 2485 2486 Unit tests 2487 2488 *******************************************************************************/ 2489 2490 unittest 2491 { 2492 auto args = new Arguments; 2493 2494 // basic 2495 auto x = args['x']; 2496 test(args.parse("")); 2497 x.required; 2498 test(args.parse("") is false); 2499 test(args.clear.parse("-x")); 2500 test(x.set); 2501 2502 // alias 2503 x.aliased('X'); 2504 test(args.clear.parse("-X")); 2505 test(x.set); 2506 2507 // unexpected arg (with sloppy) 2508 test(args.clear.parse("-y") is false); 2509 test(args.clear.parse("-y") is false); 2510 test(args.clear.parse("-y", true) is false); 2511 test(args['y'].set); 2512 test(args.clear.parse("-x -y", true)); 2513 2514 // parameters 2515 x.params(0); 2516 test(args.clear.parse("-x param")); 2517 test(x.assigned.length is 0); 2518 test(args(null).assigned.length is 1); 2519 x.params(1); 2520 test(args.clear.parse("-x=param")); 2521 test(x.assigned.length is 1); 2522 test(x.assigned[0] == "param"); 2523 test(args.clear.parse("-x param")); 2524 test(x.assigned.length is 1); 2525 test(x.assigned[0] == "param"); 2526 2527 // too many args 2528 x.params(1); 2529 test(args.clear.parse("-x param1 param2")); 2530 test(x.assigned.length is 1); 2531 test(x.assigned[0] == "param1"); 2532 test(args(null).assigned.length is 1); 2533 test(args(null).assigned[0] == "param2"); 2534 2535 // now with default params 2536 test(args.clear.parse("param1 param2 -x=blah")); 2537 test(args[null].assigned.length is 2); 2538 test(args(null).assigned.length is 2); 2539 test(x.assigned.length is 1); 2540 x.params(0); 2541 test(!args.clear.parse("-x=blah")); 2542 2543 // args as parameter 2544 test(args.clear.parse("- -x")); 2545 test(args[null].assigned.length is 1); 2546 test(args[null].assigned[0] == "-"); 2547 2548 // multiple flags, with alias and sloppy 2549 test(args.clear.parse("-xy")); 2550 test(args.clear.parse("-xyX")); 2551 test(x.set); 2552 test(args['y'].set); 2553 test(args.clear.parse("-xyz") is false); 2554 test(args.clear.parse("-xyz", true)); 2555 auto z = args['z']; 2556 test(z.set); 2557 2558 // multiple flags with trailing arg 2559 test(args.clear.parse("-xyz=10")); 2560 test(z.assigned.length is 1); 2561 2562 // again, but without sloppy param declaration 2563 z.params(0); 2564 test(!args.clear.parse("-xyz=10")); 2565 test(args.clear.parse("-xzy=10")); 2566 test(args('y').assigned.length is 1); 2567 test(args('x').assigned.length is 0); 2568 test(args('z').assigned.length is 0); 2569 2570 // x requires y 2571 x.requires('y'); 2572 test(args.clear.parse("-xy")); 2573 test(args.clear.parse("-xz") is false); 2574 test!("==")(args.errors(), "argument 'x' requires 'y'\n"); 2575 2576 // defaults 2577 z.defaults("foo"); 2578 test(args.clear.parse("-xy")); 2579 test(z.assigned.length is 1); 2580 2581 // long names, with params 2582 test(args.clear.parse("-xy --foobar") is false); 2583 test(args.clear.parse("-xy --foobar", true)); 2584 test(args["y"].set && x.set); 2585 test(args["foobar"].set); 2586 test(args.clear.parse("-xy --foobar=10")); 2587 test(args["foobar"].assigned.length is 1); 2588 test(args["foobar"].assigned[0] == "10"); 2589 2590 // smush argument z, but not others 2591 z.params; 2592 test(args.clear.parse("-xy -zsmush") is false); 2593 test(x.set); 2594 z.smush; 2595 test(args.clear.parse("-xy -zsmush")); 2596 test(z.assigned.length is 1); 2597 test(z.assigned[0] == "smush"); 2598 test(x.assigned.length is 0); 2599 z.params(0); 2600 2601 // conflict x with z 2602 x.conflicts(z); 2603 test(args.clear.parse("-xyz") is false); 2604 2605 // word mode, with prefix elimination 2606 args = new Arguments(null, null, null, null, null, null); 2607 test(args.clear.parse("foo bar wumpus") is false); 2608 test(args.clear.parse("foo bar wumpus wombat", true)); 2609 test(args("foo").set); 2610 test(args("bar").set); 2611 test(args("wumpus").set); 2612 test(args("wombat").set); 2613 2614 // use '/' instead of '-' 2615 args = new Arguments(null, null, null, null, "/", "/"); 2616 test(args.clear.parse("/foo /bar /wumpus") is false); 2617 test(args.clear.parse("/foo /bar /wumpus /wombat", true)); 2618 test(args("foo").set); 2619 test(args("bar").set); 2620 test(args("wumpus").set); 2621 test(args("wombat").set); 2622 2623 // use '/' for short and '-' for long 2624 args = new Arguments(null, null, null, null, "/", "-"); 2625 test(args.clear.parse("-foo -bar -wumpus -wombat /abc", true)); 2626 test(args("foo").set); 2627 test(args("bar").set); 2628 test(args("wumpus").set); 2629 test(args("wombat").set); 2630 test(args("a").set); 2631 test(args("b").set); 2632 test(args("c").set); 2633 2634 // "--" makes all subsequent be implicit parameters 2635 args = new Arguments; 2636 args('f').params(0); 2637 test(args.parse("-f -- -bar -wumpus -wombat --abc")); 2638 test(args('f').assigned.length is 0); 2639 test(args(null).assigned.length is 4); 2640 2641 // Confirm arguments are stored in a sorted manner 2642 args = new Arguments; 2643 test(args.clear.parse("--beta --alpha --delta --echo --charlie", true)); 2644 size_t index; 2645 foreach (arg; args) 2646 { 2647 switch ( index++ ) 2648 { 2649 case 0: continue; 2650 case 1: test("alpha" == arg.name); continue; 2651 case 2: test("beta" == arg.name); continue; 2652 case 3: test("charlie" == arg.name); continue; 2653 case 4: test("delta" == arg.name); continue; 2654 case 5: test("echo" == arg.name); continue; 2655 default: test(0); 2656 } 2657 } 2658 2659 // Test that getInt() works as expected 2660 args = new Arguments; 2661 args("num").params(1); 2662 test(args.parse("--num 100")); 2663 test(args.getInt!(uint)("num") == 100); 2664 2665 args = new Arguments; 2666 args("num").params(1); 2667 test(args.parse("--num 18446744073709551615")); 2668 test(args.getInt!(ulong)("num") == ulong.max); 2669 2670 args = new Arguments; 2671 args("num").params(1); 2672 test(args.getInt!(ulong)("num") == 0); 2673 } 2674 2675 // Test for D2 'static immutable' 2676 unittest 2677 { 2678 static immutable istring name_ = "encode"; 2679 static immutable istring conflicts_ = "decode"; 2680 static immutable istring[] restrict_ = [ "json", "yaml" ]; 2681 static immutable istring requires_ = "input"; 2682 static immutable istring help_ = "Convert from native format to JSON/Yaml"; 2683 2684 auto args = new Arguments; 2685 args(name_) 2686 .params(1) 2687 .conflicts(conflicts_) 2688 .restrict(restrict_) 2689 .requires(requires_) 2690 .help(help_); 2691 2692 }