1 /** 2 * This file is part of Dini library 3 * 4 * Copyright: Robert Pasiński 5 * License: Boost License 6 */ 7 module ini.dini; 8 9 private import std.stdio : File; 10 private import std..string : strip, indexOf; 11 private import std.traits : isSomeString; 12 private import std.array : split, replaceInPlace, join; 13 private import std.algorithm : min, max, countUntil; 14 private import std.conv : to; 15 16 private import std.stdio; 17 18 19 /** 20 * Represents ini section 21 * 22 * Example: 23 * --- 24 * Ini ini = Ini.Parse("path/to/your.conf"); 25 * string value = ini.getKey("a"); 26 * --- 27 */ 28 struct IniSection 29 { 30 /// Section name 31 protected string _name = "root"; 32 33 /// Parent 34 /// Null if none 35 protected IniSection* _parent; 36 37 /// Childs 38 protected IniSection[] _sections; 39 40 /// Keys 41 protected string[string] _keys; 42 43 44 45 /** 46 * Creates new IniSection instance 47 * 48 * Params: 49 * name = Section name 50 */ 51 public this(string name) 52 { 53 _name = name; 54 _parent = null; 55 } 56 57 58 /** 59 * Creates new IniSection instance 60 * 61 * Params: 62 * name = Section name 63 * parent = Section parent 64 */ 65 public this(string name, IniSection* parent) 66 { 67 _name = name; 68 _parent = parent; 69 } 70 71 /** 72 * Sets section key 73 * 74 * Params: 75 * name = Key name 76 * value = Value to set 77 */ 78 public void setKey(string name, string value) 79 { 80 _keys[name] = value; 81 } 82 83 /** 84 * Checks if specified key exists 85 * 86 * Params: 87 * name = Key name 88 * 89 * Returns: 90 * True if exists, false otherwise 91 */ 92 public bool hasKey(string name) 93 { 94 return (name in _keys) !is null; 95 } 96 97 /** 98 * Gets key value 99 * 100 * Params: 101 * name = Key name 102 * 103 * Returns: 104 * Key value 105 */ 106 public string getKey(string name) 107 { 108 if(!hasKey(name)) { 109 return ""; 110 } 111 112 return _keys[name]; 113 } 114 115 116 /// ditto 117 alias getKey opCall; 118 119 120 /** 121 * Removes key 122 * 123 * Params: 124 * name = Key name 125 */ 126 public void removeKey(string name) 127 { 128 _keys.remove(name); 129 } 130 131 /** 132 * Adds section 133 * 134 * Params: 135 * section = Section to add 136 */ 137 public void addSection(ref IniSection section) 138 { 139 _sections ~= section; 140 } 141 142 /** 143 * Checks if specified section exists 144 * 145 * Params: 146 * name = Section name 147 * 148 * Returns: 149 * True if exists, false otherwise 150 */ 151 public bool hasSection(string name) 152 { 153 foreach(ref section; _sections) 154 { 155 if(section.name() == name) 156 return true; 157 } 158 159 return false; 160 } 161 162 /** 163 * Returns reference to section 164 * 165 * Params: 166 * Section name 167 * 168 * Returns: 169 * Section with specified name 170 */ 171 public ref IniSection getSection(string name) 172 { 173 foreach(ref section; _sections) 174 { 175 if(section.name() == name) 176 return section; 177 } 178 179 throw new IniException("Section '"~name~"' does not exist"); 180 } 181 182 183 /// ditto 184 public alias getSection opIndex; 185 186 /** 187 * Removes section 188 * 189 * Params: 190 * name = Section name 191 */ 192 public void removeSection(string name) 193 { 194 IniSection[] childs; 195 196 foreach(section; _sections) 197 { 198 if(section.name != name) 199 childs ~= section; 200 } 201 202 _sections = childs; 203 } 204 205 /** 206 * Section name 207 * 208 * Returns: 209 * Section name 210 */ 211 public string name() @property 212 { 213 return _name; 214 } 215 216 /** 217 * Array of keys 218 * 219 * Returns: 220 * Associative array of keys 221 */ 222 public string[string] keys() @property 223 { 224 return _keys; 225 } 226 227 /** 228 * Array of sections 229 * 230 * Returns: 231 * Array of sections 232 */ 233 public IniSection[] sections() @property 234 { 235 return _sections; 236 } 237 238 /** 239 * Root section 240 */ 241 public IniSection root() @property 242 { 243 IniSection s = this; 244 245 while(s.getParent() != null) 246 s = *(s.getParent()); 247 248 return s; 249 } 250 251 /** 252 * Section parent 253 * 254 * Returns: 255 * Pointer to parent, or null if parent does not exists 256 */ 257 public IniSection* getParent() 258 { 259 return _parent; 260 } 261 262 /** 263 * Checks if current section has parent 264 * 265 * Returns: 266 * True if section has parent, false otherwise 267 */ 268 public bool hasParent() 269 { 270 return _parent != null; 271 } 272 273 /** 274 * Moves current section to another one 275 * 276 * Params: 277 * New parent 278 */ 279 public void setParent(ref IniSection parent) 280 { 281 _parent.removeSection(this.name); 282 _parent = &parent; 283 parent.addSection(this); 284 } 285 286 287 /** 288 * Parses filename 289 * 290 * Params: 291 * filename = Configuration filename 292 * doLookups = Should variable lookups be resolved after parsing? 293 */ 294 public void parse(string filename, bool doLookups = true) 295 { 296 auto file = File(filename); 297 scope(exit) file.close; 298 299 IniSection* section = &this; 300 301 int i = 0; 302 foreach(char[] line; file.byLine) 303 { 304 i++; 305 line = strip(line); 306 307 // Empty line 308 if(line.length < 1) continue; 309 310 // Comment line 311 if(line[0] == ';') continue; 312 313 // Section header 314 if(line.length >= 3 && line[0] == '[' && line[$-1] == ']') 315 { 316 section = &this; 317 auto name = line[1..$-1]; 318 string parent; 319 320 ptrdiff_t pos = name.countUntil(":"); 321 if(pos > -1) 322 { 323 parent = name[pos+1..$].strip().idup; 324 name = name[0..pos].strip(); 325 } 326 327 if(name.countUntil(".") > -1) 328 { 329 auto names = name.split("."); 330 foreach(part; names) 331 { 332 IniSection sect; 333 334 if(section.hasSection(part.idup)) { 335 sect = section.getSection(part.idup); 336 } else { 337 sect = IniSection(part.idup, section); 338 section.addSection(sect); 339 } 340 341 section = (§ion.getSection(part.idup)); 342 } 343 } 344 else 345 { 346 IniSection sect; 347 348 if(section.hasSection(name.idup)) { 349 sect = section.getSection(name.idup); 350 } else { 351 sect = IniSection(name.idup, section); 352 section.addSection(sect); 353 } 354 355 section = (&this.getSection(name.idup)); 356 } 357 358 if(parent.length > 1) 359 { 360 if(parent[0] == '.') 361 section.inherit(this.getSectionEx(parent[1..$])); 362 else 363 section.inherit(section.getParent().getSectionEx(parent)); 364 } 365 continue; 366 } 367 368 // Assignement 369 auto parts = split(line, "=", 2); 370 if(parts.length > 1) 371 { 372 auto val = parts[1].strip(); 373 if(val.length > 2 && val[0] == '"' && val[$-1] == '"') val = val[1..$-1]; 374 section.setKey(parts[0].strip().idup, val.idup); 375 continue; 376 } 377 378 throw new IniException("Syntax error at line "~to!string(i)); 379 } 380 381 if(doLookups == true) 382 parseLookups(); 383 } 384 385 /** 386 * Parses lookups 387 */ 388 public void parseLookups() 389 { 390 foreach(name, ref value; _keys) 391 { 392 ptrdiff_t start = -1; 393 char[] buf; 394 395 foreach(i, c; value) 396 { 397 if(c == '%') 398 { 399 if(start != -1) 400 { 401 IniSection sect; 402 string newValue; 403 char[][] parts; 404 405 if(buf[0] == '.') 406 { 407 parts = buf[1..$].split("."); 408 sect = this.root; 409 } 410 else 411 { 412 parts = buf.split("."); 413 sect = this; 414 } 415 416 newValue = sect.getSectionEx(parts[0..$-1].join(".").idup) 417 .getKey(parts[$-1].idup); 418 419 value.replaceInPlace(start, i+1, newValue); 420 start = -1; 421 buf = []; 422 } 423 else { 424 start = i; 425 } 426 } 427 else if(start != -1) { 428 buf ~= c; 429 } 430 } 431 } 432 433 foreach(child; _sections) 434 { 435 child.parseLookups(); 436 } 437 } 438 439 /** 440 * Returns section by name in inheriting(names connected by dot) 441 * 442 * Params: 443 * name = Section name 444 * 445 * Returns: 446 * Section 447 */ 448 public IniSection getSectionEx(string name) 449 { 450 IniSection* root = &this; 451 auto parts = name.split("."); 452 453 foreach(part; parts) 454 { 455 root = (&root.getSection(part)); 456 } 457 458 return *root; 459 } 460 461 /** 462 * Inherits keys from section 463 * 464 * Params: 465 * Section to inherit 466 */ 467 public void inherit(IniSection sect) 468 { 469 this._keys = sect.keys().dup; 470 } 471 472 /** 473 * Splits string by delimeter with limit 474 * 475 * Params: 476 * txt = Text to split 477 * delim = Delimeter 478 * limit = Limit of splits 479 * 480 * Returns: 481 * Splitted string 482 */ 483 protected T[] split(T, S)(T txt, S delim, int limit) 484 if(isSomeString!(T) && isSomeString!(S)) 485 { 486 limit -= 1; 487 T[] parts; 488 ptrdiff_t last, len = delim.length, cnt; 489 490 for(int i = 0; i <= txt.length; i++) 491 { 492 if(cnt >= limit) 493 break; 494 495 if(txt[i .. min(i + len, txt.length)] == delim) 496 { 497 parts ~= txt[last .. i]; 498 last = min(i + 1, txt.length); 499 cnt++; 500 } 501 } 502 503 parts ~= txt[last .. txt.length]; 504 505 return parts; 506 } 507 508 /** 509 * Parses Ini file 510 * 511 * Params: 512 * filename = Path to ini file 513 * 514 * Returns: 515 * IniSection root 516 */ 517 static Ini Parse(string filename) 518 { 519 Ini i; 520 i.parse(filename); 521 return i; 522 } 523 } 524 525 /// ditto 526 alias IniSection Ini; 527 528 /// 529 class IniException : Exception 530 { 531 this(string msg) 532 { 533 super(msg); 534 } 535 }