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 }