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.meta.types.Qualifiers; 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.TypeConvert: assumeUnique; 618 import ocean.core.Verify; 619 620 621 version (unittest) import ocean.core.Test; 622 623 /******************************************************************************* 624 625 The main arguments container class. 626 627 *******************************************************************************/ 628 629 public class Arguments 630 { 631 import ocean.core.Enforce : enforce; 632 633 /*************************************************************************** 634 635 Convenience aliases to access a specific argument instance 636 637 ***************************************************************************/ 638 639 public alias get opCall; // args("name") 640 public alias get opIndex; // args["name"] 641 642 643 /*************************************************************************** 644 645 Convenience alias to get the value of a boolean argument 646 647 ***************************************************************************/ 648 649 public alias getBool exists; 650 651 652 /*************************************************************************** 653 654 Application's name to use in help messages. 655 656 ***************************************************************************/ 657 658 public istring app_name; 659 660 661 /*************************************************************************** 662 663 Application's short usage description (as a format string). 664 665 This is used as a format string to print the usage. The first parameter 666 to the format string is the application's name. This string should 667 describe how to invoke the application. 668 669 If the usage description spans multiple lines, then it's better to start 670 each line with a tab character (\t). 671 672 Examples: 673 674 --- 675 676 args.usage = "{0} [OPTIONS] SOMETHING FILE"; 677 args.usage = "{0} [OPTIONS] SOMETHING FILE\n" 678 "\t{0} --version"; 679 680 --- 681 682 ***************************************************************************/ 683 684 public istring usage = "{0} [OPTIONS] [ARGS]"; 685 686 687 /*************************************************************************** 688 689 One line description of what the application does (as a format string). 690 691 This is used as a format string to print a short description of what the 692 application does. The first argument is the name of the application (but 693 the name shouldn't normally be used in the description). 694 695 ***************************************************************************/ 696 697 public istring short_desc; 698 699 700 /*************************************************************************** 701 702 Long description about the application and how to use it (as a format 703 string). 704 705 This is used as a format string to print a long description of what the 706 application does and how to use it. The first argument is the name of 707 the application. 708 709 ***************************************************************************/ 710 711 public istring long_desc; 712 713 714 /*************************************************************************** 715 716 Stack used to help in assigning implicitly assigned parameters to 717 arguments during parsing. 718 719 This stack contains argument instances of only those arguments that can 720 have one or more associated parameters. Implicit parameters always get 721 assigned to the topmost argument in the stack. Once the number of 722 parameters of the topmost argument in the stack reaches its maximum 723 configured value, that argument gets popped off the stack. Future 724 implicit assignments will then happen to the new topmost argument in the 725 stack. 726 727 The null argument is always the first one that gets pushed onto the 728 stack. This ensures that it is able to "catch" all unclaimed parameters 729 at the end. 730 731 ***************************************************************************/ 732 733 private Stack!(Argument) stack; 734 735 736 /*************************************************************************** 737 738 All argument instances. A sorted map (indexed by the argument name) is 739 used to store these so that the arguments appear in a sorted manner in 740 the help text output 741 742 ***************************************************************************/ 743 744 private SortedMap!(cstring, Argument) args; 745 746 747 /*************************************************************************** 748 749 Argument instances that have aliases. A sorted map (indexed by the 750 argument aliases) is used to store these so that the arguments appear in 751 a sorted manner in the help text output 752 753 ***************************************************************************/ 754 755 private SortedMap!(cstring, Argument) aliases; 756 757 758 /*************************************************************************** 759 760 Character to be used as the explicit assignment symbol 761 762 ***************************************************************************/ 763 764 private char eq; 765 766 767 /*************************************************************************** 768 769 The short prefix string 770 771 ***************************************************************************/ 772 773 private istring sp; 774 775 776 /*************************************************************************** 777 778 The long prefix string 779 780 ***************************************************************************/ 781 782 private istring lp; 783 784 785 /*************************************************************************** 786 787 Error messages 788 789 ***************************************************************************/ 790 791 private const(istring)[] msgs; 792 793 794 /*************************************************************************** 795 796 Format strings of all default errors 797 798 ***************************************************************************/ 799 800 private static immutable istring[] errmsg = [ 801 "argument '{0}' expects {2} parameter(s) but has {1}\n", 802 "argument '{0}' expects {3} parameter(s) but has {1}\n", 803 "argument '{0}' is missing\n", 804 "argument '{0}' requires '{4}'\n", 805 "argument '{0}' conflicts with '{4}'\n", 806 "unexpected argument '{0}'\n", 807 "argument '{0}' expects one of {5}\n", 808 "invalid parameter for argument '{0}': {4}\n", 809 ]; 810 811 812 /*************************************************************************** 813 814 Internal string used for spacing of the full help message 815 816 ***************************************************************************/ 817 818 private mstring spaces; 819 820 821 /*************************************************************************** 822 823 Temporary formatting buffer 824 825 ***************************************************************************/ 826 827 private mstring tmp_buf; 828 829 830 /*************************************************************************** 831 832 Maximum width of the column showing argument aliases in the full help 833 message 834 835 ***************************************************************************/ 836 837 private size_t aliases_width; 838 839 840 /*************************************************************************** 841 842 Maximum width of the column showing argument names in the full help 843 message 844 845 ***************************************************************************/ 846 847 private size_t long_name_width; 848 849 850 /*************************************************************************** 851 852 Constructor. 853 854 Params: 855 app_name = name of the application (to show in the help message) 856 short_desc = short description of what the application does (should 857 be one line only, preferably less than 80 characters long) 858 usage = how the application is supposed to be invoked 859 long_desc = long description of what the application does and how to 860 use it 861 sp = string to use as the short prefix (defaults to '-') 862 lp = string to use as the long prefix (defaults to '--') 863 eq = character to use as the explicit assignment symbol 864 (defaults to '=') 865 866 ***************************************************************************/ 867 868 public this ( istring app_name = null, istring short_desc = null, 869 istring usage = null, istring long_desc = null, istring sp = "-", 870 istring lp = "--", char eq = '=' ) 871 { 872 this.msgs = this.errmsg; 873 874 this.app_name = app_name; 875 this.short_desc = short_desc; 876 this.long_desc = long_desc; 877 this.sp = sp; 878 this.lp = lp; 879 this.eq = eq; 880 881 this.args = new typeof(this.args)(); 882 this.aliases = new typeof(this.aliases)(); 883 884 if ( usage.length > 0 ) 885 { 886 this.usage = usage; 887 } 888 889 this.get(null).params; // set null argument to consume params 890 } 891 892 893 /*************************************************************************** 894 895 Parses the command-line arguments into a set of Argument instances. The 896 command-line arguments are expected to be passed in a string. 897 898 Params: 899 input = string to be parsed (contains command-line arguments) 900 sloppy = true if any unexpected arguments found during parsing 901 should be accepted on-the-fly, false if unexpected arguments 902 should be treated as error 903 904 Returns: 905 true if parsing was successful, false otherwise 906 907 ***************************************************************************/ 908 909 public bool parse ( istring input, bool sloppy = false ) 910 { 911 istring[] tmp; 912 913 foreach ( s; quotes(input, " ") ) 914 { 915 tmp ~= s; 916 } 917 918 return parse(tmp, sloppy); 919 } 920 921 922 /*************************************************************************** 923 924 Parses the command-line arguments into a set of Argument instances. The 925 command-line arguments are expected to be passed in an array of strings. 926 927 Params: 928 input = array of strings to be parsed (contains command-line 929 arguments) 930 sloppy = true if any unexpected arguments found during parsing 931 should be accepted on-the-fly, false if unexpected arguments 932 should be treated as error 933 934 Returns: 935 true if parsing was successful, false otherwise 936 937 ***************************************************************************/ 938 939 public bool parse ( const(istring)[] input, bool sloppy = false ) 940 { 941 bool done; 942 int error; 943 944 stack.push(this.get(null)); 945 946 foreach ( s; input ) 947 { 948 if ( done is false ) 949 { 950 if ( s == "--" ) 951 { 952 done = true; 953 954 stack.clear.push(this.get(null)); 955 956 continue; 957 } 958 else 959 { 960 if ( argument(s, lp, sloppy, false) || 961 argument(s, sp, sloppy, true) ) 962 { 963 continue; 964 } 965 } 966 } 967 968 stack.top.append (s); 969 } 970 971 foreach ( arg; args ) 972 { 973 error |= arg.valid; 974 } 975 976 return error is 0; 977 } 978 979 980 /*************************************************************************** 981 982 Unsets all configured arguments (as if they weren't given at all on the 983 command-line), clears all parameters that may have been assigned to 984 arguments and also clears any parsing errors that may have been 985 associated with any argument(s). 986 987 Note that configured arguments are *not* removed. 988 989 Returns: 990 this object for method chaining 991 992 ***************************************************************************/ 993 994 public Arguments clear ( ) 995 { 996 stack.clear; 997 998 foreach ( arg; args ) 999 { 1000 arg.set = false; 1001 arg.values = null; 1002 arg.error = arg.None; 1003 } 1004 1005 return this; 1006 } 1007 1008 1009 /*************************************************************************** 1010 1011 Gets a reference to an argument, creating a new instance if necessary. 1012 1013 Params: 1014 name = character representing the argument to be retrieved (this is 1015 usually an alias to the argument, but could also be the argument 1016 name if the argument name is exactly one character long) 1017 1018 Returns: 1019 a reference to the argument 1020 1021 ***************************************************************************/ 1022 1023 public Argument get ( char name ) 1024 { 1025 return get(cast(cstring)(&name)[0 .. 1]); 1026 } 1027 1028 1029 /*************************************************************************** 1030 1031 Gets a reference to an argument, creating a new instance if necessary. 1032 1033 Params: 1034 name = string containing the argument name (pass null to access the 1035 special 'null' argument) 1036 1037 Returns: 1038 a reference to the argument 1039 1040 ***************************************************************************/ 1041 1042 public Argument get ( cstring name ) 1043 { 1044 auto a = name in args; 1045 1046 if ( a is null ) 1047 { 1048 auto _name = idup(name); 1049 1050 auto arg = new Argument(_name); 1051 1052 args[_name] = arg; 1053 1054 return arg; 1055 } 1056 1057 return *a; 1058 } 1059 1060 1061 /*************************************************************************** 1062 1063 Enables 'foreach' iteration over the set of configured arguments. 1064 1065 Params: 1066 dg = delegate called for each argument 1067 1068 ***************************************************************************/ 1069 1070 public int opApply ( scope int delegate(ref Argument) dg ) 1071 { 1072 int result; 1073 1074 foreach ( arg; args ) 1075 { 1076 if ( (result = dg(arg)) != 0 ) 1077 { 1078 break; 1079 } 1080 } 1081 1082 return result; 1083 } 1084 1085 /*************************************************************************** 1086 1087 Constructs a string of error messages. 1088 1089 Returns: 1090 formatted error message string 1091 1092 ***************************************************************************/ 1093 1094 public istring errors () 1095 { 1096 mstring result; 1097 1098 foreach (arg; args) 1099 { 1100 if (arg.error) 1101 { 1102 sformat( 1103 result, msgs[arg.error-1], arg.name, 1104 arg.values.length, arg.min, arg.max, arg.bogus, 1105 arg.options); 1106 } 1107 } 1108 1109 return assumeUnique(result); 1110 } 1111 1112 1113 /*************************************************************************** 1114 1115 Replaces the default error messages with the given string. 1116 Note that arguments are passed to the formatter in the following order, 1117 and these should be indexed appropriately by each of the error messages 1118 (see the 'errmsg' variable for the format string): 1119 1120 index 0: the argument name 1121 index 1: number of parameters 1122 index 2: configured minimum parameters 1123 index 3: configured maximum parameters 1124 index 4: conflicting/dependent argument (or invalid param) 1125 index 5: array of configured parameter options 1126 1127 Params: 1128 errors = string to replace the default error messages with 1129 1130 Returns: 1131 this object for method chaining 1132 1133 ***************************************************************************/ 1134 1135 public Arguments errors ( const(istring)[] errors ) 1136 { 1137 if ( errors.length is errmsg.length ) 1138 { 1139 msgs = errors; 1140 } 1141 else 1142 { 1143 verify(false); 1144 } 1145 1146 return this; 1147 } 1148 1149 1150 /*************************************************************************** 1151 1152 Exposes the configured help text for each of the configured arguments, 1153 via the given delegate. Note that the delegate will be called only for 1154 those arguments for which a help text has been configured. 1155 1156 Params: 1157 dg = delegate that will be called for each argument having a help 1158 text (the argument name and the help text itself will be sent as 1159 parameters to the delegate) 1160 1161 Returns: 1162 this object for method chaining 1163 1164 ***************************************************************************/ 1165 1166 public Arguments help ( scope void delegate ( istring arg, istring help ) dg ) 1167 { 1168 foreach ( arg; args ) 1169 { 1170 if ( arg.text.ptr ) 1171 { 1172 dg(arg.name, arg.text); 1173 } 1174 } 1175 1176 return this; 1177 } 1178 1179 1180 /*************************************************************************** 1181 1182 Displays the full help message for the application. 1183 1184 Params: 1185 output = stream where to print the help message (Stderr by default) 1186 1187 ***************************************************************************/ 1188 1189 public void displayHelp ( FormatOutput output = Stderr ) 1190 { 1191 if ( this.short_desc.length > 0 ) 1192 { 1193 output.formatln(this.short_desc, this.app_name); 1194 output.newline; 1195 } 1196 1197 output.formatln("Usage:\t" ~ this.usage, this.app_name); 1198 output.newline; 1199 1200 if ( this.long_desc.length > 0 ) 1201 { 1202 output.formatln(this.long_desc, this.app_name); 1203 output.newline; 1204 } 1205 1206 foreach ( arg; this.args ) 1207 { 1208 this.calculateSpacing(arg); 1209 } 1210 1211 output.formatln("Program options:"); 1212 1213 foreach ( arg; this.args ) 1214 { 1215 // Skip the null argument 1216 if ( arg.name.length == 0 ) 1217 { 1218 continue; 1219 } 1220 1221 output.formatln("{}", this.formatArgumentHelp(arg, this.tmp_buf)); 1222 } 1223 1224 output.newline; 1225 } 1226 1227 1228 /*************************************************************************** 1229 1230 Displays any errors that occurred. 1231 1232 Params: 1233 output = stream where to print the errors (Stderr by default) 1234 1235 ***************************************************************************/ 1236 1237 public void displayErrors ( FormatOutput output = Stderr ) 1238 { 1239 output.format("{}", this.errors()); 1240 } 1241 1242 1243 /*************************************************************************** 1244 1245 Convenience method to check whether an argument is set or not (i.e. 1246 whether it was found during parsing of the command-line arguments). 1247 1248 Params: 1249 name = name of the argument 1250 1251 Returns: 1252 true if the argument is set, false otherwise 1253 1254 ***************************************************************************/ 1255 1256 public bool getBool ( cstring name ) 1257 { 1258 auto arg = this.get(name); 1259 1260 if ( arg ) 1261 { 1262 return arg.set; 1263 } 1264 else 1265 { 1266 return false; 1267 } 1268 } 1269 1270 1271 /*************************************************************************** 1272 1273 Convenience method to get the integer value of the parameter assigned to 1274 an argument. This is valid only if the argument has been assigned 1275 exactly one parameter. 1276 1277 Params: 1278 T = type of integer to return 1279 name = name of the argument 1280 1281 Returns: 1282 integer value of the parameter assigned to the argument 1283 0 if value is missing or not a valid integer 1284 1285 ***************************************************************************/ 1286 1287 public T getInt ( T ) ( cstring name ) 1288 { 1289 auto arg = this.get(name); 1290 1291 cstring value; 1292 1293 if ( arg && arg.assigned.length == 1 ) 1294 { 1295 value = arg.assigned[0]; 1296 } 1297 1298 T num; 1299 if (toInteger(value, num)) 1300 return num; 1301 else 1302 return 0; 1303 } 1304 1305 1306 /*************************************************************************** 1307 1308 Convenience method to get the string parameter assigned to an argument. 1309 This is valid only if the argument has been assigned exactly one 1310 parameter. 1311 1312 Params: 1313 name = name of the argument 1314 1315 Returns: 1316 parameter assigned to the argument 1317 1318 ***************************************************************************/ 1319 1320 public istring getString ( cstring name ) 1321 { 1322 auto arg = this.get(name); 1323 1324 istring value; 1325 1326 if ( arg && arg.assigned.length == 1 ) 1327 { 1328 value = arg.assigned[0]; 1329 } 1330 1331 return value; 1332 } 1333 1334 1335 /*************************************************************************** 1336 1337 Tests for the presence of a switch (long/short prefix) and enables the 1338 associated argument if found. Also looks for and handles explicit 1339 parameter assignment. 1340 1341 Params: 1342 s = An individual string from the command-line arguments (includes 1343 the long/short prefix if it is an argument string) 1344 p = the prefix string (whether this is the long prefix or the short 1345 prefix is indicated by the 'flag' parameter) 1346 sloppy = true if any unexpected arguments found during parsing 1347 should be accepted on-the-fly, false if unexpected arguments 1348 should be treated as error 1349 flag = true if the prefix string given is the short prefix, false if 1350 it is the long prefix 1351 1352 Returns: 1353 true if the given string was an argument, false if it was a 1354 parameter 1355 1356 ***************************************************************************/ 1357 1358 private bool argument ( istring s, istring p, bool sloppy, bool flag ) 1359 { 1360 if ( s.length >= p.length && s[0 .. p.length] == p ) 1361 { 1362 s = s[p.length .. $]; 1363 1364 auto i = locate(s, eq); 1365 1366 if ( i < s.length ) 1367 { 1368 enable(s[0 .. i], sloppy, flag).append(s[i + 1 .. $], true); 1369 } 1370 else 1371 { 1372 // trap empty arguments; attach as param to null-arg 1373 if ( s.length ) 1374 { 1375 enable(s, sloppy, flag); 1376 } 1377 else 1378 { 1379 this.get(null).append(p, true); 1380 } 1381 } 1382 1383 return true; 1384 } 1385 1386 return false; 1387 } 1388 1389 1390 /*************************************************************************** 1391 1392 Indicates the existence of an argument, and handles sloppy arguments 1393 along with multiple-flags and smushed parameters. Note that sloppy 1394 arguments are configured with parameters enabled. 1395 1396 Params: 1397 elem = an argument name found during parsing (does not contain the 1398 long/short prefix) 1399 sloppy = true if any unexpected arguments found during parsing 1400 should be accepted on-the-fly, false if unexpected arguments 1401 should be treated as error 1402 flag = true if the argument name was preceded by the short prefix, 1403 false if it was preceded by the long prefix 1404 1405 Returns: 1406 the configured argument instance 1407 1408 ***************************************************************************/ 1409 1410 private Argument enable ( istring elem, bool sloppy, bool flag = false ) 1411 { 1412 if ( flag && elem.length > 1 ) 1413 { 1414 // locate arg for first char 1415 auto arg = enable(elem[0 .. 1], sloppy); 1416 1417 elem = elem[1 .. $]; 1418 1419 // drop further processing of this flag where in error 1420 if ( arg.error is arg.None ) 1421 { 1422 // smush remaining text or treat as additional args 1423 if ( arg.cat ) 1424 { 1425 arg.append(elem, true); 1426 } 1427 else 1428 { 1429 arg = enable(elem, sloppy, true); 1430 } 1431 } 1432 1433 return arg; 1434 } 1435 1436 // if not in args, or in aliases, then create new arg 1437 auto a = elem in args; 1438 1439 if ( a is null ) 1440 { 1441 if ( (a = elem in aliases) is null ) 1442 { 1443 return this.get(elem).params.enable(!sloppy); 1444 } 1445 } 1446 1447 return a.enable; 1448 } 1449 1450 1451 /*************************************************************************** 1452 1453 Calculates the width required to display all the aliases based on the 1454 given number of aliases (in the aliases string, each character is an 1455 individual argument alias). 1456 1457 Params: 1458 aliases = number of argument aliases 1459 1460 Returns: 1461 width required to display all the aliases 1462 1463 ***************************************************************************/ 1464 1465 private size_t aliasesWidth ( size_t aliases ) 1466 { 1467 auto width = aliases * 2; // *2 for a '-' before each alias 1468 1469 if ( aliases > 1 ) 1470 { 1471 width += (aliases - 1) * 2; // ', ' after each alias except the last 1472 } 1473 1474 return width; 1475 } 1476 1477 1478 /*************************************************************************** 1479 1480 Calculates the maximum width required to display the given argument name 1481 and its aliases. 1482 1483 Params: 1484 arg = the argument instance 1485 1486 ***************************************************************************/ 1487 1488 private void calculateSpacing ( Argument arg ) 1489 { 1490 this.long_name_width = max(this.long_name_width, arg.name.length); 1491 1492 this.aliases_width = max(this.aliases_width, 1493 this.aliasesWidth(arg.aliases.length)); 1494 } 1495 1496 1497 /*************************************************************************** 1498 1499 Formats the help text for a single argument. 1500 1501 Params: 1502 arg = argument instance for which the help text is to be formatted 1503 buf = buffer into which to format the help text 1504 1505 Returns: 1506 the formatted help text 1507 1508 ***************************************************************************/ 1509 1510 private mstring formatArgumentHelp ( Argument arg, ref mstring buf ) 1511 { 1512 buf.length = 0; 1513 assumeSafeAppend(buf); 1514 1515 sformat(buf, " "); 1516 1517 foreach ( i, al; arg.aliases ) 1518 { 1519 sformat(buf, "-{}", al); 1520 1521 if ( i != arg.aliases.length - 1 || arg.name.length ) 1522 { 1523 sformat(buf, ", "); 1524 } 1525 } 1526 1527 // there is no trailing ", " in this case, so add two spaces instead. 1528 if ( arg.aliases.length == 0 ) 1529 { 1530 sformat(buf, " "); 1531 } 1532 1533 sformat(buf, "{}", 1534 this.space(this.aliases_width - 1535 this.aliasesWidth(arg.aliases.length))); 1536 1537 sformat(buf, "--{}{} ", 1538 arg.name, this.space(this.long_name_width - arg.name.length)); 1539 1540 if ( arg.text.length ) 1541 { 1542 sformat(buf, "{}", arg.text); 1543 } 1544 1545 uint extras; 1546 1547 bool params = arg.min > 0 || arg.max > 0; 1548 1549 if ( params ) extras++; 1550 if ( arg.options.length ) extras++; 1551 if ( arg.deefalts.length ) extras++; 1552 1553 if ( extras ) 1554 { 1555 // comma separate sections if more info to come 1556 void next ( ) 1557 { 1558 extras--; 1559 1560 if ( extras ) 1561 { 1562 sformat(buf, ", "); 1563 } 1564 } 1565 1566 sformat(buf, " ("); 1567 1568 if ( params ) 1569 { 1570 if ( arg.min == arg.max ) 1571 { 1572 sformat(buf, "{} param{}", arg.min, 1573 arg.min == 1 ? "" : "s"); 1574 } 1575 else 1576 { 1577 sformat(buf, "{}-{} params", arg.min, arg.max); 1578 } 1579 1580 next(); 1581 } 1582 1583 if ( arg.options.length ) 1584 { 1585 sformat(buf, "{}", arg.options); 1586 1587 next(); 1588 } 1589 1590 if ( arg.deefalts.length ) 1591 { 1592 sformat(buf, "default: {}", arg.deefalts); 1593 1594 next(); 1595 } 1596 1597 sformat(buf, ")"); 1598 } 1599 1600 return buf; 1601 } 1602 1603 1604 /*************************************************************************** 1605 1606 Creates a string with the specified number of spaces. 1607 1608 Params: 1609 width = desired number of spaces 1610 1611 Returns: 1612 string with desired number of spaces. 1613 1614 ***************************************************************************/ 1615 1616 private cstring space ( size_t width ) 1617 { 1618 if ( width == 0 ) 1619 { 1620 return ""; 1621 } 1622 1623 this.spaces.length = width; 1624 assumeSafeAppend(this.spaces); 1625 1626 this.spaces[] = ' '; 1627 1628 return this.spaces; 1629 } 1630 1631 1632 /*************************************************************************** 1633 1634 Class that declares a specific argument instance. 1635 One of these is instantiated using one of the outer class' `get()` 1636 methods. All existing argument instances can be iterated over using the 1637 outer class' `opApply()` method. 1638 1639 ***************************************************************************/ 1640 1641 private class Argument 1642 { 1643 /*********************************************************************** 1644 1645 Enumeration of all error identifiers 1646 1647 ***********************************************************************/ 1648 1649 public enum 1650 { 1651 None, // ok (no error) 1652 1653 ParamLo, // too few parameters were assigned to this argument 1654 1655 ParamHi, // too many parameters were assigned to this argument 1656 1657 Required, // this is a required argument, but was not given 1658 1659 Requires, // this argument depends on another argument which was not 1660 // given 1661 1662 Conflict, // this argument conflicts with another given argument 1663 1664 Extra, // unexpected argument (will not trigger an error if 1665 // sloppy arguments are enabled) 1666 1667 Option, // parameter assigned is not one of the acceptable options 1668 1669 Invalid // invalid error 1670 } 1671 1672 1673 /*********************************************************************** 1674 1675 Convenience aliases 1676 1677 ***********************************************************************/ 1678 1679 public alias void delegate ( ) Invoker; 1680 public alias istring delegate ( istring value ) Inspector; 1681 1682 1683 /*********************************************************************** 1684 1685 Minimum number of parameters for this argument 1686 1687 ***********************************************************************/ 1688 1689 public int min; 1690 1691 1692 /*********************************************************************** 1693 1694 Maximum number of parameters for this argument 1695 1696 ***********************************************************************/ 1697 1698 public int max; 1699 1700 1701 /*********************************************************************** 1702 1703 The error code for this argument (0 => no error) 1704 1705 ***********************************************************************/ 1706 1707 public int error; 1708 1709 1710 /*********************************************************************** 1711 1712 Flag to indicate whether this argument is present or not 1713 1714 ***********************************************************************/ 1715 1716 public bool set; 1717 1718 1719 /*********************************************************************** 1720 1721 String in which each character is an alias for this argument 1722 1723 ***********************************************************************/ 1724 1725 public istring aliases; 1726 1727 1728 /*********************************************************************** 1729 1730 The name of the argument 1731 1732 ***********************************************************************/ 1733 1734 public istring name; 1735 1736 1737 /*********************************************************************** 1738 1739 The help text of the argument 1740 1741 ***********************************************************************/ 1742 1743 public istring text; 1744 1745 1746 /*********************************************************************** 1747 1748 Allowed parameters for this argument (there is no restriction on the 1749 acceptable parameters if this array is empty) 1750 1751 ***********************************************************************/ 1752 1753 public const(istring)[] options; 1754 1755 1756 /*********************************************************************** 1757 1758 Default parameters for this argument 1759 1760 ***********************************************************************/ 1761 1762 public const(istring)[] deefalts; 1763 1764 1765 /*********************************************************************** 1766 1767 Flag to indicate whether this argument is required or not 1768 1769 ***********************************************************************/ 1770 1771 private bool req; 1772 1773 1774 /*********************************************************************** 1775 1776 Flag to indicate whether this argument is smushable or not 1777 1778 ***********************************************************************/ 1779 1780 private bool cat; 1781 1782 1783 /*********************************************************************** 1784 1785 Flag to indicate whether this argument can accept implicit 1786 parameters or not 1787 1788 ***********************************************************************/ 1789 1790 private bool exp; 1791 1792 1793 /*********************************************************************** 1794 1795 Flag to indicate whether this argument has failed parsing or not 1796 1797 ***********************************************************************/ 1798 1799 private bool fail; 1800 1801 1802 /*********************************************************************** 1803 1804 The name of the argument that conflicts with this argument 1805 1806 ***********************************************************************/ 1807 1808 private istring bogus; 1809 1810 1811 /*********************************************************************** 1812 1813 Parameters assigned to this argument 1814 1815 ***********************************************************************/ 1816 1817 private istring[] values; 1818 1819 1820 /*********************************************************************** 1821 1822 Invocation callback 1823 1824 ***********************************************************************/ 1825 1826 private Invoker invoker; 1827 1828 1829 /*********************************************************************** 1830 1831 Inspection callback 1832 1833 ***********************************************************************/ 1834 1835 private Inspector inspector; 1836 1837 1838 /*********************************************************************** 1839 1840 Argument instances that are required by this argument 1841 1842 ***********************************************************************/ 1843 1844 private Argument[] dependees; 1845 1846 1847 /*********************************************************************** 1848 1849 Argument instances that this argument conflicts with 1850 1851 ***********************************************************************/ 1852 1853 private Argument[] conflictees; 1854 1855 1856 /*********************************************************************** 1857 1858 Constructor. 1859 1860 Params: 1861 name = name of the argument 1862 1863 ***********************************************************************/ 1864 1865 public this ( istring name ) 1866 { 1867 this.name = name; 1868 } 1869 1870 1871 /*********************************************************************** 1872 1873 Returns: 1874 the name of this argument 1875 1876 ***********************************************************************/ 1877 1878 public override istring toString ( ) 1879 { 1880 return name; 1881 } 1882 1883 1884 /*********************************************************************** 1885 1886 Returns: 1887 parameters assigned to this argument, or the default parameters 1888 if this argument was not present on the command-line 1889 1890 ***********************************************************************/ 1891 1892 public const(istring)[] assigned ( ) 1893 { 1894 return values.length ? values : deefalts; 1895 } 1896 1897 1898 /*********************************************************************** 1899 1900 Sets an alias for this argument. 1901 1902 Params: 1903 name = character to be used as an alias for this argument 1904 1905 Returns: 1906 this object for method chaining 1907 1908 ***********************************************************************/ 1909 1910 public Argument aliased ( char name ) 1911 { 1912 if ( auto arg = cast(cstring)((&name)[0..1]) in this.outer.aliases ) 1913 { 1914 verify( 1915 false, 1916 "Argument '" ~ this.name ~ "' cannot " ~ 1917 "be assigned alias '" ~ name ~ "' as it has " ~ 1918 "already been assigned to argument '" 1919 ~ arg.name ~ "'." 1920 ); 1921 } 1922 1923 this.outer.aliases[idup((&name)[0 .. 1])] = this; 1924 1925 this.aliases ~= name; 1926 1927 return this; 1928 } 1929 1930 1931 /*********************************************************************** 1932 1933 Makes this a mandatory argument. 1934 1935 Returns: 1936 this object for method chaining 1937 1938 ***********************************************************************/ 1939 1940 public Argument required ( ) 1941 { 1942 this.req = true; 1943 1944 return this; 1945 } 1946 1947 1948 /*********************************************************************** 1949 1950 Sets this argument to depend upon another argument. 1951 1952 Params: 1953 arg = argument instance which is to be set as a dependency 1954 1955 Returns: 1956 this object for method chaining 1957 1958 ***********************************************************************/ 1959 1960 public Argument requires ( Argument arg ) 1961 { 1962 dependees ~= arg; 1963 1964 return this; 1965 } 1966 1967 1968 /*********************************************************************** 1969 1970 Sets this argument to depend upon another argument. 1971 1972 Params: 1973 other = name of the argument which is to be set as a dependency 1974 1975 Returns: 1976 this object for method chaining 1977 1978 ***********************************************************************/ 1979 1980 public Argument requires ( istring other ) 1981 { 1982 return requires(this.outer.get(other)); 1983 } 1984 1985 1986 /*********************************************************************** 1987 1988 Sets this argument to depend upon another argument. 1989 1990 Params: 1991 other = alias of the argument which is to be set as a dependency 1992 1993 Returns: 1994 this object for method chaining 1995 1996 ***********************************************************************/ 1997 1998 public Argument requires ( char other ) 1999 { 2000 return requires(cast(istring)(&other)[0 .. 1]); 2001 } 2002 2003 2004 /*********************************************************************** 2005 2006 Sets this argument to conflict with another argument. 2007 2008 Params: 2009 arg = argument instance with which this argument should conflict 2010 2011 Returns: 2012 this object for method chaining 2013 2014 ***********************************************************************/ 2015 2016 public Argument conflicts ( Argument arg ) 2017 { 2018 conflictees ~= arg; 2019 2020 return this; 2021 } 2022 2023 2024 /*********************************************************************** 2025 2026 Sets this argument to conflict with another argument. 2027 2028 Params: 2029 other = name of the argument with which this argument should 2030 conflict 2031 2032 Returns: 2033 this object for method chaining 2034 2035 ***********************************************************************/ 2036 2037 public Argument conflicts ( istring other ) 2038 { 2039 return conflicts(this.outer.get(other)); 2040 } 2041 2042 2043 /*********************************************************************** 2044 2045 Sets this argument to conflict with another argument. 2046 2047 Params: 2048 other = alias of the argument with which this argument should 2049 conflict 2050 2051 Returns: 2052 this object for method chaining 2053 2054 ***********************************************************************/ 2055 2056 public Argument conflicts ( char other ) 2057 { 2058 return conflicts(cast(istring)(&other)[0 .. 1]); 2059 } 2060 2061 2062 /*********************************************************************** 2063 2064 Enables parameter assignment for this argument. The minimum and 2065 maximum number of parameters are set to 0 and 42 respectively. 2066 2067 Returns: 2068 this object for method chaining 2069 2070 ***********************************************************************/ 2071 2072 public Argument params ( ) 2073 { 2074 return params(0, 42); 2075 } 2076 2077 2078 /*********************************************************************** 2079 2080 Enables parameter assignment for this argument and sets an exact 2081 count for the number of parameters required. 2082 2083 Params: 2084 count = the number of parameters to be set 2085 2086 Returns: 2087 this object for method chaining 2088 2089 ***********************************************************************/ 2090 2091 public Argument params ( int count ) 2092 { 2093 return params(count, count); 2094 } 2095 2096 2097 /*********************************************************************** 2098 2099 Enables parameter assignment for this argument and sets the counts 2100 of both the minimum and maximum parameters required. 2101 2102 Params: 2103 min = minimum number of parameters required 2104 max = maximum number of parameters required 2105 2106 Returns: 2107 this object for method chaining 2108 2109 ***********************************************************************/ 2110 2111 public Argument params ( int min, int max ) 2112 { 2113 this.min = min; 2114 2115 this.max = max; 2116 2117 return this; 2118 } 2119 2120 2121 /*********************************************************************** 2122 2123 Adds a default parameter for this argument. 2124 2125 Params: 2126 values = default parameter to be added 2127 2128 Returns: 2129 this object for method chaining 2130 2131 ***********************************************************************/ 2132 2133 public Argument defaults ( istring values ) 2134 { 2135 this.deefalts ~= values; 2136 2137 return this; 2138 } 2139 2140 2141 /*********************************************************************** 2142 2143 Sets an inspector for this argument. The inspector delegate gets 2144 fired when a parameter is appended to this argument. 2145 The appended parameter gets sent to the delegate as the input 2146 parameter. If the appended parameter is ok, the delegate should 2147 return null. Otherwise, it should return a text string describing 2148 the issue. A non-null return value from the delegate will trigger an 2149 error. 2150 2151 Params: 2152 inspector = delegate to be called when a parameter is appended 2153 to this argument 2154 2155 Returns: 2156 this object for method chaining 2157 2158 ***********************************************************************/ 2159 2160 public Argument bind ( scope Inspector inspector ) 2161 { 2162 this.inspector = inspector; 2163 2164 return this; 2165 } 2166 2167 2168 /*********************************************************************** 2169 2170 Sets an invoker for this argument. The invoker delegate gets 2171 fired when this argument is found during parsing. 2172 2173 Params: 2174 invoker = delegate to be called when this argument's declaration 2175 is seen 2176 2177 Returns: 2178 this object for method chaining 2179 2180 ***********************************************************************/ 2181 2182 public Argument bind ( scope Invoker invoker ) 2183 { 2184 this.invoker = invoker; 2185 2186 return this; 2187 } 2188 2189 2190 /*********************************************************************** 2191 2192 Enables/disables smushing for this argument. 2193 2194 Smushing refers to omitting the explicit assignment symbol ('=' by 2195 default) or whitespace (when relying on implicit assignment) that 2196 separates an argument from its parameter. Note that smushing is 2197 possible only when assigning parameters to an argument using the 2198 argument's short prefix version. 2199 2200 Params: 2201 yes = true to enable smushing, false to disable 2202 2203 Returns: 2204 this object for method chaining 2205 2206 ***********************************************************************/ 2207 2208 public Argument smush ( bool yes = true ) 2209 { 2210 cat = yes; 2211 2212 return this; 2213 } 2214 2215 2216 /*********************************************************************** 2217 2218 Disables implicit parameter assignment to this argument. 2219 2220 Returns: 2221 this object for method chaining 2222 2223 ***********************************************************************/ 2224 2225 public Argument explicit ( ) 2226 { 2227 exp = true; 2228 2229 return this; 2230 } 2231 2232 2233 /*********************************************************************** 2234 2235 Changes the name of this argument (can be useful for naming the 2236 default argument). 2237 2238 Params: 2239 name = new name of this argument 2240 2241 Returns: 2242 this object for method chaining 2243 2244 ***********************************************************************/ 2245 2246 public Argument title ( istring name ) 2247 { 2248 this.name = name; 2249 2250 return this; 2251 } 2252 2253 2254 /*********************************************************************** 2255 2256 Sets the help text for this argument. 2257 2258 Params: 2259 text = the help text to set 2260 2261 Returns: 2262 this object for method chaining 2263 2264 ***********************************************************************/ 2265 2266 public Argument help ( istring text ) 2267 { 2268 this.text = text; 2269 2270 return this; 2271 } 2272 2273 2274 /*********************************************************************** 2275 2276 Fails the parsing immediately upon encountering this argument. This 2277 can be used for managing help text. 2278 2279 Returns: 2280 this object for method chaining 2281 2282 ***********************************************************************/ 2283 2284 public Argument halt ( ) 2285 { 2286 this.fail = true; 2287 2288 return this; 2289 } 2290 2291 2292 /*********************************************************************** 2293 2294 Restricts parameters of this argument to be in the given set. 2295 2296 Allocates copy of `options` 2297 2298 Params: 2299 options = array containing the set of acceptable parameters 2300 2301 Returns: 2302 this object for method chaining 2303 2304 ***********************************************************************/ 2305 2306 public Argument restrict ( const(istring)[] options... ) 2307 { 2308 this.options = options.dup; 2309 2310 return this; 2311 } 2312 2313 2314 /*********************************************************************** 2315 2316 Sets the flag that indicates that this argument was found during 2317 parsing. Also calls the invoker delegate, if configured. If the 2318 argument is unexpected (i.e. was not pre-configured), then an 2319 appropriate error condition gets set. 2320 2321 Params: 2322 unexpected = true if this is an unexpected argument, false 2323 otherwise 2324 2325 Returns: 2326 this object for method chaining 2327 2328 ***********************************************************************/ 2329 2330 private Argument enable ( bool unexpected = false ) 2331 { 2332 this.set = true; 2333 2334 if ( max > 0 ) 2335 { 2336 this.outer.stack.push(this); 2337 } 2338 2339 if ( invoker ) 2340 { 2341 invoker(); 2342 } 2343 2344 if ( unexpected ) 2345 { 2346 error = Extra; 2347 } 2348 2349 return this; 2350 } 2351 2352 2353 /*********************************************************************** 2354 2355 Appends the given parameter to this argument. Also calls the 2356 inspector delegate, if configured. 2357 2358 Params: 2359 value = parameter to be appended 2360 explicit = true if the parameter was explicitly assigned to this 2361 argument, false otherwise (defaults to false) 2362 2363 ***********************************************************************/ 2364 2365 private void append ( istring value, bool explicit = false ) 2366 { 2367 // pop to an argument that can accept implicit parameters? 2368 if ( explicit is false ) 2369 { 2370 auto s = &(this.outer.stack); 2371 2372 while ( s.top.exp && s.size > 1 ) 2373 { 2374 s.pop; 2375 } 2376 } 2377 2378 this.set = true; // needed for default assignments 2379 2380 values ~= value; // append new value 2381 2382 if ( error is None ) 2383 { 2384 if ( inspector ) 2385 { 2386 if ( (bogus = inspector(value)).length ) 2387 { 2388 error = Invalid; 2389 } 2390 } 2391 2392 if ( options.length ) 2393 { 2394 error = Option; 2395 2396 foreach ( option; options ) 2397 { 2398 if ( option == value ) 2399 { 2400 error = None; 2401 } 2402 } 2403 } 2404 } 2405 2406 // pop to an argument that can accept parameters 2407 auto s = &(this.outer.stack); 2408 2409 while ( s.top.values.length >= max && s.size > 1 ) 2410 { 2411 s.pop; 2412 } 2413 } 2414 2415 2416 /*********************************************************************** 2417 2418 Tests whether an error condition occurred for this argument during 2419 parsing, and if so the appropriate error code is set. 2420 2421 Returns: 2422 the error code for this argument (0 => no error) 2423 2424 ***********************************************************************/ 2425 2426 private int valid ( ) 2427 { 2428 if ( error is None ) 2429 { 2430 if ( req && !set ) 2431 { 2432 error = Required; 2433 } 2434 else 2435 { 2436 if ( set ) 2437 { 2438 // short circuit? 2439 if ( fail ) 2440 { 2441 return -1; 2442 } 2443 2444 if ( values.length < min ) 2445 { 2446 error = ParamLo; 2447 } 2448 else 2449 { 2450 if ( values.length > max ) 2451 { 2452 error = ParamHi; 2453 } 2454 else 2455 { 2456 foreach ( arg; dependees ) 2457 { 2458 if ( ! arg.set ) 2459 { 2460 error = Requires; 2461 bogus = arg.name; 2462 } 2463 } 2464 2465 foreach ( arg; conflictees ) 2466 { 2467 if ( arg.set ) 2468 { 2469 error = Conflict; 2470 bogus = arg.name; 2471 } 2472 } 2473 } 2474 } 2475 } 2476 } 2477 } 2478 2479 return error; 2480 } 2481 } 2482 } 2483 2484 2485 2486 /******************************************************************************* 2487 2488 Unit tests 2489 2490 *******************************************************************************/ 2491 2492 unittest 2493 { 2494 auto args = new Arguments; 2495 2496 // basic 2497 auto x = args['x']; 2498 test(args.parse("")); 2499 x.required; 2500 test(args.parse("") is false); 2501 test(args.clear.parse("-x")); 2502 test(x.set); 2503 2504 // alias 2505 x.aliased('X'); 2506 test(args.clear.parse("-X")); 2507 test(x.set); 2508 2509 // unexpected arg (with sloppy) 2510 test(args.clear.parse("-y") is false); 2511 test(args.clear.parse("-y") is false); 2512 test(args.clear.parse("-y", true) is false); 2513 test(args['y'].set); 2514 test(args.clear.parse("-x -y", true)); 2515 2516 // parameters 2517 x.params(0); 2518 test(args.clear.parse("-x param")); 2519 test(x.assigned.length is 0); 2520 test(args(null).assigned.length is 1); 2521 x.params(1); 2522 test(args.clear.parse("-x=param")); 2523 test(x.assigned.length is 1); 2524 test(x.assigned[0] == "param"); 2525 test(args.clear.parse("-x param")); 2526 test(x.assigned.length is 1); 2527 test(x.assigned[0] == "param"); 2528 2529 // too many args 2530 x.params(1); 2531 test(args.clear.parse("-x param1 param2")); 2532 test(x.assigned.length is 1); 2533 test(x.assigned[0] == "param1"); 2534 test(args(null).assigned.length is 1); 2535 test(args(null).assigned[0] == "param2"); 2536 2537 // now with default params 2538 test(args.clear.parse("param1 param2 -x=blah")); 2539 test(args[null].assigned.length is 2); 2540 test(args(null).assigned.length is 2); 2541 test(x.assigned.length is 1); 2542 x.params(0); 2543 test(!args.clear.parse("-x=blah")); 2544 2545 // args as parameter 2546 test(args.clear.parse("- -x")); 2547 test(args[null].assigned.length is 1); 2548 test(args[null].assigned[0] == "-"); 2549 2550 // multiple flags, with alias and sloppy 2551 test(args.clear.parse("-xy")); 2552 test(args.clear.parse("-xyX")); 2553 test(x.set); 2554 test(args['y'].set); 2555 test(args.clear.parse("-xyz") is false); 2556 test(args.clear.parse("-xyz", true)); 2557 auto z = args['z']; 2558 test(z.set); 2559 2560 // multiple flags with trailing arg 2561 test(args.clear.parse("-xyz=10")); 2562 test(z.assigned.length is 1); 2563 2564 // again, but without sloppy param declaration 2565 z.params(0); 2566 test(!args.clear.parse("-xyz=10")); 2567 test(args.clear.parse("-xzy=10")); 2568 test(args('y').assigned.length is 1); 2569 test(args('x').assigned.length is 0); 2570 test(args('z').assigned.length is 0); 2571 2572 // x requires y 2573 x.requires('y'); 2574 test(args.clear.parse("-xy")); 2575 test(args.clear.parse("-xz") is false); 2576 test!("==")(args.errors(), "argument 'x' requires 'y'\n"); 2577 2578 // defaults 2579 z.defaults("foo"); 2580 test(args.clear.parse("-xy")); 2581 test(z.assigned.length is 1); 2582 2583 // long names, with params 2584 test(args.clear.parse("-xy --foobar") is false); 2585 test(args.clear.parse("-xy --foobar", true)); 2586 test(args["y"].set && x.set); 2587 test(args["foobar"].set); 2588 test(args.clear.parse("-xy --foobar=10")); 2589 test(args["foobar"].assigned.length is 1); 2590 test(args["foobar"].assigned[0] == "10"); 2591 2592 // smush argument z, but not others 2593 z.params; 2594 test(args.clear.parse("-xy -zsmush") is false); 2595 test(x.set); 2596 z.smush; 2597 test(args.clear.parse("-xy -zsmush")); 2598 test(z.assigned.length is 1); 2599 test(z.assigned[0] == "smush"); 2600 test(x.assigned.length is 0); 2601 z.params(0); 2602 2603 // conflict x with z 2604 x.conflicts(z); 2605 test(args.clear.parse("-xyz") is false); 2606 2607 // word mode, with prefix elimination 2608 args = new Arguments(null, null, null, null, null, null); 2609 test(args.clear.parse("foo bar wumpus") is false); 2610 test(args.clear.parse("foo bar wumpus wombat", true)); 2611 test(args("foo").set); 2612 test(args("bar").set); 2613 test(args("wumpus").set); 2614 test(args("wombat").set); 2615 2616 // use '/' instead of '-' 2617 args = new Arguments(null, null, null, null, "/", "/"); 2618 test(args.clear.parse("/foo /bar /wumpus") is false); 2619 test(args.clear.parse("/foo /bar /wumpus /wombat", true)); 2620 test(args("foo").set); 2621 test(args("bar").set); 2622 test(args("wumpus").set); 2623 test(args("wombat").set); 2624 2625 // use '/' for short and '-' for long 2626 args = new Arguments(null, null, null, null, "/", "-"); 2627 test(args.clear.parse("-foo -bar -wumpus -wombat /abc", true)); 2628 test(args("foo").set); 2629 test(args("bar").set); 2630 test(args("wumpus").set); 2631 test(args("wombat").set); 2632 test(args("a").set); 2633 test(args("b").set); 2634 test(args("c").set); 2635 2636 // "--" makes all subsequent be implicit parameters 2637 args = new Arguments; 2638 args('f').params(0); 2639 test(args.parse("-f -- -bar -wumpus -wombat --abc")); 2640 test(args('f').assigned.length is 0); 2641 test(args(null).assigned.length is 4); 2642 2643 // Confirm arguments are stored in a sorted manner 2644 args = new Arguments; 2645 test(args.clear.parse("--beta --alpha --delta --echo --charlie", true)); 2646 size_t index; 2647 foreach (arg; args) 2648 { 2649 switch ( index++ ) 2650 { 2651 case 0: continue; 2652 case 1: test("alpha" == arg.name); continue; 2653 case 2: test("beta" == arg.name); continue; 2654 case 3: test("charlie" == arg.name); continue; 2655 case 4: test("delta" == arg.name); continue; 2656 case 5: test("echo" == arg.name); continue; 2657 default: test(0); 2658 } 2659 } 2660 2661 // Test that getInt() works as expected 2662 args = new Arguments; 2663 args("num").params(1); 2664 test(args.parse("--num 100")); 2665 test(args.getInt!(uint)("num") == 100); 2666 2667 args = new Arguments; 2668 args("num").params(1); 2669 test(args.parse("--num 18446744073709551615")); 2670 test(args.getInt!(ulong)("num") == ulong.max); 2671 2672 args = new Arguments; 2673 args("num").params(1); 2674 test(args.getInt!(ulong)("num") == 0); 2675 } 2676 2677 // Test for D2 'static immutable' 2678 unittest 2679 { 2680 static immutable istring name_ = "encode"; 2681 static immutable istring conflicts_ = "decode"; 2682 static immutable istring[] restrict_ = [ "json", "yaml" ]; 2683 static immutable istring requires_ = "input"; 2684 static immutable istring help_ = "Convert from native format to JSON/Yaml"; 2685 2686 auto args = new Arguments; 2687 args(name_) 2688 .params(1) 2689 .conflicts(conflicts_) 2690 .restrict(restrict_) 2691 .requires(requires_) 2692 .help(help_); 2693 2694 }