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 }