1 module ctini.common;
2 
3 import std.algorithm;
4 import std.array;
5 import std.conv;
6 import std.format;
7 import std.functional;
8 import std.range;
9 import std..string;
10 import std.traits;
11 import std.typetuple;
12 
13 import ctini.inigrammar;
14 
15 /**
16  * Represents a section that can contain settings and other sections
17  */
18 struct Section {
19     string name;
20     string id;
21     Section* parent;
22     Section[] subsections;
23     Setting[] settings;
24 
25     /**
26      * Return a human-readable representation of the section,
27      * that looks similar to the ini file, but with types
28      * and _ instead of . in the section name
29      */
30     public string toString() const{
31         auto sb = appender!string();
32 
33         sb ~= "[%s]\n".format(id);
34         sb ~= settings
35             .map!( s => s.toString() )
36             .join("\n");
37 
38         return sb.data;
39     }
40 }
41 
42 /**
43  * Represents a key-value pair from the ini
44  */
45 struct Setting {
46     string name;
47     string type;
48     string value;
49 
50     public string toString() const {
51         return format("%s %s = %s", type, name, value);
52     }
53 }
54 
55 /**
56  * The CTFE function that actually parses the ini file and returns
57  * a dictionary containing the parsed sections
58  */
59 Section[string] parseSections(string iniText) {
60 
61     auto parsed = IniGrammar.Config(iniText);
62 
63     parsed = IniGrammar.decimateTree(parsed);
64     if(!parsed.successful) {
65         //debug writeln(parsed);
66         assert(false, "Syntax Error");
67     }
68 
69     Section[string] sections;
70 
71     parsed.children
72         .map!( (pt) =>
73                 Section(
74                     pt.children[0].matches[$-1],
75                     pt.children[0].getSectionId(),
76                     findParent(sections, pt.children[0].matches),
77                     [],
78                     getSettings(pt)
79                 )
80              )
81         .apply!( (Section sec) {
82                 auto placeholder = sec.id in sections;
83                 if(placeholder !is null) {
84                     //The only field we need to copy is subsections,
85                     //because the name and id are identical by definition,
86                     //placeholders have no fields,
87                     //and the placeholder's parent should have been looked up normally
88                     sec.subsections = placeholder.subsections;
89                 }
90                 sections[sec.id] = sec;
91                 if(sec.parent) {
92                     sec.parent.subsections ~= sec;
93             }
94         });
95 
96     return sections;
97 }
98 
99 private:
100 
101 /**
102   * Returns the section's parent section, creating it if necessary,
103   * based on the 'lineage', that is, the fully qualified name of the original section.
104   *
105   * e.g., given ["A","B","C"], findParent will return a pointer to the Section called A.B
106   *
107   * If no such section exists, it is created.
108   * If lineage.length == 1 (there is no parent section), null is returned
109   */
110 Section* findParent(Section[string] sections, string[] lineage)
111 in {
112     assert( lineage.length >= 1 );
113 } body {
114 
115     if(lineage.length == 1) {
116         return null;
117     }
118 
119     Section* parent;
120 
121     string parName = lineage[0..$-1].join("_").to!string();
122     parent = parName in sections;
123 
124     //No existing parent, we'll have to make one
125     if(parent is null) {
126         auto newParent = Section(
127                 lineage[$-2],
128                 parName,
129                 findParent(sections, lineage[0..$-1]),
130                 [], //No descendents yet
131                 []  //No settings
132                 );
133         sections[parName] = newParent;
134         parent = parName in sections;
135     }
136 
137     return parent;
138 }
139 
140 /**
141   * Examines a IniGrammar.Section ParseTree, and creates an array of
142   * Settings based off the child nodes.
143   */
144 Setting[] getSettings(PT)(PT pt)
145 in {
146     assert( pt.name == "IniGrammar.Section", "Expected Section parsetree, got %s".format(pt.name) );
147 } body {
148     return pt.children
149         .filter!( t => t.name == "IniGrammar.Setting" )
150         .map!( t => 
151                 Setting(
152                     t.matches[0],
153                     t.children[1].getSettingType,
154                     t.children[1].matches[0]
155                     ))
156         .array();
157 }
158 
159 /**
160   * Returns the internal section id of a given IniGrammar.Section ParseTree
161   */
162 string getSectionId(PT)(PT pt) {
163     return pt.matches.join("_").to!string;
164 }
165 
166 /**
167  * Returns the internal section id of the parent section of a given
168  * IniGrammar.Section ParseTree.
169  */
170 string getSectionParentName(PT)(PT pt) {
171     return pt.matches[0..$-1].join("_").to!string;
172 }
173 
174 /**
175  * Returns the D type (as a string) of the setting, given the corresponding
176  * IniGrammar.Setting ParseTree
177  */
178 string getSettingType(PT)(PT pt) {
179     switch(pt.children[0].name) {
180         case "IniGrammar.String":
181             return "string";
182         case "IniGrammar.Integer":
183             return "int";
184         case "IniGrammar.Float":
185             return "float";
186         case "IniGrammar.Bool":
187             return "bool";
188         default:
189             return "auto";
190     }
191 }
192 
193 /**
194  * Eagerly calls the given function for every element of the range
195  */
196 public auto apply(alias fun, R)(R range)
197     if(isInputRange!R)
198 {
199     foreach( e; range ) {
200         unaryFun!fun(e);
201     }
202 }