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