A D package containing my single-file modules
修訂 | a74177c5e751f0c002664d261acf4574ed72bc36 (tree) |
---|---|
時間 | 2023-11-02 10:08:25 |
作者 | nemophila <stigma+osdn@disr...> |
Commiter | nemophila |
configparser: reformat and add attributes
@@ -0,0 +1,6 @@ | ||
1 | +[*.d] | |
2 | +indent_style = space | |
3 | +indent_size = 3 | |
4 | +end_of_line = lf | |
5 | +trim_trailing_whitespace = true | |
6 | +insert_final_newline = true |
@@ -52,19 +52,19 @@ import std.range.primitives : isOutputRange; | ||
52 | 52 | |
53 | 53 | public class DuplicateSectionException : Exception |
54 | 54 | { |
55 | - private string m_section; | |
55 | + private string m_section; | |
56 | 56 | |
57 | - this(string section) @safe pure | |
58 | - { | |
59 | - string msg = "Section " ~ section ~ " already exists."; | |
60 | - m_section = section; | |
61 | - super(msg); | |
62 | - } | |
57 | + this(string section) @safe pure | |
58 | + { | |
59 | + const msg = "Section " ~ section ~ " already exists."; | |
60 | + m_section = section; | |
61 | + super(msg); | |
62 | + } | |
63 | 63 | |
64 | - string section() | |
65 | - { | |
66 | - return m_section; | |
67 | - } | |
64 | + string section() const @safe pure | |
65 | + { | |
66 | + return m_section; | |
67 | + } | |
68 | 68 | } |
69 | 69 | |
70 | 70 | /// |
@@ -73,62 +73,67 @@ public class DuplicateSectionException : Exception | ||
73 | 73 | /// |
74 | 74 | public class DuplicateOptionException : Exception |
75 | 75 | { |
76 | - private string m_option; | |
77 | - private string m_section; | |
76 | + private string m_option; | |
77 | + private string m_section; | |
78 | 78 | |
79 | - this(string option, string section) @safe pure | |
80 | - { | |
81 | - string msg = "Option " ~ option ~ " in section " ~ section ~ | |
82 | - " already exists."; | |
83 | - m_option = option; | |
84 | - m_section = section; | |
85 | - super(msg); | |
86 | - } | |
79 | + this(string option, string section) @safe pure | |
80 | + { | |
81 | + const msg = "Option " ~ option ~ " in section " ~ section ~ " already exists."; | |
82 | + m_option = option; | |
83 | + m_section = section; | |
84 | + super(msg); | |
85 | + } | |
87 | 86 | |
88 | - string option() | |
89 | - { | |
90 | - return m_option; | |
91 | - } | |
87 | + string option() const @safe pure | |
88 | + { | |
89 | + return m_option; | |
90 | + } | |
92 | 91 | |
93 | - string section() | |
94 | - { | |
95 | - return m_section; | |
96 | - } | |
92 | + string section() const @safe pure | |
93 | + { | |
94 | + return m_section; | |
95 | + } | |
97 | 96 | } |
98 | 97 | |
99 | 98 | public class NoSectionException : Exception |
100 | 99 | { |
101 | - private string m_section; | |
100 | + private string m_section; | |
102 | 101 | |
103 | - this(string section) @safe pure | |
104 | - { | |
105 | - string msg = "Section '" ~ section ~ "' does not exist."; | |
106 | - m_section = section; | |
107 | - super(msg); | |
108 | - } | |
102 | + this(string section) @safe pure | |
103 | + { | |
104 | + const msg = "Section '" ~ section ~ "' does not exist."; | |
105 | + m_section = section; | |
106 | + super(msg); | |
107 | + } | |
109 | 108 | |
110 | - string section() | |
111 | - { | |
112 | - return m_section; | |
113 | - } | |
109 | + string section() const @safe pure | |
110 | + { | |
111 | + return m_section; | |
112 | + } | |
114 | 113 | } |
115 | 114 | |
116 | 115 | public class NoOptionException : Exception |
117 | 116 | { |
118 | - private string m_section; | |
119 | - private string m_option; | |
117 | + private string m_section; | |
118 | + private string m_option; | |
120 | 119 | |
121 | - this(string section, string option) @safe pure | |
122 | - { | |
123 | - string msg = "Section '" ~ section ~ "' does not have option '" ~ | |
124 | - option ~ "'."; | |
125 | - m_section = section; | |
126 | - m_option = option; | |
127 | - super(msg); | |
128 | - } | |
120 | + this(string section, string option) @safe pure | |
121 | + { | |
122 | + const msg = "Section '" ~ section ~ "' does not have option '" ~ option ~ "'."; | |
123 | + m_section = section; | |
124 | + m_option = option; | |
125 | + super(msg); | |
126 | + } | |
127 | + | |
128 | + string section() const @safe pure | |
129 | + { | |
130 | + return m_section; | |
131 | + } | |
129 | 132 | |
130 | - string section() { return m_section; } | |
131 | - string option() { return m_option; } | |
133 | + string option() const @safe pure | |
134 | + { | |
135 | + return m_option; | |
136 | + } | |
132 | 137 | } |
133 | 138 | |
134 | 139 | /** |
@@ -155,308 +160,336 @@ public class ConfigParser | ||
155 | 160 | m_strict = strict; |
156 | 161 | } |
157 | 162 | |
158 | - /** | |
159 | - * Return an array containing the available sections. | |
160 | - */ | |
161 | - string[] sections() @safe pure | |
162 | - { | |
163 | - return m_sections.keys(); | |
164 | - } | |
163 | + /** | |
164 | + * Return an array containing the available sections. | |
165 | + */ | |
166 | + string[] sections() const @safe pure | |
167 | + { | |
168 | + return m_sections.keys(); | |
169 | + } | |
165 | 170 | |
166 | - /// | |
167 | - @safe pure unittest | |
168 | - { | |
169 | - auto conf = new ConfigParser(); | |
171 | + /// | |
172 | + @safe pure unittest | |
173 | + { | |
174 | + auto conf = new ConfigParser(); | |
170 | 175 | |
171 | - assert(0 == conf.sections().length); | |
176 | + assert(0 == conf.sections().length); | |
172 | 177 | |
173 | - conf.addSection("Section"); | |
178 | + conf.addSection("Section"); | |
174 | 179 | |
175 | - assert(1 == conf.sections().length); | |
176 | - } | |
180 | + assert(1 == conf.sections().length); | |
181 | + } | |
177 | 182 | |
178 | - /** | |
179 | - * Add a section named `section` to the instance. | |
180 | - * | |
181 | - * Throws: | |
182 | - * - DuplicateSectionError if a section by the given name already | |
183 | - * exists. | |
184 | - */ | |
185 | - void addSection(string section) @safe pure | |
186 | - { | |
187 | - if (section in m_sections) | |
188 | - throw new DuplicateSectionException(section); | |
189 | - m_sections[section] = null; | |
190 | - } | |
183 | + /** | |
184 | + * Add a section named `section` to the instance. | |
185 | + * | |
186 | + * Throws: | |
187 | + * - DuplicateSectionError if a section by the given name already | |
188 | + * exists. | |
189 | + */ | |
190 | + void addSection(string section) @safe pure | |
191 | + { | |
192 | + if (section in m_sections) | |
193 | + { | |
194 | + throw new DuplicateSectionException(section); | |
195 | + } | |
196 | + m_sections[section] = null; | |
197 | + } | |
191 | 198 | |
192 | - /// | |
193 | - unittest | |
194 | - { | |
195 | - import std.exception : assertNotThrown, assertThrown; | |
199 | + /// | |
200 | + unittest | |
201 | + { | |
202 | + import std.exception : assertNotThrown, assertThrown; | |
196 | 203 | |
197 | - auto conf = new ConfigParser(); | |
204 | + auto conf = new ConfigParser(); | |
198 | 205 | |
199 | - /* doesn't yet exist */ | |
200 | - assertNotThrown!DuplicateSectionException(conf.addSection("sample")); | |
201 | - /* already exists */ | |
202 | - assertThrown!DuplicateSectionException(conf.addSection("sample")); | |
203 | - } | |
206 | + /* doesn't yet exist */ | |
207 | + assertNotThrown!DuplicateSectionException(conf.addSection("sample")); | |
208 | + /* already exists */ | |
209 | + assertThrown!DuplicateSectionException(conf.addSection("sample")); | |
210 | + } | |
204 | 211 | |
205 | - /** | |
206 | - * Indicates whether the named `section` is present in the configuration. | |
207 | - * | |
208 | - * Params: | |
209 | - * section = The section to check for in the configuration. | |
210 | - * | |
211 | - * Returns: `true` if the section exists, `false` otherwise. | |
212 | - */ | |
213 | - bool hasSection(string section) @safe pure | |
214 | - { | |
215 | - auto exists = (section in m_sections); | |
216 | - return (exists !is null); | |
217 | - } | |
212 | + /** | |
213 | + * Indicates whether the named `section` is present in the configuration. | |
214 | + * | |
215 | + * Params: | |
216 | + * section = The section to check for in the configuration. | |
217 | + * | |
218 | + * Returns: `true` if the section exists, `false` otherwise. | |
219 | + */ | |
220 | + bool hasSection(string section) @safe pure const | |
221 | + { | |
222 | + return (section in m_sections) !is null; | |
223 | + } | |
218 | 224 | |
219 | - /// | |
220 | - unittest | |
221 | - { | |
222 | - auto conf = new ConfigParser(); | |
223 | - conf.addSection("nExt"); | |
224 | - assert(true == conf.hasSection("nExt"), "Close the world."); | |
225 | - assert(false == conf.hasSection("world"), "Open the nExt."); | |
226 | - } | |
225 | + /// | |
226 | + unittest | |
227 | + { | |
228 | + auto conf = new ConfigParser(); | |
229 | + conf.addSection("nExt"); | |
230 | + assert(true == conf.hasSection("nExt"), "Close the world."); | |
231 | + assert(false == conf.hasSection("world"), "Open the nExt."); | |
232 | + } | |
227 | 233 | |
228 | - string[] options(string section) @safe pure | |
229 | - { | |
230 | - if (false == this.hasSection(section)) | |
231 | - throw new NoSectionException(section); | |
232 | - return m_sections[section].keys(); | |
233 | - } | |
234 | + string[] options(string section) @safe pure const | |
235 | + { | |
236 | + if (false == this.hasSection(section)) | |
237 | + { | |
238 | + throw new NoSectionException(section); | |
239 | + } | |
240 | + return m_sections[section].keys(); | |
241 | + } | |
234 | 242 | |
235 | - /// | |
236 | - unittest | |
237 | - { | |
238 | - import std.exception : assertNotThrown, assertThrown; | |
243 | + /// | |
244 | + unittest | |
245 | + { | |
246 | + import std.exception : assertNotThrown, assertThrown; | |
239 | 247 | |
240 | - auto conf = new ConfigParser(); | |
248 | + auto conf = new ConfigParser(); | |
241 | 249 | |
242 | - conf.addSection("Settings"); | |
250 | + conf.addSection("Settings"); | |
243 | 251 | |
244 | - assertNotThrown!NoSectionException(conf.options("Settings")); | |
245 | - assertThrown!NoSectionException(conf.options("void")); | |
252 | + assertNotThrown!NoSectionException(conf.options("Settings")); | |
253 | + assertThrown!NoSectionException(conf.options("void")); | |
246 | 254 | |
247 | - string[] options = conf.options("Settings"); | |
248 | - assert(0 == options.length, "More keys than we need"); | |
249 | - } | |
255 | + string[] options = conf.options("Settings"); | |
256 | + assert(0 == options.length, "More keys than we need"); | |
257 | + } | |
250 | 258 | |
251 | - bool hasOption(string section, string option) @safe pure | |
252 | - { | |
253 | - import std.string : toLower; | |
259 | + bool hasOption(string section, string option) @safe pure const | |
260 | + { | |
261 | + import std.string : toLower; | |
254 | 262 | |
255 | - if (false == this.hasSection(section)) | |
256 | - return false; | |
263 | + if (false == this.hasSection(section)) | |
264 | + { | |
265 | + return false; | |
266 | + } | |
257 | 267 | |
258 | - scope lowercaseOption = toLower(option); | |
259 | - auto exists = (lowercaseOption in m_sections[section]); | |
260 | - return (exists !is null); | |
261 | - } | |
262 | - /* | |
263 | - string[] read(string[] filenames) | |
264 | - { | |
265 | - return null; | |
266 | - }*/ | |
268 | + const lowercaseOption = toLower(option); | |
269 | + return (lowercaseOption in m_sections[section]) !is null; | |
270 | + } | |
267 | 271 | |
268 | - void read(string filename) | |
269 | - { | |
270 | - File file = File(filename, "r"); | |
271 | - scope(exit) { file.close(); } | |
272 | - read(file, false); | |
273 | - } | |
272 | + /* | |
273 | + string[] read(string[] filenames) | |
274 | + { | |
275 | + return null; | |
276 | + }*/ | |
274 | 277 | |
275 | - /// | |
276 | - unittest | |
277 | - { | |
278 | - import std.file : remove; | |
279 | - import std.stdio : File; | |
280 | - | |
281 | - auto configFile = File("test.conf", "w+"); | |
282 | - configFile.writeln("[Section 1]"); | |
283 | - configFile.writeln("key=value"); | |
284 | - configFile.writeln("\n[Section 2]"); | |
285 | - configFile.writeln("key2 = value"); | |
286 | - configFile.close(); | |
287 | - | |
288 | - auto conf = new ConfigParser(); | |
289 | - conf.read("test.conf"); | |
290 | - | |
291 | - assert(2 == conf.sections.length, "Incorrect Sections length"); | |
292 | - assert(true == conf.hasSection("Section 1"), | |
293 | - "Config file doesn't have Section 1"); | |
294 | - assert(true == conf.hasOption("Section 1", "key"), | |
295 | - "Config file doesn't have 'key' in 'Section 1'"); | |
296 | - | |
297 | - remove("test.conf"); | |
298 | - } | |
278 | + void read(string filename) @safe | |
279 | + { | |
280 | + File file = File(filename, "r"); | |
281 | + scope(exit) { file.close(); } | |
282 | + read(file, false); | |
283 | + } | |
299 | 284 | |
300 | - /** | |
301 | - * Parse a config file. | |
302 | - * | |
303 | - * Params: | |
304 | - * file = Reference to the file from which to read. | |
305 | - * close = Close the file when finished parsing. | |
306 | - */ | |
307 | - void read(ref File file, bool close = true) | |
308 | - { | |
309 | - import std.array : array; | |
310 | - import std.algorithm.searching : canFind; | |
311 | - import std.string : strip; | |
285 | + /// | |
286 | + unittest | |
287 | + { | |
288 | + import std.file : remove; | |
289 | + import std.stdio : File; | |
312 | 290 | |
313 | - scope(exit) { if (close) file.close(); } | |
291 | + auto configFile = File("test.conf", "w+"); | |
292 | + configFile.writeln("[Section 1]"); | |
293 | + configFile.writeln("key=value"); | |
294 | + configFile.writeln("\n[Section 2]"); | |
295 | + configFile.writeln("key2 = value"); | |
296 | + configFile.close(); | |
314 | 297 | |
315 | - string[] lines = file.byLineCopy.array; | |
298 | + auto conf = new ConfigParser(); | |
299 | + conf.read("test.conf"); | |
316 | 300 | |
317 | - for (auto i = 0; i < lines.length; i++) { | |
318 | - string line = lines[i].strip(); | |
301 | + assert(2 == conf.sections.length, "Incorrect Sections length"); | |
302 | + assert(true == conf.hasSection("Section 1"), | |
303 | + "Config file doesn't have Section 1"); | |
304 | + assert(true == conf.hasOption("Section 1", "key"), | |
305 | + "Config file doesn't have 'key' in 'Section 1'"); | |
319 | 306 | |
320 | - if (line == "") | |
321 | - continue; | |
307 | + remove("test.conf"); | |
308 | + } | |
322 | 309 | |
323 | - if ('[' == lines[i][0]) { | |
324 | - parseSectionHeader(lines[i]); | |
325 | - } else if (false == canFind(m_commentPrefixes, lines[i][0])) { | |
326 | - parseLine(lines[i]); | |
327 | - } | |
328 | - /* ignore comments */ | |
329 | - } | |
330 | - } | |
310 | + /** | |
311 | + * Parse a config file. | |
312 | + * | |
313 | + * Params: | |
314 | + * file = Reference to the file from which to read. | |
315 | + * close = Close the file when finished parsing. | |
316 | + */ | |
317 | + void read(ref File file, bool close = true) @trusted | |
318 | + { | |
319 | + import std.array : array; | |
320 | + import std.algorithm.searching : canFind; | |
321 | + import std.string : strip; | |
331 | 322 | |
332 | - /*void readString(string str) | |
333 | - { | |
334 | - }*/ | |
323 | + scope(exit) | |
324 | + { | |
325 | + if (close) | |
326 | + { | |
327 | + file.close(); | |
328 | + } | |
329 | + } | |
335 | 330 | |
336 | - /** | |
337 | - * Get an `option` value for the named `section`. | |
338 | - * | |
339 | - * Params: | |
340 | - * section = The section to look for the given `option`. | |
341 | - * option = The option to return the value of | |
342 | - * fallback = Fallback value if the `option` is not found. Can be null. | |
343 | - * | |
344 | - * Returns: | |
345 | - * - The value for `option` if it is found. | |
346 | - * - `null` if the `option` is not found and `fallback` is not provided. | |
347 | - * - `fallback` if the `option` is not found and `fallback` is provided. | |
348 | - * | |
349 | - * Throws: | |
350 | - * - NoSectionException if the `section` does not exist and no fallback is provided. | |
351 | - * - NoOptionException if the `option` does not exist and no fallback is provided. | |
352 | - */ | |
353 | - string get(string section, string option) | |
354 | - { | |
355 | - import std.string : toLower; | |
331 | + /// XXX: Can this be change to foreach(lin; file.byLine) ? | |
332 | + string[] lines = file.byLineCopy.array; | |
356 | 333 | |
357 | - scope lowercaseOption = toLower(option); | |
334 | + for (auto i = 0; i < lines.length; i++) | |
335 | + { | |
336 | + string line = lines[i].strip(); | |
358 | 337 | |
359 | - if (false == this.hasSection(section)) | |
360 | - throw new NoSectionException(section); | |
338 | + if (line == "") | |
339 | + { | |
340 | + continue; | |
341 | + } | |
361 | 342 | |
362 | - if (false == this.hasOption(section, lowercaseOption)) | |
363 | - throw new NoOptionException(section, lowercaseOption); | |
343 | + if ('[' == lines[i][0]) | |
344 | + { | |
345 | + parseSectionHeader(lines[i]); | |
346 | + } | |
347 | + else if (false == canFind(m_commentPrefixes, lines[i][0])) | |
348 | + { | |
349 | + parseLine(lines[i]); | |
350 | + } | |
351 | + /* ignore comments */ | |
352 | + } | |
353 | + } | |
364 | 354 | |
365 | - return m_sections[section][lowercaseOption]; | |
366 | - } | |
355 | + /*void readString(string str) | |
356 | + { | |
357 | + }*/ | |
367 | 358 | |
368 | - /// | |
369 | - unittest | |
370 | - { | |
371 | - import std.exception : assertThrown; | |
359 | + /** | |
360 | + * Get an `option` value for the named `section`. | |
361 | + * | |
362 | + * Params: | |
363 | + * section = The section to look for the given `option`. | |
364 | + * option = The option to return the value of | |
365 | + * fallback = Fallback value if the `option` is not found. Can be null. | |
366 | + * | |
367 | + * Returns: | |
368 | + * - The value for `option` if it is found. | |
369 | + * - `null` if the `option` is not found and `fallback` is not provided. | |
370 | + * - `fallback` if the `option` is not found and `fallback` is provided. | |
371 | + * | |
372 | + * Throws: | |
373 | + * - NoSectionException if the `section` does not exist and no fallback is provided. | |
374 | + * - NoOptionException if the `option` does not exist and no fallback is provided. | |
375 | + */ | |
376 | + string get(string section, string option) @safe pure const | |
377 | + { | |
378 | + import std.string : toLower; | |
372 | 379 | |
373 | - auto conf = new ConfigParser(); | |
374 | - conf.addSection("Section"); | |
375 | - conf.set("Section", "option", "value"); | |
380 | + const lowercaseOption = toLower(option); | |
376 | 381 | |
377 | - assert(conf.get("Section", "option") == "value"); | |
378 | - assertThrown!NoSectionException(conf.get("section", "option")); | |
379 | - assertThrown!NoOptionException(conf.get("Section", "void")); | |
380 | - } | |
382 | + if (false == this.hasSection(section)) | |
383 | + { | |
384 | + throw new NoSectionException(section); | |
385 | + } | |
381 | 386 | |
382 | - /// Ditto | |
383 | - string get(string section, string option, string fallback) | |
384 | - { | |
385 | - string res = fallback; | |
387 | + if (false == this.hasOption(section, lowercaseOption)) | |
388 | + { | |
389 | + throw new NoOptionException(section, lowercaseOption); | |
390 | + } | |
386 | 391 | |
387 | - try { | |
388 | - res = get(section, option); | |
389 | - } catch (NoSectionException e) { | |
390 | - return res; | |
391 | - } catch (NoOptionException e) { | |
392 | - return res; | |
393 | - } | |
392 | + return m_sections[section][lowercaseOption]; | |
393 | + } | |
394 | 394 | |
395 | - return res; | |
396 | - } | |
395 | + /// | |
396 | + @safe pure unittest | |
397 | + { | |
398 | + import std.exception : assertThrown; | |
397 | 399 | |
398 | - /// | |
399 | - unittest | |
400 | - { | |
401 | - import std.exception : assertThrown; | |
400 | + auto conf = new ConfigParser(); | |
401 | + conf.addSection("Section"); | |
402 | + conf.set("Section", "option", "value"); | |
402 | 403 | |
403 | - auto conf = new ConfigParser(); | |
404 | - conf.addSection("Section"); | |
405 | - conf.set("Section", "option", "value"); | |
404 | + assert(conf.get("Section", "option") == "value"); | |
405 | + assertThrown!NoSectionException(conf.get("section", "option")); | |
406 | + assertThrown!NoOptionException(conf.get("Section", "void")); | |
407 | + } | |
406 | 408 | |
407 | - assert("value" == conf.get("Section", "option")); | |
408 | - assert("fallback" == conf.get("section", "option", "fallback")); | |
409 | - assert("fallback" == conf.get("Section", "void", "fallback")); | |
409 | + /// Ditto | |
410 | + string get(string section, string option, string fallback) @safe pure const | |
411 | + { | |
412 | + try | |
413 | + { | |
414 | + return get(section, option); | |
415 | + } | |
416 | + catch (NoSectionException e) | |
417 | + { | |
418 | + return fallback; | |
419 | + } | |
420 | + catch (NoOptionException e) | |
421 | + { | |
422 | + return fallback; | |
423 | + } | |
424 | + } | |
410 | 425 | |
411 | - /* can use null for fallback */ | |
412 | - assert(null == conf.get("section", "option", null)); | |
413 | - assert(null == conf.get("Section", "void", null)); | |
414 | - } | |
426 | + /// | |
427 | + @safe pure unittest | |
428 | + { | |
429 | + import std.exception : assertThrown; | |
415 | 430 | |
416 | - /** | |
417 | - * A convenience method which casts the value of `option` in `section` | |
418 | - * to an integer. | |
419 | - * | |
420 | - * Params: | |
421 | - * section = The section to look for the given `option`. | |
422 | - * option = The option to return the value for. | |
423 | - * fallback = The fallback value to use if `option` isn't found. | |
424 | - * | |
425 | - * Returns: | |
426 | - * | |
427 | - * | |
428 | - * Throws: | |
429 | - * - NoSectionFoundException if `section` doesn't exist. | |
430 | - * - NoOptionFoundException if the `section` doesn't contain `option`. | |
431 | - * - ConvException if it failed to parse the value to an int. | |
432 | - * - ConvOverflowException if the value would overflow an int. | |
433 | - * | |
434 | - * See_Also: get() | |
435 | - */ | |
436 | - int getInt(string section, string option) | |
437 | - { | |
438 | - import std.conv : parse; | |
431 | + auto conf = new ConfigParser(); | |
432 | + conf.addSection("Section"); | |
433 | + conf.set("Section", "option", "value"); | |
439 | 434 | |
440 | - string res; | |
435 | + assert("value" == conf.get("Section", "option")); | |
436 | + assert("fallback" == conf.get("section", "option", "fallback")); | |
437 | + assert("fallback" == conf.get("Section", "void", "fallback")); | |
441 | 438 | |
442 | - res = get(section, option); | |
439 | + /* can use null for fallback */ | |
440 | + assert(null is conf.get("section", "option", null)); | |
441 | + assert(null is conf.get("Section", "void", null)); | |
442 | + } | |
443 | 443 | |
444 | - return parse!int(res); | |
445 | - } | |
444 | + /** | |
445 | + * A convenience method which casts the value of `option` in `section` | |
446 | + * to an integer. | |
447 | + * | |
448 | + * Params: | |
449 | + * section = The section to look for the given `option`. | |
450 | + * option = The option to return the value for. | |
451 | + * fallback = The fallback value to use if `option` isn't found. | |
452 | + * | |
453 | + * Returns: | |
454 | + * | |
455 | + * | |
456 | + * Throws: | |
457 | + * - NoSectionFoundException if `section` doesn't exist. | |
458 | + * - NoOptionFoundException if the `section` doesn't contain `option`. | |
459 | + * - ConvException if it failed to parse the value to an int. | |
460 | + * - ConvOverflowException if the value would overflow an int. | |
461 | + * | |
462 | + * See_Also: get() | |
463 | + */ | |
464 | + int getInt(string section, string option) @safe pure const | |
465 | + { | |
466 | + import std.conv : parse; | |
446 | 467 | |
447 | - /// Ditto | |
448 | - int getInt(string section, string option, int fallback) | |
449 | - { | |
450 | - int res = fallback; | |
468 | + auto res = get(section, option); | |
451 | 469 | |
452 | - try { | |
453 | - res = getInt(section, option); | |
454 | - } catch (Exception e) { | |
455 | - return res; | |
456 | - } | |
470 | + return parse!int(res); | |
471 | + } | |
457 | 472 | |
458 | - return res; | |
459 | - } | |
473 | + /// Ditto | |
474 | + int getInt(string section, string option, int fallback) @safe pure const | |
475 | + { | |
476 | + try | |
477 | + { | |
478 | + return getInt(section, option); | |
479 | + } | |
480 | + catch (NoSectionException nse) | |
481 | + { | |
482 | + return fallback; | |
483 | + } | |
484 | + catch (NoOptionException noe) | |
485 | + { | |
486 | + return fallback; | |
487 | + } | |
488 | + catch (ConvException ce) | |
489 | + { | |
490 | + return fallback; | |
491 | + } | |
492 | + } | |
460 | 493 | |
461 | 494 | /* |
462 | 495 | double getDouble(string section, string option) |
@@ -475,217 +508,234 @@ public class ConfigParser | ||
475 | 508 | { |
476 | 509 | }*/ |
477 | 510 | |
478 | - /** | |
479 | - * A convenience method which coerces the $(I option) in the | |
480 | - * specified $(I section) to a boolean value. | |
481 | - * | |
482 | - * Note that the accepted values for the option are "1", "yes", | |
483 | - * "true", and "on", which cause this method to return `true`, and | |
484 | - * "0", "no", "false", and "off", which cause it to return `false`. | |
485 | - * | |
486 | - * These string values are checked in a case-insensitive manner. | |
487 | - * | |
488 | - * Params: | |
489 | - * section = The section to look for the given option. | |
490 | - * option = The option to return the value for. | |
491 | - * fallback = The fallback value to use if the option was not found. | |
492 | - * | |
493 | - * Throws: | |
494 | - * - NoSectionFoundException if `section` doesn't exist. | |
495 | - * - NoOptionFoundException if the `section` doesn't contain `option`. | |
496 | - * - ConvException if any other value was found. | |
497 | - */ | |
498 | - bool getBool(string section, string option) | |
499 | - { | |
500 | - import std.string : toLower; | |
501 | - | |
502 | - string value = get(section, option); | |
503 | - | |
504 | - switch (value.toLower) | |
505 | - { | |
506 | - case "1": | |
507 | - case "yes": | |
508 | - case "true": | |
509 | - case "on": | |
510 | - return true; | |
511 | - case "0": | |
512 | - case "no": | |
513 | - case "false": | |
514 | - case "off": | |
515 | - return false; | |
516 | - default: | |
517 | - throw new ConvException("No valid boolean value found"); | |
518 | - } | |
519 | - } | |
511 | + /** | |
512 | + * A convenience method which coerces the $(I option) in the | |
513 | + * specified $(I section) to a boolean value. | |
514 | + * | |
515 | + * Note that the accepted values for the option are "1", "yes", | |
516 | + * "true", and "on", which cause this method to return `true`, and | |
517 | + * "0", "no", "false", and "off", which cause it to return `false`. | |
518 | + * | |
519 | + * These string values are checked in a case-insensitive manner. | |
520 | + * | |
521 | + * Params: | |
522 | + * section = The section to look for the given option. | |
523 | + * option = The option to return the value for. | |
524 | + * fallback = The fallback value to use if the option was not found. | |
525 | + * | |
526 | + * Throws: | |
527 | + * - NoSectionFoundException if `section` doesn't exist. | |
528 | + * - NoOptionFoundException if the `section` doesn't contain `option`. | |
529 | + * - ConvException if any other value was found. | |
530 | + */ | |
531 | + bool getBool(string section, string option) @safe pure const | |
532 | + { | |
533 | + import std.string : toLower; | |
520 | 534 | |
521 | - /// Ditto | |
522 | - bool getBool(string section, string option, bool fallback) | |
523 | - { | |
524 | - try { | |
525 | - return getBool(section, option); | |
526 | - } catch (Exception e) { | |
527 | - return fallback; | |
528 | - } | |
529 | - } | |
535 | + const value = get(section, option).toLower; | |
536 | + | |
537 | + switch (value) | |
538 | + { | |
539 | + case "1": | |
540 | + case "yes": | |
541 | + case "true": | |
542 | + case "on": | |
543 | + return true; | |
544 | + case "0": | |
545 | + case "no": | |
546 | + case "false": | |
547 | + case "off": | |
548 | + return false; | |
549 | + default: | |
550 | + throw new ConvException("No valid boolean value found"); | |
551 | + } | |
552 | + } | |
553 | + | |
554 | + /// Ditto | |
555 | + bool getBool(string section, string option, bool fallback) @safe pure const | |
556 | + { | |
557 | + try | |
558 | + { | |
559 | + return getBool(section, option); | |
560 | + } | |
561 | + catch (NoSectionException e) | |
562 | + { | |
563 | + return fallback; | |
564 | + } | |
565 | + catch (NoOptionException e) | |
566 | + { | |
567 | + return fallback; | |
568 | + } | |
569 | + catch (ConvException e) | |
570 | + { | |
571 | + return fallback; | |
572 | + } | |
573 | + } | |
530 | 574 | |
531 | 575 | /* |
532 | 576 | string[string] items(string section) |
533 | 577 | { |
534 | 578 | }*/ |
535 | 579 | |
536 | - /** | |
537 | - * Remove the specified `option` from the specified `section`. | |
538 | - * | |
539 | - * Params: | |
540 | - * section = The section to remove from. | |
541 | - * option = The option to remove from section. | |
542 | - * | |
543 | - * Retruns: | |
544 | - * `true` if option existed, false otherwise. | |
545 | - * | |
546 | - * Throws: | |
547 | - * - NoSectionException if the specified section doesn't exist. | |
548 | - */ | |
549 | - bool removeOption(string section, string option) | |
550 | - { | |
551 | - if ((section in m_sections) is null) { | |
552 | - throw new NoSectionException(section); | |
553 | - } | |
580 | + /** | |
581 | + * Remove the specified `option` from the specified `section`. | |
582 | + * | |
583 | + * Params: | |
584 | + * section = The section to remove from. | |
585 | + * option = The option to remove from section. | |
586 | + * | |
587 | + * Retruns: | |
588 | + * `true` if option existed, false otherwise. | |
589 | + * | |
590 | + * Throws: | |
591 | + * - NoSectionException if the specified section doesn't exist. | |
592 | + */ | |
593 | + bool removeOption(string section, string option) @safe pure | |
594 | + { | |
595 | + if ((section in m_sections) is null) | |
596 | + { | |
597 | + throw new NoSectionException(section); | |
598 | + } | |
554 | 599 | |
555 | - if (option in m_sections[section]) { | |
556 | - m_sections[section].remove(option); | |
557 | - return true; | |
558 | - } | |
600 | + if (option in m_sections[section]) | |
601 | + { | |
602 | + m_sections[section].remove(option); | |
603 | + return true; | |
604 | + } | |
559 | 605 | |
560 | - return false; | |
561 | - } | |
606 | + return false; | |
607 | + } | |
562 | 608 | |
563 | - /// | |
564 | - unittest | |
565 | - { | |
566 | - import std.exception : assertThrown; | |
609 | + /// | |
610 | + @safe pure unittest | |
611 | + { | |
612 | + import std.exception : assertThrown; | |
567 | 613 | |
568 | - auto conf = new ConfigParser(); | |
569 | - conf.addSection("Default"); | |
570 | - conf.set("Default", "exists", "true"); | |
614 | + auto conf = new ConfigParser(); | |
615 | + conf.addSection("Default"); | |
616 | + conf.set("Default", "exists", "true"); | |
571 | 617 | |
572 | - assertThrown!NoSectionException(conf.removeOption("void", "false")); | |
573 | - assert(false == conf.removeOption("Default", "void")); | |
574 | - assert(true == conf.removeOption("Default", "exists")); | |
575 | - } | |
618 | + assertThrown!NoSectionException(conf.removeOption("void", "false")); | |
619 | + assert(false == conf.removeOption("Default", "void")); | |
620 | + assert(true == conf.removeOption("Default", "exists")); | |
621 | + } | |
576 | 622 | |
577 | - /** | |
578 | - * Remove the specified `section` from the config. | |
579 | - * | |
580 | - * Params: | |
581 | - * section = The section to remove. | |
582 | - * | |
583 | - * Returns: | |
584 | - * `true` if the section existed, `false` otherwise. | |
585 | - */ | |
586 | - bool removeSection(string section) | |
587 | - { | |
588 | - if (section in m_sections) { | |
589 | - m_sections.remove(section); | |
590 | - return true; | |
591 | - } | |
592 | - return false; | |
593 | - } | |
623 | + /** | |
624 | + * Remove the specified `section` from the config. | |
625 | + * | |
626 | + * Params: | |
627 | + * section = The section to remove. | |
628 | + * | |
629 | + * Returns: | |
630 | + * `true` if the section existed, `false` otherwise. | |
631 | + */ | |
632 | + bool removeSection(string section) @safe pure | |
633 | + { | |
634 | + if (section in m_sections) | |
635 | + { | |
636 | + m_sections.remove(section); | |
637 | + return true; | |
638 | + } | |
639 | + return false; | |
640 | + } | |
594 | 641 | |
595 | - /// | |
596 | - unittest | |
597 | - { | |
598 | - auto conf = new ConfigParser(); | |
599 | - conf.addSection("Exists"); | |
600 | - assert(false == conf.removeSection("DoesNotExist")); | |
601 | - assert(true == conf.removeSection("Exists")); | |
602 | - } | |
642 | + /// | |
643 | + @safe pure unittest | |
644 | + { | |
645 | + auto conf = new ConfigParser(); | |
646 | + conf.addSection("Exists"); | |
647 | + assert(false == conf.removeSection("DoesNotExist")); | |
648 | + assert(true == conf.removeSection("Exists")); | |
649 | + } | |
603 | 650 | |
604 | - void set(string section, string option, string value) @safe pure | |
605 | - { | |
606 | - import std.string : toLower; | |
651 | + void set(string section, string option, string value) @safe pure | |
652 | + { | |
653 | + import std.string : toLower; | |
654 | + | |
655 | + if (false == this.hasSection(section)) | |
656 | + { | |
657 | + throw new NoSectionException(section); | |
658 | + } | |
607 | 659 | |
608 | - if (false == this.hasSection(section)) | |
609 | - throw new NoSectionException(section); | |
660 | + const lowercaseOption = toLower(option); | |
661 | + m_sections[section][lowercaseOption] = value; | |
662 | + } | |
610 | 663 | |
611 | - scope lowercaseOption = toLower(option); | |
612 | - m_sections[section][lowercaseOption] = value; | |
613 | - } | |
664 | + /// | |
665 | + @safe pure unittest | |
666 | + { | |
667 | + import std.exception : assertThrown; | |
614 | 668 | |
615 | - /// | |
616 | - unittest | |
617 | - { | |
618 | - import std.exception : assertThrown; | |
669 | + auto conf = new ConfigParser(); | |
619 | 670 | |
620 | - auto conf = new ConfigParser(); | |
671 | + assertThrown!NoSectionException(conf.set("Section", "option", "value")); | |
621 | 672 | |
622 | - assertThrown!NoSectionException(conf.set("Section", "option", | |
623 | - "value")); | |
673 | + conf.addSection("Section"); | |
674 | + conf.set("Section", "option", "value"); | |
675 | + assert(conf.get("Section", "option") == "value"); | |
676 | + } | |
624 | 677 | |
625 | - conf.addSection("Section"); | |
626 | - conf.set("Section", "option", "value"); | |
627 | - assert(conf.get("Section", "option") == "value"); | |
628 | - } | |
678 | + /// | |
679 | + /// Write a representation of the configuration to the | |
680 | + /// provided *file*. | |
681 | + /// | |
682 | + /// This representation can be parsed by future calls to | |
683 | + /// `read`. This does **not** close the file after writing. | |
684 | + /// | |
685 | + /// Params: | |
686 | + /// file = An open file which was opened in text mode. | |
687 | + /// spaceAroundDelimiters = The delimiters between keys and | |
688 | + /// values are surrounded by spaces. | |
689 | + /// | |
690 | + /// Note: Comments from the original file are not preserved when | |
691 | + /// writing the configuration back. | |
692 | + /// | |
693 | + void write(ref File file, bool spaceAroundDelimiters = true) @safe const | |
694 | + { | |
695 | + const del = spaceAroundDelimiters ? " = " : "="; | |
629 | 696 | |
630 | - /// | |
631 | - /// Write a representation of the configuration to the | |
632 | - /// provided *file*. | |
633 | - /// | |
634 | - /// This representation can be parsed by future calls to | |
635 | - /// `read`. This does **not** close the file after writing. | |
636 | - /// | |
637 | - /// Params: | |
638 | - /// file = An open file which was opened in text mode. | |
639 | - /// spaceAroundDelimiters = The delimiters between keys and | |
640 | - /// values are surrounded by spaces. | |
641 | - /// | |
642 | - /// Note: Comments from the original file are not preserved when | |
643 | - /// writing the configuration back. | |
644 | - /// | |
645 | - void write(ref File file, bool spaceAroundDelimiters = true) | |
646 | - { | |
647 | - string del = spaceAroundDelimiters ? " = " : "="; | |
648 | - | |
649 | - foreach(string section, string[string] options; m_sections) { | |
650 | - file.writefln("[%s]", section); | |
651 | - | |
652 | - foreach(string option, string value; options) { | |
653 | - file.writefln("%s%s%s", option, del, value); | |
654 | - } | |
655 | - } | |
656 | - } | |
697 | + foreach(const section, const options; m_sections) | |
698 | + { | |
699 | + file.writefln("[%s]", section); | |
657 | 700 | |
658 | - /// | |
659 | - unittest | |
660 | - { | |
661 | - import std.file : remove; | |
662 | - import std.stdio : File; | |
701 | + foreach(const option, const value; options) | |
702 | + { | |
703 | + file.writefln("%s%s%s", option, del, value); | |
704 | + } | |
705 | + } | |
706 | + } | |
663 | 707 | |
664 | - auto writer = new ConfigParser(); | |
665 | - writer.addSection("general"); | |
708 | + /// | |
709 | + @safe unittest | |
710 | + { | |
711 | + import std.file : remove; | |
712 | + import std.stdio : File; | |
666 | 713 | |
667 | - writer.addSection("GUI"); | |
668 | - writer.set("GUI", "WINDOW_WIDTH", "848"); | |
669 | - writer.set("GUI", "WINDOW_HEIGHT", "480"); | |
714 | + auto writer = new ConfigParser(); | |
715 | + writer.addSection("general"); | |
670 | 716 | |
671 | - auto file = File("test.ini", "w+"); | |
672 | - scope(exit) remove(file.name); | |
673 | - writer.write(file); | |
717 | + writer.addSection("GUI"); | |
718 | + writer.set("GUI", "WINDOW_WIDTH", "848"); | |
719 | + writer.set("GUI", "WINDOW_HEIGHT", "480"); | |
674 | 720 | |
675 | - file.rewind(); | |
721 | + auto file = File("test.ini", "w+"); | |
722 | + scope(exit) remove(file.name); | |
723 | + writer.write(file); | |
676 | 724 | |
677 | - auto reader = new ConfigParser(); | |
678 | - reader.read(file); | |
725 | + file.rewind(); | |
679 | 726 | |
680 | - assert(reader.hasSection("general"), "reader does not contain general section"); | |
727 | + auto reader = new ConfigParser(); | |
728 | + reader.read(file); | |
681 | 729 | |
682 | - assert(reader.hasSection("GUI"), "reader does not contain GUI section"); | |
683 | - assert(reader.get("GUI", "WINDOW_WIDTH") == "848", "reader GUI.WINDOW_WIDTH is not 848"); | |
684 | - assert(reader.getInt("GUI", "WINDOW_WIDTH") == 848, "reader GUI.WINDOW_WIDTH is not 848 (int)"); | |
730 | + assert(reader.hasSection("general"), "reader does not contain general section"); | |
685 | 731 | |
686 | - assert(reader.get("GUI", "WINDOW_HEIGHT") == "480", "reader GUI.WINDOW_HEIGHT is not 480"); | |
687 | - assert(reader.getInt("GUI", "WINDOW_HEIGHT") == 480, "reader GUI.WINDOW_HEIGHT is not 480 (int)"); | |
688 | - } | |
732 | + assert(reader.hasSection("GUI"), "reader does not contain GUI section"); | |
733 | + assert(reader.get("GUI", "WINDOW_WIDTH") == "848", "reader GUI.WINDOW_WIDTH is not 848"); | |
734 | + assert(reader.getInt("GUI", "WINDOW_WIDTH") == 848, "reader GUI.WINDOW_WIDTH is not 848 (int)"); | |
735 | + | |
736 | + assert(reader.get("GUI", "WINDOW_HEIGHT") == "480", "reader GUI.WINDOW_HEIGHT is not 480"); | |
737 | + assert(reader.getInt("GUI", "WINDOW_HEIGHT") == 480, "reader GUI.WINDOW_HEIGHT is not 480 (int)"); | |
738 | + } | |
689 | 739 | |
690 | 740 | /// |
691 | 741 | /// Write a representation of the configuration to the |
@@ -702,14 +752,14 @@ public class ConfigParser | ||
702 | 752 | /// Note: Comments from the original file are not preserved when |
703 | 753 | /// writing the configuration back. |
704 | 754 | /// |
705 | - void write(string filename, bool spaceAroundDelimiters = true) | |
755 | + void write(string filename, bool spaceAroundDelimiters = true) @safe const | |
706 | 756 | { |
707 | 757 | auto file = File(filename, "w+"); |
708 | 758 | write(file, spaceAroundDelimiters); |
709 | 759 | } |
710 | 760 | |
711 | 761 | /// |
712 | - unittest | |
762 | + @safe unittest | |
713 | 763 | { |
714 | 764 | import std.file : remove; |
715 | 765 |
@@ -750,24 +800,25 @@ public class ConfigParser | ||
750 | 800 | /// Note: Comments from the original file are not preserved when |
751 | 801 | /// writing the configuration back. |
752 | 802 | /// |
753 | - void write(T)(T buffer, bool spaceAroundDelimiters = true) @safe pure | |
803 | + void write(T)(T buffer, bool spaceAroundDelimiters = true) @safe pure const | |
754 | 804 | if (isOutputRange!(T, string)) |
755 | 805 | { |
756 | 806 | import std.format : format; |
757 | 807 | |
758 | 808 | const del = spaceAroundDelimiters ? " = " : "="; |
759 | 809 | |
760 | - foreach(string section, string[string] options; m_sections) | |
810 | + foreach(const section, const options; m_sections) | |
761 | 811 | { |
762 | 812 | buffer.put(format!"[%s]\n"(section)); |
763 | 813 | |
764 | - foreach(string option, string value; options) | |
814 | + foreach(const option, const value; options) | |
765 | 815 | { |
766 | 816 | buffer.put(format!"%s%s%s\n"(option, del, value)); |
767 | 817 | } |
768 | 818 | } |
769 | 819 | } |
770 | 820 | |
821 | + /// | |
771 | 822 | @safe pure unittest |
772 | 823 | { |
773 | 824 | import std.outbuffer : OutBuffer; |
@@ -792,86 +843,65 @@ public class ConfigParser | ||
792 | 843 | } |
793 | 844 | } |
794 | 845 | |
795 | - private: | |
796 | - | |
797 | - void parseSectionHeader(ref string line) | |
798 | - { | |
799 | - import std.array : appender, assocArray; | |
800 | - | |
801 | - auto sectionHeader = appender!string; | |
802 | - /* presume that the last character is ] */ | |
803 | - sectionHeader.reserve(line.length - 1); | |
804 | - string popped = line[1 .. $]; | |
805 | - | |
806 | - foreach(c; popped) { | |
807 | - if (c != ']') | |
808 | - sectionHeader.put(c); | |
809 | - else | |
810 | - break; | |
811 | - } | |
812 | - | |
813 | - m_currentSection = sectionHeader.data(); | |
846 | +private: | |
814 | 847 | |
815 | - if (m_currentSection in m_sections && m_strict) | |
816 | - throw new DuplicateSectionException(m_currentSection); | |
817 | - | |
818 | - try { | |
819 | - this.addSection(m_currentSection); | |
820 | - } catch (DuplicateSectionException) { | |
821 | - } | |
822 | - } | |
823 | - | |
824 | - void parseLine(ref string line) | |
825 | - { | |
826 | - import std.string : indexOfAny, toLower, strip; | |
827 | - | |
828 | - ptrdiff_t idx = line.indexOfAny(m_delimiters); | |
829 | - if (-1 == idx) return; | |
830 | - string option = line[0 .. idx].dup.strip.toLower; | |
831 | - string value = line[idx + 1 .. $].dup.strip; | |
848 | + void parseSectionHeader(const ref string line) @safe pure | |
849 | + { | |
850 | + import std.array : appender, assocArray; | |
832 | 851 | |
833 | - if (option in m_sections[m_currentSection] && m_strict) | |
834 | - throw new DuplicateOptionException(option, m_currentSection); | |
852 | + auto sectionHeader = appender!string; | |
853 | + /* presume that the last character is ] */ | |
854 | + sectionHeader.reserve(line.length - 1); | |
855 | + string popped = line[1 .. $]; | |
835 | 856 | |
836 | - m_sections[m_currentSection][option] = value; | |
837 | - } | |
857 | + foreach(const c; popped) | |
858 | + { | |
859 | + if (c != ']') | |
860 | + { | |
861 | + sectionHeader.put(c); | |
862 | + } | |
863 | + else | |
864 | + { | |
865 | + break; | |
866 | + } | |
867 | + } | |
838 | 868 | |
839 | - unittest | |
840 | - { | |
841 | - import std.exception : assertThrown, assertNotThrown; | |
842 | - import std.file : remove; | |
869 | + m_currentSection = sectionHeader.data(); | |
843 | 870 | |
844 | - auto f = File("config.cfg", "w+"); | |
845 | - f.writeln("[section]"); | |
846 | - f.writeln("option = value"); | |
847 | - f.writeln("Option = value"); | |
848 | - f.close(); | |
849 | - scope(exit) remove("config.cfg"); | |
871 | + if (m_currentSection in m_sections && m_strict) | |
872 | + { | |
873 | + throw new DuplicateSectionException(m_currentSection); | |
874 | + } | |
850 | 875 | |
851 | - // Duplicate option | |
852 | - scope parser = new ConfigParser(); | |
853 | - assertThrown!DuplicateOptionException(parser.read("config.cfg")); | |
876 | + try | |
877 | + { | |
878 | + this.addSection(m_currentSection); | |
879 | + } | |
880 | + catch (DuplicateSectionException) | |
881 | + { | |
882 | + /* no-op - just making sure the section exists. */ | |
883 | + } | |
884 | + } | |
854 | 885 | |
855 | - // Duplicate section | |
856 | - f = File("config.cfg", "w+"); | |
857 | - f.writeln("[section]"); | |
858 | - f.writeln("option = value"); | |
859 | - f.writeln("[section]"); | |
860 | - f.close(); | |
886 | + void parseLine(const ref string line) @safe pure | |
887 | + { | |
888 | + import std.string : indexOfAny, toLower, strip; | |
861 | 889 | |
862 | - assertThrown!DuplicateSectionException(parser.read("config.cfg")); | |
890 | + const idx = line.indexOfAny(m_delimiters); | |
863 | 891 | |
864 | - // not strict | |
865 | - scope relaxedParser = new ConfigParser(['='], [], false); | |
892 | + if (-1 == idx) | |
893 | + { | |
894 | + return; | |
895 | + } | |
866 | 896 | |
867 | - assertNotThrown!DuplicateSectionException(relaxedParser.read("config.cfg")); | |
868 | - assert(relaxedParser.hasSection("section")); | |
897 | + const option = line[0 .. idx].dup.strip.toLower; | |
898 | + const string value = line[idx + 1 .. $].dup.strip; | |
869 | 899 | |
870 | - f = File("config.cfg", "a+"); | |
871 | - f.writeln("option = newValue"); | |
872 | - f.close(); | |
900 | + if (option in m_sections[m_currentSection] && m_strict) | |
901 | + { | |
902 | + throw new DuplicateOptionException(option, m_currentSection); | |
903 | + } | |
873 | 904 | |
874 | - assertNotThrown!DuplicateOptionException(relaxedParser.read("config.cfg")); | |
875 | - assert(relaxedParser.get("section", "option") == "newValue"); | |
876 | - } | |
905 | + m_sections[m_currentSection][option] = value; | |
906 | + } | |
877 | 907 | } |