1 /* 2 * baussini.d provides a thread-safe by choice inifile wrapper. 3 * It supports template writing/reading for values. 4 * Please view example.d for an example. 5 */ 6 module baussini; 7 8 // Imports ... 9 import std.conv : to; 10 import std.string : format; 11 import std.array : replace, split, join; 12 import std.algorithm : canFind, startsWith, endsWith, stripLeft, stripRight; 13 import std.traits : isIntegral, isFloatingPoint; 14 15 import std.file; 16 // Aliasing write, read and exists from std.file to avoid name conflicts. 17 alias fwrite = std.file.write; 18 alias fread = std.file.readText; 19 alias fexists = std.file.exists; 20 21 /** 22 * An inifile exception. 23 */ 24 public class IniException : Throwable { 25 /** 26 * Creates a new instance of IniException. 27 * Params: 28 * msg = The message of the exception. 29 */ 30 this(string msg) { 31 super(msg); 32 } 33 } 34 35 /** 36 * std.traits doesn't have isArithmetic. 37 */ 38 private deprecated("isArithmetic is deprecated. write/read supports all types std.conv.to supports.") enum bool isArithmetic(T) = 39 isIntegral!T || isFloatingPoint!T; 40 41 /** 42 * An inifile section wrapper. 43 */ 44 class IniSection(bool sync) { 45 private: 46 /** 47 * The parent inifile. 48 */ 49 IniFile!(sync) m_parent; 50 /** 51 * The section name. 52 */ 53 string m_name; 54 /** 55 * The section entries. 56 */ 57 string[string] m_entries; 58 59 /** 60 * Gets the file-writable string 61 * Returns: A string compatible with an inifile. 62 */ 63 string getWritable() { 64 string[] s = [format("[%s]", m_name)]; 65 foreach (key, value; m_entries) { 66 s ~= format("%s=%s", key, value); 67 } 68 return join(s, "\r\n"); 69 } 70 71 /** 72 * Gets a string value. 73 * Params: 74 * key = The key to get. 75 * defaultValue = (lazy) The default value. 76 * Returns: The value of the key if found, defaultValue otherwise. 77 */ 78 string getValue(string key, lazy string defaultValue) { 79 static if (sync) { 80 synchronized { 81 auto val = m_entries.get(key, defaultValue); 82 return val; 83 } 84 } 85 else { 86 auto val = m_entries.get(key, defaultValue); 87 return val; 88 } 89 } 90 91 /** 92 * Closes the section and clears all entries. 93 */ 94 void close() { 95 m_entries = null; 96 } 97 public: 98 /** 99 * Creates a new instance of IniSection. 100 * Params: 101 * name = The name of the section. 102 * parent = The parent inifile. 103 */ 104 this(string name, IniFile!(sync) parent) { 105 m_name = name; 106 m_parent = parent; 107 } 108 109 /** 110 * Reads a value from the section. 111 * Params: 112 * key = The key to read. 113 * defaultValue = (lazy) The default value. 114 * Returns: The value if found, defaultValue otherwise. 115 */ 116 auto read(T)(string key, lazy string defaultValue) { 117 static if (sync) { 118 synchronized { 119 auto val = getValue(key, defaultValue); 120 return to!T(val); 121 } 122 } 123 else { 124 auto val = getValue(key, defaultValue); 125 return to!T(val); 126 } 127 } 128 129 /** 130 * Reads a value from the section. 131 * Params: 132 * key = The key to read. 133 * defaultValue = (lazy) The default value. 134 * value = (out) The value if found, defaultValue otherwise. 135 * Returns: The section. 136 */ 137 auto read(T)(string key, lazy string defaultValue, out T value) { 138 static if (sync) { 139 synchronized { 140 auto val = getValue(key, defaultValue); 141 value = to!T(val); 142 return this; 143 } 144 } 145 else { 146 auto val = getValue(key, defaultValue); 147 value = to!T(val); 148 return this; 149 } 150 } 151 152 /** 153 * Reads a value from the section. 154 * Params: 155 * key = The key to read. 156 * Throws: IniException if the key wasn't found or if the value is empty. 157 * Returns: The value if found. 158 */ 159 auto read(T)(string key) { 160 static if (sync) { 161 synchronized { 162 if(!hasKey(key)) 163 throw new IniException(format("%s does not exist in the section.", key)); 164 return to!T(getValue(key, "")); 165 } 166 } 167 else { 168 if(!hasKey(key)) 169 throw new IniException(format("%s does not exist in the section.", key)); 170 return to!T(getValue(key, "")); 171 } 172 } 173 174 /** 175 * Reads a value from the section. 176 * Params: 177 * key = The key to read. 178 * value = The value if found. 179 * Throws: IniException if the key wasn't found or if the value is empty. 180 * Returns: The section. 181 */ 182 auto read(T)(string key, out T value) { 183 static if (sync) { 184 synchronized { 185 if(!hasKey(key)) 186 throw new IniException(format("%s does not exist in the section.", key)); 187 value = to!T(getValue(key, "")); 188 return this; 189 } 190 } 191 else { 192 if(!hasKey(key)) 193 throw new IniException(format("%s does not exist in the section.", key)); 194 value = to!T(getValue(key, "")); 195 return this; 196 } 197 } 198 199 /** 200 * Writes an entry to the section. 201 * Params: 202 * key = The key of the entry. 203 * value = The value of the entry. 204 * Returns: The section. 205 */ 206 auto write(T)(string key, T value) { 207 static if (sync) { 208 synchronized { 209 m_entries[key] = to!string(value); 210 m_parent.m_changed = true; 211 return this; 212 } 213 } 214 else { 215 m_entries[key] = to!string(value); 216 m_parent.m_changed = true; 217 return this; 218 } 219 } 220 221 /** 222 * Checks whether the section has a key or not. 223 * Params: 224 * key = The key to check for. 225 * Returns: True if the key exists, false otherwise. 226 */ 227 bool hasKey(string key) { 228 static if (sync) { 229 synchronized { 230 return (key in m_entries) !is null; 231 } 232 } 233 else { 234 return (key in m_entries) !is null; 235 } 236 } 237 238 @property { 239 /** 240 * Gets the name of the section. 241 */ 242 string name() { 243 static if (sync) { 244 synchronized { 245 return m_name; 246 } 247 } 248 else { 249 return m_name; 250 } 251 } 252 253 /** 254 * Gets the keys of the section. 255 */ 256 string[] keys() { 257 static if (sync) { 258 synchronized { 259 return m_entries.keys; 260 } 261 } 262 else { 263 return m_entries.keys; 264 } 265 } 266 267 /** 268 * Gets the values of the section. 269 */ 270 string[] values() { 271 static if (sync) { 272 synchronized { 273 return m_entries.values; 274 } 275 } 276 else { 277 return m_entries.values; 278 } 279 } 280 281 /** 282 * Gets the parental inifile. 283 */ 284 IniFile!(sync) parent() { 285 static if (sync) { 286 synchronized { 287 return m_parent; 288 } 289 } 290 else { 291 return m_parent; 292 } 293 } 294 } 295 } 296 297 /** 298 * An inifile wrapper. 299 */ 300 class IniFile(bool sync) { 301 private: 302 /** 303 * The sections of the inifile. 304 */ 305 IniSection!(sync)[string] m_sections; 306 /** 307 * The file name of the inifile. 308 */ 309 string m_fileName; 310 /** 311 * A boolean determining whether the inifile has got any changes. 312 */ 313 bool m_changed; 314 315 /** 316 * Parses the inifile from text. 317 * Params: 318 * text = The text to parse. 319 */ 320 void parseFromText(string text) { 321 text = replace(text, "\r", ""); 322 scope auto lines = split(text, "\n"); 323 IniSection!(sync) currentSection; 324 foreach (sline; lines) { 325 auto line = stripLeft(sline, ' '); 326 line = stripLeft(sline, '\t'); 327 328 if (startsWith(line, ";")) 329 continue; 330 331 if (line && line.length) { 332 if (startsWith(line, "[") && endsWith(line, "]")) { 333 currentSection = new IniSection!(sync)(line[1 .. $-1], this); 334 m_sections[currentSection.name] = currentSection; 335 } 336 else if (canFind(line, "=") && currentSection) { 337 auto data = split(line, "="); 338 if (data.length == 2) { 339 auto key = stripRight(data[0], ' '); 340 key = stripLeft(key, ' '); 341 auto value = split(data[1], ";")[0]; 342 value = stripRight(value, ' '); 343 value = stripLeft(value, ' '); 344 currentSection.write(key, value); 345 } 346 } 347 } 348 } 349 } 350 351 /** 352 * Parses the inifile to text. 353 * Returns: A string representing the text. 354 */ 355 string parseToText() { 356 string s; 357 foreach (section; m_sections.values) { 358 s ~= section.getWritable() ~ "\r\n\r\n"; 359 } 360 if (s && s.length >= 2) { 361 s.length -= 1; // EOF has to be 1 one new line only. 362 return s; 363 } 364 else 365 return ""; 366 } 367 public: 368 /** 369 * Creates a new instance of IniFile. 370 * Params: 371 * fileName = The file name of the inifile. 372 */ 373 this(string fileName) { 374 m_fileName = fileName; 375 m_changed = false; 376 } 377 378 /** 379 * Checks whether the inifile exists or not. 380 * Returns: True if the file exists, false otherwise. 381 */ 382 bool exists() { 383 static if (sync) { 384 synchronized { 385 return fexists(m_fileName); 386 } 387 } 388 else { 389 return fexists(m_fileName); 390 } 391 } 392 393 /** 394 * Opens the inifile and parses its text. 395 */ 396 void open() { 397 static if (sync) { 398 synchronized { 399 parseFromText( 400 fread(m_fileName) 401 ); 402 } 403 } 404 else { 405 parseFromText( 406 fread(m_fileName) 407 ); 408 } 409 } 410 411 /** 412 * Closes the inifile and writes its text if any changes has occured. 413 */ 414 void close() { 415 static if (sync) { 416 synchronized { 417 if (!m_changed) 418 return; 419 420 fwrite(m_fileName, parseToText()); 421 foreach (section; m_sections.values) 422 section.close(); 423 m_sections = null; 424 } 425 } 426 else { 427 if (!m_changed) 428 return; 429 430 fwrite(m_fileName, parseToText()); 431 foreach (section; m_sections.values) 432 section.close(); 433 m_sections = null; 434 } 435 } 436 437 /** 438 * Checks whether the inifile has a specific section. 439 * Params: 440 * section = The section to check for existence. 441 * Returns: True if the section exists, false otherwise. 442 */ 443 bool hasSection(string section) { 444 static if (sync) { 445 synchronized { 446 return m_sections.get(section, null) !is null; 447 } 448 } 449 else { 450 return m_sections.get(section, null) !is null; 451 } 452 } 453 454 /** 455 * Gets a specific section of the inifile. 456 * Params: 457 * section = The section to get. 458 * Returns: The section. 459 */ 460 auto getSection(string section) { 461 static if (sync) { 462 synchronized { 463 if(!hasSection(section)) 464 throw new IniException(format("%s is not an existing section.", section)); 465 return m_sections[section]; 466 } 467 } 468 else { 469 if(!hasSection(section)) 470 throw new IniException(format("%s is not an existing section.", section)); 471 return m_sections[section]; 472 } 473 } 474 475 /** 476 * Adds a new section to the inifile. 477 * Params: 478 * section = The section to add. 479 */ 480 void addSection(string section) { 481 static if (sync) { 482 synchronized { 483 m_sections[section] = new IniSection!(sync)(section, this); 484 } 485 } 486 else { 487 m_sections[section] = new IniSection!(sync)(section, this); 488 } 489 } 490 491 /** 492 * Reads an entry from the inifile. 493 * Params: 494 * section = The section to read from. 495 * key = The key of the entry to read. 496 * Returns: The value read. 497 */ 498 auto read(T)(string section, string key) { 499 static if (sync) { 500 synchronized { 501 if(!hasSection(section)) 502 throw new IniException(format("%s is not an existing section.", section)); 503 return m_sections[section].read!T(key); 504 } 505 } 506 else { 507 if(!hasSection(section)) 508 throw new IniException(format("%s is not an existing section.", section)); 509 return m_sections[section].read!T(key); 510 } 511 } 512 513 /** 514 * Writes an entry to the inifile. 515 * Params: 516 * section = The section to write the entry to. 517 * key = The key of the entry. 518 * value = The value of the entry. 519 */ 520 void write(T)(string section, string key, T value) { 521 static if (sync) { 522 synchronized { 523 if(!hasSection(section)) 524 throw new IniException(format("%s is not an existing section.", section)); 525 m_sections[section].write!T(key, value); 526 } 527 } 528 else { 529 if(!hasSection(section)) 530 throw new IniException(format("%s is not an existing section.", section)); 531 m_sections[section].write!T(key, value); 532 } 533 } 534 535 /** 536 * Checks whether the inifile has a specific key. 537 * Params: 538 * section = The section to check within. 539 * key = The key to check for existence. 540 * Returns: True if the key exists, falses otherwise. 541 */ 542 bool hasKey(string section, string key) { 543 static if (sync) { 544 synchronized { 545 if(!hasSection(section)) 546 throw new IniException(format("%s is not an existing section.", section)); 547 return m_sections[section].hasKey(key); 548 } 549 } 550 else { 551 if(!hasSection(section)) 552 throw new IniException(format("%s is not an existing section.", section)); 553 return m_sections[section].hasKey(key); 554 } 555 } 556 557 @property { 558 /** 559 * Gets the filename of the inifile. 560 */ 561 string fileName() { 562 static if (sync) { 563 synchronized { 564 return m_fileName; 565 } 566 } 567 else { 568 return m_fileName; 569 } 570 } 571 572 /** 573 * Gets all the section names. 574 */ 575 string[] sectionNames() { 576 static if (sync) { 577 synchronized { 578 return m_sections.keys; 579 } 580 } 581 else { 582 return m_sections.keys; 583 } 584 } 585 586 /** 587 * Gets all the sections. 588 */ 589 IniSection!(sync)[] sections() { 590 static if (sync) { 591 synchronized { 592 return m_sections.values; 593 } 594 } 595 else { 596 return m_sections.values; 597 } 598 } 599 } 600 }