1 /** 2 * Reading ini-like files without usage of $(D inilike.file.IniLikeFile) class. 3 * Authors: 4 * $(LINK2 https://github.com/FreeSlave, Roman Chistokhodov) 5 * Copyright: 6 * Roman Chistokhodov, 2017 7 * License: 8 * $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0). 9 * See_Also: 10 * $(LINK2 http://standards.freedesktop.org/desktop-entry-spec/latest/index.html, Desktop Entry Specification) 11 */ 12 13 module inilike.read; 14 public import inilike.range; 15 public import inilike.exception; 16 import inilike.common; 17 18 /// What to do when encounter some group name in onGroup callback of $(D readIniLike). 19 enum ActionOnGroup { 20 skip, /// Skip this group entries, don't do any processing. 21 proceed, /// Process the grouo entries as usual. 22 stopAfter, /// Stop after processing this group (don't parse next groups) 23 } 24 25 /** 26 * Read ini-like file entries via the set of callbacks. 27 * Params: 28 * reader = $(D IniLikeReader) object as returned by $(D inilike.range.iniLikeReader) or similar function. 29 * onLeadingComment = Delegate to call after leading comment (i.e. the one before any group) is read. The parameter is either comment of empty line. 30 * onGroup = Delegate to call after group header is read. The parameter is group name (without brackets). Must return $(D ActionOnGroup). 31 * onKeyValue = Delegate to call after key-value entry is read and parsed. Parameters are key, value and group name. 32 * onCommentInGroup = Delegate to call after comment or empty line is read inside group section. The parameter is either comment of empty line. 33 * fileName = Optional file name parameter to use in thrown exceptions. 34 * Throws: 35 * $(D inilike.exception.IniLikeReadException) if error occured while parsing. Any exception thrown by callbacks will be transformed to $(D inilike.exception.IniLikeReadException). 36 */ 37 void readIniLike(IniLikeReader)(IniLikeReader reader, scope void delegate(string) onLeadingComment, scope ActionOnGroup delegate(string) onGroup, 38 scope void delegate(string, string, string) onKeyValue, scope void delegate(string, string) onCommentInGroup, string fileName = null 39 ) { 40 size_t lineNumber = 0; 41 42 version(DigitalMars) { 43 static void foo(size_t ) {} 44 } 45 46 try { 47 foreach(line; reader.byLeadingLines) 48 { 49 lineNumber++; 50 if (line.isComment || line.strip.empty) { 51 onLeadingComment(line); 52 } else { 53 throw new IniLikeException("Expected comment or empty line before any group"); 54 } 55 } 56 57 foreach(g; reader.byGroup) 58 { 59 lineNumber++; 60 string groupName = g.groupName; 61 62 version(DigitalMars) { 63 foo(lineNumber); //fix dmd codgen bug with -O 64 } 65 66 auto actionOnGroup = onGroup(groupName); 67 final switch(actionOnGroup) 68 { 69 case ActionOnGroup.stopAfter: 70 case ActionOnGroup.proceed: 71 { 72 foreach(line; g.byEntry) 73 { 74 lineNumber++; 75 76 if (line.isComment || line.strip.empty) { 77 onCommentInGroup(line, groupName); 78 } else { 79 const t = parseKeyValue(line); 80 81 string key = t.key.stripRight; 82 string value = t.value.stripLeft; 83 84 if (key.length == 0 && value.length == 0) { 85 throw new IniLikeException("Expected comment, empty line or key value inside group"); 86 } else { 87 onKeyValue(key, value, groupName); 88 } 89 } 90 } 91 if (actionOnGroup == ActionOnGroup.stopAfter) { 92 return; 93 } 94 } 95 break; 96 case ActionOnGroup.skip: 97 { 98 foreach(line; g.byEntry) {} 99 } 100 break; 101 } 102 } 103 } 104 catch(IniLikeEntryException e) { 105 throw new IniLikeReadException(e.msg, lineNumber, fileName, e, e.file, e.line, e.next); 106 } 107 catch (Exception e) { 108 throw new IniLikeReadException(e.msg, lineNumber, fileName, null, e.file, e.line, e.next); 109 } 110 }