1 // 2 // 3 // 4 5 module dini.iniconfig; 6 7 import std.algorithm : splitter, joiner; 8 import std.range : enumerate; 9 import std..string : indexOf, strip; 10 import std.functional : forward; 11 import std.stdio : File; 12 import std.conv : to; 13 14 import dini.inivalue; 15 16 struct IniConfigs 17 { 18 public: 19 /// Constructors 20 this(string content) pure 21 { 22 this.add(content); 23 } 24 this(File file) 25 { 26 this.add(file); 27 } 28 29 /// assignment 30 void opAssign(string content) pure 31 { 32 this.add(content); 33 } 34 void opAssign(File file) 35 { 36 this.add(file); 37 } 38 39 /// add file 40 void add(File file) 41 { 42 this.add(file.byLine().joiner("\n").to!string); 43 } 44 /// add content 45 void add(string content) pure 46 { 47 size_t npos = size_t(-1); 48 49 foreach(lineNum, ref line; content.splitter("\n").enumerate(1)) { 50 size_t pos = line.indexOf(';'); 51 52 // remove comment & trim 53 line = ( npos > pos ) ? line = line[0..pos].strip() : line.strip(); 54 55 if( null == line ) { 56 continue; 57 } 58 59 // parse key and value 60 pos = line.indexOf('='); 61 if( npos > pos ) { 62 string key = line[ 0 .. pos ].strip(); 63 string value = line[ pos+1 .. $ ].strip(); 64 if(value.length > 1 && '"' == value[0] && '"' == value[$-1]) { 65 value = value[1..$-1]; 66 } 67 68 _map[key] = value; 69 } else { 70 throw new IniConfigsException(lineNum); 71 } 72 } 73 } 74 75 /// Get ini entry 76 T get(T)(const string key, auto ref T dfltValue, bool noEmpty = true) const 77 { 78 const string* cfg = (key in _map); 79 80 // if key not exists 81 if(cfg is null) { 82 return dfltValue; 83 } 84 85 // check if config value string is empty 86 if(noEmpty && null == *cfg) { 87 return dfltValue; 88 } 89 90 //return cast(T) IniValue(*cfg); 91 return IniValue(*cfg).to!T; 92 } 93 94 /// Get ini entry 95 T get(T)(auto ref string key) const 96 { 97 const string* cfg = (key in _map); 98 99 // if key not exists or config value string is empty return default 100 if(!cfg || null == *cfg) { 101 return T.init; 102 } 103 104 //return cast(T) IniValue(*cfg); 105 return IniValue(*cfg).to!T; 106 } 107 108 /// Check if ini entry exists 109 bool has()(auto ref string key) const @safe 110 { 111 return (key in _map) !is null; 112 } 113 114 /// Check if ini configs has not entries 115 bool empty() const @nogc pure @safe 116 { 117 return !_map.length; 118 } 119 120 /// Returns counts of ini entries 121 size_t size() const @nogc pure @safe 122 { 123 return _map.length; 124 } 125 size_t length() const @nogc pure @safe 126 { 127 return _map.length; 128 } 129 130 private: 131 string[string] _map; 132 }; 133 134 // Ini parse exception 135 class IniConfigsException : Exception 136 { 137 this(size_t iniLine, string file = __FILE__, size_t line = __LINE__) pure @safe 138 { 139 super("IniConfigs: syntax error in line: " ~ iniLine.to!string, file, line); 140 } 141 this(string msg) pure @safe 142 { 143 super(msg); 144 } 145 } 146 147 148 149 /////////////////////////////////////////////////////// 150 // cd source 151 // rdmd -unittest -main dini/iniconfig 152 unittest { 153 import std.math : approxEqual; 154 155 string ini = ` 156 value1 = 1 ;value1 comment 157 value2 = hello world ;value2 comment 158 159 value3 = 1.1 160 161 value3 = 3.1415 ; value3 comment 162 163 ;value4 comment 164 value4 = 3.14159263358979361680130282241663053355296142399311 165 166 ;dfgg 167 168 ; this is a comment 169 value5=value5 170 value6 = value6 171 ; dfgdfgdgd sdfs s 172 value7= Some text with spaces 173 174 value7+ = " Some text with spaces " 175 176 value8_ = 985642 177 value8=9856428642 178 179 180 181 boolval1 = on 182 boolval2 = 1 183 boolval3 = true 184 185 boolval4 = off 186 boolval5 = 0 187 boolval6 = false 188 boolval7 = any else 189 `; 190 191 192 IniConfigs cfg; 193 194 assert(cfg.empty()); 195 assert(!cfg.size()); 196 197 try { 198 cfg = ini; 199 } catch (IniConfigsException e) { 200 assert(0, e.msg); 201 } 202 203 204 assert(!cfg.empty()); 205 assert(17 == cfg.size()); 206 207 cfg.add(` 208 value9 = 15 209 value10 = -15 210 `); 211 212 assert(19 == cfg.size()); 213 214 assert(15 == cfg.get!ubyte ("value9")); 215 assert(15 == cfg.get!ushort ("value9")); 216 assert(15 == cfg.get!uint ("value9")); 217 assert(15 == cfg.get!ulong ("value9")); 218 219 assert(-15 == cfg.get!byte ("value10")); 220 assert(-15 == cfg.get!int ("value10")); 221 assert(-15 == cfg.get!short ("value10")); 222 assert(-15 == cfg.get!long ("value10")); 223 224 225 assert( approxEqual(3.1415, cfg.get!float ("value3")) ); 226 assert( approxEqual(3.1415, cfg.get!double ("value3")) ); 227 assert( approxEqual(3.1415, cfg.get!real ("value3")) ); 228 229 assert( approxEqual(3.14159263358979361680130282241663053355296142399311, cfg.get!float ("value4"), 1e-20) ); 230 assert( approxEqual(3.14159263358979361680130282241663053355296142399311, cfg.get!double ("value4"), 1e-40) ); 231 assert( approxEqual(3.14159263358979361680130282241663053355296142399311, cfg.get!real ("value4"), 1e-60) ); 232 233 234 const string name1 = "value1"; 235 const string name1_ = "value1_"; 236 int val1 = cfg.get(name1, 5); 237 int val1_ = cfg.get(name1_, 5); 238 239 assert(1 == val1); 240 assert(5 == val1_); 241 242 assert("hello world" == cfg.get!string("value2", "default value") ); 243 assert("default value" == cfg.get!string("value2+", "default value") ); 244 245 246 assert("3.1415" == cfg.get!string("value3")); 247 assert("" == cfg.get!string("value3+")); 248 249 assert(approxEqual(3.1415, cfg.get("value3", 2.718281828459))); 250 assert(approxEqual(2.71828,cfg.get("value3+", 2.718281828459))); 251 assert(approxEqual(3.14159,cfg.get("value4", 2.718281828459))); 252 253 assert(cfg.has("value3")); 254 assert(!cfg.has("value3+")); 255 256 assert("Some text with spaces" == cfg.get!string("value7", "value7")); 257 assert(" Some text with spaces " == cfg.get!string("value7+")); 258 cfg.add(`empty_str = ""`); 259 assert("" == cfg.get!string("empty_str") ); 260 261 262 assert(985642 == cfg.get("value8_", 0) ); 263 assert(9856428642 == cfg.get("value8", size_t(0)) ); 264 assert(9856428642 == cfg.get!size_t("value8", 0) ); 265 266 267 assert(false == cfg.get("boolval0", false) ); 268 assert(true == cfg.get("boolval1", false) ); 269 assert(true == cfg.get("boolval2", false) ); 270 assert(true == cfg.get("boolval3", false) ); 271 272 assert(false == cfg.get("boolval4", true) ); 273 assert(false == cfg.get("boolval5", true) ); 274 assert(false == cfg.get("boolval6", true) ); 275 assert(false == cfg.get("boolval7", true) ); 276 277 assert(true == cfg.get!bool("boolval2") ); 278 assert(false == cfg.get!bool("boolval0") ); 279 } 280 281 282 unittest { 283 284 import dini.inivalue : IniValue; 285 import std.conv : to; 286 287 IniConfigs cfg = "value1 = 1"; 288 289 static struct A1 { 290 int a = 5; 291 this(IniValue v) 292 { 293 this.a = v.toString.to!int; 294 } 295 } 296 static struct A2 { 297 int a = 7; 298 void opAssign(IniValue v) 299 { 300 this.a = v.toString.to!int; 301 } 302 } 303 304 assert( 1 == cfg.get!A1("value1").a ); 305 assert( 5 == cfg.get!A1("value1+").a ); 306 assert( 1 == cfg.get("value1", A1()).a ); 307 assert( 5 == cfg.get("value1+", A1()).a ); 308 309 assert( 1 == cfg.get!A2("value1").a ); 310 assert( 7 == cfg.get!A2("value1+").a ); 311 assert( 1 == cfg.get("value1", A2()).a ); 312 assert( 7 == cfg.get("value1+", A2()).a ); 313 314 // 315 // Indirectly passing: 316 // 317 static struct C { 318 int a = 5; 319 } 320 static struct D { 321 int a; 322 this(IniValue v) { 323 this.a = v.toString.to!int; 324 } 325 this(C c) { 326 this.a = c.a; 327 } 328 C castToC() const { 329 return C(this.a); 330 } 331 alias castToC this; 332 } 333 static struct E { 334 int a; 335 this(IniValue v) { 336 this.a = v.toString.to!int; 337 } 338 C castToC() const { 339 return C(this.a); 340 } 341 alias castToC this; 342 } 343 344 C c1 = cfg.get!D("value1"); 345 C c2 = cfg.get!E("value1"); 346 C c3 = cfg.get("value1+", cast(D) C(3)); 347 348 assert( 1 == c1.a ); 349 assert( 1 == c2.a ); 350 assert( 3 == c3.a ); 351 352 }