1 /******************************************************************************* 2 3 copyright: Copyright (c) 2009 Kris. All rights reserved. 4 5 license: BSD style: $(LICENSE) 6 7 version: Oct 2009: Initial release 8 9 author: Kris 10 11 *******************************************************************************/ 12 13 module tango.text.Arguments; 14 15 private import tango.text.Util; 16 private import tango.util.container.more.Stack; 17 18 version=dashdash; // -- everything assigned to the null argument 19 20 /******************************************************************************* 21 22 Command-line argument parser. Simple usage is: 23 --- 24 auto args = new Arguments; 25 args.parse ("-a -b", true); 26 auto a = args("a"); 27 auto b = args("b"); 28 if (a.set && b.set) 29 ... 30 --- 31 32 Argument parameters are assigned to the last known target, such 33 that multiple parameters accumulate: 34 --- 35 args.parse ("-a=1 -a=2 foo", true); 36 assert (args('a').assigned().length is 3); 37 --- 38 39 That example results in argument 'a' assigned three parameters. 40 Two parameters are explicitly assigned using '=', while a third 41 is implicitly assigned(). Implicit parameters are often useful for 42 collecting filenames or other parameters without specifying the 43 associated argument: 44 --- 45 args.parse ("thisfile.txt thatfile.doc -v", true); 46 assert (args(null).assigned().length is 2); 47 --- 48 The 'null' argument is always defined and acts as an accumulator 49 for parameters left uncaptured by other arguments. In the above 50 instance it was assigned both parameters. 51 52 Examples thus far have used 'sloppy' argument declaration, via 53 the second argument of parse() being set true. This allows the 54 parser to create argument declaration on-the-fly, which can be 55 handy for trivial usage. However, most features require the a- 56 priori declaration of arguments: 57 --- 58 args = new Arguments; 59 args('x').required; 60 if (! args.parse("-x")) 61 // x not supplied! 62 --- 63 64 Sloppy arguments are disabled in that example, and a required 65 argument 'x' is declared. The parse() method will fail if the 66 pre-conditions are not fully met. Additional qualifiers include 67 specifying how many parameters are allowed for each individual 68 argument, default parameters, whether an argument requires the 69 presence or exclusion of another, etc. Qualifiers are typically 70 chained together and the following example shows argument "foo" 71 being made required, with one parameter, aliased to 'f', and 72 dependent upon the presence of another argument "bar": 73 --- 74 args("foo").required.params(1).aliased('f').requires("bar"); 75 args("help").aliased('?').aliased('h'); 76 --- 77 78 Parameters can be constrained to a set of matching text values, 79 and the parser will fail on mismatched input: 80 --- 81 args("greeting").restrict("hello", "yo", "gday"); 82 args("enabled").restrict("true", "false", "t", "f", "y", "n"); 83 --- 84 85 A set of declared arguments may be configured in this manner 86 and the parser will return true only where all conditions are 87 met. Where a error condition occurs you may traverse the set 88 of arguments to find out which argument has what error. This 89 can be handled like so, where arg.error holds a defined code: 90 --- 91 if (! args.parse (...)) 92 foreach (arg; args) 93 if (arg.error) 94 ... 95 --- 96 97 Error codes are as follows: 98 --- 99 None: ok (zero) 100 ParamLo: too few params for an argument 101 ParamHi: too many params for an argument 102 Required: missing argument is required 103 Requires: depends on a missing argument 104 Conflict: conflicting argument is present 105 Extra: unexpected argument (see sloppy) 106 Option: parameter does not match options 107 --- 108 109 A simpler way to handle errors is to invoke an internal format 110 routine, which constructs error messages on your behalf: 111 --- 112 if (! args.parse (...)) 113 stderr (args.errors(&stderr.layout.sprint)); 114 --- 115 116 Note that messages are constructed via a layout handler and 117 the messages themselves may be customized (for i18n purposes). 118 See the two errors() methods for more information on this. 119 120 The parser make a distinction between a short and long prefix, 121 in that a long prefix argument is always distinct while short 122 prefix arguments may be combined as a shortcut: 123 --- 124 args.parse ("--foo --bar -abc", true); 125 assert (args("foo").set); 126 assert (args("bar").set); 127 assert (args("a").set); 128 assert (args("b").set); 129 assert (args("c").set); 130 --- 131 132 In addition, short-prefix arguments may be "smushed" with an 133 associated parameter when configured to do so: 134 --- 135 args('o').params(1).smush; 136 if (args.parse ("-ofile")) 137 assert (args('o').assigned()[0] == "file"); 138 --- 139 140 There are two callback varieties supports, where one is invoked 141 when an associated argument is parsed and the other is invoked 142 as parameters are assigned(). See the bind() methods for delegate 143 signature details. 144 145 You may change the argument prefix to be something other than 146 "-" and "--" via the constructor. You might, for example, need 147 to specify a "/" indicator instead, and use ':' for explicitly 148 assigning parameters: 149 --- 150 auto args = new Args ("/", "-", ':'); 151 args.parse ("-foo:param -bar /abc"); 152 assert (args("foo").set); 153 assert (args("bar").set); 154 assert (args("a").set); 155 assert (args("b").set); 156 assert (args("c").set); 157 assert (args("foo").assigned().length is 1); 158 --- 159 160 Returning to an earlier example we can declare some specifics: 161 --- 162 args('v').params(0); 163 assert (args.parse (`-v thisfile.txt thatfile.doc`)); 164 assert (args(null).assigned().length is 2); 165 --- 166 167 Note that the -v flag is now in front of the implicit parameters 168 but ignores them because it is declared to consume none. That is, 169 implicit parameters are assigned to arguments from right to left, 170 according to how many parameters said arguments may consume. Each 171 sloppy argument consumes parameters by default, so those implicit 172 parameters would have been assigned to -v without the declaration 173 shown. On the other hand, an explicit assignment (via '=') always 174 associates the parameter with that argument even when an overflow 175 would occur (though will cause an error to be raised). 176 177 Certain parameters are used for capturing comments or other plain 178 text from the user, including whitespace and other special chars. 179 Such parameter values should be quoted on the commandline, and be 180 assigned explicitly rather than implicitly: 181 --- 182 args.parse (`--comment="-- a comment --"`); 183 --- 184 185 Without the explicit assignment, the text content might otherwise 186 be considered the start of another argument (due to how argv/argc 187 values are stripped of original quotes). 188 189 Lastly, all subsequent text is treated as paramter-values after a 190 "--" token is encountered. This notion is applied by unix systems 191 to terminate argument processing in a similar manner. Such values 192 are considered to be implicit, and are assigned to preceding args 193 in the usual right to left fashion (or to the null argument): 194 --- 195 args.parse (`-- -thisfile --thatfile`); 196 assert (args(null).assigned().length is 2); 197 --- 198 199 *******************************************************************************/ 200 201 class Arguments 202 { 203 public alias get opCall; // args("name") 204 public alias get opIndex; // args["name"] 205 206 private Stack!(Argument) stack; // args with params 207 private Argument[const(char)[]] args; // the set of args 208 private Argument[const(char)[]] aliases; // set of aliases 209 private char eq; // '=' or ':' 210 private const(char)[] sp, // short prefix 211 lp; // long prefix 212 private const(char[])[] msgs = errmsg; // error messages 213 private __gshared const const(char[])[] errmsg = // default errors 214 [ 215 "argument '{0}' expects {2} parameter(s) but has {1}\n", 216 "argument '{0}' expects {3} parameter(s) but has {1}\n", 217 "argument '{0}' is missing\n", 218 "argument '{0}' requires '{4}'\n", 219 "argument '{0}' conflicts with '{4}'\n", 220 "unexpected argument '{0}'\n", 221 "argument '{0}' expects one of {5}\n", 222 "invalid parameter for argument '{0}': {4}\n", 223 ]; 224 225 /*********************************************************************** 226 227 Construct with the specific short & long prefixes, and the 228 given assignment character (typically ':' on Windows but we 229 set the defaults to look like unix instead) 230 231 ***********************************************************************/ 232 233 this (const(char)[] sp="-", const(char)[] lp="--", char eq='=') 234 { 235 this.sp = sp; 236 this.lp = lp; 237 this.eq = eq; 238 get(null).params(); // set null argument to consume params 239 } 240 241 /*********************************************************************** 242 243 Parse string[] into a set of Argument instances. The 'sloppy' 244 option allows for unexpected arguments without error. 245 246 Returns false where an error condition occurred, whereupon the 247 arguments should be traversed to discover said condition(s): 248 --- 249 auto args = new Arguments; 250 if (! args.parse (...)) 251 stderr (args.errors(&stderr.layout.sprint)); 252 --- 253 254 ***********************************************************************/ 255 256 final bool parse (const(char)[] input, bool sloppy=false) 257 { 258 const(char[])[] tmp; 259 foreach (s; quotes(input, " ")) 260 tmp ~= s; 261 return parse (tmp, sloppy); 262 } 263 264 /*********************************************************************** 265 266 Parse a string into a set of Argument instances. The 'sloppy' 267 option allows for unexpected arguments without error. 268 269 Returns false where an error condition occurred, whereupon the 270 arguments should be traversed to discover said condition(s): 271 --- 272 auto args = new Arguments; 273 if (! args.parse (...)) 274 Stderr (args.errors(&Stderr.layout.sprint)); 275 --- 276 277 ***********************************************************************/ 278 279 final bool parse (const(char[])[] input, bool sloppy=false) 280 { 281 bool done; 282 int error; 283 284 debug(Arguments) stdout.formatln ("\ncmdline: '{}'", input); 285 stack.push (get(null)); 286 foreach (s; input) 287 { 288 debug(Arguments) stdout.formatln ("'{}'", s); 289 if (done is false) 290 { 291 if (s == "--") 292 {done=true; version(dashdash){stack.clear().push(get(null));} continue;} 293 else 294 if (argument (s, lp, sloppy, false) || 295 argument (s, sp, sloppy, true)) 296 continue; 297 } 298 stack.top.append (s); 299 } 300 foreach (arg; args) 301 error |= arg.valid(); 302 return error is 0; 303 } 304 305 /*********************************************************************** 306 307 Clear parameter assignments, flags and errors. Note this 308 does not remove any Arguments 309 310 ***********************************************************************/ 311 312 final Arguments clear () 313 { 314 stack.clear(); 315 foreach (arg; args) 316 { 317 arg.set = false; 318 arg.values = null; 319 arg.error = arg.None; 320 } 321 return this; 322 } 323 324 /*********************************************************************** 325 326 Obtain an argument reference, creating an new instance where 327 necessary. Use array indexing or opCall syntax if you prefer 328 329 ***********************************************************************/ 330 331 final Argument get (char name) 332 { 333 return get ((&name)[0..1]); 334 } 335 336 /*********************************************************************** 337 338 Obtain an argument reference, creating an new instance where 339 necessary. Use array indexing or opCall syntax if you prefer. 340 341 Pass null to access the 'default' argument (where unassigned 342 implicit parameters are gathered) 343 344 ***********************************************************************/ 345 346 final Argument get (const(char)[] name) 347 { 348 auto a = name in args; 349 if (a is null) 350 {name=name.dup; return args[name] = new Argument(name);} 351 return *a; 352 } 353 354 /*********************************************************************** 355 356 Traverse the set of arguments 357 358 ***********************************************************************/ 359 360 final int opApply (scope int delegate(ref Argument) dg) 361 { 362 int result; 363 foreach (arg; args) 364 if ((result=dg(arg)) != 0) 365 break; 366 return result; 367 } 368 369 /*********************************************************************** 370 371 Construct a string of error messages, using the given 372 delegate to format the output. You would typically pass 373 the system formatter here, like so: 374 --- 375 auto msgs = args.errors (&stderr.layout.sprint); 376 --- 377 378 The messages are replacable with custom (i18n) versions 379 instead, using the errors(char[][]) method 380 381 ***********************************************************************/ 382 383 final char[] errors (char[] delegate(char[] buf, const(char)[] fmt, ...) dg) 384 { 385 char[256] tmp; 386 char[] result; 387 foreach (arg; args) 388 { 389 if (arg.error) 390 result ~= dg (tmp, msgs[arg.error-1], arg.name, 391 arg.values.length, arg.min, arg.max, 392 arg.bogus, arg.options); 393 } 394 return result; 395 } 396 397 /*********************************************************************** 398 399 Use this method to replace the default error messages. Note 400 that arguments are passed to the formatter in the following 401 order, and these should be indexed appropriately by each of 402 the error messages (see examples in errmsg above): 403 --- 404 index 0: the argument name 405 index 1: number of parameters 406 index 2: configured minimum parameters 407 index 3: configured maximum parameters 408 index 4: conflicting/dependent argument (or invalid param) 409 index 5: array of configured parameter options 410 --- 411 412 ***********************************************************************/ 413 414 final Arguments errors (const(char[])[] errors) 415 { 416 if (errors.length is errmsg.length) 417 msgs = errors; 418 else 419 assert (false); 420 return this; 421 } 422 423 /*********************************************************************** 424 425 Expose the configured set of help text, via the given 426 delegate 427 428 ***********************************************************************/ 429 430 final Arguments help (scope void delegate(const(char)[] arg, const(char)[] help) dg) 431 { 432 foreach (arg; args) 433 if (arg.text.ptr) 434 dg (arg.name, arg.text); 435 return this; 436 } 437 438 /*********************************************************************** 439 440 Test for the presence of a switch (long/short prefix) 441 and enable the associated arg where found. Also look 442 for and handle explicit parameter assignment 443 444 ***********************************************************************/ 445 446 private bool argument (const(char)[] s, const(char)[] p, bool sloppy, bool flag) 447 { 448 if (s.length >= p.length && s[0..p.length] == p) 449 { 450 s = s [p.length..$]; 451 auto i = locate (s, eq); 452 if (i < s.length) 453 enable (s[0..i], sloppy, flag).append (s[i+1..$], true); 454 else 455 // trap empty arguments; attach as param to null-arg 456 if (s.length) 457 enable (s, sloppy, flag); 458 else 459 get(null).append (p, true); 460 return true; 461 } 462 return false; 463 } 464 465 /*********************************************************************** 466 467 Indicate the existance of an argument, and handle sloppy 468 options along with multiple-flags and smushed parameters. 469 Note that sloppy arguments are configured with parameters 470 enabled. 471 472 ***********************************************************************/ 473 474 private Argument enable (const(char)[] elem, bool sloppy, bool flag=false) 475 { 476 if (flag && elem.length > 1) 477 { 478 // locate arg for first char 479 auto arg = enable (elem[0..1], sloppy); 480 elem = elem[1..$]; 481 482 // drop further processing of this flag where in error 483 if (arg.error is arg.None) 484 { 485 // smush remaining text or treat as additional args 486 if (arg.cat) 487 arg.append (elem, true); 488 else 489 arg = enable (elem, sloppy, true); 490 } 491 return arg; 492 } 493 494 // if not in args, or in aliases, then create new arg 495 auto a = elem in args; 496 if (a is null) 497 if ((a = elem in aliases) is null) 498 return get(elem).params().enable(!sloppy); 499 return a.enable(); 500 } 501 502 /*********************************************************************** 503 504 A specific argument instance. You get one of these from 505 Arguments.get() and visit them via Arguments.opApply() 506 507 ***********************************************************************/ 508 509 class Argument 510 { 511 /*************************************************************** 512 513 Error identifiers: 514 --- 515 None: ok 516 ParamLo: too few params for an argument 517 ParamHi: too many params for an argument 518 Required: missing argument is required 519 Requires: depends on a missing argument 520 Conflict: conflicting argument is present 521 Extra: unexpected argument (see sloppy) 522 Option: parameter does not match options 523 --- 524 525 ***************************************************************/ 526 527 enum {None, ParamLo, ParamHi, Required, Requires, Conflict, Extra, Option, Invalid}; 528 529 alias void delegate() Invoker; 530 alias const(char)[] delegate(const(char)[] value) Inspector; 531 532 public int min, /// minimum params 533 max, /// maximum params 534 error; /// error condition 535 public bool set; /// arg is present 536 public char[] aliases; /// Array of aliases 537 private bool req, // arg is required 538 cat, // arg is smushable 539 exp, // implicit params 540 fail; // fail the parse 541 public const(char)[] name, // arg name 542 text; // help text 543 private const(char)[] bogus; // name of conflict 544 private const(char)[][] values, // assigned values 545 options, // validation options 546 deefalts; // configured defaults 547 private Invoker invoker; // invocation callback 548 private Inspector inspector; // inspection callback 549 private Argument[] dependees, // who we require 550 conflictees; // who we conflict with 551 552 /*************************************************************** 553 554 Create with the given name 555 556 ***************************************************************/ 557 558 this (const(char)[] name) 559 { 560 this.name = name; 561 } 562 563 /*************************************************************** 564 565 Return the name of this argument 566 567 ***************************************************************/ 568 569 override immutable(char)[] toString() 570 { 571 return name.idup; 572 } 573 574 /*************************************************************** 575 576 return the assigned parameters, or the defaults if 577 no parameters were assigned 578 579 ***************************************************************/ 580 581 final const(char[])[] assigned () 582 { 583 return values.length ? values : deefalts; 584 } 585 586 /*************************************************************** 587 588 Alias this argument with the given name. If you need 589 long-names to be aliased, create the long-name first 590 and alias it to a short one 591 592 ***************************************************************/ 593 594 final Argument aliased (char name) 595 { 596 this.outer.aliases[(&name)[0..1].idup] = this; 597 this.aliases ~= name; 598 return this; 599 } 600 601 /*************************************************************** 602 603 Make this argument a requirement 604 605 ***************************************************************/ 606 607 @property final Argument required () 608 { 609 this.req = true; 610 return this; 611 } 612 613 /*************************************************************** 614 615 Set this argument to depend upon another 616 617 ***************************************************************/ 618 619 final Argument requires (Argument arg) 620 { 621 dependees ~= arg; 622 return this; 623 } 624 625 /*************************************************************** 626 627 Set this argument to depend upon another 628 629 ***************************************************************/ 630 631 final Argument requires (const(char)[] other) 632 { 633 return requires (this.outer.get(other)); 634 } 635 636 /*************************************************************** 637 638 Set this argument to depend upon another 639 640 ***************************************************************/ 641 642 final Argument requires (char other) 643 { 644 return requires ((&other)[0..1]); 645 } 646 647 /*************************************************************** 648 649 Set this argument to conflict with another 650 651 ***************************************************************/ 652 653 final Argument conflicts (Argument arg) 654 { 655 conflictees ~= arg; 656 return this; 657 } 658 659 /*************************************************************** 660 661 Set this argument to conflict with another 662 663 ***************************************************************/ 664 665 final Argument conflicts (const(char)[] other) 666 { 667 return conflicts (this.outer.get(other)); 668 } 669 670 /*************************************************************** 671 672 Set this argument to conflict with another 673 674 ***************************************************************/ 675 676 final Argument conflicts (char other) 677 { 678 return conflicts ((&other)[0..1]); 679 } 680 681 /*************************************************************** 682 683 Enable parameter assignment: 0 to 42 by default 684 685 ***************************************************************/ 686 687 final Argument params () 688 { 689 return params (0, 42); 690 } 691 692 /*************************************************************** 693 694 Set an exact number of parameters required 695 696 ***************************************************************/ 697 698 final Argument params (int count) 699 { 700 return params (count, count); 701 } 702 703 /*************************************************************** 704 705 Set both the minimum and maximum parameter counts 706 707 ***************************************************************/ 708 709 final Argument params (int min, int max) 710 { 711 this.min = min; 712 this.max = max; 713 return this; 714 } 715 716 /*************************************************************** 717 718 Add another default parameter for this argument 719 720 ***************************************************************/ 721 722 final Argument defaults (const(char)[] values) 723 { 724 this.deefalts ~= values; 725 return this; 726 } 727 728 /*************************************************************** 729 730 Set an inspector for this argument, fired when a 731 parameter is appended to an argument. Return null 732 from the delegate when the value is ok, or a text 733 string describing the issue to trigger an error 734 735 ***************************************************************/ 736 737 final Argument bind (Inspector inspector) 738 { 739 this.inspector = inspector; 740 return this; 741 } 742 743 /*************************************************************** 744 745 Set an invoker for this argument, fired when an 746 argument declaration is seen 747 748 ***************************************************************/ 749 750 final Argument bind (Invoker invoker) 751 { 752 this.invoker = invoker; 753 return this; 754 } 755 756 /*************************************************************** 757 758 Enable smushing for this argument, where "-ofile" 759 would result in "file" being assigned to argument 760 'o' 761 762 ***************************************************************/ 763 764 final Argument smush (bool yes=true) 765 { 766 cat = yes; 767 return this; 768 } 769 770 /*************************************************************** 771 772 Disable implicit arguments 773 774 ***************************************************************/ 775 776 @property final Argument explicit () 777 { 778 exp = true; 779 return this; 780 } 781 782 /*************************************************************** 783 784 Alter the title of this argument, which can be 785 useful for naming the default argument 786 787 ***************************************************************/ 788 789 final Argument title (const(char)[] name) 790 { 791 this.name = name; 792 return this; 793 } 794 795 /*************************************************************** 796 797 Set the help text for this argument 798 799 ***************************************************************/ 800 801 final Argument help (const(char)[] text) 802 { 803 this.text = text; 804 return this; 805 } 806 807 /*************************************************************** 808 809 Fail the parse when this arg is encountered. You 810 might use this for managing help text 811 812 ***************************************************************/ 813 814 final Argument halt () 815 { 816 this.fail = true; 817 return this; 818 } 819 820 /*************************************************************** 821 822 Restrict values to one of the given set 823 824 ***************************************************************/ 825 826 final Argument restrict (const(char[])[] options ...) 827 { 828 this.options = cast(const(char)[][])options; 829 return this; 830 } 831 832 /*************************************************************** 833 834 This arg is present, but set an error condition 835 (Extra) when unexpected and sloppy is not enabled. 836 Fires any configured invoker callback. 837 838 ***************************************************************/ 839 840 private Argument enable (bool unexpected=false) 841 { 842 this.set = true; 843 if (max > 0) 844 this.outer.stack.push(this); 845 846 if (invoker) 847 invoker(); 848 if (unexpected) 849 error = Extra; 850 return this; 851 } 852 853 /*************************************************************** 854 855 Append a parameter value, invoking an inspector as 856 necessary 857 858 ***************************************************************/ 859 860 private void append (const(char)[] value, bool explicit=false) 861 { 862 Argument arg = this; 863 // pop to an argument that can accept implicit parameters? 864 if (explicit is false) 865 for (auto s=&this.outer.stack; arg.exp && s.size>1; arg=s.top) 866 s.pop(); 867 868 arg.actually_append(value); 869 // pop to an argument that can accept parameters 870 for (auto s=&this.outer.stack; arg.values.length >= max && s.size>1; arg=s.top) 871 s.pop(); 872 } 873 874 private void actually_append (const(char)[] value) 875 { 876 set = true; // needed for default assignments 877 values ~= value; // append new value 878 879 if (error is None) 880 { 881 if (inspector) 882 if ((bogus = inspector(value)).length) 883 error = Invalid; 884 885 if (options.length) 886 { 887 error = Option; 888 foreach (option; options) 889 if (option == value) 890 error = None; 891 } 892 } 893 } 894 895 /*************************************************************** 896 897 Test and set the error flag appropriately 898 899 ***************************************************************/ 900 901 private int valid () 902 { 903 if (error is None) 904 { 905 if (req && !set) 906 error = Required; 907 else if (set) 908 { 909 // short circuit? 910 if (fail) 911 return -1; 912 913 if (values.length < min) 914 error = ParamLo; 915 else 916 if (values.length > max) 917 error = ParamHi; 918 else 919 { 920 foreach (arg; dependees) 921 if (! arg.set) 922 error = Requires, bogus=arg.name; 923 924 foreach (arg; conflictees) 925 if (arg.set) 926 error = Conflict, bogus=arg.name; 927 } 928 } 929 } 930 debug(Arguments) stdout.formatln ("{}: error={}, set={}, min={}, max={}, " 931 "req={}, values={}, defaults={}, requires={}", 932 name, error, set, min, max, req, values, 933 deefalts, dependees); 934 return error; 935 } 936 } 937 } 938 939 940 /******************************************************************************* 941 942 *******************************************************************************/ 943 944 debug(UnitTest) 945 { 946 unittest 947 { 948 auto args = new Arguments; 949 950 // basic 951 auto x = args['x']; 952 assert (args.parse ("")); 953 x.required; 954 assert (args.parse ("") is false); 955 assert (args.clear().parse ("-x")); 956 assert (x.set); 957 958 // alias 959 x.aliased('X'); 960 assert (args.clear().parse ("-X")); 961 assert (x.set); 962 963 // unexpected arg (with sloppy) 964 assert (args.clear().parse ("-y") is false); 965 assert (args.clear().parse ("-y") is false); 966 assert (args.clear().parse ("-y", true) is false); 967 assert (args['y'].set); 968 assert (args.clear().parse ("-x -y", true)); 969 970 // parameters 971 x.params(0); 972 assert (args.clear().parse ("-x param")); 973 assert (x.assigned().length is 0); 974 assert (args(null).assigned().length is 1); 975 x.params(1); 976 assert (args.clear().parse ("-x=param")); 977 assert (x.assigned().length is 1); 978 assert (x.assigned()[0] == "param"); 979 assert (args.clear().parse ("-x param")); 980 assert (x.assigned().length is 1); 981 assert (x.assigned()[0] == "param"); 982 983 // too many args 984 x.params(1); 985 assert (args.clear().parse ("-x param1 param2")); 986 assert (x.assigned().length is 1); 987 assert (x.assigned()[0] == "param1"); 988 assert (args(null).assigned().length is 1); 989 assert (args(null).assigned()[0] == "param2"); 990 991 // now with default params 992 assert (args.clear().parse ("param1 param2 -x=blah")); 993 assert (args[null].assigned().length is 2); 994 assert (args(null).assigned().length is 2); 995 assert (x.assigned().length is 1); 996 x.params(0); 997 assert (!args.clear().parse ("-x=blah")); 998 999 // args as parameter 1000 assert (args.clear().parse ("- -x")); 1001 assert (args[null].assigned().length is 1); 1002 assert (args[null].assigned()[0] == "-"); 1003 1004 // multiple flags, with alias and sloppy 1005 assert (args.clear().parse ("-xy")); 1006 assert (args.clear().parse ("-xyX")); 1007 assert (x.set); 1008 assert (args['y'].set); 1009 assert (args.clear().parse ("-xyz") is false); 1010 assert (args.clear().parse ("-xyz", true)); 1011 auto z = args['z']; 1012 assert (z.set); 1013 1014 // multiple flags with trailing arg 1015 assert (args.clear().parse ("-xyz=10")); 1016 assert (z.assigned().length is 1); 1017 1018 // again, but without sloppy param declaration 1019 z.params(0); 1020 assert (!args.clear().parse ("-xyz=10")); 1021 assert (args.clear().parse ("-xzy=10")); 1022 assert (args('y').assigned().length is 1); 1023 assert (args('x').assigned().length is 0); 1024 assert (args('z').assigned().length is 0); 1025 1026 // x requires y 1027 x.requires('y'); 1028 assert (args.clear().parse ("-xy")); 1029 assert (args.clear().parse ("-xz") is false); 1030 1031 // defaults 1032 z.defaults("foo"); 1033 assert (args.clear().parse ("-xy")); 1034 assert (z.assigned().length is 1); 1035 1036 // long names, with params 1037 assert (args.clear().parse ("-xy --foobar") is false); 1038 assert (args.clear().parse ("-xy --foobar", true)); 1039 assert (args["y"].set && x.set); 1040 assert (args["foobar"].set); 1041 assert (args.clear().parse ("-xy --foobar=10")); 1042 assert (args["foobar"].assigned().length is 1); 1043 assert (args["foobar"].assigned()[0] == "10"); 1044 1045 // smush argument z, but not others 1046 z.params(); 1047 assert (args.clear().parse ("-xy -zsmush") is false); 1048 assert (x.set); 1049 z.smush(); 1050 assert (args.clear().parse ("-xy -zsmush")); 1051 assert (z.assigned().length is 1); 1052 assert (z.assigned()[0] == "smush"); 1053 assert (x.assigned().length is 0); 1054 z.params(0); 1055 1056 // conflict x with z 1057 x.conflicts(z); 1058 assert (args.clear().parse ("-xyz") is false); 1059 1060 // word mode, with prefix elimination 1061 args = new Arguments (null, null); 1062 assert (args.clear().parse ("foo bar wumpus") is false); 1063 assert (args.clear().parse ("foo bar wumpus wombat", true)); 1064 assert (args("foo").set); 1065 assert (args("bar").set); 1066 assert (args("wumpus").set); 1067 assert (args("wombat").set); 1068 1069 // use '/' instead of '-' 1070 args = new Arguments ("/", "/"); 1071 assert (args.clear().parse ("/foo /bar /wumpus") is false); 1072 assert (args.clear().parse ("/foo /bar /wumpus /wombat", true)); 1073 assert (args("foo").set); 1074 assert (args("bar").set); 1075 assert (args("wumpus").set); 1076 assert (args("wombat").set); 1077 1078 // use '/' for short and '-' for long 1079 args = new Arguments ("/", "-"); 1080 assert (args.clear().parse ("-foo -bar -wumpus -wombat /abc", true)); 1081 assert (args("foo").set); 1082 assert (args("bar").set); 1083 assert (args("wumpus").set); 1084 assert (args("wombat").set); 1085 assert (args("a").set); 1086 assert (args("b").set); 1087 assert (args("c").set); 1088 1089 // "--" makes all subsequent be implicit parameters 1090 args = new Arguments; 1091 version (dashdash) 1092 { 1093 args('f').params(0); 1094 assert (args.parse ("-f -- -bar -wumpus -wombat --abc")); 1095 assert (args('f').assigned().length is 0); 1096 assert (args(null).assigned().length is 4); 1097 } 1098 else 1099 { 1100 args('f').params(2); 1101 assert (args.parse ("-f -- -bar -wumpus -wombat --abc")); 1102 assert (args('f').assigned().length is 2); 1103 assert (args(null).assigned().length is 2); 1104 } 1105 } 1106 } 1107 1108 /******************************************************************************* 1109 1110 *******************************************************************************/ 1111 1112 debug (Arguments) 1113 { 1114 import tango.io.Stdout; 1115 1116 void main() 1117 { 1118 char[] crap = "crap"; 1119 auto args = new Arguments; 1120 1121 args(null).title("root").params.help("root help"); 1122 args('x').aliased('X').params(0).required.help("x help"); 1123 args('y').defaults("hi").params(2).smush.explicit.help("y help"); 1124 args('a').required.defaults("hi").requires('y').params(1).help("a help"); 1125 args("foobar").params(2).help("foobar help"); 1126 if (! args.parse ("'one =two' -xa=bar -y=ff -yss --foobar=blah1 --foobar barf blah2 -- a b c d e")) 1127 stdout (args.errors(&stdout.layout.sprint)); 1128 else 1129 if (args.get('x')) 1130 args.help ((char[] a, char[] b){Stdout.formatln ("{}{}\n\t{}", args.lp, a, b);}); 1131 } 1132 }