1 /** 2 * Copyright: Copyright Jason White, 2015-2016 3 * License: MIT 4 * Authors: Jason White 5 * 6 * Description: 7 * Parses arguments. 8 * 9 * TODO: 10 * - Handle bundled options 11 */ 12 module darg; 13 14 /** 15 * Generic argument parsing exception. 16 */ 17 class ArgParseError : Exception 18 { 19 /** 20 */ 21 this(string msg) pure nothrow 22 { 23 super(msg); 24 } 25 } 26 27 /** 28 * Thrown when help is requested. 29 */ 30 class ArgParseHelp : Exception 31 { 32 this(string msg) pure nothrow 33 { 34 super(msg); 35 } 36 } 37 38 /** 39 * User defined attribute for an option. 40 */ 41 struct Option 42 { 43 /// List of names the option can have. 44 string[] names; 45 46 /** 47 * Constructs the option with a list of names. Note that any leading "-" or 48 * "--" should be omitted. This is added automatically depending on the 49 * length of the name. 50 */ 51 this(string[] names...) pure nothrow 52 { 53 this.names = names; 54 } 55 56 /** 57 * Returns true if the given option name is equivalent to this option. 58 */ 59 bool opEquals(string opt) const pure nothrow 60 { 61 foreach (name; names) 62 { 63 if (name == opt) 64 return true; 65 } 66 67 return false; 68 } 69 70 unittest 71 { 72 static assert(Option("foo") == "foo"); 73 static assert(Option("foo", "f") == "foo"); 74 static assert(Option("foo", "f") == "f"); 75 static assert(Option("foo", "bar", "baz") == "foo"); 76 static assert(Option("foo", "bar", "baz") == "bar"); 77 static assert(Option("foo", "bar", "baz") == "baz"); 78 79 static assert(Option("foo", "bar") != "baz"); 80 } 81 82 /** 83 * Returns the canonical name of this option. That is, its first name. 84 */ 85 string toString() const pure nothrow 86 { 87 return names.length > 0 ? (nameToOption(names[0])) : null; 88 } 89 90 unittest 91 { 92 static assert(Option().toString is null); 93 static assert(Option("foo", "bar", "baz").toString == "--foo"); 94 static assert(Option("f", "bar", "baz").toString == "-f"); 95 } 96 } 97 98 /** 99 * An option flag. These types of options are handled specially and never have 100 * an argument. 101 */ 102 enum OptionFlag : bool 103 { 104 /// The option flag was not specified. 105 no = false, 106 107 /// The option flag was specified. 108 yes = true, 109 } 110 111 /** 112 * Multiplicity of an argument. 113 */ 114 enum Multiplicity 115 { 116 optional, 117 zeroOrMore, 118 oneOrMore, 119 } 120 121 /** 122 * User defined attribute for a positional argument. 123 */ 124 struct Argument 125 { 126 /** 127 * Name of the argument. Since this is a positional argument, this value is 128 * only used in the help string. 129 */ 130 string name; 131 132 /** 133 * Lower and upper bounds for the number of values this argument can have. 134 */ 135 size_t lowerBound = 1; 136 137 /// Ditto 138 size_t upperBound = 1; 139 140 /** 141 * Constructs an argument with the given name and count. The count specifies 142 * how many argument elements should be consumed from the command line. By 143 * default, this is 1. 144 */ 145 this(string name, size_t count = 1) pure nothrow 146 body 147 { 148 this.name = name; 149 this.lowerBound = count; 150 this.upperBound = count; 151 } 152 153 /** 154 * Constructs an argument with the given name and an upper and lower bound 155 * for how many argument elements should be consumed from the command line. 156 */ 157 this(string name, size_t lowerBound, size_t upperBound) pure nothrow 158 in { assert(lowerBound < upperBound); } 159 body 160 { 161 this.name = name; 162 this.lowerBound = lowerBound; 163 this.upperBound = upperBound; 164 } 165 166 /** 167 * An argument with a multiplicity specifier. 168 */ 169 this(string name, Multiplicity multiplicity) pure nothrow 170 { 171 this.name = name; 172 173 final switch (multiplicity) 174 { 175 case Multiplicity.optional: 176 this.lowerBound = 0; 177 this.upperBound = 1; 178 break; 179 case Multiplicity.zeroOrMore: 180 this.lowerBound = 0; 181 this.upperBound = size_t.max; 182 break; 183 case Multiplicity.oneOrMore: 184 this.lowerBound = 1; 185 this.upperBound = size_t.max; 186 break; 187 } 188 } 189 190 /** 191 * Convert to a usage string. 192 */ 193 @property string usage() const pure 194 { 195 import std.format : format; 196 197 if (lowerBound == 0) 198 { 199 if (upperBound == 1) 200 return "["~ name ~"]"; 201 else if (upperBound == upperBound.max) 202 return "["~ name ~"...]"; 203 204 return "["~ name ~"... (up to %d times)]".format(upperBound); 205 } 206 else if (lowerBound == 1) 207 { 208 if (upperBound == 1) 209 return name; 210 else if (upperBound == upperBound.max) 211 return name ~ " ["~ name ~"...]"; 212 213 return name ~ " ["~ name ~"... (up to %d times)]" 214 .format(upperBound-1); 215 } 216 217 if (lowerBound == upperBound) 218 return name ~" (multiplicity of %d)" 219 .format(upperBound); 220 221 return name ~" ["~ name ~"... (between %d and %d times)]" 222 .format(lowerBound-1, upperBound-1); 223 } 224 225 /** 226 * Get a multiplicity error string. 227 * 228 * Params: 229 * specified = The number of parameters that were specified. 230 * 231 * Returns: 232 * If there is an error, returns a string explaining the error. Otherwise, 233 * returns $(D null). 234 */ 235 @property string multiplicityError(size_t specified) const pure 236 { 237 import std.format : format; 238 239 if (specified >= lowerBound && specified <= upperBound) 240 return null; 241 242 if (specified < lowerBound) 243 { 244 if (lowerBound == 1 && upperBound == 1) 245 return "Expected a value for positional argument '%s'" 246 .format(name); 247 else 248 return ("Expected at least %d values for positional argument" ~ 249 " '%s'. Only %d values were specified.") 250 .format(lowerBound, name, specified); 251 } 252 253 // This should never happen. Argument parsing is not greedy. 254 return "Too many values specified for positional argument '%s'."; 255 } 256 } 257 258 unittest 259 { 260 with (Argument("lion")) 261 { 262 assert(name == "lion"); 263 assert(lowerBound == 1); 264 assert(upperBound == 1); 265 } 266 267 with (Argument("tiger", Multiplicity.optional)) 268 { 269 assert(lowerBound == 0); 270 assert(upperBound == 1); 271 } 272 273 with (Argument("bear", Multiplicity.zeroOrMore)) 274 { 275 assert(lowerBound == 0); 276 assert(upperBound == size_t.max); 277 } 278 279 with (Argument("dinosaur", Multiplicity.oneOrMore)) 280 { 281 assert(lowerBound == 1); 282 assert(upperBound == size_t.max); 283 } 284 } 285 286 /** 287 * Help string for an option or positional argument. 288 */ 289 struct Help 290 { 291 /// Help string. 292 string help; 293 } 294 295 /** 296 * Meta variable name. 297 */ 298 struct MetaVar 299 { 300 /// Name of the meta variable. 301 string name; 302 } 303 304 /** 305 * Function signatures that can handle arguments or options. 306 */ 307 private alias void OptionHandler() pure; 308 private alias void ArgumentHandler(string) pure; /// Ditto 309 310 /** 311 * Returns true if the given argument is a short option. That is, if it starts 312 * with a '-'. 313 */ 314 bool isShortOption(string arg) pure nothrow 315 { 316 return arg.length > 1 && arg[0] == '-' && arg[1] != '-'; 317 } 318 319 unittest 320 { 321 static assert(!isShortOption("")); 322 static assert(!isShortOption("-")); 323 static assert(!isShortOption("a")); 324 static assert(!isShortOption("ab")); 325 static assert( isShortOption("-a")); 326 static assert( isShortOption("-ab")); 327 static assert(!isShortOption("--a")); 328 static assert(!isShortOption("--abc")); 329 } 330 331 /** 332 * Returns true if the given argument is a long option. That is, if it starts 333 * with "--". 334 */ 335 bool isLongOption(string arg) pure nothrow 336 { 337 return arg.length > 2 && arg[0 .. 2] == "--" && arg[2] != '-'; 338 } 339 340 unittest 341 { 342 static assert(!isLongOption("")); 343 static assert(!isLongOption("a")); 344 static assert(!isLongOption("ab")); 345 static assert(!isLongOption("abc")); 346 static assert(!isLongOption("-")); 347 static assert(!isLongOption("-a")); 348 static assert(!isLongOption("--")); 349 static assert( isLongOption("--a")); 350 static assert( isLongOption("--arg")); 351 static assert( isLongOption("--arg=asdf")); 352 } 353 354 /** 355 * Returns true if the given argument is an option. That is, it is either a 356 * short option or a long option. 357 */ 358 bool isOption(string arg) pure nothrow 359 { 360 return isShortOption(arg) || isLongOption(arg); 361 } 362 363 private static struct OptionSplit 364 { 365 string head; 366 string tail; 367 } 368 369 /** 370 * Splits an option on "=". 371 */ 372 private auto splitOption(string option) pure 373 { 374 size_t i = 0; 375 while (i < option.length && option[i] != '=') 376 ++i; 377 378 return OptionSplit( 379 option[0 .. i], 380 (i < option.length) ? option[i+1 .. $] : null 381 ); 382 } 383 384 unittest 385 { 386 static assert(splitOption("") == OptionSplit("", null)); 387 static assert(splitOption("--foo") == OptionSplit("--foo", null)); 388 static assert(splitOption("--foo=") == OptionSplit("--foo", "")); 389 static assert(splitOption("--foo=bar") == OptionSplit("--foo", "bar")); 390 } 391 392 private static struct ArgSplit 393 { 394 const(string)[] head; 395 const(string)[] tail; 396 } 397 398 /** 399 * Splits arguments on "--". 400 */ 401 private auto splitArgs(const(string)[] args) pure 402 { 403 size_t i = 0; 404 while (i < args.length && args[i] != "--") 405 ++i; 406 407 return ArgSplit( 408 args[0 .. i], 409 (i < args.length) ? args[i+1 .. $] : [] 410 ); 411 } 412 413 unittest 414 { 415 static assert(splitArgs([]) == ArgSplit([], [])); 416 static assert(splitArgs(["a", "b"]) == ArgSplit(["a", "b"], [])); 417 static assert(splitArgs(["a", "--"]) == ArgSplit(["a"], [])); 418 static assert(splitArgs(["a", "--", "b"]) == ArgSplit(["a"], ["b"])); 419 static assert(splitArgs(["a", "--", "b", "c"]) == ArgSplit(["a"], ["b", "c"])); 420 } 421 422 /** 423 * Returns an option name without the leading ("--" or "-"). If it is not an 424 * option, returns null. 425 */ 426 private string optionToName(string option) pure nothrow 427 { 428 if (isLongOption(option)) 429 return option[2 .. $]; 430 431 if (isShortOption(option)) 432 return option[1 .. $]; 433 434 return null; 435 } 436 437 unittest 438 { 439 static assert(optionToName("--opt") == "opt"); 440 static assert(optionToName("-opt") == "opt"); 441 static assert(optionToName("-o") == "o"); 442 static assert(optionToName("opt") is null); 443 static assert(optionToName("o") is null); 444 static assert(optionToName("") is null); 445 } 446 447 /** 448 * Returns the appropriate long or short option corresponding to the given name. 449 */ 450 private string nameToOption(string name) pure nothrow 451 { 452 switch (name.length) 453 { 454 case 0: 455 return null; 456 case 1: 457 return "-" ~ name; 458 default: 459 return "--" ~ name; 460 } 461 } 462 463 unittest 464 { 465 static assert(nameToOption("opt") == "--opt"); 466 static assert(nameToOption("o") == "-o"); 467 static assert(nameToOption("") is null); 468 } 469 470 unittest 471 { 472 immutable identities = ["opt", "o", ""]; 473 474 foreach (identity; identities) 475 assert(identity.nameToOption.optionToName == identity); 476 } 477 478 /** 479 * Check if the given type is valid for an option. 480 */ 481 private template isValidOptionType(T) 482 { 483 import std.traits : isBasicType, isSomeString; 484 485 static if (isBasicType!T || 486 isSomeString!T || 487 is(T : OptionHandler) || 488 is(T : ArgumentHandler) 489 ) 490 { 491 enum isValidOptionType = true; 492 } 493 else static if (is(T A : A[])) 494 { 495 enum isValidOptionType = isValidOptionType!A; 496 } 497 else 498 { 499 enum isValidOptionType = false; 500 } 501 } 502 503 unittest 504 { 505 static assert(isValidOptionType!bool); 506 static assert(isValidOptionType!int); 507 static assert(isValidOptionType!float); 508 static assert(isValidOptionType!char); 509 static assert(isValidOptionType!string); 510 static assert(isValidOptionType!(int[])); 511 512 alias void Func1() pure; 513 alias void Func2(string) pure; 514 alias int Func3(); 515 alias int Func4(string); 516 alias void Func5(); 517 alias void Func6(string); 518 519 static assert(isValidOptionType!Func1); 520 static assert(isValidOptionType!Func2); 521 static assert(!isValidOptionType!Func3); 522 static assert(!isValidOptionType!Func4); 523 static assert(!isValidOptionType!Func5); 524 static assert(!isValidOptionType!Func6); 525 } 526 527 /** 528 * Count the number of positional arguments. 529 */ 530 size_t countArgs(Options)() pure nothrow 531 { 532 import std.traits : Identity, getUDAs; 533 import std.algorithm.comparison : min; 534 535 size_t count = 0; 536 537 foreach (member; __traits(allMembers, Options)) 538 { 539 alias symbol = Identity!(__traits(getMember, Options, member)); 540 alias argUDAs = getUDAs!(symbol, Argument); 541 count += min(argUDAs.length, 1); 542 } 543 544 return count; 545 } 546 547 /** 548 * Count the number of options. 549 */ 550 size_t countOpts(Options)() pure nothrow 551 { 552 import std.traits : Identity, getUDAs; 553 import std.algorithm.comparison : min; 554 555 size_t count = 0; 556 557 foreach (member; __traits(allMembers, Options)) 558 { 559 alias symbol = Identity!(__traits(getMember, Options, member)); 560 alias optUDAs = getUDAs!(symbol, Option); 561 count += min(optUDAs.length, 1); 562 } 563 564 return count; 565 } 566 567 unittest 568 { 569 static struct A {} 570 571 static assert(countArgs!A == 0); 572 static assert(countOpts!A == 0); 573 574 static struct B 575 { 576 @Argument("test1") 577 string test1; 578 @Argument("test2") 579 string test2; 580 581 @Option("test3", "test3a") 582 @Option("test3b", "test3c") 583 string test3; 584 } 585 586 static assert(countArgs!B == 2); 587 static assert(countOpts!B == 1); 588 589 static struct C 590 { 591 string test; 592 593 @Argument("test1") 594 string test1; 595 596 @Argument("test2") 597 @Argument("test2a") 598 string test2; 599 600 @Option("test3") 601 string test3; 602 603 @Option("test4") 604 string test4; 605 } 606 607 static assert(countArgs!C == 2); 608 static assert(countOpts!C == 2); 609 } 610 611 /** 612 * Checks if the given options are valid. 613 */ 614 private void validateOptions(Options)() pure nothrow 615 { 616 import std.traits : Identity, getUDAs, fullyQualifiedName; 617 618 foreach (member; __traits(allMembers, Options)) 619 { 620 alias symbol = Identity!(__traits(getMember, Options, member)); 621 alias optUDAs = getUDAs!(symbol, Option); 622 alias argUDAs = getUDAs!(symbol, Argument); 623 624 // Basic error checking 625 static assert(!(optUDAs.length > 0 && argUDAs.length > 0), 626 fullyQualifiedName!symbol ~" cannot be both an Option and an Argument" 627 ); 628 static assert(optUDAs.length <= 1, 629 fullyQualifiedName!symbol ~" cannot have multiple Option attributes" 630 ); 631 static assert(argUDAs.length <= 1, 632 fullyQualifiedName!symbol ~" cannot have multiple Argument attributes" 633 ); 634 635 static if (argUDAs.length > 0) 636 static assert(isValidOptionType!(typeof(symbol)), 637 fullyQualifiedName!symbol ~" is not a valid Argument type" 638 ); 639 640 static if (optUDAs.length > 0) 641 static assert(isValidOptionType!(typeof(symbol)), 642 fullyQualifiedName!symbol ~" is not a valid Option type" 643 ); 644 } 645 } 646 647 /** 648 * Checks if the given option type has an associated argument. Currently, only 649 * an OptionFlag does not have an argument. 650 */ 651 private template hasArgument(T) 652 { 653 static if (is(T : OptionFlag) || is(T : OptionHandler)) 654 enum hasArgument = false; 655 else 656 enum hasArgument = true; 657 } 658 659 unittest 660 { 661 static assert(hasArgument!string); 662 static assert(hasArgument!ArgumentHandler); 663 static assert(hasArgument!int); 664 static assert(hasArgument!bool); 665 static assert(!hasArgument!OptionFlag); 666 static assert(!hasArgument!OptionHandler); 667 } 668 669 /** 670 * Parses an argument. 671 * 672 * Throws: ArgParseError if the given argument cannot be converted to the 673 * requested type. 674 */ 675 T parseArg(T)(string arg) pure 676 { 677 import std.conv : to, ConvException; 678 679 try 680 { 681 return to!T(arg); 682 } 683 catch (ConvException e) 684 { 685 throw new ArgParseError(e.msg); 686 } 687 } 688 689 unittest 690 { 691 import std.exception : ce = collectException; 692 693 assert(parseArg!int("42") == 42); 694 assert(parseArg!string("42") == "42"); 695 assert(ce!ArgParseError(parseArg!size_t("-42"))); 696 } 697 698 /** 699 * Returns the canonical name of the given member's argument. If there is no 700 * argument for the given member, null is returned. 701 */ 702 private string argumentName(Options, string member)() pure 703 { 704 import std.traits : Identity, getUDAs; 705 import std..string : toUpper; 706 707 alias symbol = Identity!(__traits(getMember, Options, member)); 708 709 static if (hasArgument!(typeof(symbol))) 710 { 711 alias metavar = getUDAs!(symbol, MetaVar); 712 static if (metavar.length > 0) 713 return metavar[0].name; 714 else static if (is(typeof(symbol) : ArgumentHandler)) 715 return member.toUpper; 716 else 717 return "<"~ typeof(symbol).stringof ~ ">"; 718 } 719 else 720 { 721 return null; 722 } 723 } 724 725 unittest 726 { 727 static struct Options 728 { 729 @Option("test1") 730 OptionFlag test1; 731 732 @Option("test2") 733 string test2; 734 735 @Option("test3") 736 @MetaVar("asdf") 737 string test3; 738 739 private string _arg; 740 741 @Option("test4") 742 void test4(string arg) pure 743 { 744 _arg = arg; 745 } 746 747 @Option("test5") 748 @MetaVar("metavar") 749 void test5(string arg) pure 750 { 751 _arg = arg; 752 } 753 } 754 755 static assert(argumentName!(Options, "test1") == null); 756 static assert(argumentName!(Options, "test2") == "<string>"); 757 static assert(argumentName!(Options, "test3") == "asdf"); 758 static assert(argumentName!(Options, "test4") == "TEST4"); 759 static assert(argumentName!(Options, "test5") == "metavar"); 760 } 761 762 /** 763 * Constructs a printable usage string at compile time from the given options 764 * structure. 765 */ 766 string usageString(Options)(string program) pure 767 if (is(Options == struct)) 768 { 769 import std.traits : Identity, getUDAs; 770 import std.array : replicate; 771 import std..string : wrap, toUpper; 772 773 string output = "Usage: "~ program; 774 775 string indent = " ".replicate(output.length + 1); 776 777 // List all options 778 foreach (member; __traits(allMembers, Options)) 779 { 780 alias symbol = Identity!(__traits(getMember, Options, member)); 781 alias optUDAs = getUDAs!(symbol, Option); 782 783 static if (optUDAs.length > 0 && optUDAs[0].names.length > 0) 784 { 785 output ~= " ["~ nameToOption(optUDAs[0].names[0]); 786 787 if (immutable name = argumentName!(Options, member)) 788 output ~= "="~ name; 789 790 output ~= "]"; 791 } 792 } 793 794 // List all arguments 795 foreach (member; __traits(allMembers, Options)) 796 { 797 alias symbol = Identity!(__traits(getMember, Options, member)); 798 alias argUDAs = getUDAs!(symbol, Argument); 799 800 static if (argUDAs.length > 0) 801 output ~= " "~ argUDAs[0].usage; 802 } 803 804 return output.wrap(80, null, indent, 4); 805 } 806 807 /** 808 * Generates a help string for a single argument. Returns null if the given 809 * member is not an argument. 810 */ 811 private string argumentHelp(Options, string member)(size_t padding = 14) pure 812 { 813 import std.traits : Identity, getUDAs; 814 import std.array : replicate; 815 import std..string : wrap; 816 817 string output; 818 819 alias symbol = Identity!(__traits(getMember, Options, member)); 820 alias argUDAs = getUDAs!(symbol, Argument); 821 822 static if (argUDAs.length > 0) 823 { 824 enum name = argUDAs[0].name; 825 output ~= " "~ name; 826 827 alias helpUDAs = getUDAs!(symbol, Help); 828 static if (helpUDAs.length > 0) 829 { 830 if (name.length > padding) 831 output ~= "\n"; 832 833 immutable indent = " ".replicate(padding + 3); 834 immutable firstIndent = (name.length > padding) ? indent : 835 " ".replicate(padding - name.length + 2); 836 837 output ~= helpUDAs[0].help.wrap(80, firstIndent, indent, 4); 838 } 839 else 840 { 841 output ~= "\n"; 842 } 843 } 844 845 return output; 846 } 847 848 /** 849 * Generates a help string for a single option. Returns null if the given member 850 * is not an option. 851 */ 852 private string optionHelp(Options, string member)(size_t padding = 14) pure 853 { 854 import std.traits : Identity, getUDAs; 855 import std.array : replicate; 856 import std..string : wrap; 857 858 string output; 859 860 alias symbol = Identity!(__traits(getMember, Options, member)); 861 alias optUDAs = getUDAs!(symbol, Option); 862 863 static if (optUDAs.length > 0) 864 { 865 enum names = optUDAs[0].names; 866 static if (names.length > 0) 867 { 868 output ~= " " ~ nameToOption(names[0]); 869 870 foreach (name; names[1 .. $]) 871 output ~= ", " ~ nameToOption(name); 872 873 if (string arg = argumentName!(Options, member)) 874 output ~= " " ~ arg; 875 876 immutable len = output.length; 877 878 alias helpUDAs = getUDAs!(symbol, Help); 879 static if (helpUDAs.length > 0) 880 { 881 immutable overflow = len > padding+1; 882 if (overflow) 883 output ~= "\n"; 884 885 immutable indent = " ".replicate(padding+3); 886 immutable firstIndent = overflow 887 ? indent : " ".replicate((padding + 1) - len + 2); 888 889 output ~= helpUDAs[0].help.wrap(80, firstIndent, indent, 4); 890 } 891 else 892 { 893 output ~= "\n"; 894 } 895 } 896 } 897 898 return output; 899 } 900 901 /** 902 * Constructs a printable help string at compile time for the given options 903 * structure. 904 */ 905 string helpString(Options)(string description = null) pure 906 if (is(Options == struct)) 907 { 908 import std.format : format; 909 import std..string : wrap; 910 911 string output; 912 913 if (description) 914 output ~= description.wrap(80) ~ "\n"; 915 916 // List all positional arguments. 917 static if(countArgs!Options > 0) 918 { 919 output ~= "Positional arguments:\n"; 920 921 foreach (member; __traits(allMembers, Options)) 922 output ~= argumentHelp!(Options, member); 923 924 static if (countOpts!Options > 0) 925 output ~= "\n"; 926 } 927 928 // List all options 929 static if (countOpts!Options > 0) 930 { 931 output ~= "Optional arguments:\n"; 932 933 foreach (member; __traits(allMembers, Options)) 934 output ~= optionHelp!(Options, member); 935 } 936 937 return output; 938 } 939 940 /** 941 * Parsing configuration. 942 */ 943 enum Config 944 { 945 /** 946 * Enables option bundling. That is, multiple single character options can 947 * be bundled together. 948 */ 949 bundling = 1 << 0, 950 951 /** 952 * Ignore unknown options. These are then parsed as positional arguments. 953 */ 954 ignoreUnknown = 1 << 1, 955 956 /** 957 * Throw the ArgParseHelp exception when the option "help" is specified. 958 * This requires the option to exist in the options struct. 959 */ 960 handleHelp = 1 << 2, 961 962 /** 963 * Default configuration options. 964 */ 965 default_ = bundling | handleHelp, 966 } 967 968 /** 969 * Parses options from the given list of arguments. Note that the first argument 970 * is assumed to be the program name and is ignored. 971 * 972 * Returns: Options structure filled out with values. 973 * 974 * Throws: ArgParseError if arguments are invalid. 975 */ 976 T parseArgs(T)( 977 const(string[]) arguments, 978 Config config = Config.default_ 979 ) pure 980 if (is(T == struct)) 981 { 982 import std.traits : Identity, getUDAs; 983 import std.format : format; 984 import std.range : chain, enumerate, empty, front, popFront; 985 import std.algorithm.iteration : map, filter; 986 987 validateOptions!T; 988 989 T options; 990 991 auto args = splitArgs(arguments); 992 993 // Arguments that have been parsed 994 bool[] parsed; 995 parsed.length = args.head.length; 996 997 // Parsing occurs in two passes: 998 // 999 // 1. Parse all options 1000 // 2. Parse all positional arguments 1001 // 1002 // After the first pass, only positional arguments and invalid options will 1003 // be left. 1004 1005 for (size_t i = 0; i < args.head.length; ++i) 1006 { 1007 auto opt = splitOption(args.head[i]); 1008 1009 if (immutable name = optionToName(opt.head)) 1010 { 1011 foreach (member; __traits(allMembers, T)) 1012 { 1013 alias symbol = Identity!(__traits(getMember, options, member)); 1014 alias optUDAs = getUDAs!(symbol, Option); 1015 1016 static if (optUDAs.length > 0) 1017 { 1018 if (optUDAs[0] == name) 1019 { 1020 parsed[i] = true; 1021 1022 static if (hasArgument!(typeof(symbol))) 1023 { 1024 if (opt.tail) 1025 { 1026 static if (is(typeof(symbol) : ArgumentHandler)) 1027 __traits(getMember, options, member)(opt.tail); 1028 else 1029 __traits(getMember, options, member) = 1030 parseArg!(typeof(symbol))(opt.tail); 1031 } 1032 else 1033 { 1034 ++i; 1035 1036 if (i >= args.head.length || isOption(args.head[i])) 1037 throw new ArgParseError( 1038 "Expected argument for option '%s'" 1039 .format(opt.head) 1040 ); 1041 1042 static if (is(typeof(symbol) : ArgumentHandler)) 1043 __traits(getMember, options, member)(args.head[i]); 1044 else 1045 __traits(getMember, options, member) = 1046 parseArg!(typeof(symbol))(args.head[i]); 1047 1048 parsed[i] = true; 1049 } 1050 } 1051 else 1052 { 1053 if (opt.tail) 1054 throw new ArgParseError( 1055 "Option '%s' does not take an argument" 1056 .format(opt.head) 1057 ); 1058 1059 // Handle a request for help 1060 if ((config & Config.handleHelp) == 1061 Config.handleHelp && optUDAs[0] == "help") 1062 throw new ArgParseHelp(""); 1063 1064 static if (is(typeof(symbol) : OptionHandler)) 1065 __traits(getMember, options, member)(); 1066 else static if (is(typeof(symbol) : OptionFlag)) 1067 __traits(getMember, options, member) = 1068 OptionFlag.yes; 1069 else 1070 static assert(false); 1071 } 1072 } 1073 } 1074 } 1075 } 1076 } 1077 1078 if ((config & Config.ignoreUnknown) != Config.ignoreUnknown) 1079 { 1080 // Any left over options are erroneous 1081 for (size_t i = 0; i < args.head.length; ++i) 1082 { 1083 if (!parsed[i] && isOption(args.head[i])) 1084 { 1085 throw new ArgParseError( 1086 "Unknown option '"~ args.head[i] ~"'" 1087 ); 1088 } 1089 } 1090 } 1091 1092 // Left over arguments 1093 auto leftOver = args.head 1094 .enumerate 1095 .filter!(a => !parsed[a[0]]) 1096 .map!(a => a[1]) 1097 .chain(args.tail); 1098 1099 // Only positional arguments are left 1100 foreach (member; __traits(allMembers, T)) 1101 { 1102 alias symbol = Identity!(__traits(getMember, options, member)); 1103 alias argUDAs = getUDAs!(symbol, Argument); 1104 1105 static if (argUDAs.length > 0) 1106 { 1107 // Keep consuming arguments until the multiplicity is satisfied 1108 for (size_t i = 0; i < argUDAs[0].upperBound; ++i) 1109 { 1110 // Out of arguments? 1111 if (leftOver.empty) 1112 { 1113 if (i >= argUDAs[0].lowerBound) 1114 break; // Multiplicity is satisfied 1115 1116 throw new ArgParseError(argUDAs[0].multiplicityError(i)); 1117 } 1118 1119 // Set argument or add to list of arguments. 1120 static if (argUDAs[0].upperBound <= 1) 1121 { 1122 static if (is(typeof(symbol) : ArgumentHandler)) 1123 __traits(getMember, options, member)(leftOver.front); 1124 else 1125 __traits(getMember, options, member) = 1126 parseArg!(typeof(symbol))(leftOver.front); 1127 } 1128 else 1129 { 1130 static if (is(typeof(symbol) : ArgumentHandler)) 1131 __traits(getMember, options, member)(leftOver.front); 1132 else 1133 { 1134 import std.range.primitives : ElementType; 1135 __traits(getMember, options, member) ~= 1136 parseArg!(ElementType!(typeof(symbol)))(leftOver.front); 1137 } 1138 } 1139 1140 leftOver.popFront(); 1141 } 1142 } 1143 } 1144 1145 if (!leftOver.empty) 1146 throw new ArgParseError("Too many arguments specified"); 1147 1148 return options; 1149 } 1150 1151 /// 1152 unittest 1153 { 1154 static struct Options 1155 { 1156 string testValue; 1157 1158 @Option("test") 1159 void test(string arg) pure 1160 { 1161 testValue = arg; 1162 } 1163 1164 @Option("help") 1165 @Help("Prints help on command line arguments.") 1166 OptionFlag help; 1167 1168 @Option("version") 1169 @Help("Prints version information.") 1170 OptionFlag version_; 1171 1172 @Argument("path", Multiplicity.oneOrMore) 1173 @Help("Path to the build description.") 1174 string[] path; 1175 1176 @Option("dryrun", "n") 1177 @Help("Don't make any functional changes. Just print what might" ~ 1178 " happen.") 1179 OptionFlag dryRun; 1180 1181 @Option("threads", "j") 1182 @Help("The number of threads to use. Default is the number of" ~ 1183 " logical cores.") 1184 size_t threads; 1185 1186 @Option("color") 1187 @Help("When to colorize the output.") 1188 @MetaVar("{auto,always,never}") 1189 string color = "auto"; 1190 } 1191 1192 immutable options = parseArgs!Options([ 1193 "arg1", 1194 "--version", 1195 "--test", 1196 "test test", 1197 "--dryrun", 1198 "--threads", 1199 "42", 1200 "--color=test", 1201 "--", 1202 "arg2", 1203 ]); 1204 1205 assert(options == Options( 1206 "test test", 1207 OptionFlag.no, 1208 OptionFlag.yes, 1209 ["arg1", "arg2"], 1210 OptionFlag.yes, 1211 42, 1212 "test", 1213 )); 1214 } 1215 1216 /// 1217 unittest 1218 { 1219 static struct Options 1220 { 1221 @Option("help") 1222 @Help("Prints help on command line usage.") 1223 OptionFlag help; 1224 1225 @Option("version") 1226 @Help("Prints version information.") 1227 OptionFlag version_; 1228 1229 @Argument("command", Multiplicity.optional) 1230 @Help("Subcommand") 1231 string command; 1232 1233 @Argument("args", Multiplicity.zeroOrMore) 1234 @Help("Arguments for the command.") 1235 const(string)[] args; 1236 } 1237 1238 immutable options = parseArgs!Options([ 1239 "--version", 1240 "status", 1241 "--asdf", 1242 "blah blah" 1243 ], Config.ignoreUnknown); 1244 1245 assert(options == Options( 1246 OptionFlag.no, 1247 OptionFlag.yes, 1248 "status", 1249 ["--asdf", "blah blah"] 1250 )); 1251 }