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.array : join; 177 import std.format : format; 178 string[] ret; 179 180 foreach(it; __traits(allMembers, T)) { 181 if(hasUDA!(__traits(getMember, T, it), INI) 182 && !isBasicType!(typeof(__traits(getMember, T, it))) 183 && !isSomeString!(typeof(__traits(getMember, T, it))) 184 && !isArray!(typeof(__traits(getMember, T, it)))) 185 { 186 ret ~= ("case \"%s\": { line = readINIFileImpl" ~ 187 "(t.%s, input, deaph+1); } "). 188 format(fullyQualifiedName!(typeof(__traits(getMember, T, it))), 189 it 190 ); 191 } 192 } 193 194 // Avoid DMD switch fallthrough warnings 195 if (ret.length) { 196 return "switch(getSection(line)) { // " ~ fullyQualifiedName!T ~ "\n" ~ 197 ret.join("goto case; \n") ~ "goto default;\n default: return line;\n}\n"; 198 } else { 199 return "return line;"; 200 } 201 } 202 203 string buildValueParse(T)() @safe { 204 import std.traits : hasUDA, fullyQualifiedName, isArray, isBasicType, isSomeString; 205 import std.format : format; 206 string ret = "switch(getKey(line)) { // " ~ fullyQualifiedName!T ~ "\n"; 207 208 foreach(it; __traits(allMembers, T)) { 209 if(hasUDA!(__traits(getMember, T, it), INI) && (isBasicType!(typeof(__traits(getMember, T, it))) 210 || isSomeString!(typeof(__traits(getMember, T, it))))) 211 { 212 ret ~= ("case \"%s\": { t.%s = to!(typeof(t.%s))(" 213 ~ "getValue(line)); break; }\n").format(it, it, it); 214 } else if(hasUDA!(__traits(getMember, T, it), INI) 215 && isArray!(typeof(__traits(getMember, T, it)))) 216 { 217 ret ~= ("case \"%s\": { t.%s = to!(typeof(t.%s))(" 218 ~ "getValueArray(line).split(',')); break; }\n").format(it, it, it); 219 } 220 } 221 222 return ret ~ "default: break;\n}\n"; 223 } 224 225 string readINIFileImpl(T,IRange)(ref T t, ref IRange input, int deaph = 0) 226 if(isInputRange!IRange) 227 { 228 import std.conv : to; 229 import std.string : split; 230 import std.algorithm.searching : startsWith; 231 import std.traits : fullyQualifiedName; 232 debug { 233 import std.stdio : writefln; 234 } 235 debug { 236 writefln("%*s%d %s %x", deaph, "", __LINE__, fullyQualifiedName!(typeof(t)), 237 cast(void*)&input); 238 } 239 string line; 240 while(!input.empty()) { 241 line = input.front().idup; 242 input.popFront(); 243 244 if(line.startsWith(";")) { 245 continue; 246 } 247 debug { 248 writefln("%*s%d %s %s %b", deaph, "", __LINE__, line, fullyQualifiedName!T, 249 isSection(line)); 250 } 251 252 if(isSection(line) && getSection(line) != fullyQualifiedName!T) { 253 debug { 254 //pragma(msg, buildSectionParse!(T)); 255 writefln("%*s%d %s", deaph, "", __LINE__, getSection(line)); 256 writefln("%*s%d %x", deaph, "", __LINE__, 257 cast(void*)&input); 258 } 259 260 mixin(buildSectionParse!(T)); 261 } else if(isKeyValue(line)) { 262 debug { 263 //pragma(msg, buildValueParse!(T)); 264 writefln("%*s%d %s %s", deaph, "", __LINE__, getKey(line), 265 getValue(line)); 266 } 267 268 mixin(buildValueParse!(T)); 269 } 270 } 271 272 return line; 273 } 274 275 void writeComment(ORange,IRange)(ORange orange, IRange irange) @trusted 276 if(isOutputRange!(ORange, ElementType!IRange) && isInputRange!IRange) 277 { 278 size_t idx = 0; 279 foreach(it; irange) { 280 if(idx % 77 == 0) { 281 orange.put("; "); 282 } 283 orange.put(it); 284 285 if((idx+1) % 77 == 0) { 286 orange.put('\n'); 287 } 288 289 ++idx; 290 } 291 orange.put('\n'); 292 } 293 294 void writeValue(ORange,T)(ORange orange, string name, T value) @trusted 295 if(isOutputRange!(ORange, string)) 296 { 297 import std.traits : isArray, isSomeString; 298 import std.format : formattedWrite; 299 static if(isArray!T && !isSomeString!T) { 300 orange.formattedWrite("%s=\"", name); 301 foreach(idx, it; value) { 302 if(idx != 0) { 303 orange.put(','); 304 } 305 orange.formattedWrite("%s", it); 306 } 307 orange.formattedWrite("\""); 308 } else { 309 orange.formattedWrite("%s=\"%s\"\n", name, value); 310 } 311 } 312 313 string removeFromLastPoint(string input) @safe { 314 import std.string : lastIndexOf; 315 ptrdiff_t lDot = input.lastIndexOf('.'); 316 if(lDot != -1 && lDot+1 != input.length) { 317 return input[lDot+1 .. $]; 318 } else { 319 return input; 320 } 321 } 322 323 void writeValues(ORange,T)(ORange oRange, string name, T value) @trusted 324 if(isOutputRange!(ORange, string)) 325 { 326 import std.traits : isBasicType, isSomeString; 327 import std.format : formattedWrite; 328 static if(isSomeString!(ElementType!T) || isBasicType!(ElementType!T)) { 329 oRange.formattedWrite("%s=\"", removeFromLastPoint(name)); 330 foreach(idx, it; value) { 331 if(idx != 0) { 332 oRange.put(','); 333 } 334 oRange.formattedWrite("%s", it); 335 } 336 oRange.put('"'); 337 oRange.put('\n'); 338 } else { 339 for(size_t i = 0; i < value.length; ++i) { 340 oRange.formattedWrite("[%s]\n", name); 341 writeINIFileImpl(value[i], oRange, false); 342 } 343 } 344 } 345 346 void writeINIFile(T)(ref T t, string filename) @trusted { 347 import std.stdio : File; 348 auto oFile = File(filename, "w"); 349 auto oRange = oFile.lockingTextWriter(); 350 writeINIFileImpl(t, oRange, true); 351 } 352 353 void writeINIFileImpl(T,ORange)(ref T t, ORange oRange, bool section) 354 @trusted 355 { 356 import std.traits : getUDAs, hasUDA, Unqual, isArray, isBasicType, 357 isSomeString; 358 import std.format : formattedWrite; 359 if(hasUDA!(T, INI) && section) { 360 writeComment(oRange, getINI!T()); 361 } 362 363 if(section) { 364 oRange.formattedWrite("[%s]\n", getTypeName!T); 365 } 366 367 foreach(it; __traits(allMembers, T)) { 368 if(hasUDA!(__traits(getMember, T, it), INI)) { 369 static if(isBasicType!(typeof(__traits(getMember, T, it))) || 370 isSomeString!(typeof(__traits(getMember, T, it)))) 371 { 372 writeComment(oRange, getINI!(T,it)); 373 writeValue(oRange, it, __traits(getMember, t, it)); 374 } else static if(isArray!(typeof(__traits(getMember, T, it)))) { 375 writeComment(oRange, getINI!(T,it)); 376 writeValues(oRange, getTypeName!T ~ "." ~ it, 377 __traits(getMember, t, it)); 378 //} else static if(isINI!(typeof(__traits(getMember, t, it)))) { 379 } else static if(hasUDA!(__traits(getMember, t, it),INI)) 380 { 381 writeINIFileImpl(__traits(getMember, t, it), oRange, true); 382 } 383 } 384 } 385 } 386 387 version(unittest) { 388 @INI("A child must have a parent") 389 struct Child { 390 @INI("The firstname of the child") 391 string firstname; 392 393 @INI("The age of the child") 394 int age; 395 396 bool opEquals(Child other) { 397 return this.firstname == other.firstname 398 && this.age == other.age; 399 } 400 } 401 402 @INI("A Spose") 403 struct Spose { 404 @INI("The firstname of the spose") 405 string firstname; 406 407 @INI("The age of the spose") 408 int age; 409 410 @INI("The House of the spose") 411 House house; 412 413 bool opEquals(Spose other) { 414 return this.firstname == other.firstname 415 && this.age == other.age 416 && this.house == other.house; 417 } 418 } 419 420 @INI("A Dog") 421 struct Dog { 422 @INI("The name of the Dog") 423 string name; 424 425 @INI("The food consumed") 426 float kg; 427 428 bool opEquals(Dog other) { 429 import std.math : approxEqual, isNaN; 430 return this.name == other.name 431 && (approxEqual(this.kg, other.kg) 432 || (isNaN(this.kg) && isNaN(other.kg)) 433 ); 434 } 435 } 436 437 @INI("A Person") 438 struct Person { 439 @INI("The firstname of the Person") 440 string firstname; 441 442 @INI("The lastname of the Person") 443 string lastname; 444 445 @INI("The age of the Person") 446 int age; 447 448 @INI("The height of the Person") 449 float height; 450 451 @INI("Some strings with a very long long INI description that is longer" ~ 452 " than eigthy lines hopefully." 453 ) 454 string[] someStrings = [":::60180", "asd"]; 455 456 @INI("Some ints") 457 int[] someInts; 458 459 int dontShowThis; 460 461 @INI("A Spose") 462 Spose spose; 463 464 @INI("The family dog") 465 Dog dog; 466 467 bool opEquals(Person other) { 468 import std.math : approxEqual, isNaN; 469 import std.algorithm.comparison : equal; 470 return this.firstname == other.firstname 471 && this.lastname == other.lastname 472 && this.age == other.age 473 && (approxEqual(this.height, other.height) 474 || (isNaN(this.height) && isNaN(other.height))) 475 && equal(this.someStrings, other.someStrings) 476 && equal(this.someInts, other.someInts) 477 && this.spose == other.spose 478 && this.dog == other.dog; 479 } 480 } 481 482 @INI("A House") 483 struct House { 484 @INI("Number of Rooms") 485 uint rooms; 486 487 @INI("Number of Floors") 488 uint floors; 489 490 bool opEquals(House other) { 491 return this.rooms == other.rooms 492 && this.floors == other.floors; 493 } 494 } 495 } 496 497 unittest { 498 import std.stdio : writefln; 499 Person p; 500 p.firstname = "Foo"; 501 p.lastname = "Bar"; 502 p.age = 1337; 503 p.height = 7331.0; 504 505 p.someStrings ~= "Hello"; 506 p.someStrings ~= "World"; 507 508 p.someInts ~= [1,2]; 509 510 p.spose.firstname = "World"; 511 p.spose.age = 72; 512 513 p.spose.house.rooms = 5; 514 p.spose.house.floors = 2; 515 516 p.dog.name = "Wuff"; 517 p.dog.kg = 3.14; 518 519 Person p2; 520 readINIFile(p2, "test/filename.ini"); 521 writefln("\n%s\n", p2); 522 writeINIFile(p2, "test/filenameTmp.ini"); 523 524 Person p3; 525 readINIFile(p3, "test/filenameTmp.ini"); 526 527 if(p2 != p3) { 528 writefln("\n%s\n%s", p2, p3); 529 writefln("Spose equal %b", p2.spose == p3.spose); 530 writefln("Dog equal %b", p2.dog == p3.dog); 531 assert(false); 532 } 533 if(p != p3) { 534 writefln("\n%s\n%s", p, p3); 535 writefln("Spose equal %b", p.spose == p3.spose); 536 writefln("Dog equal %b", p.dog == p3.dog); 537 assert(false); 538 } 539 if(p != p2) { 540 writefln("\n%s\n%s", p, p2); 541 writefln("Spose equal %b", p.spose == p2.spose); 542 writefln("Dog equal %b", p.dog == p2.dog); 543 assert(false); 544 } 545 } 546 547 version(unittest) { 548 enum Check : string { disabled = "disabled"} 549 550 @INI 551 struct StaticAnalysisConfig { 552 @INI 553 string style_check = Check.disabled; 554 555 @INI 556 ModuleFilters filters; 557 } 558 559 private template ModuleFiltersMixin(A) { 560 const string ModuleFiltersMixin = () { 561 string s; 562 foreach (mem; __traits(allMembers, StaticAnalysisConfig)) 563 static if (is(typeof(__traits(getMember, StaticAnalysisConfig, mem)) == string)) 564 s ~= `@INI string[] ` ~ mem ~ ";\n"; 565 566 return s; 567 }(); 568 } 569 570 @INI 571 struct ModuleFilters { mixin(ModuleFiltersMixin!int); } 572 } 573 574 unittest { 575 StaticAnalysisConfig config; 576 readINIFile(config, "test/dscanner.ini"); 577 assert(config.style_check == "disabled"); 578 assert(config.filters.style_check == ["+std.algorithm"]); 579 }