1 module inifiled; 2 3 import std.range : isInputRange, isOutputRange, ElementType; 4 5 string genINIparser(T)() { 6 return ""; 7 } 8 9 struct INI { 10 string msg; 11 12 static INI opCall(string s) { 13 INI ret; 14 ret.msg = s; 15 16 return ret; 17 } 18 } 19 20 string getINI(T)() @trusted { 21 import std.traits : hasUDA; 22 foreach(it; __traits(getAttributes, T)) { 23 if(hasUDA!(T, INI)) { 24 return it.msg; 25 } 26 } 27 assert(false); 28 } 29 30 string getINI(T, string mem)() @trusted { 31 import std.traits : hasUDA; 32 foreach(it; __traits(getAttributes, __traits(getMember, T, mem))) { 33 if(hasUDA!(__traits(getMember, T, mem), INI)) { 34 return it.msg; 35 } 36 } 37 assert(false, mem); 38 } 39 40 string getTypeName(T)() @trusted { 41 import std.traits : fullyQualifiedName; 42 return fullyQualifiedName!T; 43 } 44 45 string buildStructParser(T)() { 46 import std.traits : hasUDA; 47 string ret = "switch(it) { \n"; 48 foreach(it; __traits(allMembers, T)) { 49 if(hasUDA!(__traits(getMember, T, it), INI) && ( 50 isBasicType!(typeof(__traits(getMember, T, it))) || 51 isSomeString!(typeof(__traits(getMember, T, it)))) 52 ) { 53 ret ~= 54 "case \"%s\": t.%s = to!typeof(t.%s)(it); break; %s" 55 .format(it, it, it, "\n"); 56 } 57 } 58 59 return ret; 60 } 61 62 void readINIFile(T)(ref T t, string filename) { 63 import std.stdio : File; 64 auto iFile = File(filename, "r"); 65 auto iRange = iFile.byLine(); 66 readINIFileImpl(t, iRange); 67 } 68 69 bool isSection(T)(T line) @safe nothrow if(isInputRange!T) { 70 bool f; 71 bool b; 72 73 foreach(it; line) { 74 if(it == ' ' || it == '\t') { 75 continue; 76 } else if(it == '[') { 77 f = true; 78 break; 79 } else { 80 break; 81 } 82 } 83 84 foreach_reverse(it; line) { 85 if(it == ' ' || it == '\t') { 86 continue; 87 } else if(it == ']') { 88 b = true; 89 break; 90 } else { 91 break; 92 } 93 } 94 95 return f && b; 96 } 97 98 unittest { 99 assert(isSection("[initest.Person]")); 100 assert(isSection(" [initest.Person]")); 101 assert(isSection(" [initest.Person] ")); 102 assert(!isSection(";[initest.Person] ")); 103 } 104 105 pure string getSection(T)(T line) @safe if(isInputRange!T) { 106 return getTimpl!('[',']')(line); 107 } 108 109 pure string getValue(T)(T line) @safe if(isInputRange!T) { 110 return getTimpl!('"','"')(line); 111 } 112 113 pure string getValueArray(T)(T line) @safe if(isInputRange!T) { 114 return getTimpl!('"','"')(line); 115 } 116 117 unittest { 118 assert(getValue("firstname=\"Foo\"") == "Foo"); 119 assert(getValue("firstname=\"Foo\",\"Bar\"") == "Foo\",\"Bar"); 120 } 121 122 pure string getKey(T)(T line) @safe if(isInputRange!T) { 123 import std.string : indexOf, strip; 124 import std.exception : enforce; 125 126 ptrdiff_t eq = line.indexOf('='); 127 enforce(eq != -1, "key value pair needs equal sign"); 128 129 return line[0 .. eq].strip(); 130 } 131 132 unittest { 133 assert(getKey("firstname=\"Foo\"") == "firstname"); 134 assert(getKey("lastname =\"Foo\",\"Bar\"") == "lastname"); 135 } 136 137 pure string getTimpl(char l, char r, T)(T line) @safe if(isInputRange!T) { 138 import std.string : indexOf, lastIndexOf; 139 import std.format : format; 140 ptrdiff_t l = line.indexOf(l); 141 ptrdiff_t r = line.lastIndexOf(r); 142 143 assert(l+1 < line.length, format("l+1 %u line %u", l+1, line.length)); 144 return line[l+1 .. r].idup; 145 } 146 147 pure bool isKeyValue(T)(T line) @safe if(isInputRange!T) { 148 import std.string : indexOf; 149 ptrdiff_t idx = line.indexOf('='); 150 return idx != -1; 151 } 152 153 unittest { 154 assert(getSection("[initest.Person]") == "initest.Person", 155 getSection("[initest.Person]")); 156 assert(getSection(" [initest.Person]") == "initest.Person", 157 getSection("[initest.Person]")); 158 assert(getSection(" [initest.Person] ") == "initest.Person", 159 getSection("[initest.Person]")); 160 assert(getSection("[initest.Person] ") == "initest.Person", 161 getSection("[initest.Person]")); 162 163 assert(getValue("\"initest.Person\"") == "initest.Person", 164 getValue("\"initest.Person\"")); 165 assert(getValue(" \"initest.Person\"") == "initest.Person", 166 getValue("\"initest.Person\"")); 167 assert(getValue(" \"initest.Person\" ") == "initest.Person", 168 getValue("\"initest.Person\"")); 169 assert(getValue("\"initest.Person\" ") == "initest.Person", 170 getValue("\"initest.Person\"")); 171 } 172 173 string buildSectionParse(T)() @safe { 174 import std.traits : hasUDA, fullyQualifiedName, isBasicType, isSomeString, 175 isArray; 176 import std.format : format; 177 string ret = "switch(getSection(line)) { // " ~ fullyQualifiedName!T ~ "\n"; 178 179 foreach(it; __traits(allMembers, T)) { 180 if(hasUDA!(__traits(getMember, T, it), INI) 181 && !isBasicType!(typeof(__traits(getMember, T, it))) 182 && !isSomeString!(typeof(__traits(getMember, T, it))) 183 && !isArray!(typeof(__traits(getMember, T, it)))) 184 { 185 ret ~= ("case \"%s\": { line = readINIFileImpl" ~ 186 "(t.%s, input, deaph+1); goto repeatL; }\n"). 187 format(fullyQualifiedName!(typeof(__traits(getMember, T, it))), 188 it 189 ); 190 } 191 } 192 193 return ret ~ "default: return line;\n}\n"; 194 } 195 196 string buildValueParse(T)() @safe { 197 import std.traits : hasUDA, fullyQualifiedName, isArray, isBasicType, isSomeString; 198 import std.format : format; 199 string ret = "switch(getKey(line)) { // " ~ fullyQualifiedName!T ~ "\n"; 200 201 foreach(it; __traits(allMembers, T)) { 202 if(hasUDA!(__traits(getMember, T, it), INI) && (isBasicType!(typeof(__traits(getMember, T, it))) 203 || isSomeString!(typeof(__traits(getMember, T, it))))) 204 { 205 ret ~= ("case \"%s\": { t.%s = to!(typeof(t.%s))(" 206 ~ "getValue(line)); break; }\n").format(it, it, it); 207 } else if(hasUDA!(__traits(getMember, T, it), INI) 208 && isArray!(typeof(__traits(getMember, T, it)))) 209 { 210 ret ~= ("case \"%s\": { t.%s = to!(typeof(t.%s))(" 211 ~ "getValueArray(line).split(',')); break; }\n").format(it, it, it); 212 } 213 } 214 215 return ret ~ "default: break;\n}\n"; 216 } 217 218 string readINIFileImpl(T,IRange)(ref T t, ref IRange input, int deaph = 0) 219 if(isInputRange!IRange) 220 { 221 import std.conv : to; 222 import std.string : split; 223 import std.algorithm.searching : startsWith; 224 import std.traits : fullyQualifiedName; 225 debug { 226 import std.stdio : writefln; 227 } 228 debug { 229 writefln("%*s%d %s %x", deaph, "", __LINE__, fullyQualifiedName!(typeof(t)), 230 cast(void*)&input); 231 } 232 string line; 233 while(!input.empty()) { 234 line = input.front().idup; 235 input.popFront(); 236 237 repeatL: 238 if(line.startsWith(";")) { 239 continue; 240 } 241 debug { 242 writefln("%*s%d %s %s %b", deaph, "", __LINE__, line, fullyQualifiedName!T, 243 isSection(line)); 244 } 245 246 if(isSection(line) && getSection(line) != fullyQualifiedName!T) { 247 debug { 248 //pragma(msg, buildSectionParse!(T)); 249 writefln("%*s%d %s", deaph, "", __LINE__, getSection(line)); 250 writefln("%*s%d %x", deaph, "", __LINE__, 251 cast(void*)&input); 252 } 253 254 mixin(buildSectionParse!(T)); 255 } else if(isKeyValue(line)) { 256 debug { 257 //pragma(msg, buildValueParse!(T)); 258 writefln("%*s%d %s %s", deaph, "", __LINE__, getKey(line), 259 getValue(line)); 260 } 261 262 mixin(buildValueParse!(T)); 263 } 264 } 265 266 return line; 267 } 268 269 void writeComment(ORange,IRange)(ORange orange, IRange irange) @trusted 270 if(isOutputRange!(ORange, ElementType!IRange) && isInputRange!IRange) 271 { 272 size_t idx = 0; 273 foreach(it; irange) { 274 if(idx % 77 == 0) { 275 orange.put("; "); 276 } 277 orange.put(it); 278 279 if((idx+1) % 77 == 0) { 280 orange.put('\n'); 281 } 282 283 ++idx; 284 } 285 orange.put('\n'); 286 } 287 288 void writeValue(ORange,T)(ORange orange, string name, T value) @trusted 289 if(isOutputRange!(ORange, string)) 290 { 291 import std.traits : isArray, isSomeString; 292 import std.format : formattedWrite; 293 static if(isArray!T && !isSomeString!T) { 294 orange.formattedWrite("%s=\"", name); 295 foreach(idx, it; value) { 296 if(idx != 0) { 297 orange.put(','); 298 } 299 orange.formattedWrite("%s", it); 300 } 301 orange.formattedWrite("\""); 302 } else { 303 orange.formattedWrite("%s=\"%s\"\n", name, value); 304 } 305 } 306 307 string removeFromLastPoint(string input) @safe { 308 import std.string : lastIndexOf; 309 ptrdiff_t lDot = input.lastIndexOf('.'); 310 if(lDot != -1 && lDot+1 != input.length) { 311 return input[lDot+1 .. $]; 312 } else { 313 return input; 314 } 315 } 316 317 void writeValues(ORange,T)(ORange oRange, string name, T value) @trusted 318 if(isOutputRange!(ORange, string)) 319 { 320 import std.traits : isBasicType, isSomeString; 321 import std.format : formattedWrite; 322 static if(isSomeString!(ElementType!T) || isBasicType!(ElementType!T)) { 323 oRange.formattedWrite("%s=\"", removeFromLastPoint(name)); 324 foreach(idx, it; value) { 325 if(idx != 0) { 326 oRange.put(','); 327 } 328 oRange.formattedWrite("%s", it); 329 } 330 oRange.put('"'); 331 oRange.put('\n'); 332 } else { 333 for(size_t i = 0; i < value.length; ++i) { 334 oRange.formattedWrite("[%s]\n", name); 335 writeINIFileImpl(value[i], oRange, false); 336 } 337 } 338 } 339 340 void writeINIFile(T)(ref T t, string filename) @trusted { 341 import std.stdio : File; 342 auto oFile = File(filename, "w"); 343 auto oRange = oFile.lockingTextWriter(); 344 writeINIFileImpl(t, oRange, true); 345 } 346 347 void writeINIFileImpl(T,ORange)(ref T t, ORange oRange, bool section) 348 @trusted 349 { 350 import std.traits : getUDAs, hasUDA, Unqual, isArray, isBasicType, 351 isSomeString; 352 import std.format : formattedWrite; 353 if(hasUDA!(T, INI) && section) { 354 writeComment(oRange, getINI!T()); 355 } 356 357 if(section) { 358 oRange.formattedWrite("[%s]\n", getTypeName!T); 359 } 360 361 foreach(it; __traits(allMembers, T)) { 362 if(hasUDA!(__traits(getMember, T, it), INI)) { 363 static if(isBasicType!(typeof(__traits(getMember, T, it))) || 364 isSomeString!(typeof(__traits(getMember, T, it)))) 365 { 366 writeComment(oRange, getINI!(T,it)); 367 writeValue(oRange, it, __traits(getMember, t, it)); 368 } else static if(isArray!(typeof(__traits(getMember, T, it)))) { 369 writeComment(oRange, getINI!(T,it)); 370 writeValues(oRange, getTypeName!T ~ "." ~ it, 371 __traits(getMember, t, it)); 372 //} else static if(isINI!(typeof(__traits(getMember, t, it)))) { 373 } else static if(hasUDA!(__traits(getMember, t, it),INI)) 374 { 375 writeINIFileImpl(__traits(getMember, t, it), oRange, true); 376 } 377 } 378 } 379 } 380 381 version(unittest) { 382 @INI("A child must have a parent") 383 struct Child { 384 @INI("The firstname of the child") 385 string firstname; 386 387 @INI("The age of the child") 388 int age; 389 390 bool opEquals(Child other) { 391 return this.firstname == other.firstname 392 && this.age == other.age; 393 } 394 } 395 396 @INI("A Spose") 397 struct Spose { 398 @INI("The firstname of the spose") 399 string firstname; 400 401 @INI("The age of the spose") 402 int age; 403 404 @INI("The House of the spose") 405 House house; 406 407 bool opEquals(Spose other) { 408 return this.firstname == other.firstname 409 && this.age == other.age 410 && this.house == other.house; 411 } 412 } 413 414 @INI("A Dog") 415 struct Dog { 416 @INI("The name of the Dog") 417 string name; 418 419 @INI("The food consumed") 420 float kg; 421 422 bool opEquals(Dog other) { 423 import std.math : approxEqual, isNaN; 424 return this.name == other.name 425 && (approxEqual(this.kg, other.kg) 426 || (isNaN(this.kg) && isNaN(other.kg)) 427 ); 428 } 429 } 430 431 @INI("A Person") 432 struct Person { 433 @INI("The firstname of the Person") 434 string firstname; 435 436 @INI("The lastname of the Person") 437 string lastname; 438 439 @INI("The age of the Person") 440 int age; 441 442 @INI("The height of the Person") 443 float height; 444 445 @INI("Some strings with a very long long INI description that is longer" ~ 446 " than eigthy lines hopefully." 447 ) 448 string[] someStrings = [":::60180", "asd"]; 449 450 @INI("Some ints") 451 int[] someInts; 452 453 int dontShowThis; 454 455 @INI("A Spose") 456 Spose spose; 457 458 @INI("The family dog") 459 Dog dog; 460 461 bool opEquals(Person other) { 462 import std.math : approxEqual, isNaN; 463 import std.algorithm.comparison : equal; 464 return this.firstname == other.firstname 465 && this.lastname == other.lastname 466 && this.age == other.age 467 && (approxEqual(this.height, other.height) 468 || (isNaN(this.height) && isNaN(other.height))) 469 && equal(this.someStrings, other.someStrings) 470 && equal(this.someInts, other.someInts) 471 && this.spose == other.spose 472 && this.dog == other.dog; 473 } 474 } 475 476 @INI("A House") 477 struct House { 478 @INI("Number of Rooms") 479 uint rooms; 480 481 @INI("Number of Floors") 482 uint floors; 483 484 bool opEquals(House other) { 485 return this.rooms == other.rooms 486 && this.floors == other.floors; 487 } 488 } 489 } 490 491 unittest { 492 import std.stdio : writefln; 493 Person p; 494 p.firstname = "Foo"; 495 p.lastname = "Bar"; 496 p.age = 1337; 497 p.height = 7331.0; 498 499 p.someStrings ~= "Hello"; 500 p.someStrings ~= "World"; 501 502 p.someInts ~= [1,2]; 503 504 p.spose.firstname = "World"; 505 p.spose.age = 72; 506 507 p.spose.house.rooms = 5; 508 p.spose.house.floors = 2; 509 510 p.dog.name = "Wuff"; 511 p.dog.kg = 3.14; 512 513 Person p2; 514 readINIFile(p2, "filename.ini"); 515 writefln("\n%s\n", p2); 516 writeINIFile(p2, "filenameTmp.ini"); 517 518 Person p3; 519 readINIFile(p3, "filenameTmp.ini"); 520 521 if(p2 != p3) { 522 writefln("\n%s\n%s", p2, p3); 523 writefln("Spose equal %b", p2.spose == p3.spose); 524 writefln("Dog equal %b", p2.dog == p3.dog); 525 assert(false); 526 } 527 if(p != p3) { 528 writefln("\n%s\n%s", p, p3); 529 writefln("Spose equal %b", p.spose == p3.spose); 530 writefln("Dog equal %b", p.dog == p3.dog); 531 assert(false); 532 } 533 if(p != p2) { 534 writefln("\n%s\n%s", p, p2); 535 writefln("Spose equal %b", p.spose == p2.spose); 536 writefln("Dog equal %b", p.dog == p2.dog); 537 assert(false); 538 } 539 }