1 module jcli.introspect.gather; 2 3 import jcli.introspect, jcli.core, std; 4 5 CommandInfo!CommandT commandInfoFor(alias CommandT)() 6 { 7 typeof(return) ret; 8 9 // Find which command UDA they're using. 10 static if( 11 !__traits(compiles, oneOf!(CommandT, Command, CommandDefault)) 12 ) 13 { 14 static assert(false, "oneOf won't compile. That likely means you have both @Command and @CommandDefault attached."); 15 } 16 const CommandUda = oneOf!(CommandT, Command, CommandDefault); 17 static if(is(typeof(CommandUda) : CommandDefault)) 18 ret.isDefault = true; 19 else 20 { 21 static assert(CommandUda.pattern.patterns.walkLength > 0, "@Command must specify at least one pattern."); 22 ret.pattern = CommandUda.pattern; 23 } 24 ret.description = CommandUda.description; 25 26 // Find args. 27 ret.namedArgs = findArgs!(CommandT, ArgNamed); 28 ret.positionalArgs = findArgs!(CommandT, ArgPositional); 29 auto raw = findArgs!(CommandT, ArgRaw); 30 auto overflow = findArgs!(CommandT, ArgOverflow); 31 32 if(ret.namedArgs.length == 0) ret.namedArgs = typeof(ret.namedArgs).init; 33 if(ret.positionalArgs.length == 0) ret.positionalArgs = typeof(ret.positionalArgs).init; 34 if(raw.length > 1) assert(false, "Only one symbol at most can be marked @ArgRaw"); 35 if(raw.length == 1) ret.rawArg = raw[0]; 36 if(overflow.length > 1) assert(false, "Only one symbol at most can be marked @ArgOverflow"); 37 if(overflow.length == 1) ret.overflowArg = overflow[0]; 38 39 return ret; 40 } 41 42 private auto findArgs(alias CommandT, alias UDA)() 43 { 44 ArgIntrospect!(UDA, CommandT)[] ret; 45 46 alias SymbolsWithUda = getSymbolsByUDA!(CommandT, UDA); 47 static foreach(symbol; SymbolsWithUda) 48 {{ 49 alias Udas = getUDAs!(symbol, UDA); 50 alias SymbolT = typeof(symbol); 51 static assert(Udas.length == 1, "Only one @"~UDA.stringof~" can be attached."); 52 53 alias SanityCheckUdas = AliasSeq!( 54 getUDAs!(symbol, ArgNamed), 55 getUDAs!(symbol, ArgPositional), 56 getUDAs!(symbol, ArgRaw), 57 getUDAs!(symbol, ArgOverflow), 58 ); 59 static assert(SanityCheckUdas.length == 1, "Cannot mix multiple @ArgXXX UDAs together."); 60 61 static if(__traits(compiles, { enum a = Udas[0]; })) 62 enum Uda = Udas[0]; 63 else 64 enum Uda = Udas[0].init; 65 66 // Get main info. 67 typeof(ret[0]) info; 68 info.identifier = __traits(identifier, symbol); 69 info.uda = Uda; 70 71 // Get auxiliary udas 72 static foreach(uda; __traits(getAttributes, symbol)) 73 {{ 74 static if(__traits(compiles, typeof(uda))) 75 { 76 alias UdaT = typeof(uda); 77 static if(is(UdaT == ArgExistence)) 78 info.existence |= uda; 79 else static if(is(UdaT == ArgAction)) 80 info.action = uda; 81 else static if(is(UdaT == ArgConfig)) 82 { 83 static if(uda & ArgConfig.canRedefine) 84 info.existence |= ArgExistence.multiple; 85 86 info.config = uda; 87 } 88 else static if(is(UdaT == ArgGroup)) 89 info.group = uda; 90 } 91 }} 92 93 // Set certain flags depending on data type 94 static if(isInstanceOf!(Nullable, SymbolT)) 95 { 96 info.existence |= ArgExistence.optional; 97 98 static if(is(SymbolT == Nullable!bool)) // Special case 99 info.scheme = ArgParseScheme.bool_; 100 } 101 static if(is(SymbolT == bool)) 102 { 103 info.existence |= ArgExistence.optional; 104 info.scheme = ArgParseScheme.bool_; 105 } 106 if(info.action == ArgAction.count) 107 { 108 info.scheme = ArgParseScheme.repeatableName; 109 info.existence |= ArgExistence.optional | ArgExistence.multiple; 110 } 111 112 ret ~= info; 113 }} 114 115 return ret; 116 } 117 118 private auto oneOf(alias Symbol, Udas...)() 119 { 120 alias SymbolUdas = __traits(getAttributes, Symbol); 121 static foreach(uda; SymbolUdas) 122 {{ 123 static if(__traits(compiles, typeof(uda))) 124 { 125 alias UdaT = typeof(uda); 126 enum UdaRet = uda; 127 } 128 else 129 { 130 alias UdaT = uda; 131 enum UdaRet = uda.init; 132 } 133 134 static foreach(wanted; Udas) 135 { 136 static if(is(wanted == UdaT)) 137 return UdaRet; 138 } 139 }} 140 }