1 module jcli.introspect.data; 2 3 import jcli.core; 4 5 import std.conv : to; 6 import std.traits; 7 import std.meta; 8 9 struct ArgumentCommonInfo 10 { 11 // TODO: Support nested structs. For now, no such thing. 12 // This is currently the name of the field, used to get a reference to that field. 13 string identifier; 14 ArgFlags flags; 15 ArgGroup group; 16 } 17 18 /// Reason: accessing with multiple dots is annoying and prevents easy refactoring. 19 mixin template ArgumentGetters() 20 { 21 string name() const nothrow @nogc pure @safe { return uda.name; } 22 23 inout nothrow @nogc pure @safe 24 { 25 ref inout(string) description() { return uda.description; } 26 ref inout(string) identifier() { return argument.identifier; } 27 ref inout(ArgFlags) flags() { return argument.flags; } 28 ref inout(ArgGroup) group() { return argument.group; } 29 } 30 } 31 32 struct NamedArgumentInfo 33 { 34 ArgNamed uda; 35 ArgumentCommonInfo argument; 36 37 mixin ArgumentGetters; 38 inout(Pattern) pattern() @safe nothrow @nogc inout { return uda.pattern; } 39 } 40 41 // It's a good idea to encapsulate this logic here. 42 // It can be easily modified later when we add nested structs. 43 template isNameMatch(NamedArgumentInfo namedArgumentInfo) 44 { 45 import std.algorithm; 46 /// Takes a name, like the `aa` part of `-aa`. 47 /// Tries to match the pattern specified as the template argument. 48 bool isNameMatch(string value) 49 { 50 enum caseInsensitive = namedArgumentInfo.flags.has(ArgFlags._caseInsensitiveBit); 51 { 52 bool noMatches = namedArgumentInfo 53 .pattern 54 .matches!caseInsensitive(value) 55 .empty; 56 if (!noMatches) 57 return true; 58 } 59 static if (namedArgumentInfo.flags.has(ArgFlags._repeatableNameBit)) 60 { 61 bool allSame = value.all!(a => a == value[0]); 62 if (!allSame) 63 return false; 64 bool noMatches = namedArgumentInfo 65 .pattern 66 .matches!caseInsensitive(value[0 .. 1]) 67 .empty; 68 return !noMatches; 69 } 70 else 71 { 72 return false; 73 } 74 } 75 } 76 77 struct PositionalArgumentInfo 78 { 79 ArgPositional uda; 80 ArgumentCommonInfo argument; 81 82 mixin ArgumentGetters; 83 } 84 85 // private template FieldType(TCommand, string fieldPath) 86 // { 87 // mixin("alias FieldType = typeof(TCommand." ~ fieldPath ~ ");"); 88 // } 89 90 91 private template fieldPathOf(alias argumentInfoOrFieldPath) 92 { 93 static if (is(Unqual!(typeof(argumentInfoOrFieldPath)) == ArgumentCommonInfo)) 94 enum string fieldPathOf = argumentInfoOrFieldPath.identifier; 95 else static if (is(Unqual!(typeof(argumentInfoOrFieldPath.argument)) == ArgumentCommonInfo)) 96 enum string fieldPathOf = argumentInfoOrFieldPath.argument.identifier; 97 else static if (is(Unqual!(typeof(argumentInfoOrFieldPath)) == string)) 98 enum string fieldPathOf = argumentInfoOrFieldPath; 99 else 100 static assert(false, "Unsupported thing: " ~ Unqual!(typeof(argumentInfoOrFieldPath)).stringof); 101 } 102 103 /// command.getArgumentFieldRef!argInfo 104 template getArgumentFieldRef(alias argumentInfoOrFieldPath) 105 { 106 ref auto getArgumentFieldRef(TCommand)(ref TCommand command) 107 { 108 mixin("return command." ~ fieldPathOf!argumentInfoOrFieldPath ~ ";"); 109 } 110 } 111 template getArgumentFieldSymbol(TCommand, alias argumentInfoOrFieldPath) 112 { 113 mixin("alias getArgumentFieldSymbol = TCommand." ~ fieldPathOf!argumentInfoOrFieldPath ~ ";"); 114 } 115 116 template getCommandUDAs(CommandType) 117 { 118 alias commandUDAs = AliasSeq!( 119 getUDAs!(CommandType, Command), 120 getUDAs!(CommandType, CommandDefault)); 121 122 static if (commandUDAs.length > 0) 123 { 124 alias getCommandUDAs = commandUDAs; 125 } 126 else 127 { 128 enum isValue(alias stringUDA) = is(typeof(stringUDA) : string); 129 alias stringUDAs = Filter!(isValue, 130 getUDAs!(CommandType, string)); 131 132 static if (stringUDAs.length > 0) 133 alias getCommandUDAs = AliasSeq!(stringUDAs[0]); 134 else 135 alias getCommandUDAs = AliasSeq!(); 136 } 137 } 138 139 140 // NOTE: 141 // It's not clear at all what a good design fot this is. 142 // I only feel esoteric OOP vibes when deciding whether this should be in a struct, 143 // template, whether it should be a getter, and enum, immutable, or an alias. 144 // It feels to me like it's only philosophical at this point. 145 // So I'm just going to do a single thing and call it a day. 146 // Whatever I do feels wrong tho. 147 template CommandInfo(TCommand) 148 { 149 alias CommandType = TCommand; 150 alias Arguments = CommandArgumentsInfo!TCommand; 151 152 static assert( 153 getCommandUDAs!TCommand.length <= 1, 154 "Only one command UDA is allowed."); 155 alias commandUDAs = getCommandUDAs!TCommand; 156 157 static if (commandUDAs.length == 0) 158 { 159 enum flags = CommandFlags.noCommandAttribute; 160 } 161 else 162 { 163 alias rawCommandUDA = Alias!(commandUDAs[0]); 164 165 static if (is(typeof(rawCommandUDA) : string)) 166 { 167 enum flags = CommandFlags.stringAttribute | CommandFlags.givenValue; 168 enum string udaValue = rawCommandUDA; 169 } 170 else static if (is(rawCommandUDA == Command)) 171 { 172 enum flags = CommandFlags.commandAttribute; 173 immutable udaValue = Command([__traits(identifier, TCommand)], ""); 174 } 175 else static if (is(rawCommandUDA == CommandDefault)) 176 { 177 enum flags = CommandFlags.commandAttribute | CommandFlags.explicitlyDefault; 178 immutable udaValue = CommandDefault(""); 179 } 180 else static if (is(typeof(rawCommandUDA) == Command)) 181 { 182 enum flags = CommandFlags.commandAttribute | CommandFlags.givenValue; 183 immutable udaValue = rawCommandUDA; 184 } 185 else static if (is(typeof(rawCommandUDA) == CommandDefault)) 186 { 187 enum flags = CommandFlags.commandAttribute | CommandFlags.givenValue | CommandFlags.explicitlyDefault; 188 immutable udaValue = rawCommandUDA; 189 } 190 else static assert(0); 191 192 // this part is super meh 193 static if (flags.has(CommandFlags.stringAttribute)) 194 enum string description = udaValue; 195 else 196 enum string description = udaValue.description; 197 } 198 } 199 200 template CommandArgumentsInfo(TCommand) 201 { 202 alias CommandType = TCommand; 203 204 static foreach (field; TCommand.tupleof) 205 static assert(countUDAsOf!(field, ArgNamed, ArgPositional, ArgOverflow, ArgRaw) <= 1); 206 207 // TODO: 208 // We should have another member here, with the members that are nested structs. 209 // 210 // Probably say positional arguments are not allowed for nested structs, 211 // because it would be extremely confusing to implement and nobody will probably ever use them. 212 // 213 // The nested struct can be either named (aka -name.field to give a value to the flag `field` 214 // inside that nested struct argument), or flattened (allow to simply do -field). 215 // 216 // We should create another member which would point to the arguments with the nested types perhaps? 217 // We could get the names of the types by accessing the field identifiers? 218 // ``` 219 // immutable NamedArgumentInfo[] nested; 220 // ``` 221 // Then make a helper template that would flatten these for the linear iteration, 222 // for use in the command parser. It really does not need to know about this nesting, 223 // since it would always read the arguments linearly, only matching actual qualified names, 224 // it does not care where in the struct that field is. 225 // 226 // But we still do need all the info for e.g. reading arguments from typical config format, 227 // where nesting is allowed. Like json or xml. There, the user would write out a nested object 228 // instead of assigning the fields individually, which would be kinda hard to implement without 229 // the nesting info (you'd have to reget that info from the stored dots in the idetifiers. 230 231 // TODO: 232 // A similar mechanism can be used to get pointers to data received from other commands. 233 // This will be useful when nested commands partially match the given arguments. 234 // Currently not implemented whatsoever. 235 236 // TODO: These seem to be used only as a compile-time info thing, so these should perhaps be alias seqs. 237 238 /// Includes the simple string usage, which gets converted to an ArgNamed uda. 239 immutable NamedArgumentInfo[] named = getNamedArgumentInfosOf!TCommand; 240 immutable PositionalArgumentInfo[] positional = getPositionalArgumentInfosOf!TCommand; 241 242 import std.algorithm; 243 enum size_t numRequiredPositionalArguments = positional 244 .filter!((immutable p) => p.flags.has(ArgFlags._requiredBit)) 245 .count; 246 247 private alias fieldsWithOverflow = fieldsWithUDAOf!(TCommand, ArgOverflow); 248 static assert(fieldsWithOverflow.length <= 1, "No more than one overflow argument allowed"); 249 enum takesOverflow = fieldsWithOverflow.length == 1; 250 static if (takesOverflow) 251 immutable ArgumentCommonInfo overflow = getCommonArgumentInfo!(fieldsWithOverflow[0], ArgConfig.aggregate); 252 253 254 private alias fieldsWithRaw = fieldsWithUDAOf!(TCommand, ArgRaw); 255 static assert(fieldsWithRaw.length <= 1, "No more than one raw argument allowed"); 256 enum takesRaw = fieldsWithRaw.length == 1; 257 static if (takesRaw) 258 immutable ArgumentCommonInfo raw = getCommonArgumentInfo!(fieldsWithRaw[0], ArgConfig.aggregate); 259 260 enum takesSomeArguments = named.length > 0 || positional.length > 0 || takesOverflow || takesRaw; 261 } 262 263 unittest 264 { 265 { 266 static struct S 267 { 268 @ArgNamed 269 @ArgNamed 270 string a; 271 } 272 static assert(!__traits(compiles, CommandArgumentsInfo!S)); 273 } 274 { 275 static struct S 276 { 277 @ArgNamed 278 @ArgPositional 279 string a; 280 } 281 static assert(!__traits(compiles, CommandArgumentsInfo!S)); 282 } 283 { 284 static struct S 285 { 286 @ArgPositional 287 @ArgPositional 288 string a; 289 } 290 static assert(!__traits(compiles, CommandArgumentsInfo!S)); 291 } 292 { 293 static struct S 294 { 295 @ArgOverflow 296 @ArgOverflow 297 string[] a; 298 } 299 static assert(!__traits(compiles, CommandArgumentsInfo!S)); 300 } 301 { 302 static struct S 303 { 304 @ArgOverflow 305 @ArgRaw 306 string[] a; 307 } 308 static assert(!__traits(compiles, CommandArgumentsInfo!S)); 309 } 310 { 311 static struct S 312 { 313 @ArgOverflow 314 string[] a; 315 316 @ArgRaw 317 string[] b; 318 } 319 alias Info = CommandArgumentsInfo!S; 320 assert(Info.takesOverflow); 321 assert(Info.overflow.identifier == "a"); 322 assert(Info.takesRaw); 323 assert(Info.raw.identifier == "b"); 324 assert(Info.named.length == 0); 325 assert(Info.positional.length == 0); 326 } 327 { 328 static struct S 329 { 330 @ArgPositional 331 string a; 332 } 333 alias Info = CommandArgumentsInfo!S; 334 static assert(!Info.takesOverflow); 335 static assert(!Info.takesRaw); 336 static assert(Info.positional.length == 1); 337 enum positional = Info.positional[0]; 338 static assert(positional.identifier == "a"); 339 static assert(positional.name == "a"); 340 static assert(positional.flags.has(ArgFlags._positionalArgumentBit)); 341 } 342 { 343 static struct S 344 { 345 @ArgNamed 346 string a; 347 } 348 alias Info = CommandArgumentsInfo!S; 349 static assert(!Info.takesOverflow); 350 static assert(!Info.takesRaw); 351 static assert(Info.named.length == 1); 352 enum named = Info.named[0]; 353 static assert(named.identifier == "a"); 354 static assert(named.name == "a"); 355 static assert(named.flags.has(ArgFlags._namedArgumentBit)); 356 } 357 { 358 static struct S 359 { 360 @("b") 361 string a; 362 } 363 alias Info = CommandArgumentsInfo!S; 364 static assert(!Info.takesOverflow); 365 static assert(!Info.takesRaw); 366 static assert(Info.named.length == 1); 367 enum named = Info.named[0]; 368 static assert(named.identifier == "a"); 369 static assert(named.name == "a"); 370 static assert(named.description == "b"); 371 static assert(named.flags.has(ArgFlags._namedArgumentBit)); 372 } 373 { 374 static struct S 375 { 376 @("b") 377 @ArgNamed 378 string a; 379 } 380 alias Info = CommandArgumentsInfo!S; 381 static assert(Info.named.length == 1); 382 enum named = Info.named[0]; 383 static assert(named.identifier == "a"); 384 static assert(named.name == "a"); 385 static assert(named.flags.has(ArgFlags._namedArgumentBit)); 386 387 // The description is not applied here, because 388 // ArgNamed takes precedence over the string. 389 // static assert(named.uda.description == "b"); 390 } 391 { 392 static struct S 393 { 394 @("b") 395 string a = "c"; 396 } 397 alias Info = CommandArgumentsInfo!S; 398 static assert(Info.named[0].flags.has(ArgFlags._optionalBit)); 399 } 400 { 401 static struct S 402 { 403 @ArgNamed 404 string a = "c"; 405 } 406 alias Info = CommandArgumentsInfo!S; 407 static assert(Info.named[0].flags.has(ArgFlags._optionalBit)); 408 } 409 { 410 static struct S 411 { 412 @ArgPositional 413 @(ArgConfig.optional) 414 string a; 415 416 @ArgPositional 417 string b; 418 } 419 static assert(!__traits(compiles, CommandArgumentsInfo!S)); 420 } 421 { 422 static struct S 423 { 424 @ArgPositional 425 string a = "c"; 426 427 @ArgPositional 428 string b; 429 } 430 alias Info = CommandArgumentsInfo!S; 431 static assert(Info.positional[0].flags.has(ArgFlags._requiredBit)); 432 static assert(Info.positional[1].flags.has(ArgFlags._requiredBit)); 433 } 434 { 435 static struct S 436 { 437 @ArgPositional 438 string a; 439 440 @ArgPositional 441 string b; 442 } 443 alias Info = CommandArgumentsInfo!S; 444 static assert(Info.positional.length == 2); 445 { 446 enum a = Info.positional[0]; 447 static assert(a.identifier == "a"); 448 static assert(a.name == "a"); 449 static assert(a.flags.doesNotHave(ArgFlags._optionalBit)); 450 } 451 { 452 enum b = Info.positional[1]; 453 static assert(b.identifier == "b"); 454 static assert(b.name == "b"); 455 static assert(b.flags.doesNotHave(ArgFlags._optionalBit)); 456 } 457 } 458 { 459 static struct S 460 { 461 @ArgPositional 462 string a; 463 464 @ArgPositional 465 string b = "c"; 466 } 467 alias Info = CommandArgumentsInfo!S; 468 static assert(Info.positional.length == 2); 469 { 470 enum a = Info.positional[0]; 471 static assert(a.identifier == "a"); 472 static assert(a.name == "a"); 473 static assert(a.flags.doesNotHave(ArgFlags._optionalBit)); 474 } 475 { 476 enum b = Info.positional[1]; 477 static assert(b.identifier == "b"); 478 static assert(b.name == "b"); 479 static assert(b.flags.has(ArgFlags._optionalBit)); 480 } 481 } 482 { 483 static struct S 484 { 485 @ArgPositional 486 @(ArgConfig.parseAsFlag) 487 bool a; 488 } 489 static assert(!__traits(compiles, CommandArgumentsInfo!S)); 490 } 491 { 492 static struct S 493 { 494 @ArgNamed 495 @(ArgConfig.parseAsFlag) 496 bool a; 497 } 498 alias Info = CommandArgumentsInfo!S; 499 static assert(Info.named[0].flags.has(ArgFlags._parseAsFlagBit)); 500 } 501 { 502 static struct S 503 { 504 @ArgNamed 505 @(ArgConfig.parseAsFlag) 506 bool a = true; 507 } 508 static assert(!__traits(compiles, CommandArgumentsInfo!S)); 509 } 510 { 511 // NOTE: 512 // The previous version inferred that flag. 513 // I think, inferring it could lead to bugs so I say you should specify it explicitly. 514 static struct S 515 { 516 @ArgNamed 517 bool a; 518 } 519 alias Info = CommandArgumentsInfo!S; 520 static assert(Info.named[0].flags.doesNotHave(ArgFlags._parseAsFlagBit)); 521 } 522 { 523 static struct S 524 { 525 @ArgPositional 526 bool a; 527 } 528 alias Info = CommandArgumentsInfo!S; 529 static assert(Info.positional[0].flags.doesNotHave(ArgFlags._parseAsFlagBit)); 530 } 531 { 532 static struct S 533 { 534 @ArgNamed 535 @(ArgConfig._optionalBit | ArgFlags._requiredBit) 536 string a; 537 } 538 static assert(!__traits(compiles, CommandArgumentsInfo!S)); 539 } 540 { 541 static struct S 542 { 543 @ArgNamed 544 Nullable!string a; 545 } 546 alias Info = CommandArgumentsInfo!S; 547 enum a = Info.named[0]; 548 static assert(a.flags.has(ArgFlags._optionalBit | ArgFlags._inferedOptionalityBit)); 549 } 550 { 551 static struct S 552 { 553 @ArgPositional 554 Nullable!string a; 555 } 556 alias Info = CommandArgumentsInfo!S; 557 enum a = Info.positional[0]; 558 static assert(a.flags.has(ArgFlags._optionalBit | ArgFlags._inferedOptionalityBit)); 559 } 560 { 561 static struct S 562 { 563 @ArgPositional 564 @(ArgFlags._requiredBit) 565 Nullable!string a; 566 } 567 static assert(!__traits(compiles, CommandArgumentsInfo!S)); 568 } 569 { 570 static struct S 571 { 572 @ArgPositional 573 Nullable!string a; 574 @ArgPositional 575 string b; 576 } 577 static assert(!__traits(compiles, CommandArgumentsInfo!S)); 578 } 579 { 580 static struct S 581 { 582 @ArgPositional 583 string a; 584 @ArgPositional 585 Nullable!string b; 586 } 587 alias Info = CommandArgumentsInfo!S; 588 enum a = Info.positional[0]; 589 static assert(a.flags.has(ArgFlags._requiredBit | ArgFlags._inferedOptionalityBit)); 590 enum b = Info.positional[1]; 591 static assert(b.flags.has(ArgFlags._optionalBit | ArgFlags._inferedOptionalityBit)); 592 } 593 { 594 static struct S 595 { 596 @("Hello") 597 @(ArgConfig.positional) 598 string a; 599 600 @("World") 601 string b; 602 } 603 alias Info = CommandArgumentsInfo!S; 604 605 enum a = Info.positional[0]; 606 static assert(a.flags.has(ArgFlags._requiredBit | ArgFlags._inferedOptionalityBit)); 607 static assert(a.description == "Hello"); 608 609 enum b = Info.named[0]; 610 static assert(b.flags.has(ArgFlags._requiredBit | ArgFlags._inferedOptionalityBit)); 611 static assert(b.description == "World"); 612 } 613 } 614 615 private: 616 617 import std.traits; 618 import std.meta; 619 620 // NOTE: 621 // Passing the udas as a sequence works, but trying to get them within the function does not. 622 // Apparently, it may work if we mark the function static, but afaik that's a compiler bug. 623 // Another NOTE: 624 // We still need the static here?? wtf? 625 static ArgFlags foldArgumentFlags(udas...)() 626 { 627 ArgFlags result; 628 ArgConfig[] highLevelFlags; 629 630 static foreach (uda; udas) 631 { 632 static if (is(typeof(uda) == ArgConfig)) 633 { 634 highLevelFlags ~= uda; 635 } 636 static if (is(typeof(uda) : ArgFlags)) 637 { 638 static assert(uda.doesNotHaveEither( 639 ArgFlags._inferedOptionalityBit | ArgFlags._mayChangeOptionalityWithoutBreakingThingsBit), 640 "Specifying the `_inferedOptionalityBit` or `_mayChangeOptionalityWithoutBreakingThingsBit`" 641 ~ " in user code is not allowed. The optionality will be inferred automatically by default, if possible."); 642 result |= uda; 643 } 644 } 645 646 // Validate only the high level flag combinations. 647 { 648 string validationMessage = getArgumentConfigFlagsIncompatibilityValidationMessage(highLevelFlags); 649 assert(validationMessage is null, validationMessage); 650 } 651 652 return result; 653 } 654 655 656 // This is needed as a workaround for the function below. 657 // Basically, you cannot access `field.init` or get the attributes 658 // of a field symbol in a function context. 659 // template FieldInfo(alias field) 660 // { 661 // alias FieldType = typeof(field); 662 // enum FieldType initialValue = field.init; 663 // } 664 665 // This function is needed as a separate entity as a workaround the compiler quirk that makes 666 // us unable to query attributes of field symbols in templated functions. 667 // I would've inlined it below, but we're forced to use a template, or pass the arguments as an alias seq. 668 ArgFlags inferOptionalityAndValidate(FieldType)(ArgFlags initialFlags, FieldType fieldDefaultValue) 669 { 670 import std.conv : to; 671 ArgFlags flags = initialFlags; 672 673 // Validate all flags and try to infer optionality. 674 with (ArgFlags) 675 { 676 auto validationMessage = getArgumentFlagsValidationMessage(flags); 677 assert(validationMessage is null, validationMessage); 678 679 string messageIfAddedOptional = getArgumentFlagsValidationMessage(flags | _optionalBit); 680 681 static if (is(FieldType : Nullable!T, T)) 682 { 683 assert(messageIfAddedOptional is null, 684 "Nullable types must be optional.\n" ~ messageIfAddedOptional); 685 686 if (flags.doesNotHave(_optionalBit)) 687 flags |= _optionalBit | _inferedOptionalityBit; 688 } 689 // Note: These two checks are common for both named arguments and positional arguments. 690 else if (flags.doesNotHaveEither(_optionalBit | _requiredBit)) // @suppress(dscanner.suspicious.static_if_else) 691 { 692 string messageIfAddedRequired = getArgumentFlagsValidationMessage(flags | _requiredBit); 693 // TODO: 694 // This rudimentary check fails e.g. for the following: 695 // `struct A { int a = 0; }` 696 // Aka when the value is given, but is also the default. 697 // Guys from the D server say it's probably impossible to do currently. 698 // NOTE: `is` does bitwise comparison. `!=` would fail for float.nan 699 bool canInferOptional = FieldType.init !is fieldDefaultValue; 700 701 if (messageIfAddedOptional is null && canInferOptional) 702 { 703 flags |= _optionalBit; 704 if (messageIfAddedRequired is null) 705 flags |= _mayChangeOptionalityWithoutBreakingThingsBit; 706 } 707 else 708 { 709 assert(messageIfAddedRequired is null, 710 "The type can be neither optional nor required. This check should have never been hit.\n" 711 ~ "If we added required: " ~ messageIfAddedRequired ~ "\n" 712 ~ "If we added optional: " ~ messageIfAddedOptional); 713 714 flags |= _requiredBit; 715 if (messageIfAddedOptional is null) 716 flags |= _mayChangeOptionalityWithoutBreakingThingsBit; 717 } 718 flags |= _inferedOptionalityBit; 719 } 720 } 721 722 return flags; 723 } 724 725 // NOTE: 726 // We never pass alias to field even to CTFE functions as template parameters, 727 // because the compiler essetially prevents their use there. 728 // Passing to templates is ok, but trying to do anything with them in template functions 729 // makes the compiler complain. 730 enum defaultValueOf(alias field) = __traits(child, __traits(parent, field).init, field); 731 732 // Gives some basic information associated with the given argument field. 733 public template getCommonArgumentInfo(alias field, ArgFlags initialFlags = ArgFlags.none) 734 { 735 enum flagsBeforeInference = initialFlags | foldArgumentFlags!(__traits(getAttributes, field)); 736 enum flagsAfterInference = inferOptionalityAndValidate!(typeof(field))( 737 flagsBeforeInference, defaultValueOf!field); 738 739 alias groups = getUDAs!(field, ArgGroup); 740 static assert(groups.length <= 1, "Only one group attribute is allowed"); 741 static if (groups.length == 1) 742 enum group = groups[0]; 743 else 744 enum group = ArgGroup.init; 745 746 enum identifier = __traits(identifier, field); 747 enum getCommonArgumentInfo = ArgumentCommonInfo(identifier, flagsAfterInference, groups); 748 } 749 750 template getArgumentInfo(UDAType, alias field) 751 { 752 static foreach (uda; __traits(getAttributes, field)) 753 { 754 static if (is(uda == UDAType)) 755 { 756 enum getArgumentInfo = UDAType.init; 757 } 758 else static if (is(typeof(uda) == UDAType)) 759 { 760 enum getArgumentInfo = uda; 761 } 762 } 763 } 764 765 766 template fieldsWithUDAOf(T, UDAType) 767 { 768 enum hasThatUDA(alias field) = hasUDA!(field, UDAType); 769 alias fieldsWithUDAOf = Filter!(hasThatUDA, T.tupleof); 770 } 771 unittest 772 { 773 enum Test; 774 static struct S 775 { 776 @Test string a; 777 @Test string b; 778 string c; 779 } 780 alias fields = fieldsWithUDAOf!(S, Test); 781 static assert(fields.length == 2); 782 static assert(fields[0].stringof == "a"); 783 static assert(fields[1].stringof == "b"); 784 } 785 786 template fieldWithUDAOf(T, UDAType) 787 { 788 alias allFields = fieldsWithUDAOf!(T, UDAType); 789 static assert(allFields.length > 0); 790 alias fieldWithUDAOf = allFields[0]; 791 } 792 unittest 793 { 794 enum Test; 795 { 796 static struct S 797 { 798 @Test string a; 799 string c; 800 } 801 alias field = fieldWithUDAOf!(S, Test); 802 static assert(field.stringof == "a"); 803 } 804 { 805 static struct S 806 { 807 @Test string a; 808 @Test string c; 809 } 810 alias field = fieldWithUDAOf!(S, Test); 811 static assert(field.stringof == "a"); 812 } 813 { 814 static struct S 815 { 816 string a; 817 string c; 818 } 819 static assert(!__traits(compiles, fieldWithUDAOf!(S, Test))); 820 } 821 } 822 823 template UDAInfo(alias uda) 824 { 825 static if (is(typeof(uda))) 826 { 827 alias Type = typeof(uda); 828 enum hasDefaultValue = false; 829 enum value = uda; 830 } 831 else static if (is(typeof(uda.init))) 832 { 833 alias Type = uda; 834 enum hasDefaultValue = true; 835 enum value = uda.init; 836 } 837 else 838 { 839 // Even though calling enums a type is technically wrong. 840 alias Type = uda; 841 } 842 } 843 unittest 844 { 845 struct Data { int a; } 846 enum Enum; 847 { 848 @Data int b; 849 850 alias Info = UDAInfo!(getUDAs!(b, Data)); 851 static assert(Info.hasDefaultValue); 852 static assert(is(Info.Type == Data)); 853 } 854 { 855 @Data(1) int b; 856 857 alias Info = UDAInfo!(getUDAs!(b, Data)); 858 static assert(!Info.hasDefaultValue); 859 static assert(Info.value == Data(1)); 860 static assert(is(Info.Type == Data)); 861 } 862 { 863 @Enum int b; 864 865 alias Info = UDAInfo!(getUDAs!(b, Enum)); 866 static assert(is(Info.Type == Enum)); 867 } 868 } 869 870 template countUDAsOf(alias something, UDATypes...) 871 { 872 enum countUDAsOf = 873 (){ 874 size_t result = 0; 875 static foreach (UDAType; UDATypes) 876 { 877 static foreach (uda; __traits(getAttributes, something)) 878 { 879 static if (is(UDAInfo!uda.Type == UDAType)) 880 result++; 881 } 882 } 883 return result; 884 }(); 885 } 886 887 PositionalArgumentInfo[] getPositionalArgumentInfosOf(TCommand)() pure 888 { 889 import std.algorithm; 890 import std.range; 891 892 // Since this is a static foreach, it will be easy to include type info here. 893 PositionalArgumentInfo[] result; 894 895 static foreach (field; TCommand.tupleof) 896 {{ 897 enum hasPositionalUDA = hasUDA!(field, ArgPositional); 898 // Doing this one is tiny bit costly ... 899 enum foldedArgFlags = foldArgumentFlags!(__traits(getAttributes, field)); 900 enum hasPositionalFlag = foldedArgFlags.has(ArgFlags._positionalArgumentBit); 901 902 static if (hasPositionalUDA) 903 { 904 auto info = getArgumentInfo!(ArgPositional, field); 905 if (info.name == "") 906 info.name = __traits(identifier, field); 907 } 908 else static if (hasPositionalFlag) 909 { 910 alias stringAttributes = getUDAs!(field, string); 911 912 // TODO: check for string type attribute. 913 static if (stringAttributes.length > 0) 914 auto info = ArgPositional(__traits(identifier, field), stringAttributes[0]); 915 else 916 auto info = ArgPositional(__traits(identifier, field), ""); 917 } 918 919 static if (hasPositionalUDA || hasPositionalFlag) 920 { 921 result ~= PositionalArgumentInfo( 922 info, getCommonArgumentInfo!(field, ArgFlags._positionalArgumentBit)); 923 } 924 }} 925 926 alias isOptional = p => p.flags.has(ArgFlags._optionalBit); 927 alias isRequired = p => p.flags.has(ArgFlags._requiredBit); 928 929 // If there is an optional positional argument in between required ones, 930 // we change it to positional, if possible. 931 932 int indexOfLastRequired = -1; 933 foreach (index, ref p; result) 934 { 935 if (isRequired(p)) 936 indexOfLastRequired = cast(int) index; 937 } 938 if (indexOfLastRequired == -1) 939 return result; 940 941 result 942 .take(indexOfLastRequired) 943 .filter!(isOptional) 944 .each!((ref PositionalArgumentInfo p) 945 { 946 assert(p.flags.has(ArgFlags._mayChangeOptionalityWithoutBreakingThingsBit), 947 "The positional argument " ~ p.identifier 948 ~ " cannot be optional, because it is in between two positionals."); 949 p.flags &= ~ArgFlags._mayChangeOptionalityWithoutBreakingThingsBit; 950 p.flags &= ~ArgFlags._optionalBit; 951 p.flags |= ArgFlags._requiredBit; 952 }); 953 954 return result; 955 956 // const notOptionalAfterOptional = positional 957 // // after one that isn't optional, 958 // .find!(p => p.flags.has(ArgFlags._optionalBit)) 959 // // there are no optionals. 960 // .find!(isNotOptional); 961 962 // if (!notOptionalAfterOptional.empty) 963 // { 964 // string message = "Found the following non-optional positional arguments after an optional argument: "; 965 966 // message ~= notOptionalAfterOptional 967 // .filter!isNotOptional 968 // .map!(p => p.argument.identifier) 969 // .join(", "); 970 971 // // TODO: Having a default value should imply optionality only after this check. 972 // message ~= ". They must either have a default value and or be marked with the optional attribute."; 973 974 // assert(false, message); 975 // } 976 } 977 978 ArgFlags inferArgumentFlagsSpecificToNamedArguments(FieldType)(ArgFlags flags, FieldType fieldDefaultValue) pure 979 { 980 with (ArgFlags) 981 { 982 if (flags.has(_parseAsFlagBit)) 983 { 984 static if (is(FieldType == bool) || is(FieldType : Nullable!bool)) 985 { 986 static if (is(FieldType == bool)) 987 assert(fieldDefaultValue == false, 988 "Fields marked `parseAsFlag` must have the default value false."); 989 else 990 assert(fieldDefaultValue.isNull, 991 "Nullable fields marked with `parseAsFlag` must have the default value of null."); 992 993 string messageIfAddedOptional = getArgumentFlagsValidationMessage(flags | _optionalBit); 994 995 // NOTE: This one should always be covered by the flags validation instead. 996 assert(messageIfAddedOptional is null, "Should never be hit!!\n" ~ messageIfAddedOptional); 997 998 if (flags.doesNotHave(_optionalBit)) 999 flags |= _optionalBit | _inferedOptionalityBit; 1000 } 1001 else 1002 { 1003 // TODO: maybe allow flags in the future?? 1004 assert(false, "Fields marked `parseAsFlag` must be boolean."); 1005 } 1006 } 1007 } 1008 return flags; 1009 } 1010 1011 NamedArgumentInfo[] getNamedArgumentInfosOf(TCommand)() pure 1012 { 1013 import std.traits : hasUDA; 1014 NamedArgumentInfo[] result; 1015 1016 static foreach (field; TCommand.tupleof) 1017 {{ 1018 enum hasNamed = hasUDA!(field, ArgNamed); 1019 enum foldedArgFlags = foldArgumentFlags!(__traits(getAttributes, field)); 1020 enum hasPositionalFlag = foldedArgFlags.has(ArgFlags._positionalArgumentBit); 1021 enum hasNamedFlag = foldedArgFlags.has(ArgFlags._namedArgumentBit); 1022 1023 // This check is getting quite complex, so time to refactor to be honest. 1024 enum isSimple = !hasNamed 1025 && !hasUDA!(field, ArgPositional) 1026 && !hasPositionalFlag 1027 && (hasUDA!(field, string) || hasNamedFlag); 1028 1029 static if (hasNamed) 1030 { 1031 ArgNamed uda = getArgumentInfo!(ArgNamed, field); 1032 if (uda.pattern == Pattern.init) 1033 uda.pattern = Pattern([__traits(identifier, field)]); 1034 } 1035 else static if (isSimple) 1036 { 1037 alias stringAttributes = getUDAs!(field, string); 1038 static if (stringAttributes.length > 0) 1039 ArgNamed uda = ArgNamed(__traits(identifier, field), stringAttributes[0]); 1040 else 1041 ArgNamed uda = ArgNamed(__traits(identifier, field), ""); 1042 } 1043 1044 static if (hasNamed || isSimple) 1045 { 1046 auto argument = getCommonArgumentInfo!(field, ArgFlags._namedArgumentBit); 1047 argument.flags = inferArgumentFlagsSpecificToNamedArguments!(typeof(field))( 1048 argument.flags, defaultValueOf!field); 1049 result ~= NamedArgumentInfo(uda, argument); 1050 } 1051 }} 1052 return result; 1053 }