CLI interface to medialist (fossil mirror)
修訂 | f1ae6e3ca2c4cc608d3e97ae6cb05c16085676e5 (tree) |
---|---|
時間 | 2023-04-06 11:34:50 |
作者 | mio <stigma@disr...> |
Commiter | mio |
Update mlib files for trash 0.3.0
FossilOrigin-Name: f3652b1434c672564cded98949f06eb82af7e67fcf3fc9af9c8db870d3497c69
@@ -13,7 +13,7 @@ license "GPL-3.0" | ||
13 | 13 | |
14 | 14 | # update_deps.sh |
15 | 15 | dependency "mlib" \ |
16 | - path="/home/mio/Projects/mlib" \ | |
16 | + path="./mlib" \ | |
17 | 17 | version="*" |
18 | 18 | |
19 | 19 | sourceFiles "main.d" \ |
@@ -1,775 +0,0 @@ | ||
1 | -/* | |
2 | - * Permission to use, copy, modify, and/or distribute this software for any | |
3 | - * purpose with or without fee is herby granted. | |
4 | - * | |
5 | - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES | |
6 | - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF | |
7 | - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR | |
8 | - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES | |
9 | - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN | |
10 | - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF | |
11 | - * OR IN CONNECTION WITH THE USE OR PEFORMANCE OF THIS SOFTWARE. | |
12 | - */ | |
13 | - | |
14 | - | |
15 | -/** | |
16 | - * An incomplete single-file INI parser for D. | |
17 | - * | |
18 | - * The API should be similar to python's configparse module. Internally it | |
19 | - * uses the standard D associative array. | |
20 | - * | |
21 | - * Example: | |
22 | - * --- | |
23 | - * import configparser; | |
24 | - * | |
25 | - * auto config = new ConfigParser(); | |
26 | - * // no sections initially | |
27 | - * assert(config.sections.length == 0); | |
28 | - * // Section names ("Program Settings") are case-sensitive | |
29 | - * conf.addSection("Storage Paths"); | |
30 | - * // Option names ("CONFIG_PATH") are case-insensitive | |
31 | - * // (internally, they are all converted to lower-case) | |
32 | - * conf.set("Program Settings", "CONFIG_PATH", "/home/user/.local/config"); | |
33 | - * --- | |
34 | - * | |
35 | - * Authors: nemophila | |
36 | - * Date: 2023-03-19 | |
37 | - * Homepage: https://osdn.net/users/nemophila/pf/mlib | |
38 | - * License: 0BSD | |
39 | - * Version: 0.4 | |
40 | - * | |
41 | - * History: | |
42 | - * 0.4 Add .write() | |
43 | - * 0.3 Fix option values not always being treated as lowercase. | |
44 | - * 0.2 Add .getBool() | |
45 | - * 0.1 Initial release | |
46 | - */ | |
47 | -module mlib.configparser; | |
48 | - | |
49 | -private | |
50 | -{ | |
51 | - import std.conv : ConvException; | |
52 | - import std.stdio : File; | |
53 | -} | |
54 | - | |
55 | -public class DuplicateSectionException : Exception | |
56 | -{ | |
57 | - private string m_section; | |
58 | - | |
59 | - this(string section) | |
60 | - { | |
61 | - string msg = "Section " ~ section ~ " already exists."; | |
62 | - m_section = section; | |
63 | - super(msg); | |
64 | - } | |
65 | - | |
66 | - string section() | |
67 | - { | |
68 | - return m_section; | |
69 | - } | |
70 | -} | |
71 | - | |
72 | -/// | |
73 | -/// An exception that is thrown by a strict parser which indicates | |
74 | -/// that an option appears twice within any one section. | |
75 | -/// | |
76 | -public class DuplicateOptionException : Exception | |
77 | -{ | |
78 | - private string m_option; | |
79 | - private string m_section; | |
80 | - | |
81 | - this(string option, string section) | |
82 | - { | |
83 | - string msg = "Option " ~ option ~ " in section " ~ section ~ | |
84 | - " already exists."; | |
85 | - m_option = option; | |
86 | - m_section = section; | |
87 | - super(msg); | |
88 | - } | |
89 | - | |
90 | - string option() | |
91 | - { | |
92 | - return m_option; | |
93 | - } | |
94 | - | |
95 | - string section() | |
96 | - { | |
97 | - return m_section; | |
98 | - } | |
99 | -} | |
100 | - | |
101 | -public class NoSectionException : Exception | |
102 | -{ | |
103 | - private string m_section; | |
104 | - | |
105 | - this(string section) | |
106 | - { | |
107 | - string msg = "Section '" ~ section ~ "' does not exist."; | |
108 | - m_section = section; | |
109 | - super(msg); | |
110 | - } | |
111 | - | |
112 | - string section() | |
113 | - { | |
114 | - return m_section; | |
115 | - } | |
116 | -} | |
117 | - | |
118 | -public class NoOptionException : Exception | |
119 | -{ | |
120 | - private string m_section; | |
121 | - private string m_option; | |
122 | - | |
123 | - this(string section, string option) | |
124 | - { | |
125 | - string msg = "Section '" ~ section ~ "' does not have option '" ~ | |
126 | - option ~ "'."; | |
127 | - m_section = section; | |
128 | - m_option = option; | |
129 | - super(msg); | |
130 | - } | |
131 | - | |
132 | - string section() { return m_section; } | |
133 | - string option() { return m_option; } | |
134 | -} | |
135 | - | |
136 | -/** | |
137 | - * The main configuration parser. | |
138 | - */ | |
139 | -public class ConfigParser | |
140 | -{ | |
141 | - private char[] m_delimiters; | |
142 | - private char[] m_commentPrefixes; | |
143 | - private bool m_strict; | |
144 | - | |
145 | - /** current section for parsing */ | |
146 | - private string m_currentSection; | |
147 | - private string[string][string] m_sections; | |
148 | - | |
149 | - /** | |
150 | - * Creates a new instance of ConfigParser. | |
151 | - */ | |
152 | - this(char[] delimiters = ['=', ':'], | |
153 | - char[] commentPrefixes = ['#', ';'], | |
154 | - bool strict = true) | |
155 | - { | |
156 | - m_delimiters = delimiters; | |
157 | - m_commentPrefixes = commentPrefixes; | |
158 | - m_strict = strict; | |
159 | - } | |
160 | - | |
161 | - /** | |
162 | - * Return an array containing the available sections. | |
163 | - */ | |
164 | - string[] sections() | |
165 | - { | |
166 | - return m_sections.keys(); | |
167 | - } | |
168 | - | |
169 | - /// | |
170 | - unittest | |
171 | - { | |
172 | - auto conf = new ConfigParser(); | |
173 | - | |
174 | - assert(0 == conf.sections().length); | |
175 | - | |
176 | - conf.addSection("Section"); | |
177 | - | |
178 | - assert(1 == conf.sections().length); | |
179 | - } | |
180 | - | |
181 | - /** | |
182 | - * Add a section named `section` to the instance. | |
183 | - * | |
184 | - * Throws: | |
185 | - * - DuplicateSectionError if a section by the given name already | |
186 | - * exists. | |
187 | - */ | |
188 | - void addSection(string section) | |
189 | - { | |
190 | - if (section in m_sections) | |
191 | - throw new DuplicateSectionException(section); | |
192 | - m_sections[section] = null; | |
193 | - } | |
194 | - | |
195 | - /// | |
196 | - unittest | |
197 | - { | |
198 | - import std.exception : assertNotThrown, assertThrown; | |
199 | - | |
200 | - auto conf = new ConfigParser(); | |
201 | - | |
202 | - /* doesn't yet exist */ | |
203 | - assertNotThrown!DuplicateSectionException(conf.addSection("sample")); | |
204 | - /* already exists */ | |
205 | - assertThrown!DuplicateSectionException(conf.addSection("sample")); | |
206 | - } | |
207 | - | |
208 | - /** | |
209 | - * Indicates whether the named `section` is present in the configuration. | |
210 | - * | |
211 | - * Params: | |
212 | - * section = The section to check for in the configuration. | |
213 | - * | |
214 | - * Returns: `true` if the section exists, `false` otherwise. | |
215 | - */ | |
216 | - bool hasSection(string section) | |
217 | - { | |
218 | - auto exists = (section in m_sections); | |
219 | - return (exists !is null); | |
220 | - } | |
221 | - | |
222 | - /// | |
223 | - unittest | |
224 | - { | |
225 | - auto conf = new ConfigParser(); | |
226 | - conf.addSection("nExt"); | |
227 | - assert(true == conf.hasSection("nExt"), "Close the world."); | |
228 | - assert(false == conf.hasSection("world"), "Open the nExt."); | |
229 | - } | |
230 | - | |
231 | - string[] options(string section) | |
232 | - { | |
233 | - if (false == this.hasSection(section)) | |
234 | - throw new NoSectionException(section); | |
235 | - return m_sections[section].keys(); | |
236 | - } | |
237 | - | |
238 | - /// | |
239 | - unittest | |
240 | - { | |
241 | - import std.exception : assertNotThrown, assertThrown; | |
242 | - | |
243 | - auto conf = new ConfigParser(); | |
244 | - | |
245 | - conf.addSection("Settings"); | |
246 | - | |
247 | - assertNotThrown!NoSectionException(conf.options("Settings")); | |
248 | - assertThrown!NoSectionException(conf.options("void")); | |
249 | - | |
250 | - string[] options = conf.options("Settings"); | |
251 | - assert(0 == options.length, "More keys than we need"); | |
252 | - } | |
253 | - | |
254 | - bool hasOption(string section, string option) | |
255 | - { | |
256 | - import std.string : toLower; | |
257 | - | |
258 | - if (false == this.hasSection(section)) | |
259 | - return false; | |
260 | - | |
261 | - scope lowercaseOption = toLower(option); | |
262 | - auto exists = (lowercaseOption in m_sections[section]); | |
263 | - return (exists !is null); | |
264 | - } | |
265 | - /* | |
266 | - string[] read(string[] filenames) | |
267 | - { | |
268 | - return null; | |
269 | - }*/ | |
270 | - | |
271 | - void read(string filename) | |
272 | - { | |
273 | - File file = File(filename, "r"); | |
274 | - scope(exit) { file.close(); } | |
275 | - read(file, false); | |
276 | - } | |
277 | - | |
278 | - /// | |
279 | - unittest | |
280 | - { | |
281 | - import std.file : remove; | |
282 | - import std.stdio : File; | |
283 | - | |
284 | - auto configFile = File("test.conf", "w+"); | |
285 | - configFile.writeln("[Section 1]"); | |
286 | - configFile.writeln("key=value"); | |
287 | - configFile.writeln("\n[Section 2]"); | |
288 | - configFile.writeln("key2 = value"); | |
289 | - configFile.close(); | |
290 | - | |
291 | - auto conf = new ConfigParser(); | |
292 | - conf.read("test.conf"); | |
293 | - | |
294 | - assert(2 == conf.sections.length, "Incorrect Sections length"); | |
295 | - assert(true == conf.hasSection("Section 1"), | |
296 | - "Config file doesn't have Section 1"); | |
297 | - assert(true == conf.hasOption("Section 1", "key"), | |
298 | - "Config file doesn't have 'key' in 'Section 1'"); | |
299 | - | |
300 | - remove("test.conf"); | |
301 | - } | |
302 | - | |
303 | - /** | |
304 | - * Parse a config file. | |
305 | - * | |
306 | - * Params: | |
307 | - * file = Reference to the file from which to read. | |
308 | - * close = Close the file when finished parsing. | |
309 | - */ | |
310 | - void read(ref File file, bool close = true) | |
311 | - { | |
312 | - import std.array : array; | |
313 | - import std.algorithm.searching : canFind; | |
314 | - import std.string : strip; | |
315 | - | |
316 | - scope(exit) { if (close) file.close(); } | |
317 | - | |
318 | - string[] lines = file.byLineCopy.array; | |
319 | - | |
320 | - for (auto i = 0; i < lines.length; i++) { | |
321 | - string line = lines[i].strip(); | |
322 | - | |
323 | - if (line == "") | |
324 | - continue; | |
325 | - | |
326 | - if ('[' == lines[i][0]) { | |
327 | - parseSectionHeader(lines[i]); | |
328 | - } else if (false == canFind(m_commentPrefixes, lines[i][0])) { | |
329 | - parseLine(lines[i]); | |
330 | - } | |
331 | - /* ignore comments */ | |
332 | - } | |
333 | - } | |
334 | - | |
335 | - /*void readString(string str) | |
336 | - { | |
337 | - }*/ | |
338 | - | |
339 | - /** | |
340 | - * Get an `option` value for the named `section`. | |
341 | - * | |
342 | - * Params: | |
343 | - * section = The section to look for the given `option`. | |
344 | - * option = The option to return the value of | |
345 | - * fallback = Fallback value if the `option` is not found. Can be null. | |
346 | - * | |
347 | - * Returns: | |
348 | - * - The value for `option` if it is found. | |
349 | - * - `null` if the `option` is not found and `fallback` is not provided. | |
350 | - * - `fallback` if the `option` is not found and `fallback` is provided. | |
351 | - * | |
352 | - * Throws: | |
353 | - * - NoSectionException if the `section` does not exist and no fallback is provided. | |
354 | - * - NoOptionException if the `option` does not exist and no fallback is provided. | |
355 | - */ | |
356 | - string get(string section, string option) | |
357 | - { | |
358 | - import std.string : toLower; | |
359 | - | |
360 | - scope lowercaseOption = toLower(option); | |
361 | - | |
362 | - if (false == this.hasSection(section)) | |
363 | - throw new NoSectionException(section); | |
364 | - | |
365 | - if (false == this.hasOption(section, lowercaseOption)) | |
366 | - throw new NoOptionException(section, lowercaseOption); | |
367 | - | |
368 | - return m_sections[section][lowercaseOption]; | |
369 | - } | |
370 | - | |
371 | - /// | |
372 | - unittest | |
373 | - { | |
374 | - import std.exception : assertThrown; | |
375 | - | |
376 | - auto conf = new ConfigParser(); | |
377 | - conf.addSection("Section"); | |
378 | - conf.set("Section", "option", "value"); | |
379 | - | |
380 | - assert(conf.get("Section", "option") == "value"); | |
381 | - assertThrown!NoSectionException(conf.get("section", "option")); | |
382 | - assertThrown!NoOptionException(conf.get("Section", "void")); | |
383 | - } | |
384 | - | |
385 | - /// Ditto | |
386 | - string get(string section, string option, string fallback) | |
387 | - { | |
388 | - string res = fallback; | |
389 | - | |
390 | - try { | |
391 | - res = get(section, option); | |
392 | - } catch (NoSectionException e) { | |
393 | - return res; | |
394 | - } catch (NoOptionException e) { | |
395 | - return res; | |
396 | - } | |
397 | - | |
398 | - return res; | |
399 | - } | |
400 | - | |
401 | - /// | |
402 | - unittest | |
403 | - { | |
404 | - import std.exception : assertThrown; | |
405 | - | |
406 | - auto conf = new ConfigParser(); | |
407 | - conf.addSection("Section"); | |
408 | - conf.set("Section", "option", "value"); | |
409 | - | |
410 | - assert("value" == conf.get("Section", "option")); | |
411 | - assert("fallback" == conf.get("section", "option", "fallback")); | |
412 | - assert("fallback" == conf.get("Section", "void", "fallback")); | |
413 | - | |
414 | - /* can use null for fallback */ | |
415 | - assert(null == conf.get("section", "option", null)); | |
416 | - assert(null == conf.get("Section", "void", null)); | |
417 | - } | |
418 | - | |
419 | - /** | |
420 | - * A convenience method which casts the value of `option` in `section` | |
421 | - * to an integer. | |
422 | - * | |
423 | - * Params: | |
424 | - * section = The section to look for the given `option`. | |
425 | - * option = The option to return the value for. | |
426 | - * fallback = The fallback value to use if `option` isn't found. | |
427 | - * | |
428 | - * Returns: | |
429 | - * | |
430 | - * | |
431 | - * Throws: | |
432 | - * - NoSectionFoundException if `section` doesn't exist. | |
433 | - * - NoOptionFoundException if the `section` doesn't contain `option`. | |
434 | - * - ConvException if it failed to parse the value to an int. | |
435 | - * - ConvOverflowException if the value would overflow an int. | |
436 | - * | |
437 | - * See_Also: get() | |
438 | - */ | |
439 | - int getInt(string section, string option) | |
440 | - { | |
441 | - import std.conv : parse; | |
442 | - | |
443 | - string res; | |
444 | - | |
445 | - res = get(section, option); | |
446 | - | |
447 | - return parse!int(res); | |
448 | - } | |
449 | - | |
450 | - /// Ditto | |
451 | - int getInt(string section, string option, int fallback) | |
452 | - { | |
453 | - int res = fallback; | |
454 | - | |
455 | - try { | |
456 | - res = getInt(section, option); | |
457 | - } catch (Exception e) { | |
458 | - return res; | |
459 | - } | |
460 | - | |
461 | - return res; | |
462 | - } | |
463 | - | |
464 | - /* | |
465 | - double getDouble(string section, string option) | |
466 | - { | |
467 | - } | |
468 | - | |
469 | - double getDouble(string section, string option, double fallback) | |
470 | - { | |
471 | - } | |
472 | - | |
473 | - float getFloat(string section, string option) | |
474 | - { | |
475 | - } | |
476 | - | |
477 | - float getFloat(string section, string option, float fallback) | |
478 | - { | |
479 | - }*/ | |
480 | - | |
481 | - /** | |
482 | - * A convenience method which coerces the $(I option) in the | |
483 | - * specified $(I section) to a boolean value. | |
484 | - * | |
485 | - * Note that the accepted values for the option are "1", "yes", | |
486 | - * "true", and "on", which cause this method to return `true`, and | |
487 | - * "0", "no", "false", and "off", which cause it to return `false`. | |
488 | - * | |
489 | - * These string values are checked in a case-insensitive manner. | |
490 | - * | |
491 | - * Params: | |
492 | - * section = The section to look for the given option. | |
493 | - * option = The option to return the value for. | |
494 | - * fallback = The fallback value to use if the option was not found. | |
495 | - * | |
496 | - * Throws: | |
497 | - * - NoSectionFoundException if `section` doesn't exist. | |
498 | - * - NoOptionFoundException if the `section` doesn't contain `option`. | |
499 | - * - ConvException if any other value was found. | |
500 | - */ | |
501 | - bool getBool(string section, string option) | |
502 | - { | |
503 | - import std.string : toLower; | |
504 | - | |
505 | - string value = get(section, option); | |
506 | - | |
507 | - switch (value.toLower) | |
508 | - { | |
509 | - case "1": | |
510 | - case "yes": | |
511 | - case "true": | |
512 | - case "on": | |
513 | - return true; | |
514 | - case "0": | |
515 | - case "no": | |
516 | - case "false": | |
517 | - case "off": | |
518 | - return false; | |
519 | - default: | |
520 | - throw new ConvException("No valid boolean value found"); | |
521 | - } | |
522 | - } | |
523 | - | |
524 | - /// Ditto | |
525 | - bool getBool(string section, string option, bool fallback) | |
526 | - { | |
527 | - try { | |
528 | - return getBool(section, option); | |
529 | - } catch (Exception e) { | |
530 | - return fallback; | |
531 | - } | |
532 | - } | |
533 | - | |
534 | - /* | |
535 | - string[string] items(string section) | |
536 | - { | |
537 | - }*/ | |
538 | - | |
539 | - /** | |
540 | - * Remove the specified `option` from the specified `section`. | |
541 | - * | |
542 | - * Params: | |
543 | - * section = The section to remove from. | |
544 | - * option = The option to remove from section. | |
545 | - * | |
546 | - * Retruns: | |
547 | - * `true` if option existed, false otherwise. | |
548 | - * | |
549 | - * Throws: | |
550 | - * - NoSectionException if the specified section doesn't exist. | |
551 | - */ | |
552 | - bool removeOption(string section, string option) | |
553 | - { | |
554 | - if ((section in m_sections) is null) { | |
555 | - throw new NoSectionException(section); | |
556 | - } | |
557 | - | |
558 | - if (option in m_sections[section]) { | |
559 | - m_sections[section].remove(option); | |
560 | - return true; | |
561 | - } | |
562 | - | |
563 | - return false; | |
564 | - } | |
565 | - | |
566 | - /// | |
567 | - unittest | |
568 | - { | |
569 | - import std.exception : assertThrown; | |
570 | - | |
571 | - auto conf = new ConfigParser(); | |
572 | - conf.addSection("Default"); | |
573 | - conf.set("Default", "exists", "true"); | |
574 | - | |
575 | - assertThrown!NoSectionException(conf.removeOption("void", "false")); | |
576 | - assert(false == conf.removeOption("Default", "void")); | |
577 | - assert(true == conf.removeOption("Default", "exists")); | |
578 | - } | |
579 | - | |
580 | - /** | |
581 | - * Remove the specified `section` from the config. | |
582 | - * | |
583 | - * Params: | |
584 | - * section = The section to remove. | |
585 | - * | |
586 | - * Returns: | |
587 | - * `true` if the section existed, `false` otherwise. | |
588 | - */ | |
589 | - bool removeSection(string section) | |
590 | - { | |
591 | - if (section in m_sections) { | |
592 | - m_sections.remove(section); | |
593 | - return true; | |
594 | - } | |
595 | - return false; | |
596 | - } | |
597 | - | |
598 | - /// | |
599 | - unittest | |
600 | - { | |
601 | - auto conf = new ConfigParser(); | |
602 | - conf.addSection("Exists"); | |
603 | - assert(false == conf.removeSection("DoesNotExist")); | |
604 | - assert(true == conf.removeSection("Exists")); | |
605 | - } | |
606 | - | |
607 | - void set(string section, string option, string value) | |
608 | - { | |
609 | - import std.string : toLower; | |
610 | - | |
611 | - if (false == this.hasSection(section)) | |
612 | - throw new NoSectionException(section); | |
613 | - | |
614 | - scope lowercaseOption = toLower(option); | |
615 | - m_sections[section][lowercaseOption] = value; | |
616 | - } | |
617 | - | |
618 | - /// | |
619 | - unittest | |
620 | - { | |
621 | - import std.exception : assertThrown; | |
622 | - | |
623 | - auto conf = new ConfigParser(); | |
624 | - | |
625 | - assertThrown!NoSectionException(conf.set("Section", "option", | |
626 | - "value")); | |
627 | - | |
628 | - conf.addSection("Section"); | |
629 | - conf.set("Section", "option", "value"); | |
630 | - assert(conf.get("Section", "option") == "value"); | |
631 | - } | |
632 | - | |
633 | - /// | |
634 | - /// Write a representation of the configuration to the | |
635 | - /// provided *file*. | |
636 | - /// | |
637 | - /// This representation can be parsed by future calls to | |
638 | - /// `read`. This does **not** close the file after writing. | |
639 | - /// | |
640 | - /// Params: | |
641 | - /// file = An open file which was opened in text mode. | |
642 | - /// spaceAroundDelimiters = The delimiters between keys and | |
643 | - /// values are surrounded by spaces. | |
644 | - /// | |
645 | - /// Note: Comments from the original file are not preserved when | |
646 | - /// writing the configuration back. | |
647 | - /// | |
648 | - void write(ref File file, bool spaceAroundDelimiters = true) | |
649 | - { | |
650 | - string del = spaceAroundDelimiters ? " = " : "="; | |
651 | - | |
652 | - foreach(string section, string[string] options; m_sections) { | |
653 | - file.writefln("[%s]", section); | |
654 | - | |
655 | - foreach(string option, string value; options) { | |
656 | - file.writefln("%s%s%s", option, del, value); | |
657 | - } | |
658 | - } | |
659 | - } | |
660 | - | |
661 | - /// | |
662 | - unittest | |
663 | - { | |
664 | - import std.file : remove; | |
665 | - import std.stdio : File; | |
666 | - | |
667 | - auto writer = new ConfigParser(); | |
668 | - writer.addSection("general"); | |
669 | - | |
670 | - writer.addSection("GUI"); | |
671 | - writer.set("GUI", "WINDOW_WIDTH", "848"); | |
672 | - writer.set("GUI", "WINDOW_HEIGHT", "480"); | |
673 | - | |
674 | - auto file = File("test.ini", "w+"); | |
675 | - scope(exit) remove(file.name); | |
676 | - writer.write(file); | |
677 | - | |
678 | - file.rewind(); | |
679 | - | |
680 | - auto reader = new ConfigParser(); | |
681 | - reader.read(file); | |
682 | - | |
683 | - assert(reader.hasSection("general"), "reader does not contain general section"); | |
684 | - | |
685 | - assert(reader.hasSection("GUI"), "reader does not contain GUI section"); | |
686 | - assert(reader.get("GUI", "WINDOW_WIDTH") == "848", "reader GUI.WINDOW_WIDTH is not 848"); | |
687 | - assert(reader.getInt("GUI", "WINDOW_WIDTH") == 848, "reader GUI.WINDOW_WIDTH is not 848 (int)"); | |
688 | - | |
689 | - assert(reader.get("GUI", "WINDOW_HEIGHT") == "480", "reader GUI.WINDOW_HEIGHT is not 480"); | |
690 | - assert(reader.getInt("GUI", "WINDOW_HEIGHT") == 480, "reader GUI.WINDOW_HEIGHT is not 480 (int)"); | |
691 | - } | |
692 | - | |
693 | - private: | |
694 | - | |
695 | - void parseSectionHeader(ref string line) | |
696 | - { | |
697 | - import std.array : appender, assocArray; | |
698 | - | |
699 | - auto sectionHeader = appender!string; | |
700 | - /* presume that the last character is ] */ | |
701 | - sectionHeader.reserve(line.length - 1); | |
702 | - string popped = line[1 .. $]; | |
703 | - | |
704 | - foreach(c; popped) { | |
705 | - if (c != ']') | |
706 | - sectionHeader.put(c); | |
707 | - else | |
708 | - break; | |
709 | - } | |
710 | - | |
711 | - m_currentSection = sectionHeader.data(); | |
712 | - | |
713 | - if (m_currentSection in m_sections && m_strict) | |
714 | - throw new DuplicateSectionException(m_currentSection); | |
715 | - | |
716 | - try { | |
717 | - this.addSection(m_currentSection); | |
718 | - } catch (DuplicateSectionException) { | |
719 | - } | |
720 | - } | |
721 | - | |
722 | - void parseLine(ref string line) | |
723 | - { | |
724 | - import std.string : indexOfAny, toLower, strip; | |
725 | - | |
726 | - ptrdiff_t idx = line.indexOfAny(m_delimiters); | |
727 | - if (-1 == idx) return; | |
728 | - string option = line[0 .. idx].dup.strip.toLower; | |
729 | - string value = line[idx + 1 .. $].dup.strip; | |
730 | - | |
731 | - if (option in m_sections[m_currentSection] && m_strict) | |
732 | - throw new DuplicateOptionException(option, m_currentSection); | |
733 | - | |
734 | - m_sections[m_currentSection][option] = value; | |
735 | - } | |
736 | - | |
737 | - unittest | |
738 | - { | |
739 | - import std.exception : assertThrown, assertNotThrown; | |
740 | - import std.file : remove; | |
741 | - | |
742 | - auto f = File("config.cfg", "w+"); | |
743 | - f.writeln("[section]"); | |
744 | - f.writeln("option = value"); | |
745 | - f.writeln("Option = value"); | |
746 | - f.close(); | |
747 | - scope(exit) remove("config.cfg"); | |
748 | - | |
749 | - // Duplicate option | |
750 | - scope parser = new ConfigParser(); | |
751 | - assertThrown!DuplicateOptionException(parser.read("config.cfg")); | |
752 | - | |
753 | - // Duplicate section | |
754 | - f = File("config.cfg", "w+"); | |
755 | - f.writeln("[section]"); | |
756 | - f.writeln("option = value"); | |
757 | - f.writeln("[section]"); | |
758 | - f.close(); | |
759 | - | |
760 | - assertThrown!DuplicateSectionException(parser.read("config.cfg")); | |
761 | - | |
762 | - // not strict | |
763 | - scope relaxedParser = new ConfigParser(['='], [], false); | |
764 | - | |
765 | - assertNotThrown!DuplicateSectionException(relaxedParser.read("config.cfg")); | |
766 | - assert(relaxedParser.hasSection("section")); | |
767 | - | |
768 | - f = File("config.cfg", "a+"); | |
769 | - f.writeln("option = newValue"); | |
770 | - f.close(); | |
771 | - | |
772 | - assertNotThrown!DuplicateOptionException(relaxedParser.read("config.cfg")); | |
773 | - assert(relaxedParser.get("section", "option") == "newValue"); | |
774 | - } | |
775 | -} |
@@ -1,7 +0,0 @@ | ||
1 | -/// | |
2 | -/// A collection of public domain modules for the | |
3 | -/// $(LINK2 https://dlang.org, D Programming Language). | |
4 | -/// | |
5 | -/// All modules are compatible with D versions 2.076.0 and newer. | |
6 | -/// | |
7 | -module mlib; |
@@ -18,13 +18,14 @@ | ||
18 | 18 | * macOS will be implemented in a future version. |
19 | 19 | * |
20 | 20 | * Authors: nemophila |
21 | - * Date: January 29, 2023 | |
21 | + * Date: April 05, 2023 | |
22 | 22 | * Homepage: https://osdn.net/users/nemophila/pf/mlib |
23 | 23 | * License: 0BSD |
24 | 24 | * Standards: The FreeDesktop.org Trash Specification 1.0 |
25 | - * Version: 0.2.0 | |
25 | + * Version: 0.3.0 | |
26 | 26 | * |
27 | 27 | * History: |
28 | + * 0.3.0 fix XDG naming convention bug | |
28 | 29 | * 0.2.0 added support for Windows |
29 | 30 | * 0.1.0 is the initial version |
30 | 31 | * |
@@ -291,12 +292,12 @@ version(Posix) { | ||
291 | 292 | * directory. Even if a file with the same name and location gets trashed many times, |
292 | 293 | * each subsequent trashing must not overwrite a previous copy." */ |
293 | 294 | size_t counter = 0; |
294 | - string destname = basename; | |
295 | - string infoFilename = destname.setExtension(".trashinfo"); | |
296 | - while (exists(buildPath(filesDir, destname)) || exists(buildPath(infoDir, infoFilename))) { | |
295 | + string filesFilename = basename; | |
296 | + string infoFilename = filesFilename ~ ".trashinfo"; | |
297 | + while (exists(buildPath(filesDir, filesFilename)) || exists(buildPath(infoDir, infoFilename))) { | |
297 | 298 | counter += 1; |
298 | - destname = filename ~ "_" ~ to!string(counter) ~ ext; | |
299 | - infoFilename = destname.setExtension(".trashinfo"); | |
299 | + filesFilename = basename ~ "_" ~ to!string(counter) ~ ext; | |
300 | + infoFilename = filesFilename ~ ".trashinfo"; | |
300 | 301 | } |
301 | 302 | |
302 | 303 | { |
@@ -306,7 +307,7 @@ version(Posix) { | ||
306 | 307 | infoFile.write(getInfo(path, topdir)); |
307 | 308 | } |
308 | 309 | { |
309 | - string filesPath = buildPath(filesDir, destname); | |
310 | + string filesPath = buildPath(filesDir, filesFilename); | |
310 | 311 | rename(path, filesPath); |
311 | 312 | pathInTrash = filesPath; |
312 | 313 | } |
@@ -20,16 +20,26 @@ _DIR_COMMIT="8f1f7e3c05abc2734020727892988051ce25f29e" | ||
20 | 20 | _TSH_COMMIT="1bfc9295af9ec716bf5b26c70f50b0187d7148bf" |
21 | 21 | |
22 | 22 | _DUB_RAW_URL="https://osdn.net/users/nemophila/pf/mlib/scm/blobs/$_DUB_COMMIT/dub.sdl?export=raw" |
23 | +_RDM_RAW_URL="https://osdn.net/users/nemophila/pf/mlib/scm/blobs/$_DUB_COMMIT/README?export=raw" | |
24 | +_LIC_RAW_URL="https://osdn.net/users/nemophila/pf/mlib/scm/blobs/$_DUB_COMMIT/LICENSE?export=raw" | |
25 | + | |
23 | 26 | _CNI_RAW_URL="https://osdn.net/users/nemophila/pf/mlib/scm/blobs/$_CNI_COMMIT/source/mlib/cni.d?export=raw" |
24 | 27 | _DIR_RAW_URL="https://osdn.net/users/nemophila/pf/mlib/scm/blobs/$_DIR_COMMIT/source/mlib/directories.d?export=raw" |
25 | 28 | _TSH_RAW_URL="https://osdn.net/users/nemophila/pf/mlib/scm/blobs/$_TSH_COMMIT/source/mlib/trash.d?export=raw" |
26 | 29 | |
30 | +if [ -d mlib ] | |
31 | +then | |
32 | + rm -rf mlib | |
33 | +fi | |
34 | + | |
27 | 35 | if [ $TRUE -eq $_HAVE_WGET ] |
28 | 36 | then |
29 | 37 | # Update by fetching archive with wget |
30 | - [ ! -d mlib/source/mlib ] && mkdir -p mlib/source/mlib | |
38 | + mkdir -p mlib/source/mlib | |
31 | 39 | cd mlib |
32 | 40 | wget -O "dub.sdl" "$_DUB_RAW_URL" && sleep 2 |
41 | + wget -O "README" "$_RDM_RAW_URL" && sleep 2 | |
42 | + wget -O "LICENSE" "$_LIC_RAW_URL" && sleep 2 | |
33 | 43 | cd source/mlib |
34 | 44 | wget -O "cni.d" "$_CNI_RAW_URL" && sleep 2 |
35 | 45 | wget -O "directories.d" "$_DIR_RAW_URL" && sleep 2 |
@@ -37,9 +47,11 @@ then | ||
37 | 47 | elif [ $TRUE -eq $_HAVE_CURL ] |
38 | 48 | then |
39 | 49 | # Update by fetching archive with curl |
40 | - [ ! -d mlib/source/mlib ] && mkdir -p mlib/source/mlib | |
50 | + mkdir -p mlib/source/mlib | |
41 | 51 | cd mlib |
42 | 52 | curl -o "dub.sdl" "$_DUB_RAW_URL" && sleep 2 |
53 | + curl -o "README" "$_RDM_RAW_URL" && sleep 2 | |
54 | + curl -o "LICENSE" "$_LIC_RAW_URL" && sleep 2 | |
43 | 55 | cd source/mlib |
44 | 56 | curl -o "cni.d" "$_CNI_RAW_URL" && sleep 2 |
45 | 57 | curl -o "directories.d" "$_DIR_RAW_URL" && sleep 2 |