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 }