1 module jcli.resolver.resolver; 2 3 import std.conv : to; 4 5 alias ResolveValueProvider = string[] delegate(string[] currArgs); 6 7 struct ResolveResult(alias UserDataT) 8 { 9 enum Kind 10 { 11 full, 12 partial 13 } 14 15 Kind kind; 16 ResolveNode!UserDataT*[] fullMatchChain; 17 ResolveNode!UserDataT*[] partialMatches; 18 ResolveValueProvider valueProvider; 19 } 20 21 struct ResolveNode(alias UserDataT) 22 { 23 char letter; 24 bool isFullMatch; 25 string fullMatchString; 26 ResolveNode[] children; 27 UserDataT userData; 28 ResolveValueProvider valueProvider; 29 } 30 31 alias t = Resolver!int; 32 33 final class Resolver(alias UserDataT_) 34 { 35 alias UserDataT = UserDataT_; 36 37 static struct ArgInfo 38 { 39 string arg; 40 ResolveValueProvider valueProvider; 41 } 42 43 private 44 { 45 ResolveNode!UserDataT _root; 46 } 47 48 @trusted nothrow 49 void add( 50 string[] command, 51 UserDataT userData, 52 ResolveValueProvider commandValueProvider 53 ) 54 { 55 ResolveNode!UserDataT* node = &this._root; 56 bool _1; 57 foreach(i, word; command) 58 { 59 foreach(ch; word) 60 node = &this.nextByChar(*node, ch, true, _1); 61 node.fullMatchString = word; 62 node.isFullMatch = true; 63 if(i+1 < command.length) 64 node = &this.nextByChar(*node, ' ', true, _1); 65 } 66 67 node.valueProvider = commandValueProvider; 68 node.userData = userData; 69 } 70 71 ResolveResult!UserDataT resolve( 72 string[] command 73 ) 74 { 75 ResolveResult!UserDataT ret; 76 77 ResolveNode!UserDataT* node = &this._root; 78 bool found; 79 foreach(i, word; command) 80 { 81 foreach(ch; word) 82 { 83 node = &this.nextByChar(*node, ch, false, found); 84 if(!found) 85 break; 86 } 87 if(node.isFullMatch) 88 ret.fullMatchChain ~= node; 89 if(i+1 < command.length) 90 { 91 node = &this.nextByChar(*node, ' ', false, found); 92 if(!found) 93 break; 94 } 95 } 96 97 if(found && node.isFullMatch && node.userData != UserDataT.init) 98 { 99 ret.kind = ret.Kind.full; 100 if(node.valueProvider) 101 ret.valueProvider = node.valueProvider; 102 return ret; 103 } 104 105 void addToPartial(ResolveNode!UserDataT* node) 106 { 107 foreach(ref child; node.children) 108 { 109 if(child.letter == ' ') 110 continue; 111 if(child.isFullMatch) 112 ret.partialMatches ~= &child; 113 addToPartial(&child); 114 } 115 } 116 117 addToPartial(node); 118 ret.kind = ret.Kind.partial; 119 return ret; 120 } 121 122 @safe nothrow 123 private ref NodeT nextByChar(NodeT)(ref NodeT parent, char ch, bool createIfNeeded, out bool found) 124 { 125 found = true; 126 foreach(ref node; parent.children) 127 { 128 if(node.letter == ch) 129 return node; 130 } 131 132 if(createIfNeeded) 133 { 134 parent.children ~= NodeT(ch); 135 return parent.children[$-1]; 136 } 137 else 138 { 139 found = false; 140 static if(is(NodeT == ResolveNode!UserDataT)) 141 return this._root; 142 else 143 { 144 static ResolveArgument _r; 145 return _r; 146 } 147 } 148 } 149 } 150 151 unittest 152 { 153 auto r = new Resolver!int(); 154 r.add(["cloaca"], 20, null); 155 auto re = r.resolve(["cloaca"]); 156 157 assert(re.kind == re.Kind.full); 158 assert(re.fullMatchChain.length == 1); 159 assert(re.fullMatchChain[0].fullMatchString == "cloaca"); 160 } 161 162 unittest 163 { 164 auto r = new Resolver!int(); 165 r.add(["cloaca", "knuckles"], 20, null); 166 auto re = r.resolve(["cloaca", "knuckles"]); 167 168 assert(re.kind == re.Kind.full); 169 assert(re.fullMatchChain.length == 2); 170 assert(re.fullMatchChain[0].fullMatchString == "cloaca"); 171 assert(re.fullMatchChain[1].fullMatchString == "knuckles"); 172 } 173 174 unittest 175 { 176 auto r = new Resolver!int(); 177 r.add(["cloaca"], 20, null); 178 auto re = r.resolve(["cloa"]); 179 180 assert(re.kind == re.Kind.partial); 181 assert(re.partialMatches.length == 1); 182 assert(re.partialMatches[0].fullMatchString == "cloaca", re.partialMatches[0].fullMatchString); 183 } 184 185 unittest 186 { 187 auto r = new Resolver!int(); 188 r.add(["nodders"], 20, null); 189 auto re = r.resolve(["nop"]); 190 191 assert(re.kind == re.Kind.partial, re.to!string); 192 assert(re.partialMatches.length == 1); 193 assert(re.partialMatches[0].fullMatchString == "nodders", re.partialMatches[0].fullMatchString); 194 }