1 module appbase.configuration;
2 
3 import std.traits;
4 import std.array;
5 import std.conv;
6 import std.file;
7 import std.exception;
8 import std.string;
9 import std.format;
10 import std.variant;
11 import std.algorithm.searching : canFind;
12 import std.algorithm.iteration : each;
13 
14 alias config = Configuration.getInstance;
15 
16 class ConfigurationNotLoadException : Exception
17 {
18     mixin basicExceptionCtors;
19 }
20 
21 class ConfigurationFileNotExistsException : Exception
22 {
23     mixin basicExceptionCtors;
24 }
25 
26 class ConfigurationFormatException : Exception
27 {
28     mixin basicExceptionCtors;
29 }
30 
31 class ConfigurationNotSettingException : Exception
32 {
33     mixin basicExceptionCtors;
34 }
35 
36 class ConfigurationNotReifiedException : Exception
37 {
38     mixin basicExceptionCtors;
39 }
40 
41 package class ConfigurationValue
42 {
43     ConfigurationValue opAssign(string value)
44     {
45         _value = value;
46         _reified = false;
47         _arraying = false;
48 
49         return this;
50     }
51 
52     @property void reified_value(T)(T value)
53     {
54         _reified_value = value;
55         _reified = true;
56     }
57 
58     @property T reified_value(T)()
59     {
60         enforce!ConfigurationNotReifiedException(_reified, format("ConfigurationValue %s is not reified.", _value));
61         return _reified_value.get!T;
62     }
63 
64     @property T[] array(T)()
65     {
66         if (!_arraying)
67         {
68             synchronized(ConfigurationValue.classinfo)
69             {
70                 if (!_arraying)
71                 {
72                     string[] t = _value.split(',');
73                     t.each!((ref a) => (a = (strip(a))));
74                     static if (isSomeString!T)
75                         _array = t;
76                     else static if (is(T == bool))
77                     {
78                         bool[] t2 = new bool[t.length];
79                         t.each!((i, a) => (t2[i] = [ "true", "t", "yes", "y", "1", "-1" ].canFind(a.toLowwer)));
80                         _array = t2;
81                     }
82                     else
83                     {
84                         T[] t2 = new T[t.length];
85                         t.each!((i, a) => (t2[i] = a.to!T));
86                         _array = t2;
87                     }
88 
89                     _arraying = true;
90                 }
91             }
92         }
93 
94         return _array.get!(T[]);
95     }
96 
97     @property void default_value(T)(T value)
98     {
99         _default_value = value;
100         _defaulted = true;
101     }
102 
103     @property T default_value(T)()
104     {
105         if (_defaulted)
106             return _default_value.get!T;
107         else
108             return T.init;
109     }
110 
111 private:
112 
113     string _value;
114     Variant _reified_value;
115     Variant _array;
116     Variant _default_value;
117 
118     bool _reified, _arraying, _defaulted; 
119 }
120 
121 package class ConfigurationItem(string ConfigurationType = "file")
122 {
123     this()
124     {
125         _value = new ConfigurationValue();
126     }
127 
128     @property value(string name)
129     {
130         auto v =  _map.get(name, null);
131         enforce!ConfigurationNotSettingException(v, format("%s is not in %s.", name, ConfigurationType));
132         return v;
133     }
134 
135     @property value()
136     {
137         return _value._value;
138     }
139 
140     @property void reified_value(T)(T value)
141     {
142         _value.reified_value = value;
143     }
144 
145     @property reified_value(T)()
146     {
147         return _value.reified_value!T;
148     }
149 
150     @property T[] array(T)()
151     {
152         return _value.array!T;
153     }
154 
155     @property void default_value(T)(T value)
156     {
157         _value.default_value = value;
158     }
159 
160     @property T default_value(T)()
161     {
162         return _value.default_value!T;
163     }
164 
165     auto opCast(T)()
166     {
167         static if (is(T == bool))
168             return as!bool;
169         else static if (isSomeString!T)
170             return cast(T)(value());
171         else static if (isNumeric!(T))
172             return as!T;
173         else
174             static assert(0, "Not support type.");
175     }
176 
177     auto as(T)() if (isNumeric!(T))
178     {
179         if (_value._value.length == 0)
180             return _value.default_value!T;
181         else
182             return to!T(_value._value);
183     }
184 
185     auto as(T: bool)()
186     {
187         if ([ "true", "t", "yes", "y", "1", "-1" ].canFind(_value._value.toLower))
188             return true;
189         
190         if ([ "false", "f", "no", "n", "0" ].canFind(_value._value.toLower))
191             return false;
192 
193         return _value.default_value!bool;
194     }
195 
196     auto as(T: string)()
197     {
198         if (_value._value.length == 0)
199             return _value.default_value!string;
200         else
201             return _value._value;
202     }
203 
204     auto opDispatch(string s)()
205     {
206         return value(s);
207     }
208 
209 package:
210 
211     ConfigurationValue _value;
212     ConfigurationItem!(ConfigurationType)[string] _map;
213 }
214 
215 final class Configuration
216 {
217     @property value(string name)
218     {
219         enforce!ConfigurationNotLoadException(_loaded, "Configuration is not be load().");
220         return _value.value(name);
221     }
222 
223     auto opDispatch(string s)()
224     {
225         enforce!ConfigurationNotLoadException(_loaded, "Configuration is not be load().");
226         return _value.opDispatch!(s)();
227     }
228 
229     @property __gshared static Configuration getInstance()
230     {
231         if (_instance is null)
232         {
233             synchronized(Configuration.classinfo)
234             {
235                 if (_instance is null)
236                 {
237                     _instance = new Configuration();
238                 }
239             }
240         }
241 
242         return _instance;
243     }
244 
245     void load(string filename, string section = string.init)
246     {
247         enforce!ConfigurationFileNotExistsException(exists(filename), format("Configuration file %s is not exists.", filename));
248         _value = new ConfigurationItem!("file")();
249 
250         import std.stdio : File;
251         auto f = File(filename, "r");
252         if (!f.isOpen()) return;
253         scope(exit) f.close();
254 
255         string _section = string.init;
256         int line = 1;
257 
258         while (!f.eof())
259         {
260             scope(exit) line += 1;
261             string str = f.readln();
262             str = strip(str);
263 
264             if (str.length == 0) continue;
265             if (str[0] == '#' || str[0] == ';') continue;
266 
267             auto len = cast(int)str.length - 1;
268             if (str[0] == '[' && str[len] == ']')
269             {
270                 _section = str[1..len].strip;
271                 continue;
272             }
273 
274             if (_section != section)
275                 continue;
276 
277             auto site = str.indexOf("=");
278             enforce!ConfigurationFormatException((site > 0), format("The format is error in file %s, in line %d", filename, line));
279             string key = str[0..site].strip;
280             fill(split(key, '.'), str[site + 1..$].strip);
281         }
282 
283         _loaded = true;
284     }
285 
286 private:
287 
288     void fill(string[] key, string value)
289     {
290         auto cvalue = _value;
291         foreach (ref k; key)
292         {
293             if (k.length == 0) continue;
294 
295             auto tvalue = cvalue._map.get(k, null);
296 
297             if (tvalue is null)
298             {
299                 tvalue = new ConfigurationItem!("file")();
300                 cvalue._map[k] = tvalue;
301             }
302 
303             cvalue = tvalue;
304         }
305 
306         if (cvalue is _value)
307         {
308             return;
309         }
310 
311         cvalue._value = value;
312     }
313 
314     bool _loaded;
315     ConfigurationItem!("file") _value;
316     __gshared Configuration _instance = null;
317 }