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 }