1 module jcli.autocomplete.complete;
2 
3 import jcli.core, jcli.introspect, jcli.argparser;
4 
5 struct AutoComplete(alias CommandT)
6 {
7     private
8     {
9         alias Info = CommandInfo!CommandT.Arguments;
10     }
11 
12     string[] complete(string[] args)
13     {
14         // TODO: currently scrapped, needs more work considering the rewrite.
15         static if (true)
16         {
17             return null;
18         }
19         else
20         {
21             string[] ret;
22             size_t positionalCount;
23             Pattern[] namedFound;
24             
25             // TODO: 
26             // Map the indices perhaps?
27             // There are so many things wrong with this code.
28             typeof(Info.named[0])[Pattern] namedByPattern;
29             typeof(Info.positional[0])[] positionalByPosition;
30 
31             static foreach(pos; Info.positional)
32                 positionalByPosition ~= pos;
33             static foreach(named; Info.named)
34                 namedByPattern[named.pattern] = named;
35 
36             enum State
37             {
38                 lookingForNamedValue,
39                 lookingForPositionalOrNamed
40             }
41 
42             State state;
43             auto parser = ArgParser(args);
44             ArgParser.Result lastResult;
45 
46             while(!parser.empty)
47             {
48                 lastResult = parser.front;
49 
50                 if(lastResult.kind == ArgParser.Result.Kind.rawText)
51                 {
52                     positionalCount++;
53                     parser.popFront();
54                 }
55                 else
56                 {
57                     typeof(Info.named[0]) argInfo;
58                     foreach(pattern, arg; namedByPattern)
59                     {
60                         // TODO: this is a wtf
61                         enum caseInsensitive = argInfo.flags.has(ArgFlags._caseInsensitiveBit);
62                         if(pattern.matches!caseInsensitive(lastResult.nameSlice).matched)
63                         {
64                             namedFound ~= pattern;
65                             argInfo = arg;
66                             break;
67                         }
68                     }
69                     
70                     // Skip over the name
71                     parser.popFront();
72 
73                     // Skip over its argument (what??)
74                     if(parser.front.fullSlice.length 
75                         && (parser.front.kind & ArgToken.Kind.valueBit))
76                     {
77                         lastResult = parser.front;
78                         // TODO: Duplicate logic already present in the command parser.
79                         if(argInfo.flags.has(ArgFlags._parseAsFlagBit) 
80                             || (parser.front.fullSlice == "true" 
81                                 || parser.front.fullSlice == "false"))
82                         {
83                             parser.popFront();
84                         }
85                     }
86                 }
87             }
88 
89             state = (lastResult.kind & ArgToken.Kind.argumentNameBit)
90                 ? State.lookingForNamedValue
91                 : State.lookingForPositionalOrNamed;
92 
93             if (state == State.lookingForNamedValue)
94             {
95                 bool isBool = false;
96                 static foreach(named; Info.namedArgs)
97                 {
98                     if(named.pattern.matches(lastResult.nameSlice).empty)
99                     {
100                         isBool = named.scheme == ArgParseScheme.bool_;
101 
102                         alias Symbol = getArgSymbol!named;
103 
104                         import std.traits : isInstanceOf;
105                         static if(isInstanceOf!(Nullable, typeof(Symbol)))
106                             alias SymbolT = typeof(typeof(Symbol)().get());
107                         else
108                             alias SymbolT = typeof(Symbol);
109 
110                         // Enums are a special case
111                         static if(is(SymbolT == enum))
112                         {
113                             static foreach(name; __traits(allMembers, SymbolT))
114                                 ret ~= name;
115                         }
116 
117                         static foreach(uda; __traits(getAttributes, Symbol))
118                         {
119                             // TODO:
120                         }
121                     }
122                 }
123 
124                 if(ret.length == 0)
125                 {
126                     if(isBool)
127                         ret ~= ["true", "false"];
128                     else
129                         ret ~= "[Value for argument "~lastResult.nameSlice~"]";
130                 }
131             }
132             else
133             {
134                 if(positionalCount < positionalByPosition.length)
135                 {
136                     foreach(pos; positionalByPosition[positionalCount..$])
137                         ret ~= "<"~pos.uda.name~">";
138                 }
139 
140                 import std.algorithm;
141                 foreach(pattern; namedByPattern.byKey.filter!(k => !namedFound.canFind(k)))
142                 {
143                     foreach(p; pattern.patterns.map!(p => p.length == 1 ? "-"~p : "--"~p))
144                         ret ~= p;
145                 }
146             }
147 
148             return ret;
149         }
150     }
151 }