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.meta : Alias; 533 import std.traits : getUDAs; 534 import std.algorithm.comparison : min; 535 536 size_t count = 0; 537 538 foreach (member; __traits(allMembers, Options)) 539 { 540 alias symbol = Alias!(__traits(getMember, Options, member)); 541 alias argUDAs = getUDAs!(symbol, Argument); 542 count += min(argUDAs.length, 1); 543 } 544 545 return count; 546 } 547 548 /** 549 * Count the number of options. 550 */ 551 size_t countOpts(Options)() pure nothrow 552 { 553 import std.meta : Alias; 554 import std.traits : getUDAs; 555 import std.algorithm.comparison : min; 556 557 size_t count = 0; 558 559 foreach (member; __traits(allMembers, Options)) 560 { 561 alias symbol = Alias!(__traits(getMember, Options, member)); 562 alias optUDAs = getUDAs!(symbol, Option); 563 count += min(optUDAs.length, 1); 564 } 565 566 return count; 567 } 568 569 unittest 570 { 571 static struct A {} 572 573 static assert(countArgs!A == 0); 574 static assert(countOpts!A == 0); 575 576 static struct B 577 { 578 @Argument("test1") 579 string test1; 580 @Argument("test2") 581 string test2; 582 583 @Option("test3", "test3a") 584 @Option("test3b", "test3c") 585 string test3; 586 } 587 588 static assert(countArgs!B == 2); 589 static assert(countOpts!B == 1); 590 591 static struct C 592 { 593 string test; 594 595 @Argument("test1") 596 string test1; 597 598 @Argument("test2") 599 @Argument("test2a") 600 string test2; 601 602 @Option("test3") 603 string test3; 604 605 @Option("test4") 606 string test4; 607 } 608 609 static assert(countArgs!C == 2); 610 static assert(countOpts!C == 2); 611 } 612 613 /** 614 * Checks if the given options are valid. 615 */ 616 private void validateOptions(Options)() pure nothrow 617 { 618 import std.meta : Alias; 619 import std.traits : getUDAs, fullyQualifiedName; 620 621 foreach (member; __traits(allMembers, Options)) 622 { 623 alias symbol = Alias!(__traits(getMember, Options, member)); 624 alias optUDAs = getUDAs!(symbol, Option); 625 alias argUDAs = getUDAs!(symbol, Argument); 626 627 // Basic error checking 628 static assert(!(optUDAs.length > 0 && argUDAs.length > 0), 629 fullyQualifiedName!symbol ~" cannot be both an Option and an Argument" 630 ); 631 static assert(optUDAs.length <= 1, 632 fullyQualifiedName!symbol ~" cannot have multiple Option attributes" 633 ); 634 static assert(argUDAs.length <= 1, 635 fullyQualifiedName!symbol ~" cannot have multiple Argument attributes" 636 ); 637 638 static if (argUDAs.length > 0) 639 static assert(isValidOptionType!(typeof(symbol)), 640 fullyQualifiedName!symbol ~" is not a valid Argument type" 641 ); 642 643 static if (optUDAs.length > 0) 644 static assert(isValidOptionType!(typeof(symbol)), 645 fullyQualifiedName!symbol ~" is not a valid Option type" 646 ); 647 } 648 } 649 650 /** 651 * Checks if the given option type has an associated argument. Currently, only 652 * an OptionFlag does not have an argument. 653 */ 654 private template hasArgument(T) 655 { 656 static if (is(T : OptionFlag) || is(T : OptionHandler)) 657 enum hasArgument = false; 658 else 659 enum hasArgument = true; 660 } 661 662 unittest 663 { 664 static assert(hasArgument!string); 665 static assert(hasArgument!ArgumentHandler); 666 static assert(hasArgument!int); 667 static assert(hasArgument!bool); 668 static assert(!hasArgument!OptionFlag); 669 static assert(!hasArgument!OptionHandler); 670 } 671 672 /** 673 * Parses an argument. 674 * 675 * Throws: ArgParseError if the given argument cannot be converted to the 676 * requested type. 677 */ 678 T parseArg(T)(string arg) pure 679 { 680 import std.conv : to, ConvException; 681 682 try 683 { 684 return to!T(arg); 685 } 686 catch (ConvException e) 687 { 688 throw new ArgParseError(e.msg); 689 } 690 } 691 692 unittest 693 { 694 import std.exception : ce = collectException; 695 696 assert(parseArg!int("42") == 42); 697 assert(parseArg!string("42") == "42"); 698 assert(ce!ArgParseError(parseArg!size_t("-42"))); 699 } 700 701 /** 702 * Returns the canonical name of the given member's argument. If there is no 703 * argument for the given member, null is returned. 704 */ 705 private string argumentName(Options, string member)() pure 706 { 707 import std.meta : Alias; 708 import std.traits : getUDAs; 709 import std..string : toUpper; 710 711 alias symbol = Alias!(__traits(getMember, Options, member)); 712 713 static if (hasArgument!(typeof(symbol))) 714 { 715 alias metavar = getUDAs!(symbol, MetaVar); 716 static if (metavar.length > 0) 717 return metavar[0].name; 718 else static if (is(typeof(symbol) : ArgumentHandler)) 719 return member.toUpper; 720 else 721 return "<"~ typeof(symbol).stringof ~ ">"; 722 } 723 else 724 { 725 return null; 726 } 727 } 728 729 unittest 730 { 731 static struct Options 732 { 733 @Option("test1") 734 OptionFlag test1; 735 736 @Option("test2") 737 string test2; 738 739 @Option("test3") 740 @MetaVar("asdf") 741 string test3; 742 743 private string _arg; 744 745 @Option("test4") 746 void test4(string arg) pure 747 { 748 _arg = arg; 749 } 750 751 @Option("test5") 752 @MetaVar("metavar") 753 void test5(string arg) pure 754 { 755 _arg = arg; 756 } 757 } 758 759 static assert(argumentName!(Options, "test1") == null); 760 static assert(argumentName!(Options, "test2") == "<string>"); 761 static assert(argumentName!(Options, "test3") == "asdf"); 762 static assert(argumentName!(Options, "test4") == "TEST4"); 763 static assert(argumentName!(Options, "test5") == "metavar"); 764 } 765 766 /** 767 * Constructs a printable usage string at compile time from the given options 768 * structure. 769 */ 770 string usageString(Options)(string program) pure 771 if (is(Options == struct)) 772 { 773 import std.meta : Alias; 774 import std.traits : getUDAs; 775 import std.array : replicate; 776 import std..string : wrap, toUpper; 777 778 string output = "Usage: "~ program; 779 780 string indent = " ".replicate(output.length + 1); 781 782 // List all options 783 foreach (member; __traits(allMembers, Options)) 784 { 785 alias symbol = Alias!(__traits(getMember, Options, member)); 786 alias optUDAs = getUDAs!(symbol, Option); 787 788 static if (optUDAs.length > 0 && optUDAs[0].names.length > 0) 789 { 790 output ~= " ["~ nameToOption(optUDAs[0].names[0]); 791 792 if (immutable name = argumentName!(Options, member)) 793 output ~= "="~ name; 794 795 output ~= "]"; 796 } 797 } 798 799 // List all arguments 800 foreach (member; __traits(allMembers, Options)) 801 { 802 alias symbol = Alias!(__traits(getMember, Options, member)); 803 alias argUDAs = getUDAs!(symbol, Argument); 804 805 static if (argUDAs.length > 0) 806 output ~= " "~ argUDAs[0].usage; 807 } 808 809 return output.wrap(80, null, indent, 4); 810 } 811 812 /** 813 * Generates a help string for a single argument. Returns null if the given 814 * member is not an argument. 815 */ 816 private string argumentHelp(Options, string member)(size_t padding = 14) pure 817 { 818 import std.meta : Alias; 819 import std.traits : getUDAs; 820 import std.array : replicate; 821 import std..string : wrap; 822 823 string output; 824 825 alias symbol = Alias!(__traits(getMember, Options, member)); 826 alias argUDAs = getUDAs!(symbol, Argument); 827 828 static if (argUDAs.length > 0) 829 { 830 enum name = argUDAs[0].name; 831 output ~= " "~ name; 832 833 alias helpUDAs = getUDAs!(symbol, Help); 834 static if (helpUDAs.length > 0) 835 { 836 if (name.length > padding) 837 output ~= "\n"; 838 839 immutable indent = " ".replicate(padding + 3); 840 immutable firstIndent = (name.length > padding) ? indent : 841 " ".replicate(padding - name.length + 2); 842 843 output ~= helpUDAs[0].help.wrap(80, firstIndent, indent, 4); 844 } 845 else 846 { 847 output ~= "\n"; 848 } 849 } 850 851 return output; 852 } 853 854 /** 855 * Generates a help string for a single option. Returns null if the given member 856 * is not an option. 857 */ 858 private string optionHelp(Options, string member)(size_t padding = 14) pure 859 { 860 import std.meta : Alias; 861 import std.traits : getUDAs; 862 import std.array : replicate; 863 import std..string : wrap; 864 865 string output; 866 867 alias symbol = Alias!(__traits(getMember, Options, member)); 868 alias optUDAs = getUDAs!(symbol, Option); 869 870 static if (optUDAs.length > 0) 871 { 872 enum names = optUDAs[0].names; 873 static if (names.length > 0) 874 { 875 output ~= " " ~ nameToOption(names[0]); 876 877 foreach (name; names[1 .. $]) 878 output ~= ", " ~ nameToOption(name); 879 880 if (string arg = argumentName!(Options, member)) 881 output ~= " " ~ arg; 882 883 immutable len = output.length; 884 885 alias helpUDAs = getUDAs!(symbol, Help); 886 static if (helpUDAs.length > 0) 887 { 888 immutable overflow = len > padding+1; 889 if (overflow) 890 output ~= "\n"; 891 892 immutable indent = " ".replicate(padding+3); 893 immutable firstIndent = overflow 894 ? indent : " ".replicate((padding + 1) - len + 2); 895 896 output ~= helpUDAs[0].help.wrap(80, firstIndent, indent, 4); 897 } 898 else 899 { 900 output ~= "\n"; 901 } 902 } 903 } 904 905 return output; 906 } 907 908 /** 909 * Constructs a printable help string at compile time for the given options 910 * structure. 911 */ 912 string helpString(Options)(string description = null) pure 913 if (is(Options == struct)) 914 { 915 import std.format : format; 916 import std..string : wrap; 917 918 string output; 919 920 if (description) 921 output ~= description.wrap(80) ~ "\n"; 922 923 // List all positional arguments. 924 static if(countArgs!Options > 0) 925 { 926 output ~= "Positional arguments:\n"; 927 928 foreach (member; __traits(allMembers, Options)) 929 output ~= argumentHelp!(Options, member); 930 931 static if (countOpts!Options > 0) 932 output ~= "\n"; 933 } 934 935 // List all options 936 static if (countOpts!Options > 0) 937 { 938 output ~= "Optional arguments:\n"; 939 940 foreach (member; __traits(allMembers, Options)) 941 output ~= optionHelp!(Options, member); 942 } 943 944 return output; 945 } 946 947 /** 948 * Parsing configuration. 949 */ 950 enum Config 951 { 952 /** 953 * Enables option bundling. That is, multiple single character options can 954 * be bundled together. 955 */ 956 bundling = 1 << 0, 957 958 /** 959 * Ignore unknown options. These are then parsed as positional arguments. 960 */ 961 ignoreUnknown = 1 << 1, 962 963 /** 964 * Throw the ArgParseHelp exception when the option "help" is specified. 965 * This requires the option to exist in the options struct. 966 */ 967 handleHelp = 1 << 2, 968 969 /** 970 * Default configuration options. 971 */ 972 default_ = bundling | handleHelp, 973 } 974 975 /** 976 * Parses options from the given list of arguments. Note that the first argument 977 * is assumed to be the program name and is ignored. 978 * 979 * Returns: Options structure filled out with values. 980 * 981 * Throws: ArgParseError if arguments are invalid. 982 */ 983 T parseArgs(T)( 984 const(string[]) arguments, 985 Config config = Config.default_ 986 ) pure 987 if (is(T == struct)) 988 { 989 import std.meta : Alias; 990 import std.traits : getUDAs; 991 import std.format : format; 992 import std.range : chain, enumerate, empty, front, popFront; 993 import std.algorithm.iteration : map, filter; 994 995 validateOptions!T; 996 997 T options; 998 999 auto args = splitArgs(arguments); 1000 1001 // Arguments that have been parsed 1002 bool[] parsed; 1003 parsed.length = args.head.length; 1004 1005 // Parsing occurs in two passes: 1006 // 1007 // 1. Parse all options 1008 // 2. Parse all positional arguments 1009 // 1010 // After the first pass, only positional arguments and invalid options will 1011 // be left. 1012 1013 for (size_t i = 0; i < args.head.length; ++i) 1014 { 1015 auto opt = splitOption(args.head[i]); 1016 1017 if (immutable name = optionToName(opt.head)) 1018 { 1019 foreach (member; __traits(allMembers, T)) 1020 { 1021 alias symbol = Alias!(__traits(getMember, options, member)); 1022 alias optUDAs = getUDAs!(symbol, Option); 1023 1024 static if (optUDAs.length > 0) 1025 { 1026 if (optUDAs[0] == name) 1027 { 1028 parsed[i] = true; 1029 1030 static if (hasArgument!(typeof(symbol))) 1031 { 1032 if (opt.tail) 1033 { 1034 static if (is(typeof(symbol) : ArgumentHandler)) 1035 __traits(getMember, options, member)(opt.tail); 1036 else 1037 __traits(getMember, options, member) = 1038 parseArg!(typeof(symbol))(opt.tail); 1039 } 1040 else 1041 { 1042 ++i; 1043 1044 if (i >= args.head.length || isOption(args.head[i])) 1045 throw new ArgParseError( 1046 "Expected argument for option '%s'" 1047 .format(opt.head) 1048 ); 1049 1050 static if (is(typeof(symbol) : ArgumentHandler)) 1051 __traits(getMember, options, member)(args.head[i]); 1052 else 1053 __traits(getMember, options, member) = 1054 parseArg!(typeof(symbol))(args.head[i]); 1055 1056 parsed[i] = true; 1057 } 1058 } 1059 else 1060 { 1061 if (opt.tail) 1062 throw new ArgParseError( 1063 "Option '%s' does not take an argument" 1064 .format(opt.head) 1065 ); 1066 1067 // Handle a request for help 1068 if ((config & Config.handleHelp) == 1069 Config.handleHelp && optUDAs[0] == "help") 1070 throw new ArgParseHelp(""); 1071 1072 static if (is(typeof(symbol) : OptionHandler)) 1073 __traits(getMember, options, member)(); 1074 else static if (is(typeof(symbol) : OptionFlag)) 1075 __traits(getMember, options, member) = 1076 OptionFlag.yes; 1077 else 1078 static assert(false); 1079 } 1080 } 1081 } 1082 } 1083 } 1084 } 1085 1086 if ((config & Config.ignoreUnknown) != Config.ignoreUnknown) 1087 { 1088 // Any left over options are erroneous 1089 for (size_t i = 0; i < args.head.length; ++i) 1090 { 1091 if (!parsed[i] && isOption(args.head[i])) 1092 { 1093 throw new ArgParseError( 1094 "Unknown option '"~ args.head[i] ~"'" 1095 ); 1096 } 1097 } 1098 } 1099 1100 // Left over arguments 1101 auto leftOver = args.head 1102 .enumerate 1103 .filter!(a => !parsed[a[0]]) 1104 .map!(a => a[1]) 1105 .chain(args.tail); 1106 1107 // Only positional arguments are left 1108 foreach (member; __traits(allMembers, T)) 1109 { 1110 alias symbol = Alias!(__traits(getMember, options, member)); 1111 alias argUDAs = getUDAs!(symbol, Argument); 1112 1113 static if (argUDAs.length > 0) 1114 { 1115 // Keep consuming arguments until the multiplicity is satisfied 1116 for (size_t i = 0; i < argUDAs[0].upperBound; ++i) 1117 { 1118 // Out of arguments? 1119 if (leftOver.empty) 1120 { 1121 if (i >= argUDAs[0].lowerBound) 1122 break; // Multiplicity is satisfied 1123 1124 throw new ArgParseError(argUDAs[0].multiplicityError(i)); 1125 } 1126 1127 // Set argument or add to list of arguments. 1128 static if (argUDAs[0].upperBound <= 1) 1129 { 1130 static if (is(typeof(symbol) : ArgumentHandler)) 1131 __traits(getMember, options, member)(leftOver.front); 1132 else 1133 __traits(getMember, options, member) = 1134 parseArg!(typeof(symbol))(leftOver.front); 1135 } 1136 else 1137 { 1138 static if (is(typeof(symbol) : ArgumentHandler)) 1139 __traits(getMember, options, member)(leftOver.front); 1140 else 1141 { 1142 import std.range.primitives : ElementType; 1143 __traits(getMember, options, member) ~= 1144 parseArg!(ElementType!(typeof(symbol)))(leftOver.front); 1145 } 1146 } 1147 1148 leftOver.popFront(); 1149 } 1150 } 1151 } 1152 1153 if (!leftOver.empty) 1154 throw new ArgParseError("Too many arguments specified"); 1155 1156 return options; 1157 } 1158 1159 /// 1160 unittest 1161 { 1162 static struct Options 1163 { 1164 string testValue; 1165 1166 @Option("test") 1167 void test(string arg) pure 1168 { 1169 testValue = arg; 1170 } 1171 1172 @Option("help") 1173 @Help("Prints help on command line arguments.") 1174 OptionFlag help; 1175 1176 @Option("version") 1177 @Help("Prints version information.") 1178 OptionFlag version_; 1179 1180 @Argument("path", Multiplicity.oneOrMore) 1181 @Help("Path to the build description.") 1182 string[] path; 1183 1184 @Option("dryrun", "n") 1185 @Help("Don't make any functional changes. Just print what might" ~ 1186 " happen.") 1187 OptionFlag dryRun; 1188 1189 @Option("threads", "j") 1190 @Help("The number of threads to use. Default is the number of" ~ 1191 " logical cores.") 1192 size_t threads; 1193 1194 @Option("color") 1195 @Help("When to colorize the output.") 1196 @MetaVar("{auto,always,never}") 1197 string color = "auto"; 1198 } 1199 1200 immutable options = parseArgs!Options([ 1201 "arg1", 1202 "--version", 1203 "--test", 1204 "test test", 1205 "--dryrun", 1206 "--threads", 1207 "42", 1208 "--color=test", 1209 "--", 1210 "arg2", 1211 ]); 1212 1213 assert(options == Options( 1214 "test test", 1215 OptionFlag.no, 1216 OptionFlag.yes, 1217 ["arg1", "arg2"], 1218 OptionFlag.yes, 1219 42, 1220 "test", 1221 )); 1222 } 1223 1224 /// 1225 unittest 1226 { 1227 static struct Options 1228 { 1229 @Option("help") 1230 @Help("Prints help on command line usage.") 1231 OptionFlag help; 1232 1233 @Option("version") 1234 @Help("Prints version information.") 1235 OptionFlag version_; 1236 1237 @Argument("command", Multiplicity.optional) 1238 @Help("Subcommand") 1239 string command; 1240 1241 @Argument("args", Multiplicity.zeroOrMore) 1242 @Help("Arguments for the command.") 1243 const(string)[] args; 1244 } 1245 1246 immutable options = parseArgs!Options([ 1247 "--version", 1248 "status", 1249 "--asdf", 1250 "blah blah" 1251 ], Config.ignoreUnknown); 1252 1253 assert(options == Options( 1254 OptionFlag.no, 1255 OptionFlag.yes, 1256 "status", 1257 ["--asdf", "blah blah"] 1258 )); 1259 }