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 /** 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 Add a -h/--help option to the parser. 53 Defaults to true. 54 */ 55 bool addHelp = true; 56 57 /** 58 Delegate that processes error messages if they happen during argument parsing. 59 By default all errors are printed to stderr. 60 */ 61 private void delegate(string s) nothrow errorHandlerFunc; 62 63 @property auto errorHandler(void function(string s) nothrow func) 64 { 65 return errorHandlerFunc = (string msg) { func(msg); }; 66 } 67 68 @property auto errorHandler(void delegate(string s) nothrow func) 69 { 70 return errorHandlerFunc = func; 71 } 72 73 74 private void onError(A...)(A args) const nothrow 75 { 76 import std.conv: text; 77 import std.stdio: stderr, writeln; 78 79 try 80 { 81 if(errorHandlerFunc) 82 errorHandlerFunc(text!A(args)); 83 else 84 stderr.writeln("Error: ", args); 85 } 86 catch(Exception e) 87 { 88 throw new Error(e.msg); 89 } 90 } 91 } 92 93 unittest 94 { 95 Config.init.onError("--just testing error func--",1,2.3,false); 96 Config c; 97 c.errorHandler = (string s){}; 98 c.onError("--just testing error func--",1,2.3,false); 99 } 100 101 102 struct Param(VALUE_TYPE) 103 { 104 const Config config; 105 string name; 106 107 static if(!is(VALUE_TYPE == void)) 108 VALUE_TYPE value; 109 } 110 111 alias RawParam = Param!(string[]); 112 113 114 private enum ArgumentType { unknown, positional, shortName, longName } 115 116 private auto splitArgumentName(string arg, const Config config) 117 { 118 import std.typecons : nullable; 119 import std.string : indexOf; 120 121 struct Result 122 { 123 ArgumentType type = ArgumentType.unknown; 124 string name; 125 string origName; 126 Nullable!string value; 127 } 128 129 if(arg.length == 0) 130 return Result.init; 131 132 if(arg[0] != config.namedArgChar) 133 return Result(ArgumentType.positional, string.init, string.init, nullable(arg)); 134 135 if(arg.length == 1) 136 return Result.init; 137 138 Result result; 139 140 auto idxAssignChar = config.assignChar == char.init ? -1 : arg.indexOf(config.assignChar); 141 if(idxAssignChar < 0) 142 result.origName = arg; 143 else 144 { 145 result.origName = arg[0 .. idxAssignChar]; 146 result.value = nullable(arg[idxAssignChar + 1 .. $]); 147 } 148 149 if(result.origName[1] == config.namedArgChar) 150 { 151 result.type = ArgumentType.longName; 152 result.name = result.origName[2..$]; 153 } 154 else 155 { 156 result.type = ArgumentType.shortName; 157 result.name = result.origName[1..$]; 158 } 159 160 return result; 161 } 162 163 unittest 164 { 165 import std.typecons : tuple, nullable; 166 167 static assert(splitArgumentName("", Config.init).tupleof == tuple(ArgumentType.init, string.init, string.init, Nullable!string.init).tupleof); 168 static assert(splitArgumentName("-", Config.init).tupleof == tuple(ArgumentType.init, string.init, string.init, Nullable!string.init).tupleof); 169 static assert(splitArgumentName("abc=4", Config.init).tupleof == tuple(ArgumentType.positional, string.init, string.init, "abc=4").tupleof); 170 static assert(splitArgumentName("-abc", Config.init).tupleof == tuple(ArgumentType.shortName, "abc", "-abc", Nullable!string.init).tupleof); 171 static assert(splitArgumentName("--abc", Config.init).tupleof == tuple(ArgumentType.longName, "abc", "--abc", Nullable!string.init).tupleof); 172 static assert(splitArgumentName("-abc=fd", Config.init).tupleof == tuple(ArgumentType.shortName, "abc", "-abc", "fd").tupleof); 173 static assert(splitArgumentName("--abc=fd", Config.init).tupleof == tuple(ArgumentType.longName, "abc", "--abc", "fd").tupleof); 174 static assert(splitArgumentName("-abc=", Config.init).tupleof == tuple(ArgumentType.shortName, "abc", "-abc", nullable("")).tupleof); 175 static assert(splitArgumentName("--abc=", Config.init).tupleof == tuple(ArgumentType.longName, "abc", "--abc", nullable("")).tupleof); 176 static assert(splitArgumentName("-=abc", Config.init).tupleof == tuple(ArgumentType.shortName, string.init, "-", "abc").tupleof); 177 static assert(splitArgumentName("--=abc", Config.init).tupleof == tuple(ArgumentType.longName, string.init, "--", "abc").tupleof); 178 } 179 180 181 private template defaultValuesCount(T) 182 if(!is(T == void)) 183 { 184 import std.traits; 185 186 static if(isBoolean!T) 187 { 188 enum min = 0; 189 enum max = 1; 190 } 191 else static if(isSomeString!T || isScalarType!T) 192 { 193 enum min = 1; 194 enum max = 1; 195 } 196 else static if(isStaticArray!T) 197 { 198 enum min = 1; 199 enum max = T.length; 200 } 201 else static if(isArray!T || isAssociativeArray!T) 202 { 203 enum min = 1; 204 enum max = ulong.max; 205 } 206 else static if(is(T == function)) 207 { 208 // ... function() 209 static if(__traits(compiles, { T(); })) 210 { 211 enum min = 0; 212 enum max = 0; 213 } 214 // ... function(string value) 215 else static if(__traits(compiles, { T(string.init); })) 216 { 217 enum min = 1; 218 enum max = 1; 219 } 220 // ... function(string[] value) 221 else static if(__traits(compiles, { T([string.init]); })) 222 { 223 enum min = 0; 224 enum max = ulong.max; 225 } 226 // ... function(RawParam param) 227 else static if(__traits(compiles, { T(RawParam.init); })) 228 { 229 enum min = 1; 230 enum max = ulong.max; 231 } 232 else 233 static assert(false, "Unsupported callback: " ~ T.stringof); 234 } 235 else 236 static assert(false, "Type is not supported: " ~ T.stringof); 237 } 238 239 240 private template EnumMembersAsStrings(E) 241 { 242 enum EnumMembersAsStrings = { 243 import std.traits: EnumMembers; 244 alias members = EnumMembers!E; 245 246 typeof(__traits(identifier, members[0]))[] res; 247 static foreach (i, _; members) 248 res ~= __traits(identifier, members[i]); 249 250 return res; 251 }(); 252 } 253 254 unittest 255 { 256 enum E { abc, def, ghi } 257 assert(EnumMembersAsStrings!E == ["abc", "def", "ghi"]); 258 } 259 260 private auto setDefaults(TYPE, alias symbol)(ArgumentInfo info) 261 { 262 static if(!isBoolean!TYPE) 263 info.allowBooleanNegation = false; 264 265 static if(is(TYPE == enum)) 266 info.setAllowedValues!(EnumMembersAsStrings!TYPE); 267 268 if(info.names.length == 0) 269 info.names = [ symbol ]; 270 271 if(info.minValuesCount.isNull) info.minValuesCount = defaultValuesCount!TYPE.min; 272 if(info.maxValuesCount.isNull) info.maxValuesCount = defaultValuesCount!TYPE.max; 273 274 if(info.placeholder.length == 0) 275 { 276 import std.uni : toUpper; 277 info.placeholder = info.positional ? symbol : symbol.toUpper; 278 } 279 280 return info; 281 } 282 283 unittest 284 { 285 ArgumentInfo info; 286 info.allowBooleanNegation = true; 287 info.position = 0; 288 289 auto res = info.setDefaults!(int, "default-name"); 290 assert(!res.allowBooleanNegation); 291 assert(res.names == [ "default-name" ]); 292 assert(res.minValuesCount == defaultValuesCount!int.min); 293 assert(res.maxValuesCount == defaultValuesCount!int.max); 294 assert(res.placeholder == "default-name"); 295 296 info.placeholder = "myvalue"; 297 res = info.setDefaults!(int, "default-name"); 298 assert(res.placeholder == "myvalue"); 299 } 300 301 unittest 302 { 303 ArgumentInfo info; 304 info.allowBooleanNegation = true; 305 306 auto res = info.setDefaults!(bool, "default_name"); 307 assert(res.allowBooleanNegation); 308 assert(res.names == ["default_name"]); 309 assert(res.minValuesCount == defaultValuesCount!bool.min); 310 assert(res.maxValuesCount == defaultValuesCount!bool.max); 311 assert(res.placeholder == "DEFAULT_NAME"); 312 313 info.placeholder = "myvalue"; 314 res = info.setDefaults!(bool, "default_name"); 315 assert(res.placeholder == "myvalue"); 316 } 317 318 unittest 319 { 320 enum E { a=1, b=1, c } 321 static assert(EnumMembersAsStrings!E == ["a","b","c"]); 322 323 ArgumentInfo info; 324 auto res = info.setDefaults!(E, "default-name"); 325 assert(res.placeholder == "{a,b,c}"); 326 327 info.placeholder = "myvalue"; 328 res = info.setDefaults!(E, "default-name"); 329 assert(res.placeholder == "myvalue"); 330 } 331 332 333 private auto checkDuplicates(alias sortedRange, string errorMsg)() { 334 static if(sortedRange.length >= 2) 335 { 336 enum value = { 337 import std.conv : to; 338 339 foreach(i; 1..sortedRange.length-1) 340 if(sortedRange[i-1] == sortedRange[i]) 341 return sortedRange[i].to!string; 342 343 return ""; 344 }(); 345 static assert(value.length == 0, errorMsg ~ value); 346 } 347 348 return true; 349 } 350 351 private bool checkArgumentNames(T)() 352 { 353 enum names = () { 354 import std.algorithm : sort; 355 356 string[] names; 357 static foreach (sym; getSymbolsByUDA!(T, ArgumentUDA)) 358 {{ 359 enum argUDA = getUDAs!(__traits(getMember, T, __traits(identifier, sym)), ArgumentUDA)[0]; 360 361 static assert(!argUDA.info.positional || argUDA.info.names.length <= 1, 362 "Positional argument should have exactly one name: "~T.stringof~"."~sym.stringof); 363 364 static foreach (name; argUDA.info.names) 365 { 366 static assert(name.length > 0, "Argument name can't be empty: "~T.stringof~"."~sym.stringof); 367 368 names ~= name; 369 } 370 }} 371 372 return names.sort; 373 }(); 374 375 return checkDuplicates!(names, "Argument name appears more than once: "); 376 } 377 378 private bool checkPositionalIndexes(T)() 379 { 380 import std.conv : to; 381 import std.range : lockstep, iota; 382 383 384 enum positions = () { 385 import std.algorithm : sort; 386 387 uint[] positions; 388 static foreach (sym; getSymbolsByUDA!(T, ArgumentUDA)) 389 {{ 390 enum argUDA = getUDAs!(__traits(getMember, T, __traits(identifier, sym)), ArgumentUDA)[0]; 391 392 static if (argUDA.info.positional) 393 positions ~= argUDA.info.position.get; 394 }} 395 396 return positions.sort; 397 }(); 398 399 if(!checkDuplicates!(positions, "Positional arguments have duplicated position: ")) 400 return false; 401 402 static foreach (i, pos; lockstep(iota(0, positions.length), positions)) 403 static assert(i == pos, "Positional arguments have missed position: " ~ i.to!string); 404 405 return true; 406 } 407 408 private struct Group 409 { 410 string name; 411 string description; 412 413 private size_t[] arguments; 414 415 auto ref Description(string text) 416 { 417 description = text; 418 return this; 419 } 420 421 } 422 423 auto ArgumentGroup(string name) 424 { 425 return Group(name); 426 } 427 428 unittest 429 { 430 auto g = ArgumentGroup("name").Description("description"); 431 assert(g.name == "name"); 432 assert(g.description == "description"); 433 } 434 435 436 private struct RestrictionGroup 437 { 438 string location; 439 440 enum Type { together, exclusive } 441 Type type; 442 443 private size_t[] arguments; 444 } 445 446 auto RequiredTogether(string file=__FILE__, uint line = __LINE__)() 447 { 448 import std.conv: to; 449 return RestrictionGroup(file~":"~line.to!string, RestrictionGroup.Type.together); 450 } 451 452 auto MutuallyExclusive(string file=__FILE__, uint line = __LINE__)() 453 { 454 import std.conv: to; 455 return RestrictionGroup(file~":"~line.to!string, RestrictionGroup.Type.exclusive); 456 } 457 458 unittest 459 { 460 auto t = RequiredTogether(); 461 assert(t.location.length > 0); 462 assert(t.type == RestrictionGroup.Type.together); 463 464 auto e = MutuallyExclusive(); 465 assert(e.location.length > 0); 466 assert(e.type == RestrictionGroup.Type.exclusive); 467 } 468 469 470 private alias ParseFunction(RECEIVER) = bool delegate(in Config config, string argName, ref RECEIVER receiver, string[] rawValues); 471 private alias Restriction = bool delegate(in Config config, in bool[size_t] cliArgs); 472 473 // Have to do this magic because closures are not supported in CFTE 474 // DMD v2.098.0 prints "Error: closures are not yet supported in CTFE" 475 auto partiallyApply(alias fun,C...)(C context) 476 { 477 import std.traits: ParameterTypeTuple; 478 import core.lifetime: move, forward; 479 480 return &new class(move(context)) 481 { 482 C context; 483 484 this(C ctx) 485 { 486 foreach(i, ref c; context) 487 c = move(ctx[i]); 488 } 489 490 auto opCall(ParameterTypeTuple!fun[context.length..$] args) const 491 { 492 return fun(context, forward!args); 493 } 494 }.opCall; 495 } 496 497 private struct Restrictions 498 { 499 static Restriction RequiredArg(ArgumentInfo info)(size_t index) 500 { 501 return partiallyApply!((size_t index, in Config config, in bool[size_t] cliArgs) 502 { 503 if(index in cliArgs) 504 return true; 505 506 config.onError("The following argument is required: ", info.names[0].getArgumentName(config)); 507 return false; 508 })(index); 509 } 510 511 static bool RequiredTogether(in Config config, 512 in bool[size_t] cliArgs, 513 in size_t[] restrictionArgs, 514 in ArgumentInfo[] allArgs) 515 { 516 size_t foundIndex = size_t.max; 517 size_t missedIndex = size_t.max; 518 519 foreach(index; restrictionArgs) 520 { 521 if(index in cliArgs) 522 { 523 if(foundIndex == size_t.max) 524 foundIndex = index; 525 } 526 else if(missedIndex == size_t.max) 527 missedIndex = index; 528 529 if(foundIndex != size_t.max && missedIndex != size_t.max) 530 { 531 config.onError("Missed argument '", allArgs[missedIndex].names[0].getArgumentName(config), 532 "' - it is required by argument '", allArgs[foundIndex].names[0].getArgumentName(config),"'"); 533 return false; 534 } 535 } 536 537 return true; 538 } 539 540 static bool MutuallyExclusive(in Config config, 541 in bool[size_t] cliArgs, 542 in size_t[] restrictionArgs, 543 in ArgumentInfo[] allArgs) 544 { 545 size_t foundIndex = size_t.max; 546 547 foreach(index; restrictionArgs) 548 if(index in cliArgs) 549 { 550 if(foundIndex == size_t.max) 551 foundIndex = index; 552 else 553 { 554 config.onError("Argument '", allArgs[foundIndex].names[0].getArgumentName(config), 555 "' is not allowed with argument '", allArgs[index].names[0].getArgumentName(config),"'"); 556 return false; 557 } 558 559 } 560 561 return true; 562 } 563 } 564 565 private struct Arguments(RECEIVER) 566 { 567 static assert(getSymbolsByUDA!(RECEIVER, TrailingArguments).length <= 1, 568 "Type "~RECEIVER.stringof~" must have at most one 'TrailingArguments' UDA"); 569 570 private enum _validate = checkArgumentNames!RECEIVER && 571 checkPositionalIndexes!RECEIVER; 572 573 574 575 immutable string function(string str) convertCase; 576 577 private ArgumentInfo[] arguments; 578 private ParseFunction!RECEIVER[] parseFunctions; 579 580 // named arguments 581 private size_t[string] argsNamed; 582 583 // positional arguments 584 private size_t[] argsPositional; 585 586 587 Group[] groups; 588 enum requiredGroupIndex = 0; 589 enum optionalGroupIndex = 1; 590 591 size_t[string] groupsByName; 592 593 Restriction[] restrictions; 594 RestrictionGroup[] restrictionGroups; 595 596 @property ref Group requiredGroup() { return groups[requiredGroupIndex]; } 597 @property ref const(Group) requiredGroup() const { return groups[requiredGroupIndex]; } 598 @property ref Group optionalGroup() { return groups[optionalGroupIndex]; } 599 @property ref const(Group) optionalGroup() const { return groups[optionalGroupIndex]; } 600 601 @property auto positionalArguments() const { return argsPositional; } 602 603 604 this(bool caseSensitive) 605 { 606 if(caseSensitive) 607 convertCase = s => s; 608 else 609 convertCase = (string str) 610 { 611 import std.uni : toUpper; 612 return str.toUpper; 613 }; 614 615 groups = [ Group("Required arguments"), Group("Optional arguments") ]; 616 } 617 618 private void addArgument(ArgumentInfo info, RestrictionGroup[] restrictions, Group group)(ParseFunction!RECEIVER parse) 619 { 620 auto index = (group.name in groupsByName); 621 if(index !is null) 622 addArgument!(info, restrictions)(parse, groups[*index]); 623 else 624 { 625 groupsByName[group.name] = groups.length; 626 groups ~= group; 627 addArgument!(info, restrictions)(parse, groups[$-1]); 628 } 629 } 630 631 private void addArgument(ArgumentInfo info, RestrictionGroup[] restrictions = [])(ParseFunction!RECEIVER parse) 632 { 633 static if(info.required) 634 addArgument!(info, restrictions)(parse, requiredGroup); 635 else 636 addArgument!(info, restrictions)(parse, optionalGroup); 637 } 638 639 private void addArgument(ArgumentInfo info, RestrictionGroup[] argRestrictions = [])(ParseFunction!RECEIVER parse, ref Group group) 640 { 641 static assert(info.names.length > 0); 642 643 immutable index = arguments.length; 644 645 static if(info.positional) 646 { 647 if(argsPositional.length <= info.position.get) 648 argsPositional.length = info.position.get + 1; 649 650 argsPositional[info.position.get] = index; 651 } 652 else 653 static foreach(name; info.names) 654 { 655 assert(!(name in argsNamed), "Duplicated argument name: "~name); 656 argsNamed[convertCase(name)] = index; 657 } 658 659 arguments ~= info; 660 parseFunctions ~= parse; 661 group.arguments ~= index; 662 663 static if(info.required) 664 restrictions ~= Restrictions.RequiredArg!info(index); 665 666 static foreach(restriction; argRestrictions) 667 addRestriction!(info, restriction)(index); 668 } 669 670 private void addRestriction(ArgumentInfo info, RestrictionGroup restriction)(size_t argIndex) 671 { 672 auto groupIndex = (restriction.location in groupsByName); 673 auto index = groupIndex !is null 674 ? *groupIndex 675 : { 676 auto index = groupsByName[restriction.location] = restrictionGroups.length; 677 restrictionGroups ~= restriction; 678 return index; 679 }(); 680 681 restrictionGroups[index].arguments ~= argIndex; 682 } 683 684 685 private bool checkRestrictions(in bool[size_t] cliArgs, in Config config) const 686 { 687 foreach(restriction; restrictions) 688 if(!restriction(config, cliArgs)) 689 return false; 690 691 foreach(restriction; restrictionGroups) 692 final switch(restriction.type) 693 { 694 case RestrictionGroup.Type.together: 695 if(!Restrictions.RequiredTogether(config, cliArgs, restriction.arguments, arguments)) 696 return false; 697 break; 698 case RestrictionGroup.Type.exclusive: 699 if(!Restrictions.MutuallyExclusive(config, cliArgs, restriction.arguments, arguments)) 700 return false; 701 break; 702 } 703 704 return true; 705 } 706 707 708 private auto findArgumentImpl(const size_t* pIndex) const 709 { 710 import std.typecons : Tuple; 711 712 alias Result = Tuple!(size_t, "index", const(ArgumentInfo)*, "arg", ParseFunction!RECEIVER, "parse"); 713 714 return pIndex ? Result(*pIndex, &arguments[*pIndex], parseFunctions[*pIndex]) : Result(size_t.max, null, null); 715 } 716 717 auto findPositionalArgument(size_t position) const 718 { 719 return findArgumentImpl(position < argsPositional.length ? &argsPositional[position] : null); 720 } 721 722 auto findNamedArgument(string name) const 723 { 724 return findArgumentImpl(convertCase(name) in argsNamed); 725 } 726 727 static if(getSymbolsByUDA!(RECEIVER, TrailingArguments).length == 1) 728 { 729 private void setTrailingArgs(ref RECEIVER receiver, string[] rawValues) const 730 { 731 enum symbol = __traits(identifier, getSymbolsByUDA!(RECEIVER, TrailingArguments)[0]); 732 auto target = &__traits(getMember, receiver, symbol); 733 734 static if(__traits(compiles, { *target = rawValues; })) 735 *target = rawValues; 736 else 737 static assert(false, "Type '"~typeof(*target).stringof~"' of `"~ 738 RECEIVER.stringof~"."~symbol~"` is not supported for 'TrailingArguments' UDA"); 739 } 740 } 741 } 742 743 private alias ParsingFunction(alias symbol, alias uda, ArgumentInfo info, RECEIVER) = 744 delegate(in Config config, string argName, ref RECEIVER receiver, string[] rawValues) 745 { 746 try 747 { 748 if(!info.checkValuesCount(config, argName, rawValues.length)) 749 return false; 750 751 auto param = RawParam(config, argName, rawValues); 752 753 auto target = &__traits(getMember, receiver, symbol); 754 755 static if(is(typeof(target) == function) || is(typeof(target) == delegate)) 756 return uda.parsingFunc.parse(target, param); 757 else 758 return uda.parsingFunc.parse(*target, param); 759 } 760 catch(Exception e) 761 { 762 config.onError(argName, ": ", e.msg); 763 return false; 764 } 765 }; 766 767 768 private void addArgument(alias symbol, RECEIVER)(ref Arguments!RECEIVER args) 769 { 770 alias member = __traits(getMember, RECEIVER, symbol); 771 772 static assert(getUDAs!(member, ArgumentUDA).length <= 1, 773 "Member "~RECEIVER.stringof~"."~symbol~" has multiple '*Argument' UDAs"); 774 775 static assert(getUDAs!(member, Group).length <= 1, 776 "Member "~RECEIVER.stringof~"."~symbol~" has multiple 'Group' UDAs"); 777 778 static if(getUDAs!(member, ArgumentUDA).length > 0) 779 enum uda = getUDAs!(member, ArgumentUDA)[0]; 780 else 781 enum uda = NamedArgument(); 782 783 enum info = uda.info.setDefaults!(typeof(member), symbol); 784 785 enum restrictions = { 786 RestrictionGroup[] restrictions; 787 static foreach(gr; getUDAs!(member, RestrictionGroup)) 788 restrictions ~= gr; 789 return restrictions; 790 }(); 791 792 static if(getUDAs!(member, Group).length > 0) 793 args.addArgument!(info, restrictions, getUDAs!(member, Group)[0])(ParsingFunction!(symbol, uda, info, RECEIVER)); 794 else 795 args.addArgument!(info, restrictions)(ParsingFunction!(symbol, uda, info, RECEIVER)); 796 } 797 798 private auto createArguments(RECEIVER)(bool caseSensitive) 799 { 800 auto args = Arguments!RECEIVER(caseSensitive); 801 802 enum hasNoUDAs = getSymbolsByUDA!(RECEIVER, ArgumentUDA ).length == 0 && 803 getSymbolsByUDA!(RECEIVER, NamedArgument).length == 0; 804 805 static foreach(sym; __traits(allMembers, RECEIVER)) 806 {{ 807 alias mem = __traits(getMember,RECEIVER,sym); 808 809 static if(!is(mem)) // skip types 810 static if(hasNoUDAs || hasUDA!(mem, ArgumentUDA) || hasUDA!(mem, NamedArgument)) 811 addArgument!(sym)(args); 812 }} 813 814 return args; 815 } 816 817 unittest 818 { 819 struct T 820 { 821 @(NamedArgument) 822 int a; 823 @(NamedArgument.Optional()) 824 int b; 825 @(NamedArgument.Required()) 826 int c; 827 @(NamedArgument) 828 int d; 829 @(NamedArgument.Required()) 830 int e; 831 @(NamedArgument) 832 int f; 833 } 834 static assert(createArguments!T(true).arguments.length == 6); 835 836 auto a = createArguments!T(true); 837 assert(a.requiredGroup.arguments == [2,4]); 838 assert(a.argsNamed == ["a":0LU, "b":1LU, "c":2LU, "d":3LU, "e":4LU, "f":5LU]); 839 assert(a.argsPositional == []); 840 } 841 842 unittest 843 { 844 struct T 845 { 846 int a,b,c,d,e,f; 847 } 848 static assert(createArguments!T(true).arguments.length == 6); 849 850 auto a = createArguments!T(true); 851 assert(a.requiredGroup.arguments == []); 852 assert(a.argsNamed == ["a":0LU, "b":1LU, "c":2LU, "d":3LU, "e":4LU, "f":5LU]); 853 assert(a.argsPositional == []); 854 } 855 856 unittest 857 { 858 struct T1 859 { 860 @(NamedArgument("1")) 861 @(NamedArgument("2")) 862 int a; 863 } 864 static assert(!__traits(compiles, { createArguments!T1(true); })); 865 866 struct T2 867 { 868 @(NamedArgument("1")) 869 int a; 870 @(NamedArgument("1")) 871 int b; 872 } 873 static assert(!__traits(compiles, { createArguments!T1(true); })); 874 875 struct T3 876 { 877 @(PositionalArgument(0)) int a; 878 @(PositionalArgument(0)) int b; 879 } 880 static assert(!__traits(compiles, { createArguments!T3(true); })); 881 882 struct T4 883 { 884 @(PositionalArgument(0)) int a; 885 @(PositionalArgument(2)) int b; 886 } 887 static assert(!__traits(compiles, { createArguments!T4(true); })); 888 } 889 890 private void checkArgumentName(T)(char namedArgChar) 891 { 892 import std.exception: enforce; 893 894 static foreach(sym; getSymbolsByUDA!(T, ArgumentUDA)) 895 static foreach(name; getUDAs!(__traits(getMember, T, __traits(identifier, sym)), ArgumentUDA)[0].info.names) 896 enforce(name[0] != namedArgChar, "Name of argument should not begin with '"~namedArgChar~"': "~name); 897 } 898 899 private auto consumeValuesFromCLI(ref string[] args, in ArgumentInfo argumentInfo, in Config config) 900 { 901 import std.range: empty, front, popFront; 902 903 immutable minValuesCount = argumentInfo.minValuesCount.get; 904 immutable maxValuesCount = argumentInfo.maxValuesCount.get; 905 906 string[] values; 907 908 if(minValuesCount > 0) 909 { 910 if(minValuesCount < args.length) 911 { 912 values = args[0..minValuesCount]; 913 args = args[minValuesCount..$]; 914 } 915 else 916 { 917 values = args; 918 args = []; 919 } 920 } 921 922 while(!args.empty && 923 values.length < maxValuesCount && 924 (config.endOfArgs.length == 0 || args.front != config.endOfArgs) && 925 (args.front.length == 0 || args.front[0] != config.namedArgChar)) 926 { 927 values ~= args.front; 928 args.popFront(); 929 } 930 931 return values; 932 } 933 934 935 private enum helpArgument = { 936 ArgumentInfo arg; 937 arg.names = ["h","help"]; 938 arg.description = "Show this help message and exit"; 939 arg.minValuesCount = 0; 940 arg.maxValuesCount = 0; 941 arg.allowBooleanNegation = false; 942 arg.parsingTerminateCode = 0; 943 return arg; 944 }(); 945 946 struct ParseCLIResult 947 { 948 int resultCode; 949 950 private bool done; 951 952 bool opCast(type)() if (is(type == bool)) 953 { 954 return done; 955 } 956 957 private static enum failure = ParseCLIResult(1); 958 private static enum success = ParseCLIResult(0, true); 959 } 960 961 private ParseCLIResult parseCLIKnownArgs(T)(ref T receiver, 962 string[] args, 963 out string[] unrecognizedArgs, 964 const ref CommandArguments!T command, 965 in Config config) 966 { 967 import std.algorithm: map; 968 import std.array: assocArray; 969 import std.range: repeat, empty, front, popFront, join; 970 import std.typecons : tuple; 971 972 checkArgumentName!T(config.namedArgChar); 973 974 bool[size_t] cliArgs; 975 976 alias parseNamedArg = (arg, res) { 977 args.popFront(); 978 979 auto values = arg.value.isNull ? consumeValuesFromCLI(args, *res.arg, config) : [ arg.value.get ]; 980 981 if(!res.parse(config, arg.origName, receiver, values)) 982 return false; 983 984 cliArgs[res.index] = true; 985 986 return true; 987 }; 988 989 size_t positionalArgIdx = 0; 990 991 while(!args.empty) 992 { 993 if(config.endOfArgs.length > 0 && args.front == config.endOfArgs) 994 { 995 // End of arguments 996 static if(is(typeof(command.arguments.setTrailingArgs))) 997 command.arguments.setTrailingArgs(receiver, args[1..$]); 998 else 999 unrecognizedArgs ~= args[1..$]; 1000 break; 1001 } 1002 1003 auto arg = splitArgumentName(args.front, config); 1004 1005 final switch(arg.type) 1006 { 1007 case ArgumentType.positional: 1008 { 1009 auto res = command.arguments.findPositionalArgument(positionalArgIdx); 1010 if(res.arg is null) 1011 goto case ArgumentType.unknown; 1012 1013 auto values = consumeValuesFromCLI(args, *res.arg, config); 1014 1015 if(!res.parse(config, res.arg.names[0], receiver, values)) 1016 return ParseCLIResult.failure; 1017 1018 positionalArgIdx++; 1019 1020 cliArgs[res.index] = true; 1021 1022 break; 1023 } 1024 1025 case ArgumentType.longName: 1026 { 1027 if(arg.name.length == 0) 1028 { 1029 config.onError("Empty argument name: ", args.front); 1030 return ParseCLIResult.failure; 1031 } 1032 1033 auto res = command.arguments.findNamedArgument(arg.name); 1034 if(res.arg !is null) 1035 { 1036 if(!parseNamedArg(arg, res)) 1037 return ParseCLIResult.failure; 1038 1039 if(!res.arg.parsingTerminateCode.isNull) 1040 return ParseCLIResult(res.arg.parsingTerminateCode.get); 1041 1042 break; 1043 } 1044 1045 import std.algorithm : startsWith; 1046 1047 if(arg.name.startsWith("no-")) 1048 { 1049 res = command.arguments.findNamedArgument(arg.name[3..$]); 1050 if(res.arg !is null && res.arg.allowBooleanNegation) 1051 { 1052 args.popFront(); 1053 1054 if(!res.parse(config, arg.origName, receiver, ["false"])) 1055 return ParseCLIResult.failure; 1056 1057 cliArgs[res.index] = true; 1058 1059 break; 1060 } 1061 } 1062 1063 goto case ArgumentType.unknown; 1064 } 1065 1066 case ArgumentType.shortName: 1067 { 1068 if(arg.name.length == 0) 1069 { 1070 config.onError("Empty argument name: ", args.front); 1071 return ParseCLIResult.failure; 1072 } 1073 1074 auto res = command.arguments.findNamedArgument(arg.name); 1075 if(res.arg !is null) 1076 { 1077 if(!parseNamedArg(arg, res)) 1078 return ParseCLIResult.failure; 1079 1080 if(!res.arg.parsingTerminateCode.isNull) 1081 return ParseCLIResult(res.arg.parsingTerminateCode.get); 1082 1083 break; 1084 } 1085 1086 if(arg.name.length == 1) 1087 goto case ArgumentType.unknown; 1088 1089 if(!config.bundling) 1090 { 1091 auto name = [arg.name[0]]; 1092 res = command.arguments.findNamedArgument(name); 1093 if(res.arg is null || res.arg.minValuesCount != 1) 1094 goto case ArgumentType.unknown; 1095 1096 if(!res.parse(config, "-"~name, receiver, [arg.name[1..$]])) 1097 return ParseCLIResult.failure; 1098 1099 cliArgs[res.index] = true; 1100 1101 if(!res.arg.parsingTerminateCode.isNull) 1102 return ParseCLIResult(res.arg.parsingTerminateCode.get); 1103 1104 args.popFront(); 1105 break; 1106 } 1107 else 1108 { 1109 while(arg.name.length > 0) 1110 { 1111 auto name = [arg.name[0]]; 1112 res = command.arguments.findNamedArgument(name); 1113 if(res.arg is null) 1114 goto case ArgumentType.unknown; 1115 1116 if(res.arg.minValuesCount == 0) 1117 { 1118 if(!res.parse(config, "-"~name, receiver, [])) 1119 return ParseCLIResult.failure; 1120 1121 cliArgs[res.index] = true; 1122 1123 arg.name = arg.name[1..$]; 1124 } 1125 else if(res.arg.minValuesCount == 1) 1126 { 1127 if(!res.parse(config, "-"~name, receiver, [arg.name[1..$]])) 1128 return ParseCLIResult.failure; 1129 1130 cliArgs[res.index] = true; 1131 1132 arg.name = []; 1133 } 1134 else 1135 { 1136 // trigger an error 1137 res.arg.checkValuesCount(config, name, 1); 1138 return ParseCLIResult.failure; 1139 } 1140 1141 if(!res.arg.parsingTerminateCode.isNull) 1142 return ParseCLIResult(res.arg.parsingTerminateCode.get); 1143 } 1144 1145 if(arg.name.length == 0) 1146 { 1147 args.popFront(); 1148 break; 1149 } 1150 } 1151 1152 goto case ArgumentType.unknown; 1153 } 1154 1155 case ArgumentType.unknown: 1156 unrecognizedArgs ~= args.front; 1157 args.popFront(); 1158 } 1159 } 1160 1161 if(!command.arguments.checkRestrictions(cliArgs, config)) 1162 return ParseCLIResult.failure; 1163 1164 return ParseCLIResult.success; 1165 } 1166 1167 ParseCLIResult parseCLIKnownArgs(T)(ref T receiver, 1168 string[] args, 1169 out string[] unrecognizedArgs, 1170 in Config config = Config.init) 1171 { 1172 auto command = CommandArguments!T(config); 1173 return parseCLIKnownArgs(receiver, args, unrecognizedArgs, command, config); 1174 } 1175 1176 auto parseCLIKnownArgs(T)(ref T receiver, ref string[] args, in Config config = Config.init) 1177 { 1178 string[] unrecognizedArgs; 1179 1180 auto res = parseCLIKnownArgs(receiver, args, unrecognizedArgs, config); 1181 if(res) 1182 args = unrecognizedArgs; 1183 1184 return res; 1185 } 1186 1187 Nullable!T parseCLIKnownArgs(T)(ref string[] args, in Config config = Config.init) 1188 { 1189 import std.typecons : nullable; 1190 1191 T receiver; 1192 1193 return parseCLIKnownArgs(receiver, args, config) ? receiver.nullable : Nullable!T.init; 1194 } 1195 1196 int parseCLIKnownArgs(T, FUNC)(string[] args, FUNC func, in Config config = Config.init, T initialValue = T.init) 1197 if(__traits(compiles, { func(T.init, args); })) 1198 { 1199 alias value = initialValue; 1200 1201 auto res = parseCLIKnownArgs(value, args, config); 1202 if(!res) 1203 return res.resultCode; 1204 1205 static if(__traits(compiles, { int a = cast(int) func(value, args); })) 1206 return cast(int) func(value, args); 1207 else 1208 { 1209 func(value, args); 1210 return 0; 1211 } 1212 } 1213 1214 1215 auto parseCLIArgs(T)(ref T receiver, string[] args, in Config config = Config.init) 1216 { 1217 string[] unrecognizedArgs; 1218 1219 auto res = parseCLIKnownArgs(receiver, args, unrecognizedArgs, config); 1220 1221 if(res && unrecognizedArgs.length > 0) 1222 { 1223 config.onError("Unrecognized arguments: ", unrecognizedArgs); 1224 return ParseCLIResult.failure; 1225 } 1226 1227 return res; 1228 } 1229 1230 Nullable!T parseCLIArgs(T)(string[] args, in Config config = Config.init) 1231 { 1232 import std.typecons : nullable; 1233 1234 T receiver; 1235 1236 return parseCLIArgs(receiver, args, config) ? receiver.nullable : Nullable!T.init; 1237 } 1238 1239 int parseCLIArgs(T, FUNC)(string[] args, FUNC func, in Config config = Config.init, T initialValue = T.init) 1240 if(__traits(compiles, { func(T.init); })) 1241 { 1242 alias value = initialValue; 1243 1244 auto res = parseCLIArgs(value, args, config); 1245 if(!res) 1246 return res.resultCode; 1247 1248 static if(__traits(compiles, { int a = cast(int) func(value); })) 1249 return cast(int) func(value); 1250 else 1251 { 1252 func(value); 1253 return 0; 1254 } 1255 } 1256 1257 unittest 1258 { 1259 import std.exception; 1260 1261 struct T 1262 { 1263 @(NamedArgument("--")) 1264 int a; 1265 } 1266 static assert(!__traits(compiles, { enum p = parseCLIArgs!T([]); })); 1267 assertThrown(parseCLIArgs!T([])); 1268 } 1269 1270 unittest 1271 { 1272 1273 import std.conv; 1274 import std.traits; 1275 1276 struct params 1277 { 1278 int no_a; 1279 1280 @(PositionalArgument(0, "a") 1281 .Description("Argument 'a'") 1282 .Validation!((int a) { return a > 3;}) 1283 .PreValidation!((string s) { return s.length > 0;}) 1284 .Validation!((int a) { return a > 0;}) 1285 ) 1286 int a; 1287 1288 int no_b; 1289 1290 @(NamedArgument(["b", "boo"]).Description("Flag boo") 1291 .AllowNoValue!55 1292 ) 1293 int b; 1294 1295 int no_c; 1296 } 1297 1298 enum p = CommandArguments!params(Config.init); 1299 static assert(p.arguments.findNamedArgument("a").arg is null); 1300 static assert(p.arguments.findNamedArgument("b").arg !is null); 1301 static assert(p.arguments.findNamedArgument("boo").arg !is null); 1302 static assert(p.arguments.findPositionalArgument(0).arg !is null); 1303 static assert(p.arguments.findPositionalArgument(1).arg is null); 1304 } 1305 1306 unittest 1307 { 1308 import std.typecons : tuple; 1309 1310 struct T 1311 { 1312 string a; 1313 string b; 1314 } 1315 1316 auto test(string[] args) 1317 { 1318 return tuple(args.parseCLIKnownArgs!T.get, args); 1319 } 1320 1321 assert(test(["-a","A","--"]) == tuple(T("A"), [])); 1322 static assert(test(["-a","A","--","-b","B"]) == tuple(T("A"), ["-b","B"])); 1323 1324 { 1325 T args; 1326 1327 args.parseCLIArgs([ "-a", "A"]); 1328 args.parseCLIArgs([ "-b", "B"]); 1329 1330 assert(args == T("A","B")); 1331 } 1332 } 1333 1334 unittest 1335 { 1336 struct T 1337 { 1338 string a; 1339 } 1340 1341 { 1342 auto test_called(string[] args) 1343 { 1344 bool called; 1345 auto dg = (T t) { 1346 called = true; 1347 }; 1348 assert(args.parseCLIArgs!T(dg) == 0 || !called); 1349 return called; 1350 } 1351 1352 static assert(test_called([])); 1353 assert(test_called([])); 1354 assert(!test_called(["-g"])); 1355 } 1356 { 1357 auto test_called(string[] args) 1358 { 1359 bool called; 1360 auto dg = (T t, string[] args) { 1361 assert(args.length == 0 || args == ["-g"]); 1362 called = true; 1363 }; 1364 assert(args.parseCLIKnownArgs!T(dg) == 0); 1365 return called; 1366 } 1367 1368 assert(test_called([])); 1369 static assert(test_called(["-g"])); 1370 } 1371 } 1372 1373 unittest 1374 { 1375 struct T 1376 { 1377 string a; 1378 } 1379 1380 int my_main(T command) 1381 { 1382 // do something 1383 return 0; 1384 } 1385 1386 static assert(["-a","aa"].parseCLIArgs!T(&my_main) == 0); 1387 assert(["-a","aa"].parseCLIArgs!T(&my_main) == 0); 1388 } 1389 1390 unittest 1391 { 1392 struct T 1393 { 1394 string a; 1395 } 1396 1397 auto args = [ "-a", "A", "-c", "C" ]; 1398 1399 assert(parseCLIKnownArgs!T(args).get == T("A")); 1400 assert(args == ["-c", "C"]); 1401 } 1402 1403 unittest 1404 { 1405 1406 struct T 1407 { 1408 @NamedArgument string x; 1409 @NamedArgument string foo; 1410 @(PositionalArgument(0, "a").Optional()) string a; 1411 @(PositionalArgument(1, "b").Optional()) string[] b; 1412 } 1413 static assert(["--foo","FOO","-x","X"].parseCLIArgs!T.get == T("X", "FOO")); 1414 static assert(["--foo=FOO","-x=X"].parseCLIArgs!T.get == T("X", "FOO")); 1415 static assert(["--foo=FOO","1","-x=X"].parseCLIArgs!T.get == T("X", "FOO", "1")); 1416 static assert(["--foo=FOO","1","2","3","4"].parseCLIArgs!T.get == T(string.init, "FOO", "1",["2","3","4"])); 1417 static assert(["-xX"].parseCLIArgs!T.get == T("X")); 1418 assert(["--foo","FOO","-x","X"].parseCLIArgs!T.get == T("X", "FOO")); 1419 assert(["--foo=FOO","-x=X"].parseCLIArgs!T.get == T("X", "FOO")); 1420 assert(["--foo=FOO","1","-x=X"].parseCLIArgs!T.get == T("X", "FOO", "1")); 1421 assert(["--foo=FOO","1","2","3","4"].parseCLIArgs!T.get == T(string.init, "FOO", "1",["2","3","4"])); 1422 assert(["-xX"].parseCLIArgs!T.get == T("X")); 1423 1424 struct T1 1425 { 1426 @(PositionalArgument(0, "a")) string[3] a; 1427 @(PositionalArgument(1, "b")) string[] b; 1428 } 1429 static assert(["1","2","3","4","5","6"].parseCLIArgs!T1.get == T1(["1","2","3"],["4","5","6"])); 1430 assert(["1","2","3","4","5","6"].parseCLIArgs!T1.get == T1(["1","2","3"],["4","5","6"])); 1431 1432 struct T2 1433 { 1434 bool foo = true; 1435 } 1436 static assert(["--no-foo"].parseCLIArgs!T2.get == T2(false)); 1437 assert(["--no-foo"].parseCLIArgs!T2.get == T2(false)); 1438 } 1439 1440 unittest 1441 { 1442 struct T 1443 { 1444 @(PositionalArgument(0, "a").Optional()) 1445 string a = "not set"; 1446 1447 @(NamedArgument.Required()) 1448 int b; 1449 } 1450 1451 static assert(["-b", "4"].parseCLIArgs!T.get == T("not set", 4)); 1452 assert(["-b", "4"].parseCLIArgs!T.get == T("not set", 4)); 1453 } 1454 1455 unittest 1456 { 1457 struct T 1458 { 1459 string x; 1460 string foo; 1461 } 1462 1463 auto test(T)(string[] args) 1464 { 1465 Config config; 1466 config.caseSensitive = false; 1467 1468 return args.parseCLIArgs!T(config).get; 1469 } 1470 1471 static assert(test!T(["--Foo","FOO","-X","X"]) == T("X", "FOO")); 1472 static assert(test!T(["--FOo=FOO","-X=X"]) == T("X", "FOO")); 1473 assert(test!T(["--Foo","FOO","-X","X"]) == T("X", "FOO")); 1474 assert(test!T(["--FOo=FOO","-X=X"]) == T("X", "FOO")); 1475 } 1476 1477 unittest 1478 { 1479 auto test(T)(string[] args) 1480 { 1481 Config config; 1482 config.bundling = true; 1483 1484 return args.parseCLIArgs!T(config).get; 1485 } 1486 1487 struct T 1488 { 1489 bool a; 1490 bool b; 1491 } 1492 static assert(test!T(["-a","-b"]) == T(true, true)); 1493 static assert(test!T(["-ab"]) == T(true, true)); 1494 assert(test!T(["-a","-b"]) == T(true, true)); 1495 assert(test!T(["-ab"]) == T(true, true)); 1496 } 1497 1498 unittest 1499 { 1500 struct T 1501 { 1502 bool b; 1503 } 1504 1505 static assert(["-b"] .parseCLIArgs!T.get == T(true)); 1506 static assert(["-b","true"] .parseCLIArgs!T.get == T(true)); 1507 static assert(["-b","false"].parseCLIArgs!T.get == T(false)); 1508 static assert(["-b=true"] .parseCLIArgs!T.get == T(true)); 1509 static assert(["-b=false"] .parseCLIArgs!T.get == T(false)); 1510 assert(["-b"] .parseCLIArgs!T.get == T(true)); 1511 assert(["-b","true"] .parseCLIArgs!T.get == T(true)); 1512 assert(["-b","false"].parseCLIArgs!T.get == T(false)); 1513 assert(["-b=true"] .parseCLIArgs!T.get == T(true)); 1514 assert(["-b=false"] .parseCLIArgs!T.get == T(false)); 1515 } 1516 1517 struct Main 1518 { 1519 mixin template parseCLIKnownArgs(TYPE, alias newMain, Config config = Config.init) 1520 { 1521 int main(string[] argv) 1522 { 1523 return parseCLIKnownArgs!TYPE(argv[1..$], (TYPE values, string[] args) => newMain(values, args), config); 1524 } 1525 } 1526 1527 mixin template parseCLIArgs(TYPE, alias newMain, Config config = Config.init) 1528 { 1529 int main(string[] argv) 1530 { 1531 return parseCLIArgs!TYPE(argv[1..$], (TYPE values) => newMain(values), config); 1532 } 1533 } 1534 } 1535 1536 unittest 1537 { 1538 struct T 1539 { 1540 int a; 1541 } 1542 1543 static assert(__traits(compiles, { mixin Main.parseCLIArgs!(T, (params) => 0); })); 1544 static assert(__traits(compiles, { mixin Main.parseCLIKnownArgs!(T, (params, args) => 0); })); 1545 } 1546 1547 1548 private struct Parsers 1549 { 1550 static auto Convert(T)(string value) 1551 { 1552 import std.conv: to; 1553 return value.length > 0 ? value.to!T : T.init; 1554 } 1555 1556 static auto PassThrough(string[] values) 1557 { 1558 return values; 1559 } 1560 } 1561 1562 unittest 1563 { 1564 static assert(Parsers.Convert!int("7") == 7); 1565 static assert(Parsers.Convert!string("7") == "7"); 1566 static assert(Parsers.Convert!char("7") == '7'); 1567 1568 static assert(Parsers.PassThrough(["7","8"]) == ["7","8"]); 1569 } 1570 1571 1572 private struct Actions 1573 { 1574 static auto Assign(DEST, SRC=DEST)(ref DEST param, SRC value) 1575 { 1576 param = value; 1577 } 1578 1579 static auto Append(T)(ref T param, T value) 1580 { 1581 param ~= value; 1582 } 1583 1584 static auto Extend(T)(ref T[] param, T value) 1585 { 1586 param ~= value; 1587 } 1588 1589 static auto CallFunction(F)(ref F func, RawParam param) 1590 { 1591 // ... func() 1592 static if(__traits(compiles, { func(); })) 1593 { 1594 func(); 1595 } 1596 // ... func(string value) 1597 else static if(__traits(compiles, { func(param.value[0]); })) 1598 { 1599 foreach(value; param.value) 1600 func(value); 1601 } 1602 // ... func(string[] value) 1603 else static if(__traits(compiles, { func(param.value); })) 1604 { 1605 func(param.value); 1606 } 1607 // ... func(RawParam param) 1608 else static if(__traits(compiles, { func(param); })) 1609 { 1610 func(param); 1611 } 1612 else 1613 static assert(false, "Unsupported callback: " ~ F.stringof); 1614 } 1615 1616 static auto CallFunctionNoParam(F)(ref F func, Param!void param) 1617 { 1618 // ... func() 1619 static if(__traits(compiles, { func(); })) 1620 { 1621 func(); 1622 } 1623 // ... func(string value) 1624 else static if(__traits(compiles, { func(string.init); })) 1625 { 1626 func(string.init); 1627 } 1628 // ... func(string[] value) 1629 else static if(__traits(compiles, { func([]); })) 1630 { 1631 func([]); 1632 } 1633 // ... func(Param!void param) 1634 else static if(__traits(compiles, { func(param); })) 1635 { 1636 func(param); 1637 } 1638 else 1639 static assert(false, "Unsupported callback: " ~ F.stringof); 1640 } 1641 } 1642 1643 unittest 1644 { 1645 int i; 1646 Actions.Assign!(int)(i,7); 1647 assert(i == 7); 1648 } 1649 1650 unittest 1651 { 1652 int[] i; 1653 Actions.Append!(int[])(i,[1,2,3]); 1654 Actions.Append!(int[])(i,[7,8,9]); 1655 assert(i == [1,2,3,7,8,9]); 1656 1657 alias test = (int[] v1, int[] v2) { 1658 int[] res; 1659 1660 Param!(int[]) param; 1661 1662 alias F = Actions.Append!(int[]); 1663 param.value = v1; ActionFunc!(F, int[], int[])(res, param); 1664 1665 param.value = v2; ActionFunc!(F, int[], int[])(res, param); 1666 1667 return res; 1668 }; 1669 static assert(test([1,2,3],[7,8,9]) == [1,2,3,7,8,9]); 1670 } 1671 1672 unittest 1673 { 1674 int[][] i; 1675 Actions.Extend!(int[])(i,[1,2,3]); 1676 Actions.Extend!(int[])(i,[7,8,9]); 1677 assert(i == [[1,2,3],[7,8,9]]); 1678 } 1679 1680 1681 private struct Validators 1682 { 1683 static auto ValueInList(alias values, TYPE)(in Param!TYPE param) 1684 { 1685 import std.array : assocArray, join; 1686 import std.range : repeat, front; 1687 import std.conv: to; 1688 1689 enum valuesAA = assocArray(values, false.repeat); 1690 enum allowedValues = values.to!(string[]).join(','); 1691 1692 static if(is(typeof(values.front) == TYPE)) 1693 auto paramValues = [param.value]; 1694 else 1695 auto paramValues = param.value; 1696 1697 foreach(value; paramValues) 1698 if(!(value in valuesAA)) 1699 { 1700 param.config.onError("Invalid value '", value, "' for argument '", param.name, "'.\nValid argument values are: ", allowedValues); 1701 return false; 1702 } 1703 1704 return true; 1705 } 1706 } 1707 1708 1709 // values => bool 1710 // bool validate(T value) 1711 // bool validate(T[i] value) 1712 // bool validate(Param!T param) 1713 private struct ValidateFunc(alias F, T, string funcName="Validation") 1714 { 1715 static bool opCall(Param!T param) 1716 { 1717 static if(is(F == void)) 1718 { 1719 return true; 1720 } 1721 else static if(__traits(compiles, { F(param); })) 1722 { 1723 // bool validate(Param!T param) 1724 return cast(bool) F(param); 1725 } 1726 else static if(__traits(compiles, { F(param.value); })) 1727 { 1728 // bool validate(T values) 1729 return cast(bool) F(param.value); 1730 } 1731 else static if(/*isArray!T &&*/ __traits(compiles, { F(param.value[0]); })) 1732 { 1733 // bool validate(T[i] value) 1734 foreach(value; param.value) 1735 if(!F(value)) 1736 return false; 1737 return true; 1738 } 1739 else 1740 static assert(false, funcName~" function is not supported for type "~T.stringof~": "~typeof(F).stringof); 1741 } 1742 } 1743 1744 unittest 1745 { 1746 auto test(alias F, T)(T[] values) 1747 { 1748 Param!(T[]) param; 1749 param.value = values; 1750 return ValidateFunc!(F, T[])(param); 1751 } 1752 1753 // bool validate(T[] values) 1754 static assert(test!((string[] a) => true, string)(["1","2","3"])); 1755 static assert(test!((int[] a) => true, int)([1,2,3])); 1756 1757 // bool validate(T value) 1758 static assert(test!((string a) => true, string)(["1","2","3"])); 1759 static assert(test!((int a) => true, int)([1,2,3])); 1760 1761 // bool validate(Param!T param) 1762 static assert(test!((RawParam p) => true, string)(["1","2","3"])); 1763 static assert(test!((Param!(int[]) p) => true, int)([1,2,3])); 1764 } 1765 1766 unittest 1767 { 1768 static assert(ValidateFunc!(void, string[])(RawParam(Config.init, "", ["1","2","3"]))); 1769 1770 static assert(!__traits(compiles, { ValidateFunc!(() {}, string[])(config, "", ["1","2","3"]); })); 1771 static assert(!__traits(compiles, { ValidateFunc!((int,int) {}, string[])(config, "", ["1","2","3"]); })); 1772 } 1773 1774 1775 private template ParseType(alias F, T) 1776 { 1777 import std.traits : Unqual; 1778 1779 static if(is(F == void)) 1780 alias ParseType = Unqual!T; 1781 else static if(Parameters!F.length == 0) 1782 static assert(false, "Parse function should take at least one parameter"); 1783 else static if(Parameters!F.length == 1) 1784 { 1785 // T action(arg) 1786 alias ParseType = Unqual!(ReturnType!F); 1787 static assert(!is(ParseType == void), "Parse function should return value"); 1788 } 1789 else static if(Parameters!F.length == 2 && is(Parameters!F[0] == Config)) 1790 { 1791 // T action(Config config, arg) 1792 alias ParseType = Unqual!(ReturnType!F); 1793 static assert(!is(ParseType == void), "Parse function should return value"); 1794 } 1795 else static if(Parameters!F.length == 2) 1796 { 1797 // ... action(ref T param, arg) 1798 alias ParseType = Parameters!F[0]; 1799 } 1800 else static if(Parameters!F.length == 3) 1801 { 1802 // ... action(Config config, ref T param, arg) 1803 alias ParseType = Parameters!F[1]; 1804 } 1805 else static if(Parameters!F.length == 4) 1806 { 1807 // ... action(Config config, string argName, ref T param, arg) 1808 alias ParseType = Parameters!F[2]; 1809 } 1810 else 1811 static assert(false, "Parse function has too many parameters: "~Parameters!F.stringof); 1812 } 1813 1814 unittest 1815 { 1816 static assert(is(ParseType!(void, double) == double)); 1817 static assert(!__traits(compiles, { ParseType!((){}, double) p; })); 1818 static assert(!__traits(compiles, { ParseType!((int,int,int,int,int){}, double) p; })); 1819 1820 // T action(arg) 1821 static assert(is(ParseType!((int)=>3, double) == int)); 1822 static assert(!__traits(compiles, { ParseType!((int){}, double) p; })); 1823 // T action(Config config, arg) 1824 static assert(is(ParseType!((Config config, int)=>3, double) == int)); 1825 static assert(!__traits(compiles, { ParseType!((Config config, int){}, double) p; })); 1826 // ... action(ref T param, arg) 1827 static assert(is(ParseType!((ref int, string v) {}, double) == int)); 1828 // ... action(Config config, ref T param, arg) 1829 static assert(is(ParseType!((Config config, ref int, string v) {}, double) == int)); 1830 // ... action(Config config, string argName, ref T param, arg) 1831 //static assert(is(ParseType!((Config config, string argName, ref int, string v) {}, double) == int)); 1832 } 1833 1834 1835 // T parse(string[] values) 1836 // T parse(string value) 1837 // T parse(RawParam param) 1838 // bool parse(ref T receiver, RawParam param) 1839 // void parse(ref T receiver, RawParam param) 1840 private struct ParseFunc(alias F, T) 1841 { 1842 alias ParseType = .ParseType!(F, T); 1843 1844 static bool opCall(ref ParseType receiver, RawParam param) 1845 { 1846 static if(is(F == void)) 1847 { 1848 foreach(value; param.value) 1849 receiver = Parsers.Convert!T(value); 1850 return true; 1851 } 1852 // T parse(string[] values) 1853 else static if(__traits(compiles, { receiver = cast(ParseType) F(param.value); })) 1854 { 1855 receiver = cast(ParseType) F(param.value); 1856 return true; 1857 } 1858 // T parse(string value) 1859 else static if(__traits(compiles, { receiver = cast(ParseType) F(param.value[0]); })) 1860 { 1861 foreach(value; param.value) 1862 receiver = cast(ParseType) F(value); 1863 return true; 1864 } 1865 // T parse(RawParam param) 1866 else static if(__traits(compiles, { receiver = cast(ParseType) F(param); })) 1867 { 1868 receiver = cast(ParseType) F(param); 1869 return true; 1870 } 1871 // bool parse(ref T receiver, RawParam param) 1872 // void parse(ref T receiver, RawParam param) 1873 else static if(__traits(compiles, { F(receiver, param); })) 1874 { 1875 static if(__traits(compiles, { auto res = cast(bool) F(receiver, param); })) 1876 { 1877 // bool parse(ref T receiver, RawParam param) 1878 return cast(bool) F(receiver, param); 1879 } 1880 else 1881 { 1882 // void parse(ref T receiver, RawParam param) 1883 F(receiver, param); 1884 return true; 1885 } 1886 } 1887 else 1888 static assert(false, "Parse function is not supported"); 1889 } 1890 } 1891 1892 unittest 1893 { 1894 int i; 1895 RawParam param; 1896 param.value = ["1","2","3"]; 1897 assert(ParseFunc!(void, int)(i, param)); 1898 assert(i == 3); 1899 } 1900 1901 unittest 1902 { 1903 auto test(alias F, T)(string[] values) 1904 { 1905 T value; 1906 RawParam param; 1907 param.value = values; 1908 assert(ParseFunc!(F, T)(value, param)); 1909 return value; 1910 } 1911 1912 // T parse(string value) 1913 static assert(test!((string a) => a, string)(["1","2","3"]) == "3"); 1914 1915 // T parse(string[] values) 1916 static assert(test!((string[] a) => a, string[])(["1","2","3"]) == ["1","2","3"]); 1917 1918 // T parse(RawParam param) 1919 static assert(test!((RawParam p) => p.value[0], string)(["1","2","3"]) == "1"); 1920 1921 // bool parse(ref T receiver, RawParam param) 1922 static assert(test!((ref string[] r, RawParam p) { r = p.value; return true; }, string[])(["1","2","3"]) == ["1","2","3"]); 1923 1924 // void parse(ref T receiver, RawParam param) 1925 static assert(test!((ref string[] r, RawParam p) { r = p.value; }, string[])(["1","2","3"]) == ["1","2","3"]); 1926 } 1927 1928 1929 // bool action(ref T receiver, ParseType value) 1930 // void action(ref T receiver, ParseType value) 1931 // bool action(ref T receiver, Param!ParseType param) 1932 // void action(ref T receiver, Param!ParseType param) 1933 private struct ActionFunc(alias F, T, ParseType) 1934 { 1935 static bool opCall(ref T receiver, Param!ParseType param) 1936 { 1937 static if(is(F == void)) 1938 { 1939 Actions.Assign!(T, ParseType)(receiver, param.value); 1940 return true; 1941 } 1942 // bool action(ref T receiver, ParseType value) 1943 // void action(ref T receiver, ParseType value) 1944 else static if(__traits(compiles, { F(receiver, param.value); })) 1945 { 1946 static if(__traits(compiles, { auto res = cast(bool) F(receiver, param.value); })) 1947 { 1948 // bool action(ref T receiver, ParseType value) 1949 return cast(bool) F(receiver, param.value); 1950 } 1951 else 1952 { 1953 // void action(ref T receiver, ParseType value) 1954 F(receiver, param.value); 1955 return true; 1956 } 1957 } 1958 // bool action(ref T receiver, Param!ParseType param) 1959 // void action(ref T receiver, Param!ParseType param) 1960 else static if(__traits(compiles, { F(receiver, param); })) 1961 { 1962 static if(__traits(compiles, { auto res = cast(bool) F(receiver, param); })) 1963 { 1964 // bool action(ref T receiver, Param!ParseType param) 1965 return cast(bool) F(receiver, param); 1966 } 1967 else 1968 { 1969 // void action(ref T receiver, Param!ParseType param) 1970 F(receiver, param); 1971 return true; 1972 } 1973 } 1974 else 1975 static assert(false, "Action function is not supported"); 1976 } 1977 } 1978 1979 unittest 1980 { 1981 auto param(T)(T values) 1982 { 1983 Param!T param; 1984 param.value = values; 1985 return param; 1986 } 1987 auto test(alias F, T)(T values) 1988 { 1989 T receiver; 1990 assert(ActionFunc!(F, T, T)(receiver, param(values))); 1991 return receiver; 1992 } 1993 1994 static assert(test!(void, string[])(["1","2","3"]) == ["1","2","3"]); 1995 1996 static assert(!__traits(compiles, { test!(() {}, string[])(["1","2","3"]); })); 1997 static assert(!__traits(compiles, { test!((int,int) {}, string[])(["1","2","3"]); })); 1998 1999 // bool action(ref T receiver, ParseType value) 2000 static assert(test!((ref string[] p, string[] a) { p=a; return true; }, string[])(["1","2","3"]) == ["1","2","3"]); 2001 2002 // void action(ref T receiver, ParseType value) 2003 static assert(test!((ref string[] p, string[] a) { p=a; }, string[])(["1","2","3"]) == ["1","2","3"]); 2004 2005 // bool action(ref T receiver, Param!ParseType param) 2006 static assert(test!((ref string[] p, Param!(string[]) a) { p=a.value; return true; }, string[]) (["1","2","3"]) == ["1","2","3"]); 2007 2008 // void action(ref T receiver, Param!ParseType param) 2009 static assert(test!((ref string[] p, Param!(string[]) a) { p=a.value; }, string[])(["1","2","3"]) == ["1","2","3"]); 2010 } 2011 2012 2013 // => receiver + bool 2014 // DEST action() 2015 // bool action(ref DEST receiver) 2016 // void action(ref DEST receiver) 2017 // bool action(ref DEST receiver, Param!void param) 2018 // void action(ref DEST receiver, Param!void param) 2019 private struct NoValueActionFunc(alias F, T) 2020 { 2021 static bool opCall(ref T receiver, Param!void param) 2022 { 2023 static if(is(F == void)) 2024 { 2025 assert(false, "No-value action function is not provided"); 2026 } 2027 else static if(__traits(compiles, { receiver = cast(T) F(); })) 2028 { 2029 // DEST action() 2030 receiver = cast(T) F(); 2031 return true; 2032 } 2033 else static if(__traits(compiles, { F(receiver); })) 2034 { 2035 static if(__traits(compiles, { auto res = cast(bool) F(receiver); })) 2036 { 2037 // bool action(ref DEST receiver) 2038 return cast(bool) F(receiver); 2039 } 2040 else 2041 { 2042 // void action(ref DEST receiver) 2043 F(receiver); 2044 return true; 2045 } 2046 } 2047 else static if(__traits(compiles, { F(receiver, param); })) 2048 { 2049 static if(__traits(compiles, { auto res = cast(bool) F(receiver, param); })) 2050 { 2051 // bool action(ref DEST receiver, Param!void param) 2052 return cast(bool) F(receiver, param); 2053 } 2054 else 2055 { 2056 // void action(ref DEST receiver, Param!void param) 2057 F(receiver, param); 2058 return true; 2059 } 2060 } 2061 else 2062 static assert(false, "No-value action function has too many parameters: "~Parameters!F.stringof); 2063 } 2064 } 2065 2066 unittest 2067 { 2068 auto test(alias F, T)() 2069 { 2070 T receiver; 2071 assert(NoValueActionFunc!(F, T)(receiver, Param!void.init)); 2072 return receiver; 2073 } 2074 2075 static assert(!__traits(compiles, { NoValueActionFunc!(() {}, int); })); 2076 static assert(!__traits(compiles, { NoValueActionFunc!((int) {}, int); })); 2077 static assert(!__traits(compiles, { NoValueActionFunc!((int,int) {}, int); })); 2078 static assert(!__traits(compiles, { NoValueActionFunc!((int,int,int) {}, int); })); 2079 2080 // DEST action() 2081 static assert(test!(() => 7, int) == 7); 2082 2083 // bool action(ref DEST param) 2084 static assert(test!((ref int p) { p=7; return true; }, int) == 7); 2085 2086 // void action(ref DEST param) 2087 static assert(test!((ref int p) { p=7; }, int) == 7); 2088 2089 // bool action(ref DEST receiver, Param!void param) 2090 static assert(test!((ref int r, Param!void p) { r=7; return true; }, int) == 7); 2091 2092 // void action(ref DEST receiver, Param!void param) 2093 static assert(test!((ref int r, Param!void p) { r=7; }, int) == 7); 2094 } 2095 2096 2097 private void splitValues(ref RawParam param) 2098 { 2099 if(param.config.arraySep == char.init) 2100 return; 2101 2102 import std.array : array, split; 2103 import std.algorithm : map, joiner; 2104 2105 param.value = param.value.map!((string s) => s.split(param.config.arraySep)).joiner.array; 2106 } 2107 2108 unittest 2109 { 2110 alias test = (char arraySep, string[] values) 2111 { 2112 Config config; 2113 config.arraySep = arraySep; 2114 2115 auto param = RawParam(config, "", values); 2116 2117 splitValues(param); 2118 2119 return param.value; 2120 }; 2121 2122 static assert(test(',', []) == []); 2123 static assert(test(',', ["a","b","c"]) == ["a","b","c"]); 2124 static assert(test(',', ["a,b","c","d,e,f"]) == ["a","b","c","d","e","f"]); 2125 static assert(test(' ', ["a,b","c","d,e,f"]) == ["a,b","c","d,e,f"]); 2126 } 2127 2128 2129 private struct ValueParseFunctions(alias PreProcess, 2130 alias PreValidation, 2131 alias Parse, 2132 alias Validation, 2133 alias Action, 2134 alias NoValueAction) 2135 { 2136 alias changePreProcess (alias func) = ValueParseFunctions!( func, PreValidation, Parse, Validation, Action, NoValueAction); 2137 alias changePreValidation(alias func) = ValueParseFunctions!(PreProcess, func, Parse, Validation, Action, NoValueAction); 2138 alias changeParse (alias func) = ValueParseFunctions!(PreProcess, PreValidation, func, Validation, Action, NoValueAction); 2139 alias changeValidation (alias func) = ValueParseFunctions!(PreProcess, PreValidation, Parse, func, Action, NoValueAction); 2140 alias changeAction (alias func) = ValueParseFunctions!(PreProcess, PreValidation, Parse, Validation, func, NoValueAction); 2141 alias changeNoValueAction(alias func) = ValueParseFunctions!(PreProcess, PreValidation, Parse, Validation, Action, func); 2142 2143 template addDefaults(T) 2144 { 2145 static if(is(PreProcess == void)) 2146 alias preProc = DefaultValueParseFunctions!T; 2147 else 2148 alias preProc = DefaultValueParseFunctions!T.changePreProcess!PreProcess; 2149 2150 static if(is(PreValidation == void)) 2151 alias preVal = preProc; 2152 else 2153 alias preVal = preProc.changePreValidation!PreValidation; 2154 2155 static if(is(Parse == void)) 2156 alias parse = preVal; 2157 else 2158 alias parse = preVal.changeParse!Parse; 2159 2160 static if(is(Validation == void)) 2161 alias val = parse; 2162 else 2163 alias val = parse.changeValidation!Validation; 2164 2165 static if(is(Action == void)) 2166 alias action = val; 2167 else 2168 alias action = val.changeAction!Action; 2169 2170 static if(is(NoValueAction == void)) 2171 alias addDefaults = action; 2172 else 2173 alias addDefaults = action.changeNoValueAction!NoValueAction; 2174 } 2175 2176 2177 // Procedure to process (parse) the values to an argument of type T 2178 // - if there is a value(s): 2179 // - pre validate raw strings 2180 // - parse raw strings 2181 // - validate parsed values 2182 // - action with values 2183 // - if there is no value: 2184 // - action if no value 2185 // Requirement: rawValues.length must be correct 2186 static bool parse(T)(ref T receiver, RawParam param) 2187 { 2188 return addDefaults!T.parseImpl(receiver, param); 2189 } 2190 static bool parseImpl(T)(ref T receiver, ref RawParam rawParam) 2191 { 2192 alias ParseType(T) = .ParseType!(Parse, T); 2193 2194 alias preValidation = ValidateFunc!(PreValidation, string[], "Pre validation"); 2195 alias parse(T) = ParseFunc!(Parse, T); 2196 alias validation(T) = ValidateFunc!(Validation, ParseType!T); 2197 alias action(T) = ActionFunc!(Action, T, ParseType!T); 2198 alias noValueAction(T) = NoValueActionFunc!(NoValueAction, T); 2199 2200 if(rawParam.value.length == 0) 2201 { 2202 return noValueAction!T(receiver, Param!void(rawParam.config, rawParam.name)); 2203 } 2204 else 2205 { 2206 static if(!is(PreProcess == void)) 2207 PreProcess(rawParam); 2208 2209 if(!preValidation(rawParam)) 2210 return false; 2211 2212 auto parsedParam = Param!(ParseType!T)(rawParam.config, rawParam.name); 2213 2214 if(!parse!T(parsedParam.value, rawParam)) 2215 return false; 2216 2217 if(!validation!T(parsedParam)) 2218 return false; 2219 2220 if(!action!T(receiver, parsedParam)) 2221 return false; 2222 2223 return true; 2224 } 2225 } 2226 } 2227 2228 2229 private template DefaultValueParseFunctions(T) 2230 if(!is(T == void)) 2231 { 2232 import std.traits; 2233 import std.conv: to; 2234 2235 static if(is(T == enum)) 2236 { 2237 alias DefaultValueParseFunctions = ValueParseFunctions!( 2238 void, // pre process 2239 Validators.ValueInList!(EnumMembersAsStrings!T, typeof(RawParam.value)), // pre validate 2240 void, // parse 2241 void, // validate 2242 void, // action 2243 void // no-value action 2244 ); 2245 } 2246 else static if(isSomeString!T || isNumeric!T) 2247 { 2248 alias DefaultValueParseFunctions = ValueParseFunctions!( 2249 void, // pre process 2250 void, // pre validate 2251 void, // parse 2252 void, // validate 2253 void, // action 2254 void // no-value action 2255 ); 2256 } 2257 else static if(isBoolean!T) 2258 { 2259 alias DefaultValueParseFunctions = ValueParseFunctions!( 2260 void, // pre process 2261 void, // pre validate 2262 (string value) // parse 2263 { 2264 switch(value) 2265 { 2266 case "": goto case; 2267 case "yes": goto case; 2268 case "y": return true; 2269 case "no": goto case; 2270 case "n": return false; 2271 default: return value.to!T; 2272 } 2273 }, 2274 void, // validate 2275 void, // action 2276 (ref T result) { result = true; } // no-value action 2277 ); 2278 } 2279 else static if(isSomeChar!T) 2280 { 2281 alias DefaultValueParseFunctions = ValueParseFunctions!( 2282 void, // pre process 2283 void, // pre validate 2284 (string value) // parse 2285 { 2286 return value.length > 0 ? value[0].to!T : T.init; 2287 }, 2288 void, // validate 2289 void, // action 2290 void // no-value action 2291 ); 2292 } 2293 else static if(isArray!T) 2294 { 2295 import std.traits: ForeachType; 2296 2297 alias TElement = ForeachType!T; 2298 2299 static if(!isArray!TElement || isSomeString!TElement) // 1D array 2300 { 2301 static if(!isStaticArray!T) 2302 alias action = Actions.Append!T; 2303 else 2304 alias action = Actions.Assign!T; 2305 2306 alias DefaultValueParseFunctions = DefaultValueParseFunctions!TElement 2307 .changePreProcess!splitValues 2308 .changeParse!((ref T receiver, RawParam param) 2309 { 2310 static if(!isStaticArray!T) 2311 { 2312 if(receiver.length < param.value.length) 2313 receiver.length = param.value.length; 2314 } 2315 2316 foreach(i, value; param.value) 2317 { 2318 if(!DefaultValueParseFunctions!TElement.parse(receiver[i], 2319 RawParam(param.config, param.name, [value]))) 2320 return false; 2321 } 2322 2323 return true; 2324 }) 2325 .changeAction!(action) 2326 .changeNoValueAction!((ref T param) {}); 2327 } 2328 else static if(!isArray!(ForeachType!TElement) || isSomeString!(ForeachType!TElement)) // 2D array 2329 { 2330 alias DefaultValueParseFunctions = DefaultValueParseFunctions!TElement 2331 .changeAction!(Actions.Extend!TElement) 2332 .changeNoValueAction!((ref T param) { param ~= TElement.init; }); 2333 } 2334 else 2335 { 2336 static assert(false, "Multi-dimentional arrays are not supported: " ~ T.stringof); 2337 } 2338 } 2339 else static if(isAssociativeArray!T) 2340 { 2341 import std.string : indexOf; 2342 alias DefaultValueParseFunctions = ValueParseFunctions!( 2343 splitValues, // pre process 2344 void, // pre validate 2345 Parsers.PassThrough, // parse 2346 void, // validate 2347 (ref T recepient, Param!(string[]) param) // action 2348 { 2349 alias K = KeyType!T; 2350 alias V = ValueType!T; 2351 2352 foreach(input; param.value) 2353 { 2354 auto j = indexOf(input, param.config.assignChar); 2355 if(j < 0) 2356 return false; 2357 2358 K key; 2359 if(!DefaultValueParseFunctions!K.parse(key, RawParam(param.config, param.name, [input[0 .. j]]))) 2360 return false; 2361 2362 V value; 2363 if(!DefaultValueParseFunctions!V.parse(value, RawParam(param.config, param.name, [input[j + 1 .. $]]))) 2364 return false; 2365 2366 recepient[key] = value; 2367 } 2368 return true; 2369 }, 2370 (ref T param) {} // no-value action 2371 ); 2372 } 2373 else static if(is(T == delegate)) 2374 { 2375 alias DefaultValueParseFunctions = ValueParseFunctions!( 2376 void, // pre process 2377 void, // pre validate 2378 Parsers.PassThrough, // parse 2379 void, // validate 2380 Actions.CallFunction!T, // action 2381 Actions.CallFunctionNoParam!T // no-value action 2382 ); 2383 } 2384 else 2385 static assert(false, "Type is not supported: " ~ T.stringof); 2386 } 2387 2388 unittest 2389 { 2390 enum MyEnum { foo, bar, } 2391 2392 import std.meta: AliasSeq; 2393 static foreach(T; AliasSeq!(string, bool, int, double, char, MyEnum)) 2394 static foreach(R; AliasSeq!(T, T[], T[][])) 2395 {{ 2396 // ensure that this compiles 2397 R receiver; 2398 RawParam param; 2399 param.value = [""]; 2400 DefaultValueParseFunctions!R.parse(receiver, param); 2401 }} 2402 } 2403 2404 unittest 2405 { 2406 alias test(R) = (string[][] values) 2407 { 2408 auto config = Config('=', ','); 2409 R receiver; 2410 foreach(value; values) 2411 { 2412 assert(DefaultValueParseFunctions!R.parse(receiver, RawParam(config, "", value))); 2413 } 2414 return receiver; 2415 }; 2416 2417 static assert(test!(string[])([["1","2","3"], [], ["4"]]) == ["1","2","3","4"]); 2418 static assert(test!(string[][])([["1","2","3"], [], ["4"]]) == [["1","2","3"],[],["4"]]); 2419 2420 static assert(test!(string[string])([["a=bar","b=foo"], [], ["b=baz","c=boo"]]) == ["a":"bar", "b":"baz", "c":"boo"]); 2421 2422 static assert(test!(string[])([["1,2,3"], [], ["4"]]) == ["1","2","3","4"]); 2423 static assert(test!(string[string])([["a=bar,b=foo"], [], ["b=baz,c=boo"]]) == ["a":"bar", "b":"baz", "c":"boo"]); 2424 2425 static assert(test!(int[])([["1","2","3"], [], ["4"]]) == [1,2,3,4]); 2426 static assert(test!(int[][])([["1","2","3"], [], ["4"]]) == [[1,2,3],[],[4]]); 2427 2428 } 2429 2430 unittest 2431 { 2432 import std.math: isNaN; 2433 enum MyEnum { foo, bar, } 2434 2435 alias test(T) = (string[] values) 2436 { 2437 T receiver; 2438 RawParam param; 2439 param.value = values; 2440 assert(DefaultValueParseFunctions!T.parse(receiver, param)); 2441 return receiver; 2442 }; 2443 2444 static assert(test!string([""]) == ""); 2445 static assert(test!string(["foo"]) == "foo"); 2446 static assert(isNaN(test!double([""]))); 2447 static assert(test!double(["-12.34"]) == -12.34); 2448 static assert(test!double(["12.34"]) == 12.34); 2449 static assert(test!uint(["1234"]) == 1234); 2450 static assert(test!int([""]) == int.init); 2451 static assert(test!int(["-1234"]) == -1234); 2452 static assert(test!char([""]) == char.init); 2453 static assert(test!char(["f"]) == 'f'); 2454 static assert(test!bool([]) == true); 2455 static assert(test!bool([""]) == true); 2456 static assert(test!bool(["yes"]) == true); 2457 static assert(test!bool(["y"]) == true); 2458 static assert(test!bool(["true"]) == true); 2459 static assert(test!bool(["no"]) == false); 2460 static assert(test!bool(["n"]) == false); 2461 static assert(test!bool(["false"]) == false); 2462 static assert(test!MyEnum(["foo"]) == MyEnum.foo); 2463 static assert(test!MyEnum(["bar"]) == MyEnum.bar); 2464 static assert(test!(MyEnum[])(["bar","foo"]) == [MyEnum.bar, MyEnum.foo]); 2465 static assert(test!(string[string])(["a=bar","b=foo"]) == ["a":"bar", "b":"foo"]); 2466 static assert(test!(MyEnum[string])(["a=bar","b=foo"]) == ["a":MyEnum.bar, "b":MyEnum.foo]); 2467 static assert(test!(int[MyEnum])(["bar=3","foo=5"]) == [MyEnum.bar:3, MyEnum.foo:5]); 2468 } 2469 2470 2471 private struct ArgumentInfo 2472 { 2473 string[] names; 2474 2475 string description; 2476 string placeholder; 2477 2478 private void setAllowedValues(alias names)() 2479 { 2480 if(placeholder.length == 0) 2481 { 2482 import std.conv: to; 2483 import std.array: join; 2484 import std.format: format; 2485 placeholder = "{%s}".format(names.to!(string[]).join(',')); 2486 } 2487 } 2488 2489 bool hideFromHelp = false; // if true then this argument is not printed on help page 2490 2491 bool required; 2492 2493 Nullable!uint position; 2494 2495 @property bool positional() const { return !position.isNull; } 2496 2497 Nullable!ulong minValuesCount; 2498 Nullable!ulong maxValuesCount; 2499 2500 private bool checkValuesCount(in Config config, string argName, ulong count) const 2501 { 2502 immutable min = minValuesCount.get; 2503 immutable max = maxValuesCount.get; 2504 2505 // override for boolean flags 2506 if(allowBooleanNegation && count == 1) 2507 return true; 2508 2509 if(min == max && count != min) 2510 { 2511 config.onError("argument ",argName,": expected ",min,min == 1 ? " value" : " values"); 2512 return false; 2513 } 2514 if(count < min) 2515 { 2516 config.onError("argument ",argName,": expected at least ",min,min == 1 ? " value" : " values"); 2517 return false; 2518 } 2519 if(count > max) 2520 { 2521 config.onError("argument ",argName,": expected at most ",max,max == 1 ? " value" : " values"); 2522 return false; 2523 } 2524 2525 return true; 2526 } 2527 2528 private bool allowBooleanNegation = true; 2529 2530 Nullable!int parsingTerminateCode; 2531 } 2532 2533 2534 2535 //////////////////////////////////////////////////////////////////////////////////////////////////// 2536 // User defined attributes 2537 //////////////////////////////////////////////////////////////////////////////////////////////////// 2538 private struct ArgumentUDA(alias ValueParseFunctions) 2539 { 2540 ArgumentInfo info; 2541 2542 alias parsingFunc = ValueParseFunctions; 2543 2544 2545 2546 auto ref Description(string text) 2547 { 2548 info.description = text; 2549 return this; 2550 } 2551 2552 auto ref HideFromHelp(bool hide = true) 2553 { 2554 info.hideFromHelp = hide; 2555 return this; 2556 } 2557 2558 auto ref Placeholder(string value) 2559 { 2560 info.placeholder = value; 2561 return this; 2562 } 2563 2564 auto ref Required() 2565 { 2566 info.required = true; 2567 return this; 2568 } 2569 2570 auto ref Optional() 2571 { 2572 info.required = false; 2573 return this; 2574 } 2575 2576 auto ref NumberOfValues(ulong num) 2577 { 2578 info.minValuesCount = num; 2579 info.maxValuesCount = num; 2580 return this; 2581 } 2582 2583 auto ref NumberOfValues(ulong min, ulong max) 2584 { 2585 info.minValuesCount = min; 2586 info.maxValuesCount = max; 2587 return this; 2588 } 2589 2590 auto ref MinNumberOfValues(ulong min) 2591 { 2592 assert(min <= info.maxValuesCount.get(ulong.max)); 2593 2594 info.minValuesCount = min; 2595 return this; 2596 } 2597 2598 auto ref MaxNumberOfValues(ulong max) 2599 { 2600 assert(max >= info.minValuesCount.get(0)); 2601 2602 info.maxValuesCount = max; 2603 return this; 2604 } 2605 2606 // ReverseSwitch 2607 } 2608 2609 unittest 2610 { 2611 auto arg = NamedArgument.Description("desc").Placeholder("text"); 2612 assert(arg.info.description == "desc"); 2613 assert(arg.info.placeholder == "text"); 2614 assert(!arg.info.hideFromHelp); 2615 assert(!arg.info.required); 2616 assert(arg.info.minValuesCount.isNull); 2617 assert(arg.info.maxValuesCount.isNull); 2618 2619 arg = arg.HideFromHelp().Required().NumberOfValues(10); 2620 assert(arg.info.hideFromHelp); 2621 assert(arg.info.required); 2622 assert(arg.info.minValuesCount.get == 10); 2623 assert(arg.info.maxValuesCount.get == 10); 2624 2625 arg = arg.Optional().NumberOfValues(20,30); 2626 assert(!arg.info.required); 2627 assert(arg.info.minValuesCount.get == 20); 2628 assert(arg.info.maxValuesCount.get == 30); 2629 2630 arg = arg.MinNumberOfValues(2).MaxNumberOfValues(3); 2631 assert(arg.info.minValuesCount.get == 2); 2632 assert(arg.info.maxValuesCount.get == 3); 2633 } 2634 2635 unittest 2636 { 2637 struct T 2638 { 2639 @(NamedArgument.NumberOfValues(1,3)) 2640 int[] a; 2641 @(NamedArgument.NumberOfValues(2)) 2642 int[] b; 2643 } 2644 2645 assert(["-a","1","2","3","-b","4","5"].parseCLIArgs!T.get == T([1,2,3],[4,5])); 2646 assert(["-a","1","-b","4","5"].parseCLIArgs!T.get == T([1],[4,5])); 2647 } 2648 2649 private enum bool isArgumentUDA(T) = (is(typeof(T.info) == ArgumentInfo) && is(T.parsingFunc)); 2650 2651 2652 auto PreValidation(alias func, ARG)(ARG arg) 2653 if(isArgumentUDA!ARG) 2654 { 2655 return ArgumentUDA!(arg.parsingFunc.changePreValidation!func)(arg.tupleof); 2656 } 2657 2658 auto Parse(alias func, ARG)(ARG arg) 2659 if(isArgumentUDA!ARG) 2660 { 2661 return ArgumentUDA!(arg.parsingFunc.changeParse!func)(arg.tupleof); 2662 } 2663 2664 auto Validation(alias func, ARG)(ARG arg) 2665 if(isArgumentUDA!ARG) 2666 { 2667 return ArgumentUDA!(arg.parsingFunc.changeValidation!func)(arg.tupleof); 2668 } 2669 2670 auto Action(alias func, ARG)(ARG arg) 2671 if(isArgumentUDA!ARG) 2672 { 2673 return ArgumentUDA!(arg.parsingFunc.changeAction!func)(arg.tupleof); 2674 } 2675 2676 auto AllowNoValue(alias valueToUse, ARG)(ARG arg) 2677 if(isArgumentUDA!ARG) 2678 { 2679 auto desc = ArgumentUDA!(arg.parsingFunc.changeNoValueAction!(() { return valueToUse; }))(arg.tupleof); 2680 desc.info.minValuesCount = 0; 2681 return desc; 2682 } 2683 2684 auto RequireNoValue(alias valueToUse, ARG)(ARG arg) 2685 if(isArgumentUDA!ARG) 2686 { 2687 auto desc = arg.AllowNoValue!valueToUse; 2688 desc.info.minValuesCount = 0; 2689 desc.info.maxValuesCount = 0; 2690 return desc; 2691 } 2692 2693 auto Counter(ARG)(ARG arg) 2694 if(isArgumentUDA!ARG) 2695 { 2696 struct CounterParsingFunction 2697 { 2698 static bool parse(T)(ref T receiver, const ref RawParam param) 2699 { 2700 assert(param.value.length == 0); 2701 2702 ++receiver; 2703 2704 return true; 2705 } 2706 } 2707 2708 auto desc = ArgumentUDA!(CounterParsingFunction)(arg.tupleof); 2709 desc.info.minValuesCount = 0; 2710 desc.info.maxValuesCount = 0; 2711 return desc; 2712 } 2713 2714 2715 unittest 2716 { 2717 struct T 2718 { 2719 @(NamedArgument.Counter()) int a; 2720 } 2721 2722 static assert(["-a","-a","-a"].parseCLIArgs!T.get == T(3)); 2723 assert(["-a","-a","-a"].parseCLIArgs!T.get == T(3)); 2724 } 2725 2726 2727 auto AllowedValues(alias values, ARG)(ARG arg) 2728 { 2729 import std.array : assocArray; 2730 import std.range : repeat; 2731 2732 enum valuesAA = assocArray(values, false.repeat); 2733 2734 auto desc = arg.Validation!(Validators.ValueInList!(values, KeyType!(typeof(valuesAA)))); 2735 desc.info.setAllowedValues!values; 2736 return desc; 2737 } 2738 2739 2740 unittest 2741 { 2742 struct T 2743 { 2744 @(NamedArgument.AllowedValues!([1,3,5])) int a; 2745 } 2746 2747 static assert(["-a","3"].parseCLIArgs!T.get == T(3)); 2748 assert(["-a","2"].parseCLIArgs!T.isNull); 2749 assert(["-a","3"].parseCLIArgs!T.get == T(3)); 2750 } 2751 2752 unittest 2753 { 2754 struct T 2755 { 2756 @(NamedArgument.AllowedValues!(["apple","pear","banana"])) 2757 string fruit; 2758 } 2759 2760 static assert(["--fruit", "apple"].parseCLIArgs!T.get == T("apple")); 2761 assert(["--fruit", "kiwi"].parseCLIArgs!T.isNull); 2762 } 2763 2764 unittest 2765 { 2766 enum Fruit { apple, pear, banana } 2767 struct T 2768 { 2769 @NamedArgument 2770 Fruit fruit; 2771 } 2772 2773 static assert(["--fruit", "apple"].parseCLIArgs!T.get == T(Fruit.apple)); 2774 assert(["--fruit", "kiwi"].parseCLIArgs!T.isNull); 2775 } 2776 2777 2778 auto PositionalArgument(uint pos) 2779 { 2780 auto arg = ArgumentUDA!(ValueParseFunctions!(void, void, void, void, void, void))(ArgumentInfo()).Required(); 2781 arg.info.position = pos; 2782 return arg; 2783 } 2784 2785 auto PositionalArgument(uint pos, string name) 2786 { 2787 auto arg = ArgumentUDA!(ValueParseFunctions!(void, void, void, void, void, void))(ArgumentInfo([name])).Required(); 2788 arg.info.position = pos; 2789 return arg; 2790 } 2791 2792 auto NamedArgument(string[] name...) 2793 { 2794 return ArgumentUDA!(ValueParseFunctions!(void, void, void, void, void, void))(ArgumentInfo(name)).Optional(); 2795 } 2796 2797 auto NamedArgument(string name) 2798 { 2799 return ArgumentUDA!(ValueParseFunctions!(void, void, void, void, void, void))(ArgumentInfo([name])).Optional(); 2800 } 2801 2802 struct TrailingArguments {} 2803 2804 2805 unittest 2806 { 2807 struct T 2808 { 2809 string a; 2810 string b; 2811 2812 @TrailingArguments string[] args; 2813 } 2814 2815 static assert(["-a","A","--","-b","B"].parseCLIArgs!T.get == T("A","",["-b","B"])); 2816 assert(["-a","A","--","-b","B"].parseCLIArgs!T.get == T("A","",["-b","B"])); 2817 } 2818 2819 unittest 2820 { 2821 struct T 2822 { 2823 @NamedArgument int i; 2824 @NamedArgument(["u","u1"]) uint u; 2825 @NamedArgument("d","d1") double d; 2826 } 2827 2828 static assert(["-i","-5","-u","8","-d","12.345"].parseCLIArgs!T.get == T(-5,8,12.345)); 2829 static assert(["-i","-5","-u1","8","-d1","12.345"].parseCLIArgs!T.get == T(-5,8,12.345)); 2830 assert(["-i","-5","-u","8","-d","12.345"].parseCLIArgs!T.get == T(-5,8,12.345)); 2831 assert(["-i","-5","-u1","8","-d1","12.345"].parseCLIArgs!T.get == T(-5,8,12.345)); 2832 } 2833 2834 unittest 2835 { 2836 struct T 2837 { 2838 @(NamedArgument) int[] a; 2839 @(NamedArgument) int[][] b; 2840 } 2841 2842 static assert(["-a","1","2","3","-a","4","5"].parseCLIArgs!T.get.a == [1,2,3,4,5]); 2843 static assert(["-b","1","2","3","-b","4","5"].parseCLIArgs!T.get.b == [[1,2,3],[4,5]]); 2844 assert(["-a","1","2","3","-a","4","5"].parseCLIArgs!T.get.a == [1,2,3,4,5]); 2845 assert(["-b","1","2","3","-b","4","5"].parseCLIArgs!T.get.b == [[1,2,3],[4,5]]); 2846 } 2847 2848 unittest 2849 { 2850 struct T 2851 { 2852 @NamedArgument int[] a; 2853 } 2854 2855 Config cfg; 2856 cfg.arraySep = ','; 2857 2858 assert(["-a","1,2,3","-a","4","5"].parseCLIArgs!T(cfg).get == T([1,2,3,4,5])); 2859 } 2860 2861 unittest 2862 { 2863 struct T 2864 { 2865 @NamedArgument int[string] a; 2866 } 2867 2868 static assert(["-a=foo=3","-a","boo=7"].parseCLIArgs!T.get.a == ["foo":3,"boo":7]); 2869 assert(["-a=foo=3","-a","boo=7"].parseCLIArgs!T.get.a == ["foo":3,"boo":7]); 2870 } 2871 2872 unittest 2873 { 2874 struct T 2875 { 2876 @NamedArgument int[string] a; 2877 } 2878 2879 Config cfg; 2880 cfg.arraySep = ','; 2881 2882 assert(["-a=foo=3,boo=7"].parseCLIArgs!T(cfg).get.a == ["foo":3,"boo":7]); 2883 assert(["-a","foo=3,boo=7"].parseCLIArgs!T(cfg).get.a == ["foo":3,"boo":7]); 2884 } 2885 2886 unittest 2887 { 2888 struct T 2889 { 2890 enum Fruit { apple, pear }; 2891 2892 @NamedArgument Fruit a; 2893 } 2894 2895 static assert(["-a","apple"].parseCLIArgs!T.get == T(T.Fruit.apple)); 2896 static assert(["-a=pear"].parseCLIArgs!T.get == T(T.Fruit.pear)); 2897 assert(["-a","apple"].parseCLIArgs!T.get == T(T.Fruit.apple)); 2898 assert(["-a=pear"].parseCLIArgs!T.get == T(T.Fruit.pear)); 2899 } 2900 2901 unittest 2902 { 2903 struct T 2904 { 2905 @NamedArgument string[] a; 2906 } 2907 2908 assert(["-a","1,2,3","-a","4","5"].parseCLIArgs!T.get == T(["1,2,3","4","5"])); 2909 2910 Config cfg; 2911 cfg.arraySep = ','; 2912 2913 assert(["-a","1,2,3","-a","4","5"].parseCLIArgs!T(cfg).get == T(["1","2","3","4","5"])); 2914 } 2915 2916 unittest 2917 { 2918 struct T 2919 { 2920 @(NamedArgument.AllowNoValue !10) int a; 2921 @(NamedArgument.RequireNoValue!20) int b; 2922 } 2923 2924 static assert(["-a"].parseCLIArgs!T.get.a == 10); 2925 static assert(["-b"].parseCLIArgs!T.get.b == 20); 2926 static assert(["-a", "30"].parseCLIArgs!T.get.a == 30); 2927 assert(["-a"].parseCLIArgs!T.get.a == 10); 2928 assert(["-b"].parseCLIArgs!T.get.b == 20); 2929 assert(["-a", "30"].parseCLIArgs!T.get.a == 30); 2930 assert(["-b", "30"].parseCLIArgs!T.isNull); 2931 } 2932 2933 unittest 2934 { 2935 struct T 2936 { 2937 @(NamedArgument 2938 .PreValidation!((string s) { return s.length > 1 && s[0] == '!'; }) 2939 .Parse !((string s) { return s[1]; }) 2940 .Validation !((char v) { return v >= '0' && v <= '9'; }) 2941 .Action !((ref int a, char v) { a = v - '0'; }) 2942 ) 2943 int a; 2944 } 2945 2946 static assert(["-a","!4"].parseCLIArgs!T.get.a == 4); 2947 assert(["-a","!4"].parseCLIArgs!T.get.a == 4); 2948 } 2949 2950 unittest 2951 { 2952 static struct T 2953 { 2954 int a; 2955 2956 @(NamedArgument("a")) void foo() { a++; } 2957 } 2958 2959 static assert(["-a","-a","-a","-a"].parseCLIArgs!T.get.a == 4); 2960 assert(["-a","-a","-a","-a"].parseCLIArgs!T.get.a == 4); 2961 } 2962 2963 2964 //private 2965 string getProgramName() 2966 { 2967 import core.runtime: Runtime; 2968 import std.path: baseName; 2969 return Runtime.args[0].baseName; 2970 } 2971 2972 unittest 2973 { 2974 assert(getProgramName().length > 0); 2975 } 2976 2977 2978 private struct CommandInfo 2979 { 2980 private string name; 2981 private string usage; 2982 private string description; 2983 private string epilog; 2984 2985 auto ref Usage(string text) 2986 { 2987 usage = text; 2988 return this; 2989 } 2990 2991 auto ref Description(string text) 2992 { 2993 description = text; 2994 return this; 2995 } 2996 2997 auto ref Epilog(string text) 2998 { 2999 epilog = text; 3000 return this; 3001 } 3002 } 3003 3004 auto Command(string name = "") 3005 { 3006 return CommandInfo(name); 3007 } 3008 3009 unittest 3010 { 3011 assert(Command("MYPROG").name == "MYPROG"); 3012 } 3013 3014 3015 private struct CommandArguments(RECEIVER) 3016 { 3017 static assert(getUDAs!(RECEIVER, CommandInfo).length <= 1); 3018 3019 static if(getUDAs!(RECEIVER, CommandInfo).length == 0) 3020 CommandInfo info; 3021 else 3022 CommandInfo info = getUDAs!(RECEIVER, CommandInfo)[0]; 3023 3024 Arguments!RECEIVER arguments; 3025 3026 3027 private this(in Config config) 3028 { 3029 arguments = createArguments!RECEIVER(config.caseSensitive); 3030 3031 if(config.addHelp) 3032 { 3033 arguments.addArgument!helpArgument(delegate (in Config config, string argName, ref RECEIVER receiver, string[] rawValues) 3034 { 3035 import std.stdio: stdout; 3036 3037 printHelp(stdout.lockingTextWriter(), this, config); 3038 3039 return true; 3040 }); 3041 } 3042 } 3043 } 3044 3045 //////////////////////////////////////////////////////////////////////////////////////////////////// 3046 // Help-printing functions 3047 //////////////////////////////////////////////////////////////////////////////////////////////////// 3048 private void printValue(Output)(auto ref Output output, in ArgumentInfo info) 3049 { 3050 if(info.maxValuesCount.get == 0) 3051 return; 3052 3053 if(info.minValuesCount.get == 0) 3054 output.put('['); 3055 3056 output.put(info.placeholder); 3057 if(info.maxValuesCount.get > 1) 3058 output.put(" ..."); 3059 3060 if(info.minValuesCount.get == 0) 3061 output.put(']'); 3062 } 3063 3064 unittest 3065 { 3066 auto test(int min, int max) 3067 { 3068 ArgumentInfo info; 3069 info.placeholder = "v"; 3070 info.minValuesCount = min; 3071 info.maxValuesCount = max; 3072 3073 import std.array: appender; 3074 auto a = appender!string; 3075 a.printValue(info); 3076 return a[]; 3077 } 3078 3079 assert(test(0,0) == ""); 3080 assert(test(0,1) == "[v]"); 3081 assert(test(0,5) == "[v ...]"); 3082 assert(test(1,1) == "v"); 3083 assert(test(1,5) == "v ..."); 3084 assert(test(3,3) == "v ..."); 3085 assert(test(3,5) == "v ..."); 3086 } 3087 3088 3089 private string getArgumentName(string name, in Config config) 3090 { 3091 name = config.namedArgChar ~ name; 3092 return name.length > 2 ? config.namedArgChar ~ name : name; 3093 } 3094 3095 unittest 3096 { 3097 assert(getArgumentName("f", Config.init) == "-f"); 3098 assert(getArgumentName("foo", Config.init) == "--foo"); 3099 } 3100 3101 3102 private void printInvocation(Output)(auto ref Output output, in ArgumentInfo info, in string[] names, in Config config) 3103 { 3104 if(info.positional) 3105 output.printValue(info); 3106 else 3107 { 3108 import std.algorithm: each; 3109 3110 names.each!((i, name) 3111 { 3112 if(i > 0) 3113 output.put(", "); 3114 3115 output.put(getArgumentName(name, config)); 3116 3117 if(info.maxValuesCount.get > 0) 3118 { 3119 output.put(' '); 3120 output.printValue(info); 3121 } 3122 }); 3123 } 3124 } 3125 3126 unittest 3127 { 3128 auto test(bool positional) 3129 { 3130 ArgumentInfo info; 3131 info.placeholder = "v"; 3132 if(positional) 3133 info.position = 0; 3134 3135 import std.array: appender; 3136 auto a = appender!string; 3137 a.printInvocation(info.setDefaults!(int, "foo"), ["f","foo"], Config.init); 3138 return a[]; 3139 } 3140 3141 assert(test(false) == "-f v, --foo v"); 3142 assert(test(true) == "v"); 3143 } 3144 3145 3146 private void printUsage(Output)(auto ref Output output, in ArgumentInfo info, in Config config) 3147 { 3148 if(!info.required) 3149 output.put('['); 3150 3151 output.printInvocation(info, [info.names[0]], config); 3152 3153 if(!info.required) 3154 output.put(']'); 3155 } 3156 3157 unittest 3158 { 3159 auto test(bool required, bool positional) 3160 { 3161 ArgumentInfo info; 3162 info.names ~= "foo"; 3163 info.placeholder = "v"; 3164 info.required = required; 3165 if(positional) 3166 info.position = 0; 3167 3168 import std.array: appender; 3169 auto a = appender!string; 3170 a.printUsage(info.setDefaults!(int, "foo"), Config.init); 3171 return a[]; 3172 } 3173 3174 assert(test(false, false) == "[--foo v]"); 3175 assert(test(false, true) == "[v]"); 3176 assert(test(true, false) == "--foo v"); 3177 assert(test(true, true) == "v"); 3178 } 3179 3180 3181 private void substituteProg(Output)(auto ref Output output, string text, string prog) 3182 { 3183 import std.array: replaceInto; 3184 output.replaceInto(text, "%(PROG)", prog); 3185 } 3186 3187 unittest 3188 { 3189 import std.array: appender; 3190 auto a = appender!string; 3191 a.substituteProg("this is some text where %(PROG) is substituted but PROG and prog are not", "-myprog-"); 3192 assert(a[] == "this is some text where -myprog- is substituted but PROG and prog are not"); 3193 } 3194 3195 3196 private string spaces(ulong num) 3197 { 3198 import std.range: repeat; 3199 import std.array: array; 3200 return ' '.repeat(num).array; 3201 } 3202 3203 unittest 3204 { 3205 assert(spaces(0) == ""); 3206 assert(spaces(1) == " "); 3207 assert(spaces(5) == " "); 3208 } 3209 3210 private void printUsage(T, Output)(auto ref Output output, in CommandArguments!T cmd, in Config config) 3211 { 3212 output.put("usage: "); 3213 3214 if(cmd.info.usage.length > 0) 3215 substituteProg(output, cmd.info.usage, cmd.info.name); 3216 else 3217 { 3218 import std.algorithm: filter, each, map; 3219 3220 alias print = (r) => r 3221 .filter!((ref _) => !_.hideFromHelp) 3222 .each!((ref _) 3223 { 3224 output.put(' '); 3225 output.printUsage(_, config); 3226 }); 3227 3228 output.put(cmd.info.name.length > 0 ? cmd.info.name : getProgramName()); 3229 3230 // named args 3231 print(cmd.arguments.arguments.filter!((ref _) => !_.positional)); 3232 // positional args 3233 print(cmd.arguments.positionalArguments.map!(ref (_) => cmd.arguments.arguments[_])); 3234 3235 output.put('\n'); 3236 } 3237 } 3238 3239 void printUsage(T, Output)(auto ref Output output, in Config config) 3240 { 3241 printUsage(output, CommandArguments!T(config), config); 3242 } 3243 3244 unittest 3245 { 3246 @(Command("MYPROG").Usage("custom usage of %(PROG)")) 3247 struct T 3248 { 3249 string s; 3250 } 3251 3252 auto test(string usage) 3253 { 3254 import std.array: appender; 3255 3256 auto a = appender!string; 3257 a.printUsage!T(Config.init); 3258 return a[]; 3259 } 3260 3261 enum expected = "usage: custom usage of MYPROG"; 3262 static assert(test("custom usage of %(PROG)") == expected); 3263 assert(test("custom usage of %(PROG)") == expected); 3264 } 3265 3266 3267 private void printHelp(Output, ARGS)(auto ref Output output, in Group group, ARGS args, int helpPosition) 3268 { 3269 import std.string: wrap, leftJustify; 3270 3271 if(group.arguments.length == 0 || group.name.length == 0) 3272 return; 3273 3274 output.put(group.name); 3275 output.put(":\n"); 3276 3277 if(group.description.length > 0) 3278 { 3279 output.put(" "); 3280 output.put(group.description); 3281 output.put("\n\n"); 3282 } 3283 3284 immutable ident = spaces(helpPosition + 2); 3285 3286 foreach(idx; group.arguments) 3287 { 3288 auto arg = &args[idx]; 3289 3290 if(arg.invocation.length == 0) 3291 continue; 3292 else if(arg.invocation.length <= helpPosition - 4) // 2=indent, 2=two spaces between invocation and help text 3293 { 3294 import std.array: appender; 3295 3296 auto invocation = appender!string; 3297 invocation ~= " "; 3298 invocation ~= arg.invocation.leftJustify(helpPosition); 3299 output.put(arg.help.wrap(80-2, invocation[], ident)); 3300 } 3301 else 3302 { 3303 // long action name; start on the next line 3304 output.put(" "); 3305 output.put(arg.invocation); 3306 output.put("\n"); 3307 output.put(arg.help.wrap(80-2, ident, ident)); 3308 } 3309 } 3310 3311 output.put('\n'); 3312 } 3313 3314 3315 private void printHelp(T, Output)(auto ref Output output, in CommandArguments!T cmd, in Config config) 3316 { 3317 import std.algorithm: map, maxElement, min; 3318 import std.array: appender, array; 3319 3320 printUsage(output, cmd, config); 3321 output.put('\n'); 3322 3323 if(cmd.info.description.length > 0) 3324 { 3325 output.put(cmd.info.description); 3326 output.put("\n\n"); 3327 } 3328 3329 // pre-compute the output 3330 auto args = 3331 cmd.arguments.arguments 3332 .map!((ref _) 3333 { 3334 import std.typecons : tuple; 3335 3336 if(_.hideFromHelp) 3337 return tuple!("invocation","help")("", ""); 3338 3339 auto invocation = appender!string; 3340 invocation.printInvocation(_, _.names, config); 3341 3342 return tuple!("invocation","help")(invocation[], _.description); 3343 }).array; 3344 3345 immutable maxInvocationWidth = args.map!(_ => _.invocation.length).maxElement; 3346 immutable helpPosition = min(maxInvocationWidth + 4, 24); 3347 3348 //user-defined groups 3349 foreach(ref group; cmd.arguments.groups[2..$]) 3350 output.printHelp(group, args, helpPosition); 3351 3352 //required args 3353 output.printHelp(cmd.arguments.requiredGroup, args, helpPosition); 3354 3355 //optionals args 3356 output.printHelp(cmd.arguments.optionalGroup, args, helpPosition); 3357 3358 if(cmd.info.epilog.length > 0) 3359 { 3360 output.put(cmd.info.epilog); 3361 output.put('\n'); 3362 } 3363 } 3364 3365 void printHelp(T, Output)(auto ref Output output, in Config config) 3366 { 3367 printHelp(output, CommandArguments!T(config), config); 3368 } 3369 3370 unittest 3371 { 3372 @(Command("MYPROG") 3373 .Description("custom description") 3374 .Epilog("custom epilog") 3375 ) 3376 struct T 3377 { 3378 @NamedArgument string s; 3379 @(NamedArgument.Placeholder("VALUE")) string p; 3380 3381 @(NamedArgument.HideFromHelp()) string hidden; 3382 3383 enum Fruit { apple, pear }; 3384 @(NamedArgument(["f","fruit"]).Required().Description("This is a help text for fruit. Very very very very very very very very very very very very very very very very very very very long text")) Fruit f; 3385 3386 @(NamedArgument.AllowedValues!([1,4,16,8])) int i; 3387 3388 @(PositionalArgument(0).Description("This is a help text for param0. Very very very very very very very very very very very very very very very very very very very long text")) string param0; 3389 @(PositionalArgument(1).AllowedValues!(["q","a"])) string param1; 3390 3391 @TrailingArguments string[] args; 3392 } 3393 3394 auto test(alias func)() 3395 { 3396 import std.array: appender; 3397 3398 auto a = appender!string; 3399 func!T(a, Config.init); 3400 return a[]; 3401 } 3402 static assert(test!printUsage.length > 0); // ensure that it works at compile time 3403 static assert(test!printHelp .length > 0); // ensure that it works at compile time 3404 3405 assert(test!printUsage == "usage: MYPROG [-s S] [-p VALUE] -f {apple,pear} [-i {1,4,16,8}] [-h] param0 {q,a}\n"); 3406 assert(test!printHelp == "usage: MYPROG [-s S] [-p VALUE] -f {apple,pear} [-i {1,4,16,8}] [-h] param0 {q,a}\n\n"~ 3407 "custom description\n\n"~ 3408 "Required arguments:\n"~ 3409 " -f {apple,pear}, --fruit {apple,pear}\n"~ 3410 " This is a help text for fruit. Very very very very\n"~ 3411 " very very very very very very very very very very\n"~ 3412 " very very very very very long text\n"~ 3413 " param0 This is a help text for param0. Very very very very\n"~ 3414 " very very very very very very very very very very\n"~ 3415 " very very very very very long text\n"~ 3416 " {q,a} \n\n"~ 3417 "Optional arguments:\n"~ 3418 " -s S \n"~ 3419 " -p VALUE \n"~ 3420 " -i {1,4,16,8} \n"~ 3421 " -h, --help Show this help message and exit\n\n"~ 3422 "custom epilog\n"); 3423 } 3424 3425 unittest 3426 { 3427 @Command("MYPROG") 3428 struct T 3429 { 3430 @(ArgumentGroup("group1").Description("group1 description")) 3431 { 3432 @NamedArgument 3433 { 3434 string a; 3435 string b; 3436 } 3437 @PositionalArgument(0) string p; 3438 } 3439 3440 @(ArgumentGroup("group2").Description("group2 description")) 3441 @NamedArgument 3442 { 3443 string c; 3444 string d; 3445 } 3446 @PositionalArgument(1) string q; 3447 } 3448 3449 auto test(alias func)() 3450 { 3451 import std.array: appender; 3452 3453 auto a = appender!string; 3454 func!T(a, Config.init); 3455 return a[]; 3456 } 3457 3458 assert(test!printHelp == "usage: MYPROG [-a A] [-b B] [-c C] [-d D] [-h] p q\n\n"~ 3459 "group1:\n"~ 3460 " group1 description\n\n"~ 3461 " -a A \n"~ 3462 " -b B \n"~ 3463 " p \n\n"~ 3464 "group2:\n"~ 3465 " group2 description\n\n"~ 3466 " -c C \n"~ 3467 " -d D \n\n"~ 3468 "Required arguments:\n"~ 3469 " q \n\n"~ 3470 "Optional arguments:\n"~ 3471 " -h, --help Show this help message and exit\n\n"); 3472 } 3473 3474 unittest 3475 { 3476 @Command("MYPROG") 3477 struct T 3478 { 3479 @(NamedArgument.HideFromHelp()) string s; 3480 } 3481 3482 assert(parseCLIArgs!T(["-h","-s","asd"]).isNull()); 3483 assert(parseCLIArgs!T(["-h"], (T t) { assert(false); }) == 0); 3484 3485 auto args = ["-h","-s","asd"]; 3486 assert(parseCLIKnownArgs!T(args).isNull()); 3487 assert(args.length == 3); 3488 assert(parseCLIKnownArgs!T(["-h"], (T t, string[] args) { assert(false); }) == 0); 3489 } 3490 3491 unittest 3492 { 3493 @Command("MYPROG") 3494 struct T 3495 { 3496 @(NamedArgument.Required()) string s; 3497 } 3498 3499 assert(parseCLIArgs!T([], (T t) { assert(false); }) != 0); 3500 } 3501 3502 unittest 3503 { 3504 @Command("MYPROG") 3505 struct T 3506 { 3507 @MutuallyExclusive() 3508 { 3509 string a; 3510 string b; 3511 } 3512 } 3513 assert(parseCLIArgs!T(["-a","a","-b","b"], (T t) { assert(false); }) != 0); 3514 assert(parseCLIArgs!T(["-a","a"], (T t) {}) == 0); 3515 assert(parseCLIArgs!T(["-b","b"], (T t) {}) == 0); 3516 assert(parseCLIArgs!T([], (T t) {}) == 0); 3517 } 3518 3519 unittest 3520 { 3521 @Command("MYPROG") 3522 struct T 3523 { 3524 @RequiredTogether() 3525 { 3526 string a; 3527 string b; 3528 } 3529 } 3530 assert(parseCLIArgs!T(["-a","a","-b","b"], (T t) {}) == 0); 3531 assert(parseCLIArgs!T(["-a","a"], (T t) { assert(false); }) != 0); 3532 assert(parseCLIArgs!T(["-b","b"], (T t) { assert(false); }) != 0); 3533 assert(parseCLIArgs!T([], (T t) {}) == 0); 3534 }