1 module argparse; 2 3 4 import std.typecons: Nullable; 5 import std.traits; 6 7 struct Config 8 { 9 /** 10 The assignment character used in options with parameters. 11 Defaults to '='. 12 */ 13 char assignChar = '='; 14 15 /** 16 When set to char.init, parameters to array and associative array receivers are 17 treated as an individual argument. That is, only one argument is appended or 18 inserted per appearance of the option switch. If `arraySep` is set to 19 something else, then each parameter is first split by the separator, and the 20 individual pieces are treated as arguments to the same option. 21 22 Defaults to char.init 23 */ 24 char arraySep = char.init; 25 26 /** 27 The option character. 28 Defaults to '-'. 29 */ 30 char namedArgChar = '-'; 31 32 /** 33 The string that conventionally marks the end of all options. 34 Assigning an empty string to `endOfArgs` effectively disables it. 35 Defaults to "--". 36 */ 37 string endOfArgs = "--"; 38 39 /**caseSensitive 40 If set then argument names are case-sensitive. 41 Defaults to true. 42 */ 43 bool caseSensitive = true; 44 45 /** 46 Single-letter arguments can be bundled together, i.e. "-abc" is the same as "-a -b -c". 47 Disabled by default. 48 */ 49 bool bundling = false; 50 51 /** 52 Delegate that processes error messages if they happen during argument parsing. 53 By default all errors are printed to stderr. 54 */ 55 private void delegate(string s) nothrow errorHandlerFunc; 56 57 @property auto errorHandler(void function(string s) nothrow func) 58 { 59 return errorHandlerFunc = (string msg) { func(msg); }; 60 } 61 62 @property auto errorHandler(void delegate(string s) nothrow func) 63 { 64 return errorHandlerFunc = func; 65 } 66 67 68 private bool onError(A...)(A args) const nothrow 69 { 70 import std.conv: text; 71 import std.stdio: stderr, writeln; 72 73 try 74 { 75 if(errorHandlerFunc) 76 errorHandlerFunc(text!A(args)); 77 else 78 stderr.writeln("Error: ", args); 79 } 80 catch(Exception e) 81 { 82 throw new Error(e.msg); 83 } 84 85 return false; 86 } 87 } 88 89 unittest 90 { 91 Config.init.onError("--just testing error func--",1,2.3,false); 92 Config c; 93 c.errorHandler = (string s){}; 94 c.onError("--just testing error func--",1,2.3,false); 95 } 96 97 98 struct Param(VALUE_TYPE) 99 { 100 const Config config; 101 string name; 102 103 static if(!is(VALUE_TYPE == void)) 104 VALUE_TYPE value; 105 } 106 107 alias RawParam = Param!(string[]); 108 109 110 private enum ArgumentType { unknown, positional, shortName, longName } 111 112 private auto splitArgumentName(string arg, const Config config) 113 { 114 import std.typecons : nullable; 115 import std.string : indexOf; 116 117 struct Result 118 { 119 ArgumentType type = ArgumentType.unknown; 120 string name; 121 string origName; 122 Nullable!string value; 123 } 124 125 if(arg.length == 0) 126 return Result.init; 127 128 if(arg[0] != config.namedArgChar) 129 return Result(ArgumentType.positional, string.init, string.init, nullable(arg)); 130 131 if(arg.length == 1) 132 return Result.init; 133 134 Result result; 135 136 auto idxAssignChar = config.assignChar == char.init ? -1 : arg.indexOf(config.assignChar); 137 if(idxAssignChar < 0) 138 result.origName = arg; 139 else 140 { 141 result.origName = arg[0 .. idxAssignChar]; 142 result.value = nullable(arg[idxAssignChar + 1 .. $]); 143 } 144 145 if(result.origName[1] == config.namedArgChar) 146 { 147 result.type = ArgumentType.longName; 148 result.name = result.origName[2..$]; 149 } 150 else 151 { 152 result.type = ArgumentType.shortName; 153 result.name = result.origName[1..$]; 154 } 155 156 return result; 157 } 158 159 unittest 160 { 161 import std.typecons : tuple, nullable; 162 163 static assert(splitArgumentName("", Config.init).tupleof == tuple(ArgumentType.init, string.init, string.init, Nullable!string.init).tupleof); 164 static assert(splitArgumentName("-", Config.init).tupleof == tuple(ArgumentType.init, string.init, string.init, Nullable!string.init).tupleof); 165 static assert(splitArgumentName("abc=4", Config.init).tupleof == tuple(ArgumentType.positional, string.init, string.init, "abc=4").tupleof); 166 static assert(splitArgumentName("-abc", Config.init).tupleof == tuple(ArgumentType.shortName, "abc", "-abc", Nullable!string.init).tupleof); 167 static assert(splitArgumentName("--abc", Config.init).tupleof == tuple(ArgumentType.longName, "abc", "--abc", Nullable!string.init).tupleof); 168 static assert(splitArgumentName("-abc=fd", Config.init).tupleof == tuple(ArgumentType.shortName, "abc", "-abc", "fd").tupleof); 169 static assert(splitArgumentName("--abc=fd", Config.init).tupleof == tuple(ArgumentType.longName, "abc", "--abc", "fd").tupleof); 170 static assert(splitArgumentName("-abc=", Config.init).tupleof == tuple(ArgumentType.shortName, "abc", "-abc", nullable("")).tupleof); 171 static assert(splitArgumentName("--abc=", Config.init).tupleof == tuple(ArgumentType.longName, "abc", "--abc", nullable("")).tupleof); 172 static assert(splitArgumentName("-=abc", Config.init).tupleof == tuple(ArgumentType.shortName, string.init, "-", "abc").tupleof); 173 static assert(splitArgumentName("--=abc", Config.init).tupleof == tuple(ArgumentType.longName, string.init, "--", "abc").tupleof); 174 } 175 176 177 private template defaultValuesCount(T) 178 if(!is(T == void)) 179 { 180 import std.traits; 181 182 static if(isBoolean!T) 183 { 184 enum min = 0; 185 enum max = 1; 186 } 187 else static if(isSomeString!T || isScalarType!T) 188 { 189 enum min = 1; 190 enum max = 1; 191 } 192 else static if(isStaticArray!T) 193 { 194 enum min = 1; 195 enum max = T.length; 196 } 197 else static if(isArray!T || isAssociativeArray!T) 198 { 199 enum min = 1; 200 enum max = ulong.max; 201 } 202 else static if(is(T == function)) 203 { 204 // ... function() 205 static if(__traits(compiles, { T(); })) 206 { 207 enum min = 0; 208 enum max = 0; 209 } 210 // ... function(string value) 211 else static if(__traits(compiles, { T(string.init); })) 212 { 213 enum min = 1; 214 enum max = 1; 215 } 216 // ... function(string[] value) 217 else static if(__traits(compiles, { T([string.init]); })) 218 { 219 enum min = 0; 220 enum max = ulong.max; 221 } 222 // ... function(RawParam param) 223 else static if(__traits(compiles, { T(RawParam.init); })) 224 { 225 enum min = 1; 226 enum max = ulong.max; 227 } 228 else 229 static assert(false, "Unsupported callback: " ~ T.stringof); 230 } 231 else 232 static assert(false, "Type is not supported: " ~ T.stringof); 233 } 234 235 private auto addDefaultValuesCount(T)(ArgumentInfo info) 236 { 237 if(info.minValuesCount.isNull) info.minValuesCount = defaultValuesCount!T.min; 238 if(info.maxValuesCount.isNull) info.maxValuesCount = defaultValuesCount!T.max; 239 240 return info; 241 } 242 243 244 245 private bool checkMemberWithMultiArgs(T)() 246 { 247 static foreach (sym; getSymbolsByUDA!(T, ArgumentUDA)) 248 static assert(getUDAs!(__traits(getMember, T, __traits(identifier, sym)), ArgumentUDA).length == 1, 249 "Member "~T.stringof~"."~sym.stringof~" has multiple '*Argument' UDAs"); 250 251 return true; 252 } 253 254 private auto checkDuplicates(alias sortedRange, string errorMsg)() { 255 import std.range : lockstep; 256 import std.conv : to; 257 258 static if(sortedRange.length >= 2) 259 static foreach(value1, value2; lockstep(sortedRange[0..$-1], sortedRange[1..$])) 260 static assert(value1 != value2, errorMsg ~ value1.to!string); 261 262 return true; 263 } 264 265 private bool checkArgumentNames(T)() 266 { 267 enum names = () { 268 import std.algorithm : sort; 269 270 string[] names; 271 static foreach (sym; getSymbolsByUDA!(T, ArgumentUDA)) 272 {{ 273 enum argUDA = getUDAs!(__traits(getMember, T, __traits(identifier, sym)), ArgumentUDA)[0]; 274 275 static assert(!argUDA.info.positional || argUDA.info.names.length <= 1, 276 "Positional argument should have exactly one name: "~T.stringof~"."~sym.stringof); 277 278 static foreach (name; argUDA.info.names) 279 { 280 static assert(name.length > 0, "Argument name can't be empty: "~T.stringof~"."~sym.stringof); 281 282 names ~= name; 283 } 284 }} 285 286 return names.sort; 287 }(); 288 289 return checkDuplicates!(names, "Argument name appears more than once: "); 290 } 291 292 private bool checkPositionalIndexes(T)() 293 { 294 import std.conv : to; 295 import std.range : lockstep, iota; 296 297 298 enum positions = () { 299 import std.algorithm : sort; 300 301 uint[] positions; 302 static foreach (sym; getSymbolsByUDA!(T, ArgumentUDA)) 303 {{ 304 enum argUDA = getUDAs!(__traits(getMember, T, __traits(identifier, sym)), ArgumentUDA)[0]; 305 306 static if (argUDA.info.positional) 307 positions ~= argUDA.info.position.get; 308 }} 309 310 return positions.sort; 311 }(); 312 313 if(!checkDuplicates!(positions, "Positional arguments have duplicated position: ")) 314 return false; 315 316 static foreach (i, pos; lockstep(iota(0, positions.length), positions)) 317 static assert(i == pos, "Positional arguments have missed position: " ~ i.to!string); 318 319 return true; 320 } 321 322 private ulong countArguments(T)() 323 { 324 return getSymbolsByUDA!(T, ArgumentUDA).length; 325 } 326 327 private ulong countPositionalArguments(T)() 328 { 329 ulong count; 330 static foreach (sym; getSymbolsByUDA!(T, ArgumentUDA)) 331 static if (getUDAs!(__traits(getMember, T, __traits(identifier, sym)), ArgumentUDA)[0].info.positional) 332 count++; 333 334 return count; 335 } 336 337 338 private struct Arguments(T) 339 { 340 static assert(getSymbolsByUDA!(T, ArgumentUDA).length > 0, "Type "~T.stringof~" has no members with '*Argument' UDA"); 341 static assert(getSymbolsByUDA!(T, TrailingArgumentUDA).length <= 1, "Type "~T.stringof~" must have at most one 'TrailingArguments' UDA"); 342 343 private enum _validate = checkMemberWithMultiArgs!T && checkArgumentNames!T && checkPositionalIndexes!T; 344 345 346 struct Argument 347 { 348 ArgumentInfo info; 349 350 private bool function(in Config config, string argName, ref T receiver, string[] rawValues) parsingFunc; 351 352 353 bool parse(in Config config, string argName, ref T receiver, string[] rawValues) const 354 { 355 try 356 { 357 return info.checkValuesCount(config, argName, rawValues.length) && 358 parsingFunc(config, argName, receiver, rawValues); 359 } 360 catch(Exception e) 361 { 362 return config.onError(argName, ": ", e.msg); 363 } 364 } 365 } 366 367 immutable string function(string str) convertCase; 368 369 static if(getSymbolsByUDA!(T, TrailingArgumentUDA).length == 1) 370 { 371 private void setTrailingArgs(ref T receiver, string[] rawValues) 372 { 373 alias symbol = getSymbolsByUDA!(T, TrailingArgumentUDA)[0]; 374 375 static if(__traits(compiles, { __traits(getMember, receiver, symbol.stringof) = rawValues; })) 376 __traits(getMember, receiver, symbol.stringof) = rawValues; 377 else 378 static assert(false, "Type '"~typeof(__traits(getMember, receiver, symbol.stringof)).stringof~"' of `"~ 379 T.stringof~"."~symbol.stringof~"` is not supported for 'TrailingArguments' UDA"); 380 } 381 } 382 383 private Argument[countArguments!T] arguments; 384 385 // named arguments 386 private ulong[string] argsNamed; 387 388 // positional arguments 389 private ulong[countPositionalArguments!T] argsPositional; 390 391 // required arguments 392 private bool[ulong] argsRequired; 393 394 395 @property auto requiredArguments() const { return argsRequired; } 396 397 398 this(bool caseSensitive) 399 { 400 if(caseSensitive) 401 convertCase = s => s; 402 else 403 convertCase = (string str) 404 { 405 import std.uni : toUpper; 406 return str.toUpper; 407 }; 408 409 ulong idx = 0; 410 411 static foreach(sym; getSymbolsByUDA!(T, ArgumentUDA)) 412 {{ 413 alias member = __traits(getMember, T, __traits(identifier, sym)); 414 415 enum argUDA = getUDAs!(member, ArgumentUDA)[0]; 416 417 static if(argUDA.info.positional) 418 argsPositional[argUDA.info.position.get] = idx; 419 else 420 static foreach (name; argUDA.info.names) 421 argsNamed[convertCase(name)] = idx; 422 423 static if(argUDA.info.required) 424 argsRequired[idx] = true; 425 426 enum arg = Argument( 427 () { 428 auto info = argUDA.info; 429 430 static if(!isBoolean!(typeof(member))) 431 info.allowBooleanNegation = false; 432 433 static if(argUDA.info.positional && argUDA.info.names.length == 0) 434 info.names ~= sym.stringof; 435 436 return info.addDefaultValuesCount!(typeof(member)); 437 }(), 438 function (in Config config, string argName, ref T receiver, string[] rawValues) 439 { 440 auto param = RawParam(config, argName, rawValues); 441 442 static if(is(typeof(__traits(getMember, receiver, __traits(identifier, sym))) == function)) 443 { 444 auto target = &__traits(getMember, receiver, __traits(identifier, sym)); 445 return argUDA.parsingFunc.parse(target, param); 446 } 447 else 448 return argUDA.parsingFunc.parse(__traits(getMember, receiver, __traits(identifier, sym)), param); 449 } 450 ); 451 452 arguments[idx++] = arg; 453 }} 454 } 455 456 457 private auto findArgumentImpl(const ulong* pIndex) const 458 { 459 import std.typecons : Tuple; 460 461 alias Result = Tuple!(ulong, "index", typeof(&arguments[0]), "arg"); 462 463 return pIndex ? Result(*pIndex, &arguments[*pIndex]) : Result(ulong.max, null); 464 } 465 466 auto findPositionalArgument(ulong position) const 467 { 468 return findArgumentImpl(position < argsPositional.length ? &argsPositional[position] : null); 469 } 470 471 auto findNamedArgument(string name) const 472 { 473 return findArgumentImpl(convertCase(name) in argsNamed); 474 } 475 476 } 477 478 unittest 479 { 480 struct T 481 { 482 @(NamedArgument("a")) 483 int a; 484 @(NamedArgument("b").Optional()) 485 int b; 486 @(NamedArgument("c").Required()) 487 int c; 488 @(NamedArgument("d")) 489 int d; 490 @(NamedArgument("e").Required()) 491 int e; 492 @(NamedArgument("f")) 493 int f; 494 } 495 static assert(Arguments!T(true).requiredArguments.keys == [2,4]); 496 } 497 498 unittest 499 { 500 struct T0 501 { 502 int a; 503 } 504 static assert(!__traits(compiles, { Arguments!T0(true); })); 505 506 struct T1 507 { 508 @(NamedArgument("1")) 509 @(NamedArgument("2")) 510 int a; 511 } 512 static assert(!__traits(compiles, { Arguments!T1(true); })); 513 514 struct T2 515 { 516 @(NamedArgument("1")) 517 int a; 518 @(NamedArgument("1")) 519 int b; 520 } 521 static assert(!__traits(compiles, { Arguments!T1(true); })); 522 523 struct T3 524 { 525 @(PositionalArgument(0)) int a; 526 @(PositionalArgument(0)) int b; 527 } 528 static assert(!__traits(compiles, { Arguments!T3(true); })); 529 530 struct T4 531 { 532 @(PositionalArgument(0)) int a; 533 @(PositionalArgument(2)) int b; 534 } 535 static assert(!__traits(compiles, { Arguments!T4(true); })); 536 } 537 538 private void checkArgumentName(T)(char namedArgChar) 539 { 540 import std.exception: enforce; 541 542 static foreach(sym; getSymbolsByUDA!(T, ArgumentUDA)) 543 static foreach(name; getUDAs!(__traits(getMember, T, __traits(identifier, sym)), ArgumentUDA)[0].info.names) 544 enforce(name[0] != namedArgChar, "Name of argument should not begin with '"~namedArgChar~"': "~name); 545 } 546 547 struct CommandLineParser(T) 548 { 549 import std.range; 550 import std.typecons : tuple; 551 552 private immutable Config config; 553 554 private Arguments!T arguments; 555 556 557 this(in Config config) 558 { 559 checkArgumentName!T(config.namedArgChar); 560 561 this.config = config; 562 563 arguments = Arguments!T(config.caseSensitive); 564 } 565 566 private auto consumeValues(ref string[] args, in ArgumentInfo argumentInfo) const 567 { 568 immutable minValuesCount = argumentInfo.minValuesCount.get; 569 immutable maxValuesCount = argumentInfo.maxValuesCount.get; 570 571 string[] values; 572 573 if(minValuesCount > 0) 574 { 575 if(minValuesCount < args.length) 576 { 577 values = args[0..minValuesCount]; 578 args = args[minValuesCount..$]; 579 } 580 else 581 { 582 values = args; 583 args = []; 584 } 585 } 586 587 while(!args.empty && 588 values.length < maxValuesCount && 589 (config.endOfArgs.length == 0 || args.front != config.endOfArgs) && 590 (args.front.length == 0 || args.front[0] != config.namedArgChar)) 591 { 592 values ~= args.front; 593 args.popFront(); 594 } 595 596 return values; 597 } 598 599 bool parseArgs(ref T receiver, string[] args) 600 { 601 string[] unrecognizedArgs; 602 immutable res = parseKnownArgs(receiver, args, unrecognizedArgs); 603 if(!res) 604 return false; 605 606 if(unrecognizedArgs.length > 0) 607 return config.onError("Unrecognized arguments: ", unrecognizedArgs); 608 609 return true; 610 } 611 612 bool parseKnownArgs(ref T receiver, string[] args, out string[] unrecognizedArgs) 613 { 614 auto requiredArgs = arguments.requiredArguments.dup; 615 616 alias parseNamedArg = (arg, res) { 617 args.popFront(); 618 619 auto values = arg.value.isNull ? consumeValues(args, res.arg.info) : [ arg.value.get ]; 620 621 if(!res.arg.parse(config, arg.origName, receiver, values)) 622 return false; 623 624 requiredArgs.remove(res.index); 625 626 return true; 627 }; 628 629 ulong positionalArgIdx = 0; 630 631 while(!args.empty) 632 { 633 if(config.endOfArgs.length > 0 && args.front == config.endOfArgs) 634 { 635 // End of arguments 636 static if(is(typeof(arguments.setTrailingArgs))) 637 arguments.setTrailingArgs(receiver, args[1..$]); 638 else 639 unrecognizedArgs ~= args[1..$]; 640 break; 641 } 642 643 auto arg = splitArgumentName(args.front, config); 644 645 final switch(arg.type) 646 { 647 case ArgumentType.positional: 648 { 649 auto res = arguments.findPositionalArgument(positionalArgIdx); 650 if(res.arg is null) 651 goto case ArgumentType.unknown; 652 653 auto values = consumeValues(args, res.arg.info); 654 655 if(!res.arg.parse(config, res.arg.info.names[0], receiver, values)) 656 return false; 657 658 positionalArgIdx++; 659 660 requiredArgs.remove(res.index); 661 662 break; 663 } 664 665 case ArgumentType.longName: 666 { 667 if(arg.name.length == 0) 668 return config.onError("Empty argument name: ", args.front); 669 670 auto res = arguments.findNamedArgument(arg.name); 671 if(res.arg !is null) 672 { 673 if(!parseNamedArg(arg, res)) 674 return false; 675 676 break; 677 } 678 679 import std.algorithm : startsWith; 680 681 if(arg.name.startsWith("no-")) 682 { 683 res = arguments.findNamedArgument(arg.name[3..$]); 684 if(res.arg !is null && res.arg.info.allowBooleanNegation) 685 { 686 args.popFront(); 687 688 if(!res.arg.parse(config, arg.origName, receiver, ["false"])) 689 return false; 690 691 requiredArgs.remove(res.index); 692 693 break; 694 } 695 } 696 697 goto case ArgumentType.unknown; 698 } 699 700 case ArgumentType.shortName: 701 { 702 if(arg.name.length == 0) 703 return config.onError("Empty argument name: ", args.front); 704 705 auto res = arguments.findNamedArgument(arg.name); 706 if(res.arg !is null) 707 { 708 if(!parseNamedArg(arg, res)) 709 return false; 710 711 break; 712 } 713 714 if(arg.name.length == 1) 715 goto case ArgumentType.unknown; 716 717 if(!config.bundling) 718 { 719 auto name = [arg.name[0]]; 720 res = arguments.findNamedArgument(name); 721 if(res.arg is null || res.arg.info.minValuesCount != 1) 722 goto case ArgumentType.unknown; 723 724 if(!res.arg.parse(config, "-"~name, receiver, [arg.name[1..$]])) 725 return false; 726 727 requiredArgs.remove(res.index); 728 729 args.popFront(); 730 break; 731 } 732 else 733 { 734 while(arg.name.length > 0) 735 { 736 auto name = [arg.name[0]]; 737 res = arguments.findNamedArgument(name); 738 if(res.arg is null) 739 goto case ArgumentType.unknown; 740 741 if(res.arg.info.minValuesCount == 0) 742 { 743 if(!res.arg.parse(config, "-"~name, receiver, [])) 744 return false; 745 746 requiredArgs.remove(res.index); 747 748 arg.name = arg.name[1..$]; 749 } 750 else if(res.arg.info.minValuesCount == 1) 751 { 752 if(!res.arg.parse(config, "-"~name, receiver, [arg.name[1..$]])) 753 return false; 754 755 requiredArgs.remove(res.index); 756 757 arg.name = []; 758 } 759 else // trigger an error 760 return res.arg.info.checkValuesCount(config, name, 1); 761 } 762 763 if(arg.name.length == 0) 764 { 765 args.popFront(); 766 break; 767 } 768 } 769 770 goto case ArgumentType.unknown; 771 } 772 773 case ArgumentType.unknown: 774 unrecognizedArgs ~= args.front; 775 args.popFront(); 776 } 777 } 778 779 if(requiredArgs.length > 0) 780 { 781 import std.algorithm : map; 782 return config.onError("The following arguments are required: ", 783 requiredArgs.keys.map!(idx => arguments.arguments[idx].info.names[0]).join(", ")); 784 } 785 786 return true; 787 } 788 } 789 790 auto parseCLIArgs(T)(string[] args, Config config = Config.init) 791 { 792 import std.typecons : nullable; 793 794 T receiver; 795 796 return CommandLineParser!T(config).parseArgs(receiver, args) ? receiver.nullable : Nullable!T.init; 797 } 798 799 auto parseCLIKnownArgs(T)(ref string[] args, Config config = Config.init) 800 { 801 import std.typecons : nullable; 802 803 T receiver; 804 string[] unrecognizedArgs; 805 806 if(!CommandLineParser!T(config).parseKnownArgs(receiver, args, unrecognizedArgs)) 807 return Nullable!T.init; 808 else 809 { 810 args = unrecognizedArgs; 811 return receiver.nullable; 812 } 813 } 814 815 816 unittest 817 { 818 import std.exception; 819 820 struct T 821 { 822 @(NamedArgument("--")) 823 int a; 824 } 825 static assert(!__traits(compiles, { enum p = CommandLineParser!T(Config.init); })); 826 assertThrown(CommandLineParser!T(Config.init)); 827 } 828 829 unittest 830 { 831 832 import std.conv; 833 import std.traits; 834 835 struct params 836 { 837 int no_a; 838 839 @(PositionalArgument(0, "a") 840 .HelpText("Argument 'a'") 841 .Validation!((int a) { return a > 3;}) 842 .PreValidation!((string s) { return s.length > 0;}) 843 .Validation!((int a) { return a > 0;}) 844 ) 845 int a; 846 847 int no_b; 848 849 @(NamedArgument(["b", "boo"]).HelpText("Flag boo") 850 .AllowNoValue!55 851 ) 852 int b; 853 854 int no_c; 855 } 856 857 enum p = Arguments!params(true); 858 static assert(p.findNamedArgument("a").arg is null); 859 static assert(p.findNamedArgument("b").arg !is null); 860 static assert(p.findNamedArgument("boo").arg !is null); 861 static assert(p.findPositionalArgument(0).arg !is null); 862 static assert(p.findPositionalArgument(1).arg is null); 863 864 params args; 865 p.findPositionalArgument(0).arg.parse(Config.init, "", args, ["123"]); 866 p.findNamedArgument("b").arg.parse(Config.init, "", args, ["456"]); 867 p.findNamedArgument("boo").arg.parse(Config.init, "", args, ["789"]); 868 assert(args.a == 123); 869 assert(args.b == 789); 870 } 871 872 unittest 873 { 874 import std.typecons : tuple; 875 876 struct T 877 { 878 @NamedArgument("a") string a; 879 @NamedArgument("b") string b; 880 } 881 882 auto test(string[] args) 883 { 884 return tuple(args.parseCLIKnownArgs!T.get, args); 885 } 886 887 static assert(test(["-a","A","--","-b","B"]) == tuple(T("A"), ["-b","B"])); 888 889 { 890 enum parser = CommandLineParser!T(Config.init); 891 892 T args; 893 parser.parseArgs(args, [ "-a", "A"]); 894 parser.parseArgs(args, [ "-b", "B"]); 895 896 assert(args == T("A","B")); 897 } 898 } 899 900 unittest 901 { 902 903 struct T 904 { 905 @NamedArgument("x") string x; 906 @NamedArgument("foo") string foo; 907 @(PositionalArgument(0, "a").Optional()) string a; 908 @(PositionalArgument(1, "b").Optional()) string[] b; 909 } 910 static assert(["--foo","FOO","-x","X"].parseCLIArgs!T.get == T("X", "FOO")); 911 static assert(["--foo=FOO","-x=X"].parseCLIArgs!T.get == T("X", "FOO")); 912 static assert(["--foo=FOO","1","-x=X"].parseCLIArgs!T.get == T("X", "FOO", "1")); 913 static assert(["--foo=FOO","1","2","3","4"].parseCLIArgs!T.get == T(string.init, "FOO", "1",["2","3","4"])); 914 static assert(["-xX"].parseCLIArgs!T.get == T("X")); 915 916 struct T1 917 { 918 @(PositionalArgument(0, "a")) string[3] a; 919 @(PositionalArgument(1, "b")) string[] b; 920 } 921 static assert(["1","2","3","4","5","6"].parseCLIArgs!T1.get == T1(["1","2","3"],["4","5","6"])); 922 923 struct T2 924 { 925 @NamedArgument("foo") bool foo = true; 926 } 927 static assert(["--no-foo"].parseCLIArgs!T2.get == T2(false)); 928 } 929 930 unittest 931 { 932 struct T 933 { 934 @(PositionalArgument(0, "a").Optional()) 935 string a = "not set"; 936 937 @(NamedArgument("b").Required()) 938 int b; 939 } 940 941 static assert(["-b", "4"].parseCLIArgs!T.get == T("not set", 4)); 942 } 943 944 unittest 945 { 946 struct T 947 { 948 @NamedArgument("x") string x; 949 @NamedArgument("foo") string foo; 950 } 951 952 auto test(T)(string[] args) 953 { 954 Config config; 955 config.caseSensitive = false; 956 957 return args.parseCLIArgs!T(config).get; 958 } 959 960 static assert(test!T(["--Foo","FOO","-X","X"]) == T("X", "FOO")); 961 static assert(test!T(["--FOo=FOO","-X=X"]) == T("X", "FOO")); 962 } 963 964 unittest 965 { 966 auto test(T)(string[] args) 967 { 968 Config config; 969 config.bundling = true; 970 971 return args.parseCLIArgs!T(config).get; 972 } 973 974 struct T 975 { 976 @NamedArgument("a") bool a; 977 @NamedArgument("b") bool b; 978 } 979 static assert(test!T(["-a","-b"]) == T(true, true)); 980 static assert(test!T(["-ab"]) == T(true, true)); 981 } 982 983 unittest 984 { 985 struct T 986 { 987 @NamedArgument("b") bool b; 988 } 989 990 static assert(["-b"] .parseCLIArgs!T.get == T(true)); 991 static assert(["-b","true"] .parseCLIArgs!T.get == T(true)); 992 static assert(["-b","false"].parseCLIArgs!T.get == T(false)); 993 static assert(["-b=true"] .parseCLIArgs!T.get == T(true)); 994 static assert(["-b=false"] .parseCLIArgs!T.get == T(false)); 995 } 996 997 998 private struct Parsers 999 { 1000 static auto Convert(T)(string value) 1001 { 1002 import std.conv: to; 1003 return value.length > 0 ? value.to!T : T.init; 1004 } 1005 1006 static auto PassThrough(string[] values) 1007 { 1008 return values; 1009 } 1010 } 1011 1012 unittest 1013 { 1014 static assert(Parsers.Convert!int("7") == 7); 1015 static assert(Parsers.Convert!string("7") == "7"); 1016 static assert(Parsers.Convert!char("7") == '7'); 1017 1018 static assert(Parsers.PassThrough(["7","8"]) == ["7","8"]); 1019 } 1020 1021 1022 private struct Actions 1023 { 1024 static auto Assign(DEST, SRC=DEST)(ref DEST param, SRC value) 1025 { 1026 param = value; 1027 } 1028 1029 static auto Append(T)(ref T param, T value) 1030 { 1031 param ~= value; 1032 } 1033 1034 static auto Extend(T)(ref T[] param, T value) 1035 { 1036 param ~= value; 1037 } 1038 1039 static auto CallFunction(F)(ref F func, RawParam param) 1040 { 1041 // ... func() 1042 static if(__traits(compiles, { func(); })) 1043 { 1044 func(); 1045 } 1046 // ... func(string value) 1047 else static if(__traits(compiles, { func(param.value[0]); })) 1048 { 1049 foreach(value; param.value) 1050 func(value); 1051 } 1052 // ... func(string[] value) 1053 else static if(__traits(compiles, { func(param.value); })) 1054 { 1055 func(param.value); 1056 } 1057 // ... func(RawParam param) 1058 else static if(__traits(compiles, { func(param); })) 1059 { 1060 func(param); 1061 } 1062 else 1063 static assert(false, "Unsupported callback: " ~ F.stringof); 1064 } 1065 1066 static auto CallFunctionNoParam(F)(ref F func, Param!void param) 1067 { 1068 // ... func() 1069 static if(__traits(compiles, { func(); })) 1070 { 1071 func(); 1072 } 1073 // ... func(string value) 1074 else static if(__traits(compiles, { func(string.init); })) 1075 { 1076 func(string.init); 1077 } 1078 // ... func(string[] value) 1079 else static if(__traits(compiles, { func([]); })) 1080 { 1081 func([]); 1082 } 1083 // ... func(Param!void param) 1084 else static if(__traits(compiles, { func(param); })) 1085 { 1086 func(param); 1087 } 1088 else 1089 static assert(false, "Unsupported callback: " ~ F.stringof); 1090 } 1091 } 1092 1093 unittest 1094 { 1095 int i; 1096 Actions.Assign!(int)(i,7); 1097 assert(i == 7); 1098 } 1099 1100 unittest 1101 { 1102 int[] i; 1103 Actions.Append!(int[])(i,[1,2,3]); 1104 Actions.Append!(int[])(i,[7,8,9]); 1105 assert(i == [1,2,3,7,8,9]); 1106 1107 alias test = (int[] v1, int[] v2) { 1108 int[] res; 1109 1110 Param!(int[]) param; 1111 1112 alias F = Actions.Append!(int[]); 1113 param.value = v1; ActionFunc!(F, int[], int[])(res, param); 1114 1115 param.value = v2; ActionFunc!(F, int[], int[])(res, param); 1116 1117 return res; 1118 }; 1119 static assert(test([1,2,3],[7,8,9]) == [1,2,3,7,8,9]); 1120 } 1121 1122 unittest 1123 { 1124 int[][] i; 1125 Actions.Extend!(int[])(i,[1,2,3]); 1126 Actions.Extend!(int[])(i,[7,8,9]); 1127 assert(i == [[1,2,3],[7,8,9]]); 1128 } 1129 1130 1131 // values => bool 1132 // bool validate(T value) 1133 // bool validate(T[i] value) 1134 // bool validate(Param!T param) 1135 private struct ValidateFunc(alias F, T, string funcName="Validation") 1136 { 1137 static bool opCall(Param!T param) 1138 { 1139 static if(is(F == void)) 1140 { 1141 return true; 1142 } 1143 else static if(__traits(compiles, { F(param); })) 1144 { 1145 // bool validate(Param!T param) 1146 return cast(bool) F(param); 1147 } 1148 else static if(__traits(compiles, { F(param.value); })) 1149 { 1150 // bool validate(T values) 1151 return cast(bool) F(param.value); 1152 } 1153 else static if(/*isArray!T &&*/ __traits(compiles, { F(param.value[0]); })) 1154 { 1155 // bool validate(T[i] value) 1156 foreach(value; param.value) 1157 if(!F(value)) 1158 return false; 1159 return true; 1160 } 1161 else 1162 static assert(false, funcName~" function is not supported"); 1163 } 1164 } 1165 1166 unittest 1167 { 1168 auto test(alias F, T)(T[] values) 1169 { 1170 Param!(T[]) param; 1171 param.value = values; 1172 return ValidateFunc!(F, T[])(param); 1173 } 1174 1175 // bool validate(T[] values) 1176 static assert(test!((string[] a) => true, string)(["1","2","3"])); 1177 static assert(test!((int[] a) => true, int)([1,2,3])); 1178 1179 // bool validate(T value) 1180 static assert(test!((string a) => true, string)(["1","2","3"])); 1181 static assert(test!((int a) => true, int)([1,2,3])); 1182 1183 // bool validate(Param!T param) 1184 static assert(test!((RawParam p) => true, string)(["1","2","3"])); 1185 static assert(test!((Param!(int[]) p) => true, int)([1,2,3])); 1186 } 1187 1188 unittest 1189 { 1190 static assert(ValidateFunc!(void, string[])(RawParam(Config.init, "", ["1","2","3"]))); 1191 1192 static assert(!__traits(compiles, { ValidateFunc!(() {}, string[])(config, "", ["1","2","3"]); })); 1193 static assert(!__traits(compiles, { ValidateFunc!((int,int) {}, string[])(config, "", ["1","2","3"]); })); 1194 } 1195 1196 1197 private template ParseType(alias F, T) 1198 { 1199 import std.traits : Unqual; 1200 1201 static if(is(F == void)) 1202 alias ParseType = Unqual!T; 1203 else static if(Parameters!F.length == 0) 1204 static assert(false, "Parse function should take at least one parameter"); 1205 else static if(Parameters!F.length == 1) 1206 { 1207 // T action(arg) 1208 alias ParseType = Unqual!(ReturnType!F); 1209 static assert(!is(ParseType == void), "Parse function should return value"); 1210 } 1211 else static if(Parameters!F.length == 2 && is(Parameters!F[0] == Config)) 1212 { 1213 // T action(Config config, arg) 1214 alias ParseType = Unqual!(ReturnType!F); 1215 static assert(!is(ParseType == void), "Parse function should return value"); 1216 } 1217 else static if(Parameters!F.length == 2) 1218 { 1219 // ... action(ref T param, arg) 1220 alias ParseType = Parameters!F[0]; 1221 } 1222 else static if(Parameters!F.length == 3) 1223 { 1224 // ... action(Config config, ref T param, arg) 1225 alias ParseType = Parameters!F[1]; 1226 } 1227 else static if(Parameters!F.length == 4) 1228 { 1229 // ... action(Config config, string argName, ref T param, arg) 1230 alias ParseType = Parameters!F[2]; 1231 } 1232 else 1233 static assert(false, "Parse function has too many parameters: "~Parameters!F.stringof); 1234 } 1235 1236 unittest 1237 { 1238 static assert(is(ParseType!(void, double) == double)); 1239 static assert(!__traits(compiles, { ParseType!((){}, double) p; })); 1240 static assert(!__traits(compiles, { ParseType!((int,int,int,int,int){}, double) p; })); 1241 1242 // T action(arg) 1243 static assert(is(ParseType!((int)=>3, double) == int)); 1244 static assert(!__traits(compiles, { ParseType!((int){}, double) p; })); 1245 // T action(Config config, arg) 1246 static assert(is(ParseType!((Config config, int)=>3, double) == int)); 1247 static assert(!__traits(compiles, { ParseType!((Config config, int){}, double) p; })); 1248 // ... action(ref T param, arg) 1249 static assert(is(ParseType!((ref int, string v) {}, double) == int)); 1250 // ... action(Config config, ref T param, arg) 1251 static assert(is(ParseType!((Config config, ref int, string v) {}, double) == int)); 1252 // ... action(Config config, string argName, ref T param, arg) 1253 //static assert(is(ParseType!((Config config, string argName, ref int, string v) {}, double) == int)); 1254 } 1255 1256 1257 // T parse(string[] values) 1258 // T parse(string value) 1259 // T parse(RawParam param) 1260 // bool parse(ref T receiver, RawParam param) 1261 // void parse(ref T receiver, RawParam param) 1262 private struct ParseFunc(alias F, T) 1263 { 1264 alias ParseType = .ParseType!(F, T); 1265 1266 static bool opCall(ref ParseType receiver, RawParam param) 1267 { 1268 static if(is(F == void)) 1269 { 1270 foreach(value; param.value) 1271 receiver = Parsers.Convert!T(value); 1272 return true; 1273 } 1274 // T parse(string[] values) 1275 else static if(__traits(compiles, { receiver = cast(ParseType) F(param.value); })) 1276 { 1277 receiver = cast(ParseType) F(param.value); 1278 return true; 1279 } 1280 // T parse(string value) 1281 else static if(__traits(compiles, { receiver = cast(ParseType) F(param.value[0]); })) 1282 { 1283 foreach(value; param.value) 1284 receiver = cast(ParseType) F(value); 1285 return true; 1286 } 1287 // T parse(RawParam param) 1288 else static if(__traits(compiles, { receiver = cast(ParseType) F(param); })) 1289 { 1290 receiver = cast(ParseType) F(param); 1291 return true; 1292 } 1293 // bool parse(ref T receiver, RawParam param) 1294 // void parse(ref T receiver, RawParam param) 1295 else static if(__traits(compiles, { F(receiver, param); })) 1296 { 1297 static if(__traits(compiles, { auto res = cast(bool) F(receiver, param); })) 1298 { 1299 // bool parse(ref T receiver, RawParam param) 1300 return cast(bool) F(receiver, param); 1301 } 1302 else 1303 { 1304 // void parse(ref T receiver, RawParam param) 1305 F(receiver, param); 1306 return true; 1307 } 1308 } 1309 else 1310 static assert(false, "Parse function is not supported"); 1311 } 1312 } 1313 1314 unittest 1315 { 1316 int i; 1317 RawParam param; 1318 param.value = ["1","2","3"]; 1319 assert(ParseFunc!(void, int)(i, param)); 1320 assert(i == 3); 1321 } 1322 1323 unittest 1324 { 1325 auto test(alias F, T)(string[] values) 1326 { 1327 T value; 1328 RawParam param; 1329 param.value = values; 1330 assert(ParseFunc!(F, T)(value, param)); 1331 return value; 1332 } 1333 1334 // T parse(string value) 1335 static assert(test!((string a) => a, string)(["1","2","3"]) == "3"); 1336 1337 // T parse(string[] values) 1338 static assert(test!((string[] a) => a, string[])(["1","2","3"]) == ["1","2","3"]); 1339 1340 // T parse(RawParam param) 1341 static assert(test!((RawParam p) => p.value[0], string)(["1","2","3"]) == "1"); 1342 1343 // bool parse(ref T receiver, RawParam param) 1344 static assert(test!((ref string[] r, RawParam p) { r = p.value; return true; }, string[])(["1","2","3"]) == ["1","2","3"]); 1345 1346 // void parse(ref T receiver, RawParam param) 1347 static assert(test!((ref string[] r, RawParam p) { r = p.value; }, string[])(["1","2","3"]) == ["1","2","3"]); 1348 } 1349 1350 1351 // bool action(ref T receiver, ParseType value) 1352 // void action(ref T receiver, ParseType value) 1353 // bool action(ref T receiver, Param!ParseType param) 1354 // void action(ref T receiver, Param!ParseType param) 1355 private struct ActionFunc(alias F, T, ParseType) 1356 { 1357 static bool opCall(ref T receiver, Param!ParseType param) 1358 { 1359 static if(is(F == void)) 1360 { 1361 Actions.Assign!(T, ParseType)(receiver, param.value); 1362 return true; 1363 } 1364 // bool action(ref T receiver, ParseType value) 1365 // void action(ref T receiver, ParseType value) 1366 else static if(__traits(compiles, { F(receiver, param.value); })) 1367 { 1368 static if(__traits(compiles, { auto res = cast(bool) F(receiver, param.value); })) 1369 { 1370 // bool action(ref T receiver, ParseType value) 1371 return cast(bool) F(receiver, param.value); 1372 } 1373 else 1374 { 1375 // void action(ref T receiver, ParseType value) 1376 F(receiver, param.value); 1377 return true; 1378 } 1379 } 1380 // bool action(ref T receiver, Param!ParseType param) 1381 // void action(ref T receiver, Param!ParseType param) 1382 else static if(__traits(compiles, { F(receiver, param); })) 1383 { 1384 static if(__traits(compiles, { auto res = cast(bool) F(receiver, param); })) 1385 { 1386 // bool action(ref T receiver, Param!ParseType param) 1387 return cast(bool) F(receiver, param); 1388 } 1389 else 1390 { 1391 // void action(ref T receiver, Param!ParseType param) 1392 F(receiver, param); 1393 return true; 1394 } 1395 } 1396 else 1397 static assert(false, "Action function is not supported"); 1398 } 1399 } 1400 1401 unittest 1402 { 1403 auto param(T)(T values) 1404 { 1405 Param!T param; 1406 param.value = values; 1407 return param; 1408 } 1409 auto test(alias F, T)(T values) 1410 { 1411 T receiver; 1412 assert(ActionFunc!(F, T, T)(receiver, param(values))); 1413 return receiver; 1414 } 1415 1416 static assert(test!(void, string[])(["1","2","3"]) == ["1","2","3"]); 1417 1418 static assert(!__traits(compiles, { test!(() {}, string[])(["1","2","3"]); })); 1419 static assert(!__traits(compiles, { test!((int,int) {}, string[])(["1","2","3"]); })); 1420 1421 // bool action(ref T receiver, ParseType value) 1422 static assert(test!((ref string[] p, string[] a) { p=a; return true; }, string[])(["1","2","3"]) == ["1","2","3"]); 1423 1424 // void action(ref T receiver, ParseType value) 1425 static assert(test!((ref string[] p, string[] a) { p=a; }, string[])(["1","2","3"]) == ["1","2","3"]); 1426 1427 // bool action(ref T receiver, Param!ParseType param) 1428 static assert(test!((ref string[] p, Param!(string[]) a) { p=a.value; return true; }, string[]) (["1","2","3"]) == ["1","2","3"]); 1429 1430 // void action(ref T receiver, Param!ParseType param) 1431 static assert(test!((ref string[] p, Param!(string[]) a) { p=a.value; }, string[])(["1","2","3"]) == ["1","2","3"]); 1432 } 1433 1434 1435 // => receiver + bool 1436 // DEST action() 1437 // bool action(ref DEST receiver) 1438 // void action(ref DEST receiver) 1439 // bool action(ref DEST receiver, Param!void param) 1440 // void action(ref DEST receiver, Param!void param) 1441 private struct NoValueActionFunc(alias F, T) 1442 { 1443 static bool opCall(ref T receiver, Param!void param) 1444 { 1445 static if(is(F == void)) 1446 { 1447 assert(false, "No-value action function is not provided"); 1448 } 1449 else static if(__traits(compiles, { receiver = cast(T) F(); })) 1450 { 1451 // DEST action() 1452 receiver = cast(T) F(); 1453 return true; 1454 } 1455 else static if(__traits(compiles, { F(receiver); })) 1456 { 1457 static if(__traits(compiles, { auto res = cast(bool) F(receiver); })) 1458 { 1459 // bool action(ref DEST receiver) 1460 return cast(bool) F(receiver); 1461 } 1462 else 1463 { 1464 // void action(ref DEST receiver) 1465 F(receiver); 1466 return true; 1467 } 1468 } 1469 else static if(__traits(compiles, { F(receiver, param); })) 1470 { 1471 static if(__traits(compiles, { auto res = cast(bool) F(receiver, param); })) 1472 { 1473 // bool action(ref DEST receiver, Param!void param) 1474 return cast(bool) F(receiver, param); 1475 } 1476 else 1477 { 1478 // void action(ref DEST receiver, Param!void param) 1479 F(receiver, param); 1480 return true; 1481 } 1482 } 1483 else 1484 static assert(false, "No-value action function has too many parameters: "~Parameters!F.stringof); 1485 } 1486 } 1487 1488 unittest 1489 { 1490 auto test(alias F, T)() 1491 { 1492 T receiver; 1493 assert(NoValueActionFunc!(F, T)(receiver, Param!void.init)); 1494 return receiver; 1495 } 1496 1497 static assert(!__traits(compiles, { NoValueActionFunc!(() {}, int); })); 1498 static assert(!__traits(compiles, { NoValueActionFunc!((int) {}, int); })); 1499 static assert(!__traits(compiles, { NoValueActionFunc!((int,int) {}, int); })); 1500 static assert(!__traits(compiles, { NoValueActionFunc!((int,int,int) {}, int); })); 1501 1502 // DEST action() 1503 static assert(test!(() => 7, int) == 7); 1504 1505 // bool action(ref DEST param) 1506 static assert(test!((ref int p) { p=7; return true; }, int) == 7); 1507 1508 // void action(ref DEST param) 1509 static assert(test!((ref int p) { p=7; }, int) == 7); 1510 1511 // bool action(ref DEST receiver, Param!void param) 1512 static assert(test!((ref int r, Param!void p) { r=7; return true; }, int) == 7); 1513 1514 // void action(ref DEST receiver, Param!void param) 1515 static assert(test!((ref int r, Param!void p) { r=7; }, int) == 7); 1516 } 1517 1518 1519 private void splitValues(ref RawParam param) 1520 { 1521 if(param.config.arraySep == char.init) 1522 return; 1523 1524 import std.array : array, split; 1525 import std.algorithm : map, joiner; 1526 1527 param.value = param.value.map!((string s) => s.split(param.config.arraySep)).joiner.array; 1528 } 1529 1530 unittest 1531 { 1532 alias test = (char arraySep, string[] values) 1533 { 1534 Config config; 1535 config.arraySep = arraySep; 1536 1537 auto param = RawParam(config, "", values); 1538 1539 splitValues(param); 1540 1541 return param.value; 1542 }; 1543 1544 static assert(test(',', []) == []); 1545 static assert(test(',', ["a","b","c"]) == ["a","b","c"]); 1546 static assert(test(',', ["a,b","c","d,e,f"]) == ["a","b","c","d","e","f"]); 1547 static assert(test(' ', ["a,b","c","d,e,f"]) == ["a,b","c","d,e,f"]); 1548 } 1549 1550 1551 private struct ValueParseFunctions(alias PreProcess, 1552 alias PreValidation, 1553 alias Parse, 1554 alias Validation, 1555 alias Action, 1556 alias NoValueAction) 1557 { 1558 alias changePreProcess (alias func) = ValueParseFunctions!( func, PreValidation, Parse, Validation, Action, NoValueAction); 1559 alias changePreValidation(alias func) = ValueParseFunctions!(PreProcess, func, Parse, Validation, Action, NoValueAction); 1560 alias changeParse (alias func) = ValueParseFunctions!(PreProcess, PreValidation, func, Validation, Action, NoValueAction); 1561 alias changeValidation (alias func) = ValueParseFunctions!(PreProcess, PreValidation, Parse, func, Action, NoValueAction); 1562 alias changeAction (alias func) = ValueParseFunctions!(PreProcess, PreValidation, Parse, Validation, func, NoValueAction); 1563 alias changeNoValueAction(alias func) = ValueParseFunctions!(PreProcess, PreValidation, Parse, Validation, Action, func); 1564 1565 template addDefaults(T) 1566 { 1567 static if(is(PreProcess == void)) 1568 alias preProc = DefaultValueParseFunctions!T; 1569 else 1570 alias preProc = DefaultValueParseFunctions!T.changePreProcess!PreProcess; 1571 1572 static if(is(PreValidation == void)) 1573 alias preVal = preProc; 1574 else 1575 alias preVal = preProc.changePreValidation!PreValidation; 1576 1577 static if(is(Parse == void)) 1578 alias parse = preVal; 1579 else 1580 alias parse = preVal.changeParse!Parse; 1581 1582 static if(is(Validation == void)) 1583 alias val = parse; 1584 else 1585 alias val = parse.changeValidation!Validation; 1586 1587 static if(is(Action == void)) 1588 alias action = val; 1589 else 1590 alias action = val.changeAction!Action; 1591 1592 static if(is(NoValueAction == void)) 1593 alias addDefaults = action; 1594 else 1595 alias addDefaults = action.changeNoValueAction!NoValueAction; 1596 } 1597 1598 1599 // Procedure to process (parse) the values to an argument of type T 1600 // - if there is a value(s): 1601 // - pre validate raw strings 1602 // - parse raw strings 1603 // - validate parsed values 1604 // - action with values 1605 // - if there is no value: 1606 // - action if no value 1607 // Requirement: rawValues.length must be correct 1608 static bool parse(T)(ref T receiver, RawParam param) 1609 { 1610 return addDefaults!T.parseImpl(receiver, param); 1611 } 1612 static bool parseImpl(T)(ref T receiver, ref RawParam rawParam) 1613 { 1614 alias ParseType(T) = .ParseType!(Parse, T); 1615 1616 alias preValidation = ValidateFunc!(PreValidation, string[], "Pre validation"); 1617 alias parse(T) = ParseFunc!(Parse, T); 1618 alias validation(T) = ValidateFunc!(Validation, ParseType!T); 1619 alias action(T) = ActionFunc!(Action, T, ParseType!T); 1620 alias noValueAction(T) = NoValueActionFunc!(NoValueAction, T); 1621 1622 if(rawParam.value.length == 0) 1623 { 1624 return noValueAction!T(receiver, Param!void(rawParam.config, rawParam.name)); 1625 } 1626 else 1627 { 1628 static if(!is(PreProcess == void)) 1629 PreProcess(rawParam); 1630 1631 if(!preValidation(rawParam)) 1632 return false; 1633 1634 auto parsedParam = Param!(ParseType!T)(rawParam.config, rawParam.name); 1635 1636 if(!parse!T(parsedParam.value, rawParam)) 1637 return false; 1638 1639 if(!validation!T(parsedParam)) 1640 return false; 1641 1642 if(!action!T(receiver, parsedParam)) 1643 return false; 1644 1645 return true; 1646 } 1647 } 1648 } 1649 1650 1651 private template DefaultValueParseFunctions(T) 1652 if(!is(T == void)) 1653 { 1654 import std.traits; 1655 import std.conv: to; 1656 1657 static if(isSomeString!T || isNumeric!T || is(T == enum)) 1658 { 1659 alias DefaultValueParseFunctions = ValueParseFunctions!( 1660 void, // pre process 1661 void, // pre validate 1662 void, // parse 1663 void, // validate 1664 void, // action 1665 void // no-value action 1666 ); 1667 } 1668 else static if(isBoolean!T) 1669 { 1670 alias DefaultValueParseFunctions = ValueParseFunctions!( 1671 void, // pre process 1672 void, // pre validate 1673 (string value) // parse 1674 { 1675 switch(value) 1676 { 1677 case "": goto case; 1678 case "yes": goto case; 1679 case "y": return true; 1680 case "no": goto case; 1681 case "n": return false; 1682 default: return value.to!T; 1683 } 1684 }, 1685 void, // validate 1686 void, // action 1687 (ref T result) { result = true; } // no-value action 1688 ); 1689 } 1690 else static if(isSomeChar!T) 1691 { 1692 alias DefaultValueParseFunctions = ValueParseFunctions!( 1693 void, // pre process 1694 void, // pre validate 1695 (string value) // parse 1696 { 1697 return value.length > 0 ? value[0].to!T : T.init; 1698 }, 1699 void, // validate 1700 void, // action 1701 void // no-value action 1702 ); 1703 } 1704 else static if(isArray!T) 1705 { 1706 import std.traits: ForeachType; 1707 1708 alias TElement = ForeachType!T; 1709 1710 static if(!isArray!TElement || isSomeString!TElement) // 1D array 1711 { 1712 static if(!isStaticArray!T) 1713 alias action = Actions.Append!T; 1714 else 1715 alias action = Actions.Assign!T; 1716 1717 alias DefaultValueParseFunctions = DefaultValueParseFunctions!TElement 1718 .changePreProcess!splitValues 1719 .changeParse!((ref T receiver, RawParam param) 1720 { 1721 static if(!isStaticArray!T) 1722 { 1723 if(receiver.length < param.value.length) 1724 receiver.length = param.value.length; 1725 } 1726 1727 foreach(i, value; param.value) 1728 { 1729 if(!DefaultValueParseFunctions!TElement.parse(receiver[i], 1730 RawParam(param.config, param.name, [value]))) 1731 return false; 1732 } 1733 1734 return true; 1735 }) 1736 .changeAction!(action) 1737 .changeNoValueAction!((ref T param) {}); 1738 } 1739 else static if(!isArray!(ForeachType!TElement) || isSomeString!(ForeachType!TElement)) // 2D array 1740 { 1741 alias DefaultValueParseFunctions = DefaultValueParseFunctions!TElement 1742 .changeAction!(Actions.Extend!TElement) 1743 .changeNoValueAction!((ref T param) { param ~= TElement.init; }); 1744 } 1745 else 1746 { 1747 static assert(false, "Multi-dimentional arrays are not supported: " ~ T.stringof); 1748 } 1749 } 1750 else static if(isAssociativeArray!T) 1751 { 1752 import std.string : indexOf; 1753 alias DefaultValueParseFunctions = ValueParseFunctions!( 1754 splitValues, // pre process 1755 void, // pre validate 1756 Parsers.PassThrough, // parse 1757 void, // validate 1758 (ref T recepient, Param!(string[]) param) // action 1759 { 1760 alias K = KeyType!T; 1761 alias V = ValueType!T; 1762 1763 foreach(input; param.value) 1764 { 1765 auto j = indexOf(input, param.config.assignChar); 1766 if(j < 0) 1767 return false; 1768 1769 K key; 1770 if(!DefaultValueParseFunctions!K.parse(key, RawParam(param.config, param.name, [input[0 .. j]]))) 1771 return false; 1772 1773 V value; 1774 if(!DefaultValueParseFunctions!V.parse(value, RawParam(param.config, param.name, [input[j + 1 .. $]]))) 1775 return false; 1776 1777 recepient[key] = value; 1778 } 1779 return true; 1780 }, 1781 (ref T param) {} // no-value action 1782 ); 1783 } 1784 else static if(is(T == delegate)) 1785 { 1786 alias DefaultValueParseFunctions = ValueParseFunctions!( 1787 void, // pre process 1788 void, // pre validate 1789 Parsers.PassThrough, // parse 1790 void, // validate 1791 Actions.CallFunction!T, // action 1792 Actions.CallFunctionNoParam!T // no-value action 1793 ); 1794 } 1795 else 1796 static assert(false, "Type is not supported: " ~ T.stringof); 1797 } 1798 1799 unittest 1800 { 1801 enum MyEnum { foo, bar, } 1802 1803 import std.meta: AliasSeq; 1804 static foreach(T; AliasSeq!(string, bool, int, double, char, MyEnum)) 1805 static foreach(R; AliasSeq!(T, T[], T[][])) 1806 {{ 1807 // ensure that this compiles 1808 R receiver; 1809 RawParam param; 1810 param.value = [""]; 1811 DefaultValueParseFunctions!R.parse(receiver, param); 1812 }} 1813 } 1814 1815 unittest 1816 { 1817 alias test(R) = (string[][] values) 1818 { 1819 auto config = Config('=', ','); 1820 R receiver; 1821 foreach(value; values) 1822 { 1823 assert(DefaultValueParseFunctions!R.parse(receiver, RawParam(config, "", value))); 1824 } 1825 return receiver; 1826 }; 1827 1828 static assert(test!(string[])([["1","2","3"], [], ["4"]]) == ["1","2","3","4"]); 1829 static assert(test!(string[][])([["1","2","3"], [], ["4"]]) == [["1","2","3"],[],["4"]]); 1830 1831 static assert(test!(string[string])([["a=bar","b=foo"], [], ["b=baz","c=boo"]]) == ["a":"bar", "b":"baz", "c":"boo"]); 1832 1833 static assert(test!(string[])([["1,2,3"], [], ["4"]]) == ["1","2","3","4"]); 1834 static assert(test!(string[string])([["a=bar,b=foo"], [], ["b=baz,c=boo"]]) == ["a":"bar", "b":"baz", "c":"boo"]); 1835 1836 static assert(test!(int[])([["1","2","3"], [], ["4"]]) == [1,2,3,4]); 1837 static assert(test!(int[][])([["1","2","3"], [], ["4"]]) == [[1,2,3],[],[4]]); 1838 1839 } 1840 1841 unittest 1842 { 1843 import std.math: isNaN; 1844 enum MyEnum { foo, bar, } 1845 1846 alias test(T) = (string[] values) 1847 { 1848 T receiver; 1849 RawParam param; 1850 param.value = values; 1851 assert(DefaultValueParseFunctions!T.parse(receiver, param)); 1852 return receiver; 1853 }; 1854 1855 static assert(test!string([""]) == ""); 1856 static assert(test!string(["foo"]) == "foo"); 1857 static assert(isNaN(test!double([""]))); 1858 static assert(test!double(["-12.34"]) == -12.34); 1859 static assert(test!double(["12.34"]) == 12.34); 1860 static assert(test!uint(["1234"]) == 1234); 1861 static assert(test!int([""]) == int.init); 1862 static assert(test!int(["-1234"]) == -1234); 1863 static assert(test!char([""]) == char.init); 1864 static assert(test!char(["f"]) == 'f'); 1865 static assert(test!bool([]) == true); 1866 static assert(test!bool([""]) == true); 1867 static assert(test!bool(["yes"]) == true); 1868 static assert(test!bool(["y"]) == true); 1869 static assert(test!bool(["true"]) == true); 1870 static assert(test!bool(["no"]) == false); 1871 static assert(test!bool(["n"]) == false); 1872 static assert(test!bool(["false"]) == false); 1873 static assert(test!MyEnum(["foo"]) == MyEnum.foo); 1874 static assert(test!MyEnum(["bar"]) == MyEnum.bar); 1875 static assert(test!(MyEnum[])(["bar","foo"]) == [MyEnum.bar, MyEnum.foo]); 1876 static assert(test!(string[string])(["a=bar","b=foo"]) == ["a":"bar", "b":"foo"]); 1877 static assert(test!(MyEnum[string])(["a=bar","b=foo"]) == ["a":MyEnum.bar, "b":MyEnum.foo]); 1878 static assert(test!(int[MyEnum])(["bar=3","foo=5"]) == [MyEnum.bar:3, MyEnum.foo:5]); 1879 } 1880 1881 1882 private struct ArgumentInfo 1883 { 1884 string[] names; 1885 1886 string helpText; 1887 1888 bool hideFromHelp = false; // if true then this argument is not printed on help page 1889 1890 bool required; 1891 1892 Nullable!uint position; 1893 1894 @property bool positional() const { return !position.isNull; } 1895 1896 Nullable!ulong minValuesCount; 1897 Nullable!ulong maxValuesCount; 1898 1899 private bool checkValuesCount(in Config config, string argName, ulong count) const 1900 { 1901 immutable min = minValuesCount.get; 1902 immutable max = maxValuesCount.get; 1903 1904 // override for boolean flags 1905 if(allowBooleanNegation && count == 1) 1906 return true; 1907 1908 if(min == max && count != min) 1909 return config.onError("argument ",argName,": expected ",min,min == 1 ? " value" : " values"); 1910 if(count < min) 1911 return config.onError("argument ",argName,": expected at least ",min,min == 1 ? " value" : " values"); 1912 if(count > max) 1913 return config.onError("argument ",argName,": expected at most ",max,max == 1 ? " value" : " values"); 1914 1915 return true; 1916 } 1917 1918 private bool allowBooleanNegation = true; 1919 } 1920 1921 1922 1923 private struct ArgumentUDA(alias ValueParseFunctions) 1924 { 1925 ArgumentInfo info; 1926 1927 alias parsingFunc = ValueParseFunctions; 1928 1929 1930 1931 auto ref HelpText(string text) 1932 { 1933 info.helpText = text; 1934 return this; 1935 } 1936 1937 auto ref HideFromHelp(bool hide = true) 1938 { 1939 info.hideFromHelp = hide; 1940 return this; 1941 } 1942 1943 auto ref Required() 1944 { 1945 info.required = true; 1946 return this; 1947 } 1948 1949 auto ref Optional() 1950 { 1951 info.required = false; 1952 return this; 1953 } 1954 1955 auto ref NumberOfValues(ulong num)() 1956 if(num > 0) 1957 { 1958 info.minValuesCount = num; 1959 info.maxValuesCount = num; 1960 return this; 1961 } 1962 1963 auto ref NumberOfValues(ulong min, ulong max)() 1964 if(0 < min && min <= max) 1965 { 1966 info.minValuesCount = min; 1967 info.maxValuesCount = max; 1968 return this; 1969 } 1970 1971 auto ref MinNumberOfValues(ulong min)() 1972 if(0 < min) 1973 { 1974 assert(min <= info.maxValuesCount.get(ulong.max)); 1975 1976 info.minValuesCount = min; 1977 return this; 1978 } 1979 1980 auto ref MaxNumberOfValues(ulong max)() 1981 if(0 < max) 1982 { 1983 assert(max >= info.minValuesCount.get(0)); 1984 1985 info.maxValuesCount = max; 1986 return this; 1987 } 1988 1989 // ReverseSwitch 1990 } 1991 1992 private enum bool isArgumentUDA(T) = (is(typeof(T.info) == ArgumentInfo) && is(T.parsingFunc)); 1993 1994 1995 auto PreValidation(alias func, ARG)(ARG arg) 1996 if(isArgumentUDA!ARG) 1997 { 1998 return ArgumentUDA!(arg.parsingFunc.changePreValidation!func)(arg.tupleof); 1999 } 2000 2001 auto Parse(alias func, ARG)(ARG arg) 2002 if(isArgumentUDA!ARG) 2003 { 2004 return ArgumentUDA!(arg.parsingFunc.changeParse!func)(arg.tupleof); 2005 } 2006 2007 auto Validation(alias func, ARG)(ARG arg) 2008 if(isArgumentUDA!ARG) 2009 { 2010 return ArgumentUDA!(arg.parsingFunc.changeValidation!func)(arg.tupleof); 2011 } 2012 2013 auto Action(alias func, ARG)(ARG arg) 2014 if(isArgumentUDA!ARG) 2015 { 2016 return ArgumentUDA!(arg.parsingFunc.changeAction!func)(arg.tupleof); 2017 } 2018 2019 auto AllowNoValue(alias valueToUse, ARG)(ARG arg) 2020 if(isArgumentUDA!ARG) 2021 { 2022 auto desc = ArgumentUDA!(arg.parsingFunc.changeNoValueAction!(() { return valueToUse; }))(arg.tupleof); 2023 desc.info.minValuesCount = 0; 2024 return desc; 2025 } 2026 2027 auto RequireNoValue(alias valueToUse, ARG)(ARG arg) 2028 if(isArgumentUDA!ARG) 2029 { 2030 auto desc = arg.AllowNoValue!valueToUse; 2031 desc.info.minValuesCount = 0; 2032 desc.info.maxValuesCount = 0; 2033 return desc; 2034 } 2035 2036 auto Counter(ARG)(ARG arg) 2037 if(isArgumentUDA!ARG) 2038 { 2039 struct CounterParsingFunction 2040 { 2041 static bool parse(T)(ref T receiver, const ref RawParam param) 2042 { 2043 assert(param.value.length == 0); 2044 2045 ++receiver; 2046 2047 return true; 2048 } 2049 } 2050 2051 auto desc = ArgumentUDA!(CounterParsingFunction)(arg.tupleof); 2052 desc.info.minValuesCount = 0; 2053 desc.info.maxValuesCount = 0; 2054 return desc; 2055 } 2056 2057 2058 unittest 2059 { 2060 struct T 2061 { 2062 @(NamedArgument("a").Counter()) int a; 2063 } 2064 2065 static assert(["-a","-a","-a"].parseCLIArgs!T.get == T(3)); 2066 } 2067 2068 2069 auto AllowedValues(alias values, ARG)(ARG arg) 2070 { 2071 import std.array : assocArray; 2072 import std.range : cycle; 2073 2074 enum valuesAA = assocArray(values, cycle([false])); 2075 2076 return arg.Validation!((KeyType!(typeof(valuesAA)) value) => value in valuesAA); 2077 } 2078 2079 2080 unittest 2081 { 2082 struct T 2083 { 2084 @(NamedArgument("a").AllowedValues!([1,3,5])) int a; 2085 } 2086 2087 static assert(["-a","2"].parseCLIArgs!T.isNull); 2088 static assert(["-a","3"].parseCLIArgs!T.get == T(3)); 2089 } 2090 2091 2092 auto PositionalArgument(uint pos) 2093 { 2094 auto arg = ArgumentUDA!(ValueParseFunctions!(void, void, void, void, void, void))(ArgumentInfo()).Required(); 2095 arg.info.position = pos; 2096 return arg; 2097 } 2098 2099 auto PositionalArgument(uint pos, string name) 2100 { 2101 auto arg = ArgumentUDA!(ValueParseFunctions!(void, void, void, void, void, void))(ArgumentInfo([name])).Required(); 2102 arg.info.position = pos; 2103 return arg; 2104 } 2105 2106 auto NamedArgument(string[] name) 2107 { 2108 return ArgumentUDA!(ValueParseFunctions!(void, void, void, void, void, void))(ArgumentInfo(name)).Optional(); 2109 } 2110 2111 auto NamedArgument(string name) 2112 { 2113 return ArgumentUDA!(ValueParseFunctions!(void, void, void, void, void, void))(ArgumentInfo([name])).Optional(); 2114 } 2115 2116 private struct TrailingArgumentUDA 2117 { 2118 } 2119 2120 auto TrailingArguments() 2121 { 2122 return TrailingArgumentUDA(); 2123 } 2124 2125 unittest 2126 { 2127 struct T 2128 { 2129 @NamedArgument("a") string a; 2130 @NamedArgument("b") string b; 2131 2132 @TrailingArguments() string[] args; 2133 } 2134 2135 static assert(["-a","A","--","-b","B"].parseCLIArgs!T.get == T("A","",["-b","B"])); 2136 } 2137 2138 unittest 2139 { 2140 struct T 2141 { 2142 @NamedArgument("i") int i; 2143 @NamedArgument("u") uint u; 2144 @NamedArgument("d") double d; 2145 } 2146 2147 static assert(["-i","-5","-u","8","-d","12.345"].parseCLIArgs!T.get == T(-5,8,12.345)); 2148 } 2149 2150 unittest 2151 { 2152 struct T 2153 { 2154 @(NamedArgument("a")) int[] a; 2155 @(NamedArgument("b")) int[][] b; 2156 } 2157 2158 static assert(["-a","1","2","3","-a","4","5"].parseCLIArgs!T.get.a == [1,2,3,4,5]); 2159 static assert(["-b","1","2","3","-b","4","5"].parseCLIArgs!T.get.b == [[1,2,3],[4,5]]); 2160 } 2161 2162 unittest 2163 { 2164 struct T 2165 { 2166 @(NamedArgument("a")) int[] a; 2167 } 2168 2169 Config cfg; 2170 cfg.arraySep = ','; 2171 2172 assert(["-a","1,2,3","-a","4","5"].parseCLIArgs!T(cfg).get == T([1,2,3,4,5])); 2173 } 2174 2175 unittest 2176 { 2177 struct T 2178 { 2179 @(NamedArgument("a")) int[string] a; 2180 } 2181 2182 static assert(["-a=foo=3","-a","boo=7"].parseCLIArgs!T.get.a == ["foo":3,"boo":7]); 2183 } 2184 2185 unittest 2186 { 2187 struct T 2188 { 2189 @(NamedArgument("a")) int[string] a; 2190 } 2191 2192 Config cfg; 2193 cfg.arraySep = ','; 2194 2195 assert(["-a=foo=3,boo=7"].parseCLIArgs!T(cfg).get.a == ["foo":3,"boo":7]); 2196 assert(["-a","foo=3,boo=7"].parseCLIArgs!T(cfg).get.a == ["foo":3,"boo":7]); 2197 } 2198 2199 unittest 2200 { 2201 struct T 2202 { 2203 enum Fruit { apple, pear }; 2204 2205 @(NamedArgument("a")) Fruit a; 2206 } 2207 2208 static assert(["-a","apple"].parseCLIArgs!T.get == T(T.Fruit.apple)); 2209 static assert(["-a=pear"].parseCLIArgs!T.get == T(T.Fruit.pear)); 2210 } 2211 2212 unittest 2213 { 2214 struct T 2215 { 2216 @(NamedArgument("a")) string[] a; 2217 } 2218 2219 assert(["-a","1,2,3","-a","4","5"].parseCLIArgs!T.get == T(["1,2,3","4","5"])); 2220 2221 Config cfg; 2222 cfg.arraySep = ','; 2223 2224 assert(["-a","1,2,3","-a","4","5"].parseCLIArgs!T(cfg).get == T(["1","2","3","4","5"])); 2225 } 2226 2227 unittest 2228 { 2229 struct T 2230 { 2231 @(NamedArgument("a").AllowNoValue !10) int a; 2232 @(NamedArgument("b").RequireNoValue!20) int b; 2233 } 2234 2235 static assert(["-a"].parseCLIArgs!T.get.a == 10); 2236 static assert(["-b"].parseCLIArgs!T.get.b == 20); 2237 static assert(["-a", "30"].parseCLIArgs!T.get.a == 30); 2238 assert(["-b", "30"].parseCLIArgs!T.isNull); 2239 } 2240 2241 unittest 2242 { 2243 struct T 2244 { 2245 @(NamedArgument("a") 2246 .PreValidation!((string s) { return s.length > 1 && s[0] == '!'; }) 2247 .Parse !((string s) { return s[1]; }) 2248 .Validation !((char v) { return v >= '0' && v <= '9'; }) 2249 .Action !((ref int a, char v) { a = v - '0'; }) 2250 ) 2251 int a; 2252 } 2253 2254 static assert(["-a","!4"].parseCLIArgs!T.get.a == 4); 2255 } 2256 2257 unittest 2258 { 2259 static struct T 2260 { 2261 int a; 2262 2263 @(NamedArgument("a")) void foo() { a++; } 2264 } 2265 2266 static assert(["-a","-a","-a","-a"].parseCLIArgs!T.get.a == 4); 2267 }