1 /** 2 * Automatic compile-time configuration 3 * discovery and provisioning 4 * 5 * Authors: Tristan Brice Velloza Kildaire (deavmi) 6 */ 7 module hummus.cfg; 8 9 import std.traits : Fields, FieldNameTuple; 10 import niknaks.meta : isStructType, isClassType; 11 12 version(unittest) 13 { 14 import gogga.mixins; 15 } 16 17 import std.string : format; 18 import niknaks.functional : Optional; 19 import hummus.provider; 20 21 version(unittest) 22 { 23 import std.stdio : writeln; 24 } 25 26 /** 27 * Given a name and a root value 28 * this will generate the name as 29 * `<rootVal>.<n>` if and only if 30 * `rootVal` is not empty. If it 31 * is empty then the name is returned 32 * as `n`, unchanged. 33 * 34 * Params: 35 * n = the name 36 * rootVal = the root value 37 * Returns: the transformed name 38 */ 39 private string generateName(string n, string rootVal) 40 { 41 if (rootVal.length) 42 { 43 return rootVal ~ "." ~ n; 44 } 45 return n; 46 } 47 48 /** 49 * Generates the field names of the given 50 * structure and fills them in with values 51 * from the given provider. 52 * 53 * Params: 54 * s = the structure 55 * p = the provider for names 56 */ 57 package void fieldsOf(T)(ref T s, Provider p) 58 { 59 // assume roots name is "" 60 fieldsOf(s, p, ""); 61 } 62 63 /** 64 * This is a compile-time recursive 65 * function that wil generate multiple 66 * versions of itself in order to discover 67 * the full structure of the struct 68 * type `T`. 69 * 70 * The struct will be updated via `ref` 71 * (via reference) and values will be 72 * assigned to it via the provider `p`. 73 * 74 * In order for naming to be hierachial 75 * a root value is passed along as an 76 * auxillary piece of data 77 * 78 * Names will always be `fieldName` 79 * and if in a struct then `structFieldName.fieldName` 80 * and so on... 81 * 82 * Params: 83 * s = the structure 84 * p = the provider 85 * r = the root value 86 */ 87 package void fieldsOf(T)(ref T s, Provider p, string r) // todo: niknaks - is-struct check 88 if (isStructType!(T)()) 89 { 90 // compile time gen: assignment lines 91 alias ft_s = Fields!(T); 92 alias fn_s = FieldNameTuple!(T); 93 94 version(unittest) 95 { 96 writeln("Struct on entry: ", s); 97 scope (exit) 98 { 99 writeln("Struct on exit: ", s); 100 } 101 102 writeln("Fields of struct: '", __traits(identifier, T), "'"); 103 scope (exit) 104 { 105 writeln("Processed '", __traits(identifier, T), "'"); 106 } 107 } 108 109 // Loop through each pair and process 110 static foreach (c; 0 .. fn_s.length) 111 { 112 version(unittest) 113 writeln("Exmine member '", fn_s[c], "'"); 114 115 version(unittest) 116 writeln("Exmine member '", __traits(getMember, s, fn_s[c]), "'"); 117 118 // if the current member's type is 119 // a struct-type 120 static if (isStructType!(typeof(__traits(getMember, s, fn_s[c])))) 121 { 122 version(unittest) 123 pragma(msg, "The '", fn_s[c], "' is a struct type"); 124 125 // recurse on this member (it is a struct type) 126 fieldsOf(__traits(getMember, s, fn_s[c]), p, generateName(fn_s[c], r)); 127 } 128 // todo: disallow class types 129 else static if (isClassType!(typeof(__traits(getMember, s, fn_s[c])))) 130 { 131 pragma(msg, "We do not yet support class types, which '", fn_s[c], "' is"); 132 static assert(false); 133 } 134 else 135 { 136 version(unittest) 137 pragma(msg, "The '", fn_s[c], "' is a primitive type"); 138 139 // ask provider for value, if it has one, then 140 // attempt to assign it 141 if (p.provide(generateName(fn_s[c], r)).isPresent()) 142 { 143 // todo: catch failing to!(T)(V) call exception 144 import std.conv : to; 145 146 version(unittest) 147 DEBUG(format("Trying to convert '%s'", p.provide(generateName(fn_s[c], r)).get())); 148 149 __traits(getMember, s, fn_s[c]) = to!( 150 typeof(__traits(getMember, s, fn_s[c])) 151 )( 152 p.provide(generateName(fn_s[c], r)).get() 153 ); 154 } 155 156 } 157 } 158 } 159 160 /** 161 * Given a structure and a provider this 162 * will discover all the required fields 163 * and then fill those fields with values 164 * provided by the provider. 165 * 166 * The root name will prefix all names 167 * as `<rootName>.` 168 * 169 * Params: 170 * structType = the structure 171 * p = the provider 172 * rootName = name of the root 173 */ 174 public void fill(T)(ref T structType, Provider p, string rootName) 175 { 176 fieldsOf(structType, p, rootName); 177 } 178 179 /** 180 * Given a structure and a provider this 181 * will discover all the required fields 182 * and then fill those fields with values 183 * provided by the provider 184 * 185 * Params: 186 * structType = the structure 187 * p = the provider 188 */ 189 public void fill(T)(ref T structType, Provider p) 190 { 191 fieldsOf(structType, p); 192 } 193 194 version (unittest) 195 { 196 import std.stdio : writeln; 197 import std.string : format; 198 199 private class DummySink : Provider 200 { 201 bool[string] _s; 202 public bool provideImpl(string n, ref string v) 203 { 204 _s[n] = true; 205 if (n == "adress") 206 { 207 return false; 208 } 209 else if (n == "porto") 210 { 211 v = "443"; 212 return true; 213 } 214 else if (n == "s.x") 215 { 216 v = "10"; 217 return true; 218 } 219 else if (n == "s.y") 220 { 221 v = "-10"; 222 return true; 223 } 224 // todo: disable below for conv error as it gets 225 // the value below whioch fails to convert 226 // else if 227 228 v = format("v:%s", n); 229 return true; 230 } 231 } 232 } 233 234 /** 235 * Tests the discovery and filling 236 * process on a complex structure 237 * which fields that are of structure 238 * types too (nested structures). 239 */ 240 unittest 241 { 242 struct X 243 { 244 private string p; 245 } 246 247 struct A 248 { 249 private int x, y; 250 private X z; 251 } 252 253 struct MinhaConfiguracao 254 { 255 private string adres; 256 private size_t porto; 257 private A s; 258 // private A si; 259 } 260 261 auto mc = MinhaConfiguracao(); 262 263 auto ds = new DummySink(); 264 fill(mc, ds); 265 writeln("Provider had requests for: ", ds._s.keys); 266 267 // show how the struct was filed 268 // up 269 writeln(mc); 270 271 assert(mc.porto == 443); 272 assert(mc.s.x == 10); 273 assert(mc.s.y == -10); 274 }