1 module jcli.autocomplete.complete;
2 
3 import jcli.core, jcli.introspect, jcli.argparser, std;
4 
5 struct AutoComplete(alias CommandT)
6 {
7     private
8     {
9         alias Info = commandInfoFor!CommandT;
10     }
11 
12     string[] complete(string[] args)
13     {
14         string[] ret;
15         size_t positionalCount;
16         Pattern[] namedFound;
17         typeof(Info.namedArgs[0])[Pattern] namedByPattern;
18         typeof(Info.positionalArgs[0])[] positionalByPosition;
19 
20         static foreach(pos; Info.positionalArgs)
21             positionalByPosition ~= pos;
22         static foreach(named; Info.namedArgs)
23             namedByPattern[named.uda.pattern] = named;
24 
25         enum State
26         {
27             lookingForNamedValue,
28             lookingForPositionalOrNamed
29         }
30 
31         State state;
32         auto parser = ArgParser(args);
33         ArgParser.Result lastResult;
34 
35         while(!parser.empty)
36         {
37             lastResult = parser.front;
38 
39             if(lastResult.kind == ArgParser.Result.Kind.rawText)
40             {
41                 positionalCount++;
42                 parser.popFront();
43             }
44             else
45             {
46                 typeof(Info.namedArgs[0]) argInfo;
47                 foreach(pattern, arg; namedByPattern)
48                 {
49                     if(pattern.match(lastResult.nameSlice).matched)
50                     {
51                         namedFound ~= pattern;
52                         argInfo = arg;
53                         break;
54                     }
55                 }
56                 
57                 // Skip over the name
58                 parser.popFront();
59 
60                 // Skip over its argument
61                 if(parser.front.fullSlice.length && parser.front.kind == ArgParser.Result.Kind.rawText)
62                 {
63                     lastResult = parser.front;
64                     if(argInfo.scheme != ArgParseScheme.bool_ || (parser.front.fullSlice == "true" || parser.front.fullSlice == "false"))
65                         parser.popFront();
66                 }
67             }
68         }
69 
70         state = (lastResult.kind == ArgParser.Result.Kind.argument)
71             ? State.lookingForNamedValue
72             : State.lookingForPositionalOrNamed;
73 
74         if(state == State.lookingForNamedValue)
75         {
76             bool isBool = false;
77             static foreach(named; Info.namedArgs)
78             {
79                 if(named.uda.pattern.match(lastResult.nameSlice).matched)
80                 {
81                     isBool = named.scheme == ArgParseScheme.bool_;
82 
83                     alias Symbol = getArgSymbol!named;
84                     static if(isInstanceOf!(Nullable, typeof(Symbol)))
85                         alias SymbolT = typeof(typeof(Symbol)().get());
86                     else
87                         alias SymbolT = typeof(Symbol);
88 
89                     // Enums are a special case
90                     static if(is(SymbolT == enum))
91                     {
92                         static foreach(name; __traits(allMembers, SymbolT))
93                             ret ~= name;
94                     }
95 
96                     static foreach(uda; __traits(getAttributes, Symbol))
97                     {
98                         // TODO:
99                     }
100                 }
101             }
102 
103             if(ret.length == 0)
104             {
105                 if(isBool)
106                     ret ~= ["true", "false"];
107                 else
108                     ret ~= "[Value for argument "~lastResult.nameSlice~"]";
109             }
110         }
111         else
112         {
113             if(positionalCount < positionalByPosition.length)
114             {
115                 foreach(pos; positionalByPosition[positionalCount..$])
116                     ret ~= "<"~pos.uda.name~">";
117             }
118 
119             foreach(pattern; namedByPattern.byKey.filter!(k => !namedFound.canFind(k)))
120             {
121                 foreach(p; pattern.patterns.map!(p => p.length == 1 ? "-"~p : "--"~p))
122                     ret ~= p;
123             }
124         }
125 
126         return ret;
127     }
128 }