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 }