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 }