1 module ctini.rtini; 2 3 import std.array; 4 import std.algorithm; 5 import std.conv; 6 import std.exception; 7 import std.file; 8 import std.traits; 9 import std.typetuple; 10 11 import ctini.common; 12 13 14 IniSection iniConfig(string iniFile) { 15 return iniConfigFromString(readText(iniFile)); 16 } 17 18 IniSection iniConfigFromString(string iniText) { 19 return parseSections(iniText).makeVariants(); 20 } 21 22 struct IniSection { 23 IniSetting[string] children; 24 25 public template opDispatch(string name) { 26 27 @property 28 public IniSection opDispatch() 29 { 30 auto subsec = name in children; 31 enforce( subsec !is null, "This section has no subsection called "~name); 32 enforce( subsec.currentType == IniSetting.SettingType.section, name~" is not a subsection"); 33 34 return subsec.get!IniSection(); 35 } 36 37 @property 38 public T opDispatch(T)() { 39 auto subsec = name in children; 40 enforce( subsec !is null, "This section has no property called "~name); 41 //enforce( subsec.currentType == IniSetting.SettingType.section, name~" is not a subsection"); 42 43 return subsec.get!T(); 44 } 45 } 46 47 public T get(T)(string name) 48 if( IniSetting.allowed!T ) 49 { 50 auto set = name in children; 51 enforce( set !is null, "This section has no setting called "~name); 52 53 return set.get!T(); 54 } 55 } 56 57 /** 58 * Quick and dirty limited tagged union, because std.variant can't do recursive definitions 59 * 60 * Retrieving the correct type is enforced by in contracts, so AssertErrors are thrown 61 * if the wrong type is attempted, and who knows what you'll get if you compile in release mode. 62 */ 63 struct IniSetting { 64 65 enum SettingType { string_, int_, float_, bool_, section, void_ }; 66 67 union SettingValue { 68 string string_; 69 int int_; 70 float float_; 71 bool bool_; 72 IniSection section; 73 }; 74 75 SettingType currentType = SettingType.void_; 76 SettingValue values; 77 78 static template allowed(T) { 79 enum bool allowed = isOneOf!(T, string, int, float, bool, IniSection); 80 } 81 82 this(string type, string value) { 83 84 switch(type) { 85 case "string": 86 values.string_ = value[1..$-1]; //Strip off the quote marks 87 currentType = SettingType.string_; 88 break; 89 90 case "int": 91 values.int_ = value.to!int; 92 currentType = SettingType.int_; 93 break; 94 95 case "float": 96 values.float_ = value.to!float; 97 currentType = SettingType.float_; 98 break; 99 100 case "bool": 101 values.bool_ = value.to!bool; 102 currentType = SettingType.bool_; 103 break; 104 105 default: 106 assert(0, "Cannot store type "~type); 107 } 108 109 } 110 111 this(IniSection value) 112 { 113 values.section = value; 114 currentType = SettingType.section; 115 } 116 117 T get(T : string)() 118 in { 119 assert(currentType == SettingType.string_, 120 "Attempted to access a string, when really there was a "~currentType.to!string); 121 } body { 122 return values.string_; 123 } 124 125 T get(T : int)() 126 in { 127 assert(currentType == SettingType.int_, 128 "Attempted to access an int, when really there was a "~currentType.to!string); 129 } body { 130 return values.int_; 131 } 132 133 T get(T : float)() 134 in { 135 assert(currentType == SettingType.float_, 136 "Attempted to access a float, when really there was a "~currentType.to!string); 137 } body { 138 return values.float_; 139 } 140 141 T get(T : bool)() 142 in { 143 assert(currentType == SettingType.bool_, 144 "Attempted to access a bool, when really there was a "~currentType.to!string); 145 } body { 146 return values.bool_; 147 } 148 149 T get(T : IniSection)() 150 in { 151 assert(currentType == SettingType.section, 152 "Attempted to access an IniSection, when really there was a "~currentType.to!string); 153 } body { 154 return values.section; 155 } 156 157 } 158 159 private: 160 161 IniSection makeVariants(Section[string] sections) { 162 163 IniSection root; 164 165 //Convert and add the top level sections 166 sections.values 167 .filter!( sec => sec.parent is null ) 168 .apply!( (sec) => ( 169 root.children[sec.name] = IniSetting(makeIniSection(sec)) 170 )); 171 172 return root; 173 } 174 175 IniSection makeIniSection(Section section) { 176 IniSection isec; 177 178 //Convert and add all the settings into the hashmap 179 foreach( set; section.settings ) { 180 181 isec.children[set.name] = IniSetting(set.type, set.value); 182 183 } 184 185 //Now, recursively add all the subsections 186 foreach( subsec; section.subsections ) { 187 188 IniSection isubsec = makeIniSection(subsec); 189 190 isec.children[subsec.name] = IniSetting(isubsec); 191 192 } 193 194 return isec; 195 } 196 197 template isOneOf(T, U...) { 198 enum bool isOneOf = staticIndexOf!(T, U) >= 0; 199 }