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 }