1 /// Contains the UDAs used and recognised by infogen, and any systems built on top of it. 2 module jaster.cli.infogen.udas; 3 4 import std.meta : staticMap, Filter; 5 import std.traits; 6 import jaster.cli.infogen, jaster.cli.udas; 7 8 /++ 9 + Attach this to any struct/class that represents the default command. 10 + 11 + See_Also: 12 + `jaster.cli 13 + ++/ 14 struct CommandDefault 15 { 16 /// The command's description. 17 string description = "N/A"; 18 } 19 20 /++ 21 + Attach this to any struct/class that represents a command. 22 + 23 + See_Also: 24 + `jaster.cli.core.CommandLineInterface` for more details. 25 + +/ 26 struct Command 27 { 28 /// The pattern used to match against this command. Can contain spaces. 29 Pattern pattern; 30 31 /// The command's description. 32 string description; 33 34 /// 35 this(string pattern, string description = "N/A") 36 { 37 this.pattern = Pattern(pattern); 38 this.description = description; 39 } 40 } 41 42 /++ 43 + Attach this to any member field to mark it as a named argument. 44 + 45 + See_Also: 46 + `jaster.cli.core.CommandLineInterface` for more details. 47 + +/ 48 struct CommandNamedArg 49 { 50 /// The pattern used to match against this argument. Cannot contain spaces. 51 Pattern pattern; 52 53 /// The argument's description. 54 string description; 55 56 /// 57 this(string pattern, string description = "N/A") 58 { 59 this.pattern = Pattern(pattern); 60 this.description = description; 61 } 62 } 63 64 /++ 65 + Attach this to any member field to mark it as a positional argument. 66 + 67 + See_Also: 68 + `jaster.cli.core.CommandLineInterface` for more details. 69 + +/ 70 struct CommandPositionalArg 71 { 72 /// The position that this argument appears at. 73 size_t position; 74 75 /// The name of this argument. Used during help-text generation. 76 string name = "VALUE"; 77 78 /// The description of this argument. 79 string description = "N/A"; 80 } 81 82 /++ 83 + Attach this to any member field to add it to a help text group. 84 + 85 + See_Also: 86 + `jaster.cli.core.CommandLineInterface` for more details. 87 + +/ 88 struct CommandArgGroup 89 { 90 /// The name of the group to put the arg under. 91 string name; 92 93 /++ 94 + The description of the group. 95 + 96 + Notes: 97 + The intended usage of this UDA is to apply it to a group of args at the same time, instead of attaching it onto 98 + singular args: 99 + 100 + ``` 101 + @CommandArgGroup("group1", "Some description") 102 + { 103 + @CommandPositionalArg... 104 + } 105 + ``` 106 + ++/ 107 string description; 108 } 109 110 /++ 111 + Attach this onto a `string[]` member field to mark it as the "raw arg list". 112 + 113 + TLDR; Given the command `"tool.exe command value1 value2 --- value3 value4 value5"`, the member field this UDA is attached to 114 + will be populated as `["value3", "value4", "value5"]` 115 + ++/ 116 struct CommandRawListArg {} 117 118 // Legacy, keep undocumented. 119 alias CommandRawArg = CommandRawListArg; 120 121 enum isSomeCommand(alias CommandT) = hasUDA!(CommandT, Command) || hasUDA!(CommandT, CommandDefault); 122 123 enum isSymbol(alias ArgT) = __traits(compiles, __traits(getAttributes, ArgT)); 124 125 enum isRawListArgument(alias ArgT) = isSymbol!ArgT && hasUDA!(ArgT, CommandRawListArg); // Don't include in isSomeArgument 126 enum isNamedArgument(alias ArgT) = isSymbol!ArgT && hasUDA!(ArgT, CommandNamedArg); 127 enum isPositionalArgument(alias ArgT) = isSymbol!ArgT && hasUDA!(ArgT, CommandPositionalArg); 128 enum isSomeArgument(alias ArgT) = isNamedArgument!ArgT || isPositionalArgument!ArgT; 129 130 package template getCommandArguments(alias CommandT) 131 { 132 static assert(is(CommandT == struct) || is(CommandT == class), "Only classes or structs can be used as commands."); 133 static assert(isSomeCommand!CommandT, "Type "~CommandT.stringof~" is not marked with @Command or @CommandDefault."); 134 135 alias toSymbol(string name) = __traits(getMember, CommandT, name); 136 alias Members = staticMap!(toSymbol, __traits(allMembers, CommandT)); 137 alias getCommandArguments = Filter!(isSomeArgument, Members); 138 } 139 /// 140 unittest 141 { 142 @CommandDefault 143 static struct C 144 { 145 @CommandNamedArg int a; 146 @CommandPositionalArg int b; 147 int c; 148 } 149 150 static assert(getCommandArguments!C.length == 2); 151 static assert(getNamedArguments!C.length == 1); 152 static assert(getPositionalArguments!C.length == 1); 153 } 154 155 package alias getNamedArguments(alias CommandT) = Filter!(isNamedArgument, getCommandArguments!CommandT); 156 package alias getPositionalArguments(alias CommandT) = Filter!(isPositionalArgument, getCommandArguments!CommandT);