1 /** 2 * INI parsing functionality. 3 * 4 * Examples: 5 * --- 6 * auto ini = Ini.ParseString("test = bar") 7 * writeln("Value of test is ", ini["test"]); 8 * --- 9 */ 10 module dini.parser; 11 12 import std.algorithm : min, max, countUntil; 13 import std.array : split, replaceInPlace, join; 14 import std.file : readText; 15 import std.stdio : File; 16 import std..string : strip, splitLines; 17 import std.traits : isSomeString; 18 import std.range : ElementType; 19 import std.conv : to; 20 import dini.reader : UniversalINIReader, INIException, INIToken; 21 22 23 /** 24 * Represents ini section 25 * 26 * Example: 27 * --- 28 * Ini ini = Ini.Parse("path/to/your.conf"); 29 * string value = ini.getKey("a"); 30 * --- 31 */ 32 struct IniSection 33 { 34 /// Section name 35 protected string _name = "root"; 36 37 /// Parent 38 /// Null if none 39 protected IniSection* _parent; 40 41 /// Childs 42 protected IniSection[] _sections; 43 44 /// Keys 45 protected string[string] _keys; 46 47 48 49 /** 50 * Creates new IniSection instance 51 * 52 * Params: 53 * name = Section name 54 */ 55 public this(string name) 56 { 57 _name = name; 58 _parent = null; 59 } 60 61 62 /** 63 * Creates new IniSection instance 64 * 65 * Params: 66 * name = Section name 67 * parent = Section parent 68 */ 69 public this(string name, IniSection* parent) 70 { 71 _name = name; 72 _parent = parent; 73 } 74 75 /** 76 * Sets section key 77 * 78 * Params: 79 * name = Key name 80 * value = Value to set 81 */ 82 public void setKey(string name, string value) 83 { 84 _keys[name] = value; 85 } 86 87 /** 88 * Checks if specified key exists 89 * 90 * Params: 91 * name = Key name 92 * 93 * Returns: 94 * True if exists, false otherwise 95 */ 96 public bool hasKey(string name) @safe nothrow @nogc 97 { 98 return (name in _keys) !is null; 99 } 100 101 /** 102 * Gets key value 103 * 104 * Params: 105 * name = Key name 106 * 107 * Returns: 108 * Key value 109 * 110 * Throws: 111 * IniException if key does not exists 112 */ 113 public string getKey(string name) 114 { 115 if(!hasKey(name)) { 116 throw new IniException("Key '"~name~"' does not exists"); 117 } 118 119 return _keys[name]; 120 } 121 122 123 /// ditto 124 alias getKey opCall; 125 126 /** 127 * Gets key value or defaultValue if key does not exist 128 * 129 * Params: 130 * name = Key name 131 * defaultValue = Default value 132 * 133 * Returns: 134 * Key value or defaultValue 135 * 136 */ 137 public string getKey(string name, string defaultValue) @safe nothrow 138 { 139 return hasKey(name) ? _keys[name] : defaultValue; 140 } 141 142 /** 143 * Removes key 144 * 145 * Params: 146 * name = Key name 147 */ 148 public void removeKey(string name) 149 { 150 _keys.remove(name); 151 } 152 153 /** 154 * Adds section 155 * 156 * Params: 157 * section = Section to add 158 */ 159 public void addSection(ref IniSection section) 160 { 161 _sections ~= section; 162 } 163 164 /** 165 * Checks if specified section exists 166 * 167 * Params: 168 * name = Section name 169 * 170 * Returns: 171 * True if exists, false otherwise 172 */ 173 public bool hasSection(string name) 174 { 175 foreach(ref section; _sections) 176 { 177 if(section.name() == name) 178 return true; 179 } 180 181 return false; 182 } 183 184 /** 185 * Returns reference to section 186 * 187 * Params: 188 * Section name 189 * 190 * Returns: 191 * Section with specified name 192 */ 193 public ref IniSection getSection(string name) 194 { 195 foreach(ref section; _sections) 196 { 197 if(section.name() == name) 198 return section; 199 } 200 201 throw new IniException("Section '"~name~"' does not exists"); 202 } 203 204 205 /// ditto 206 public alias getSection opIndex; 207 208 /** 209 * Removes section 210 * 211 * Params: 212 * name = Section name 213 */ 214 public void removeSection(string name) 215 { 216 IniSection[] childs; 217 218 foreach(section; _sections) 219 { 220 if(section.name != name) 221 childs ~= section; 222 } 223 224 _sections = childs; 225 } 226 227 /** 228 * Section name 229 * 230 * Returns: 231 * Section name 232 */ 233 public string name() @property 234 { 235 return _name; 236 } 237 238 /** 239 * Array of keys 240 * 241 * Returns: 242 * Associative array of keys 243 */ 244 public string[string] keys() @property 245 { 246 return _keys; 247 } 248 249 /** 250 * Array of sections 251 * 252 * Returns: 253 * Array of sections 254 */ 255 public IniSection[] sections() @property 256 { 257 return _sections; 258 } 259 260 /** 261 * Root section 262 */ 263 public IniSection root() @property 264 { 265 IniSection s = this; 266 267 while(s.getParent() != null) 268 s = *(s.getParent()); 269 270 return s; 271 } 272 273 /** 274 * Section parent 275 * 276 * Returns: 277 * Pointer to parent, or null if parent does not exists 278 */ 279 public IniSection* getParent() 280 { 281 return _parent; 282 } 283 284 /** 285 * Checks if current section has parent 286 * 287 * Returns: 288 * True if section has parent, false otherwise 289 */ 290 public bool hasParent() 291 { 292 return _parent != null; 293 } 294 295 /** 296 * Moves current section to another one 297 * 298 * Params: 299 * New parent 300 */ 301 public void setParent(ref IniSection parent) 302 { 303 _parent.removeSection(this.name); 304 _parent = &parent; 305 parent.addSection(this); 306 } 307 308 309 /** 310 * Parses filename 311 * 312 * Params: 313 * filename = Configuration filename 314 * doLookups = Should variable lookups be resolved after parsing? 315 */ 316 public void parse(string filename, bool doLookups = true) 317 { 318 parseString(readText(filename), doLookups); 319 } 320 321 public void parse(File* file, bool doLookups = true) 322 { 323 string data = file.byLine().join().to!string; 324 parseString(data, doLookups); 325 } 326 327 public void parseWith(Reader)(string filename, bool doLookups = true) 328 { 329 parseStringWith!Reader(readText(filename), doLookups); 330 } 331 332 public void parseWith(Reader)(File* file, bool doLookups = true) 333 { 334 string data = file.byLine().join().to!string; 335 parseStringWith!Reader(data, doLookups); 336 } 337 338 public void parseString(string data, bool doLookups = true) 339 { 340 parseStringWith!UniversalINIReader(data, doLookups); 341 } 342 343 public void parseStringWith(Reader)(string data, bool doLookups = true) 344 { 345 IniSection* section = &this; 346 347 auto reader = Reader(data); 348 alias KeyType = reader.KeyType; 349 while (reader.next()) switch (reader.type) with (INIToken) { 350 case SECTION: 351 section = &this; 352 string name = reader.value.get!string; 353 auto parts = name.split(":"); 354 355 // [section : parent] 356 if (parts.length > 1) 357 name = parts[0].strip; 358 359 IniSection child = IniSection(name, section); 360 361 if (parts.length > 1) { 362 string parent = parts[1].strip; 363 child.inherit(section.getSectionEx(parent)); 364 } 365 section.addSection(child); 366 section = §ion.getSection(name); 367 break; 368 369 case KEY: 370 section.setKey(reader.value.get!KeyType.name, reader.value.get!KeyType.value); 371 break; 372 373 default: 374 break; 375 } 376 377 if(doLookups == true) 378 parseLookups(); 379 } 380 381 /** 382 * Parses lookups 383 */ 384 public void parseLookups() 385 { 386 foreach (name, ref value; _keys) 387 { 388 ptrdiff_t start = -1; 389 char[] buf; 390 391 foreach (i, c; value) { 392 if (c == '%') { 393 if (start != -1) { 394 IniSection sect; 395 string newValue; 396 char[][] parts; 397 398 if (buf[0] == '.') { 399 parts = buf[1..$].split("."); 400 sect = this.root; 401 } 402 else { 403 parts = buf.split("."); 404 sect = this; 405 } 406 407 newValue = sect.getSectionEx(parts[0..$-1].join(".").idup).getKey(parts[$-1].idup); 408 409 value.replaceInPlace(start, i+1, newValue); 410 start = -1; 411 buf = []; 412 } 413 else { 414 start = i; 415 } 416 } 417 else if (start != -1) { 418 buf ~= c; 419 } 420 } 421 } 422 423 foreach(child; _sections) { 424 child.parseLookups(); 425 } 426 } 427 428 /** 429 * Returns section by name in inheriting(names connected by dot) 430 * 431 * Params: 432 * name = Section name 433 * 434 * Returns: 435 * Section 436 */ 437 public IniSection getSectionEx(string name) 438 { 439 IniSection* root = &this; 440 auto parts = name.split("."); 441 442 foreach(part; parts) { 443 root = (&root.getSection(part)); 444 } 445 446 return *root; 447 } 448 449 /** 450 * Inherits keys from section 451 * 452 * Params: 453 * Section to inherit 454 */ 455 public void inherit(IniSection sect) 456 { 457 this._keys = sect.keys().dup; 458 } 459 460 461 public void save(string filename) 462 { 463 import std.file; 464 465 if (exists(filename)) 466 remove(filename); 467 468 File file = File(filename, "w"); 469 470 foreach (section; _sections) { 471 file.writeln("[" ~ section.name() ~ "]"); 472 473 string[string] propertiesInSection = section.keys(); 474 foreach (key; propertiesInSection.keys) { 475 file.writeln(key ~ " = " ~ propertiesInSection[key]); 476 } 477 478 file.writeln(); 479 } 480 481 file.close(); 482 } 483 484 485 /** 486 * Parses Ini file 487 * 488 * Params: 489 * filename = Path to ini file 490 * 491 * Returns: 492 * IniSection root 493 */ 494 static Ini Parse(string filename, bool parseLookups = true) 495 { 496 Ini i; 497 i.parse(filename, parseLookups); 498 return i; 499 } 500 501 502 /** 503 * Parses Ini file with specified reader 504 * 505 * Params: 506 * filename = Path to ini file 507 * 508 * Returns: 509 * IniSection root 510 */ 511 static Ini ParseWith(Reader)(string filename, bool parseLookups = true) 512 { 513 Ini i; 514 i.parseWith!Reader(filename, parseLookups); 515 return i; 516 } 517 518 static Ini ParseString(string data, bool parseLookups = true) 519 { 520 Ini i; 521 i.parseString(data, parseLookups); 522 return i; 523 } 524 525 static Ini ParseStringWith(Reader)(string data, bool parseLookups = true) 526 { 527 Ini i; 528 i.parseStringWith!Reader(data, parseLookups); 529 return i; 530 } 531 } 532 533 // Compat 534 alias INIException IniException; 535 536 /// ditto 537 alias IniSection Ini; 538 539 540 /// 541 Struct siphon(Struct)(Ini ini) 542 { 543 import std.traits; 544 Struct ans; 545 if(ini.hasSection(Struct.stringof)) 546 foreach(ti, Name; FieldNameTuple!(Struct)) 547 { 548 alias ToType = typeof(ans.tupleof[ti]); 549 if(ini[Struct.stringof].hasKey(Name)) 550 ans.tupleof[ti] = to!ToType(ini[Struct.stringof].getKey(Name)); 551 } 552 return ans; 553 } 554 555 unittest { 556 struct Section { 557 int var; 558 } 559 560 auto ini = Ini.ParseString("[Section]\nvar=3"); 561 auto m = ini.siphon!Section; 562 assert(m.var == 3); 563 } 564 565 566 unittest { 567 auto data = q"( 568 key1 = value 569 570 # comment 571 572 test = bar ; comment 573 574 [section 1] 575 key1 = new key 576 num = 151 577 empty 578 579 580 [ various ] 581 "quoted key"= VALUE 123 582 583 quote_multiline = """ 584 this is value 585 """ 586 587 escape_sequences = "yay\nboo" 588 escaped_newlines = abcd \ 589 efg 590 )"; 591 592 auto ini = Ini.ParseString(data); 593 assert(ini.getKey("key1") == "value"); 594 assert(ini.getKey("test") == "bar ; comment"); 595 596 assert(ini.hasSection("section 1")); 597 with (ini["section 1"]) { 598 assert(getKey("key1") == "new key"); 599 assert(getKey("num") == "151"); 600 assert(getKey("empty") == ""); 601 } 602 603 assert(ini.hasSection("various")); 604 with (ini["various"]) { 605 assert(getKey("quoted key") == "VALUE 123"); 606 assert(getKey("quote_multiline") == "\n this is value\n"); 607 assert(getKey("escape_sequences") == "yay\nboo"); 608 assert(getKey("escaped_newlines") == "abcd efg"); 609 } 610 } 611 612 unittest { 613 auto data = q"EOF 614 key1 = value 615 616 # comment 617 618 test = bar ; comment 619 620 [section 1] 621 key1 = new key 622 num = 151 623 empty 624 625 EOF"; 626 627 auto ini = Ini.ParseString(data); 628 assert(ini.getKey("key1") == "value"); 629 assert(ini.getKey("test") == "bar ; comment"); 630 assert(ini.hasSection("section 1")); 631 assert(ini["section 1"]("key1") == "new key"); 632 assert(ini["section 1"]("num") == "151"); 633 assert(ini["section 1"]("empty") == ""); 634 } 635 636 unittest { 637 auto data = q"EOF 638 [def] 639 name1=value1 640 name2=value2 641 642 [foo : def] 643 name1=Name1 from foo. Lookup for def.name2: %name2% 644 EOF"; 645 646 // Parse file 647 auto ini = Ini.ParseString(data, true); 648 649 assert(ini["foo"].getKey("name1") 650 == "Name1 from foo. Lookup for def.name2: value2"); 651 } 652 653 unittest { 654 auto data = q"EOF 655 [section] 656 name=%value% 657 EOF"; 658 659 // Create ini struct instance 660 Ini ini; 661 Ini iniSec = IniSection("section"); 662 ini.addSection(iniSec); 663 664 // Set key value 665 ini["section"].setKey("value", "verify"); 666 667 // Now, you can use value in ini file 668 ini.parseString(data); 669 670 assert(ini["section"].getKey("name") == "verify"); 671 } 672 673 674 unittest { 675 import dini.reader; 676 677 alias MyReader = INIReader!( 678 UniversalINIFormat, 679 UniversalINIReader.CurrentFlags & ~INIFlags.ProcessEscapes, 680 UniversalINIReader.CurrentBoxer 681 ); 682 auto ini = Ini.ParseStringWith!MyReader(`path=C:\Path`); 683 assert(ini("path") == `C:\Path`); 684 }