12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022 |
- ///
- /// Permission to use, copy, modify, and/or distribute this software for any
- /// purpose with or without fee is herby granted.
- ///
- /// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- /// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- /// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- /// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- /// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- /// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- /// OR IN CONNECTION WITH THE USE OR PEFORMANCE OF THIS SOFTWARE.
- ///
- ///
- /// An incomplete single-file INI parser for D.
- ///
- /// The API should be similar to python's configparse module. Internally it
- /// uses the standard D associative array.
- ///
- /// Example:
- /// ---
- /// import configparser;
- ///
- /// auto config = new ConfigParser();
- /// // no sections initially
- /// assert(config.sections.length == 0);
- /// // Section names ("Program Settings") are case-sensitive
- /// conf.addSection("Storage Paths");
- /// // Option names ("CONFIG_PATH") are case-insensitive
- /// // (internally, they are all converted to lower-case)
- /// conf.set("Program Settings", "CONFIG_PATH", "/home/user/.local/config");
- /// ---
- ///
- /// Authors: Mio
- /// Date: 2023-11-11
- /// Homepage: https://codeberg.org/supercell/mlib
- /// License: 0BSD
- /// Version: 0.5.2
- ///
- /// History:
- /// 0.5.2 Make sections() and options() to be @trusted
- /// 0.5.1 Implement ConfigParserException
- /// 0.5 Add .write(string), .write(OutputRange), and .readString(string)
- /// 0.4 Add .write(File)
- /// 0.3 Fix option values not always being treated as lowercase.
- /// 0.2 Add .getBool()
- /// 0.1 Initial release
- ///
- module mlib.configparser;
- import std.conv : ConvException;
- import std.stdio : File;
- import std.range.primitives : isOutputRange;
- public class ConfigParserException : Exception
- {
- this(string msg) @safe pure
- {
- super(msg);
- }
- }
- public class DuplicateSectionException : ConfigParserException
- {
- private string m_section;
- this(string section) @safe pure
- {
- const msg = "Section " ~ section ~ " already exists.";
- m_section = section;
- super(msg);
- }
- ///
- /// The section that caused this exception.
- ///
- string section() const @safe pure
- {
- return m_section;
- }
- }
- ///
- /// An exception that is thrown by a strict parser which indicates
- /// that an option appears twice within any one section.
- ///
- public class DuplicateOptionException : ConfigParserException
- {
- private string m_option;
- private string m_section;
- this(string option, string section) @safe pure
- {
- const msg = "Option " ~ option ~ " in section " ~ section ~ " already exists.";
- m_option = option;
- m_section = section;
- super(msg);
- }
- ///
- /// The option that caused this exception.
- ///
- string option() const @safe pure
- {
- return m_option;
- }
- ///
- /// The section that the responsible option is a part of.
- ///
- string section() const @safe pure
- {
- return m_section;
- }
- }
- ///
- /// An exception that is thrown when a specified section could not be
- /// found.
- ///
- public class NoSectionException : ConfigParserException
- {
- private string m_section;
- this(string section) @safe pure
- {
- const msg = "Section '" ~ section ~ "' does not exist.";
- m_section = section;
- super(msg);
- }
- ///
- /// The section that could not be found.
- ///
- string section() const @safe pure
- {
- return m_section;
- }
- }
- ///
- /// An exception that is thrown when a specified option could not be
- /// found in the specified section.
- ///
- public class NoOptionException : ConfigParserException
- {
- private string m_section;
- private string m_option;
- this(string section, string option) @safe pure
- {
- const msg = "Section '" ~ section ~ "' does not have option '" ~ option ~ "'.";
- m_section = section;
- m_option = option;
- super(msg);
- }
- ///
- /// The section that was specified.
- ///
- string section() const @safe pure
- {
- return m_section;
- }
- ///
- /// The option that could not be found.
- ///
- string option() const @safe pure
- {
- return m_option;
- }
- }
- ///
- /// The main configuration parser.
- ///
- public class ConfigParser
- {
- private char[] m_delimiters;
- private char[] m_commentPrefixes;
- private bool m_strict;
- /** current section for parsing */
- private string m_currentSection;
- private string[string][string] m_sections;
- ///
- /// Creates a new instance of ConfigParser.
- ///
- /// Params:
- /// delimiters = The characters used to divide keys from values.
- /// commentPrefixes = The characters used to prefix comments in
- /// otherwise empty lines.
- /// strict = Should the parser prevent any duplicate sections or
- /// options when reading from a single source.
- ///
- this(char[] delimiters = ['=', ':'], char[] commentPrefixes = ['#', ';'],
- bool strict = true) @safe pure
- {
- m_delimiters = delimiters;
- m_commentPrefixes = commentPrefixes;
- m_strict = strict;
- }
- ///
- /// Return an array containing the available sections.
- ///
- string[] sections() const @trusted pure
- {
- return m_sections.keys();
- }
- ///
- @safe pure unittest
- {
- auto conf = new ConfigParser();
- assert(0 == conf.sections().length);
- conf.addSection("Section");
- assert(1 == conf.sections().length);
- }
- ///
- /// Add a section named `section` to the instance.
- ///
- /// Throws:
- /// - DuplicateSectionError if a section by the given name already
- /// exists.
- ///
- void addSection(string section) @safe pure
- {
- if (section in m_sections)
- {
- throw new DuplicateSectionException(section);
- }
- m_sections[section] = null;
- }
- ///
- @safe pure unittest
- {
- import std.exception : assertNotThrown, assertThrown;
- auto conf = new ConfigParser();
- /* doesn't yet exist */
- assertNotThrown!DuplicateSectionException(conf.addSection("sample"));
- /* already exists */
- assertThrown!DuplicateSectionException(conf.addSection("sample"));
- }
- ///
- /// Indicates whether the named `section` is present in the configuration.
- ///
- /// Params:
- /// section = The section to check for in the configuration.
- ///
- /// Returns: `true` if the section exists, `false` otherwise.
- ///
- bool hasSection(string section) @safe pure const
- {
- return (section in m_sections) !is null;
- }
- ///
- @safe pure unittest
- {
- auto conf = new ConfigParser();
- conf.addSection("nExt");
- assert(true == conf.hasSection("nExt"), "Close the world.");
- assert(false == conf.hasSection("world"), "Open the nExt.");
- }
- ///
- /// Returns a list of options available from the specified *section*.
- ///
- /// Throws:
- /// - NoSectionException if the specified exception does not exist.
- ///
- string[] options(string section) @trusted pure const
- {
- if (false == this.hasSection(section))
- {
- throw new NoSectionException(section);
- }
- return m_sections[section].keys();
- }
- ///
- @safe pure unittest
- {
- import std.exception : assertNotThrown, assertThrown;
- auto conf = new ConfigParser();
- conf.addSection("Settings");
- assertNotThrown!NoSectionException(conf.options("Settings"));
- assertThrown!NoSectionException(conf.options("void"));
- string[] options = conf.options("Settings");
- assert(0 == options.length, "More keys than we need");
- }
- ///
- /// If the given *section* exists, and contains the given *option*,
- /// return true; otherwise return false.
- ///
- bool hasOption(string section, string option) @safe pure const
- {
- import std.string : toLower;
- if (false == this.hasSection(section))
- {
- return false;
- }
- const lowercaseOption = toLower(option);
- return (lowercaseOption in m_sections[section]) !is null;
- }
- ///
- @safe pure unittest
- {
- auto writer = new ConfigParser();
- writer.addSection("valid_section");
- writer.set("valid_section", "valid_option", "value");
- assert(false == writer.hasOption("invalid_section", "invalid_option"));
- assert(false == writer.hasOption("valid_section", "invalid_option"));
- assert(true == writer.hasOption("valid_section", "valid_option"));
- }
- /*
- string[] read(string[] filenames)
- {
- return null;
- }*/
- ///
- /// Attempt to read and parse configuration data from a file
- /// specified by *filename*.
- ///
- void read(string filename) @safe
- {
- File file = File(filename, "r");
- scope(exit) { file.close(); }
- read(file, false);
- }
- ///
- @safe unittest
- {
- import std.file : remove;
- import std.stdio : File;
- auto configFile = File("test.conf", "w+");
- configFile.writeln("[Section 1]");
- configFile.writeln("key=value");
- configFile.writeln("\n[Section 2]");
- configFile.writeln("key2 = value");
- configFile.close();
- auto conf = new ConfigParser();
- conf.read("test.conf");
- assert(2 == conf.sections.length, "Incorrect Sections length");
- assert(true == conf.hasSection("Section 1"),
- "Config file doesn't have Section 1");
- assert(true == conf.hasOption("Section 1", "key"),
- "Config file doesn't have 'key' in 'Section 1'");
- remove("test.conf");
- }
- ///
- /// Parse a config file.
- ///
- /// Params:
- /// file = Reference to the file from which to read.
- /// close = Close the file when finished parsing.
- ///
- void read(ref File file, bool close = true) @trusted
- {
- import std.array : array;
- import std.algorithm.searching : canFind;
- import std.string : strip;
- scope(exit)
- {
- if (close)
- {
- file.close();
- }
- }
- foreach(const(char)[] inputLine; file.byLine)
- {
- const line = cast(string)strip(inputLine);
- if (line == "" || canFind(m_commentPrefixes, line[0]))
- {
- /* ignore empty lines or comments */
- continue;
- }
- if ('[' == line[0])
- {
- parseSectionHeader(line);
- }
- else
- {
- parseLine(line);
- }
- }
- }
- ///
- /// Parser configuration data from a string.
- ///
- void readString(string str) @safe pure
- {
- import std.algorithm.searching : canFind;
- import std.string : lineSplitter, strip;
- foreach(inputLine; lineSplitter(str))
- {
- const line = strip(inputLine);
- if ("" == line || canFind(m_commentPrefixes, line[0]))
- {
- /* ignore empty lines or comment lines */
- continue;
- }
- if ('[' == line[0])
- {
- parseSectionHeader(line);
- }
- else
- {
- parseLine(line);
- }
- }
- }
- ///
- @safe pure unittest
- {
- const input = "[section]
- option = value
- [second section]
- option = value";
- auto reader = new ConfigParser();
- reader.readString(input);
- assert(reader.hasSection("section"));
- assert(reader.hasSection("second section"));
- assert("value" == reader.get("section", "option"));
- assert("value" == reader.get("second section", "option"));
- }
- ///
- /// Get an `option` value for the named `section`.
- ///
- /// Params:
- /// section = The section to look for the given `option`.
- /// option = The option to return the value of
- /// fallback = Fallback value if the `option` is not found. Can be null.
- ///
- /// Returns:
- /// - The value for `option` if it is found.
- /// - `null` if the `option` is not found and `fallback` is not provided.
- /// - `fallback` if the `option` is not found and `fallback` is provided.
- ///
- /// Throws:
- /// - NoSectionException if the `section` does not exist and no fallback is provided.
- /// - NoOptionException if the `option` does not exist and no fallback is provided.
- ///
- string get(string section, string option) @safe pure const
- {
- import std.string : toLower;
- const lowercaseOption = toLower(option);
- if (false == this.hasSection(section))
- {
- throw new NoSectionException(section);
- }
- if (false == this.hasOption(section, lowercaseOption))
- {
- throw new NoOptionException(section, lowercaseOption);
- }
- return m_sections[section][lowercaseOption];
- }
- ///
- @safe pure unittest
- {
- import std.exception : assertThrown;
- auto conf = new ConfigParser();
- conf.addSection("Section");
- conf.set("Section", "option", "value");
- assert(conf.get("Section", "option") == "value");
- assertThrown!NoSectionException(conf.get("section", "option"));
- assertThrown!NoOptionException(conf.get("Section", "void"));
- }
- /// Ditto
- string get(string section, string option, string fallback) @safe pure const
- {
- try
- {
- return get(section, option);
- }
- catch (NoSectionException e)
- {
- return fallback;
- }
- catch (NoOptionException e)
- {
- return fallback;
- }
- }
- ///
- @safe pure unittest
- {
- import std.exception : assertThrown;
- auto conf = new ConfigParser();
- conf.addSection("Section");
- conf.set("Section", "option", "value");
- assert("value" == conf.get("Section", "option"));
- assert("fallback" == conf.get("section", "option", "fallback"));
- assert("fallback" == conf.get("Section", "void", "fallback"));
- /* can use null for fallback */
- assert(null is conf.get("section", "option", null));
- assert(null is conf.get("Section", "void", null));
- }
- ///
- /// A convenience method which parses the value of `option` in `section`
- /// to an integer.
- ///
- /// Params:
- /// section = The section to look for the given `option`.
- /// option = The option to return the value for.
- /// fallback = The fallback value to use if `option` isn't found.
- ///
- /// Throws:
- /// - NoSectionFoundException if `section` doesn't exist.
- /// - NoOptionFoundException if the `section` doesn't contain `option`.
- /// - ConvException if it failed to parse the value to an int.
- /// - ConvOverflowException if the value would overflow an int.
- /// /// See_Also: get()
- ///
- int getInt(string section, string option) @safe pure const
- {
- import std.conv : parse;
- auto res = get(section, option);
- return parse!int(res);
- }
- /// Ditto
- int getInt(string section, string option, int fallback) @safe pure const
- {
- try
- {
- return getInt(section, option);
- }
- catch (NoSectionException nse)
- {
- return fallback;
- }
- catch (NoOptionException noe)
- {
- return fallback;
- }
- catch (ConvException ce)
- {
- return fallback;
- }
- }
- /*
- double getDouble(string section, string option)
- {
- }
- double getDouble(string section, string option, double fallback)
- {
- }
- float getFloat(string section, string option)
- {
- }
- float getFloat(string section, string option, float fallback)
- {
- }*/
- ///
- /// A convenience method which coerces the $(I option) in the
- /// specified $(I section) to a boolean value.
- ///
- /// Note that the accepted values for the option are "1", "yes",
- /// "true", and "on", which cause this method to return `true`, and
- /// "0", "no", "false", and "off", which cause it to return `false`.
- ///
- /// These string values are checked in a case-insensitive manner.
- ///
- /// Params:
- /// section = The section to look for the given option.
- /// option = The option to return the value for.
- /// fallback = The fallback value to use if the option was not found.
- ///
- /// Throws:
- /// - NoSectionFoundException if `section` doesn't exist.
- /// - NoOptionFoundException if the `section` doesn't contain `option`.
- /// - ConvException if any other value was found.
- ///
- bool getBool(string section, string option) @safe pure const
- {
- import std.string : toLower;
- const value = get(section, option).toLower;
- switch (value)
- {
- case "1":
- case "yes":
- case "true":
- case "on":
- return true;
- case "0":
- case "no":
- case "false":
- case "off":
- return false;
- default:
- throw new ConvException("No valid boolean value found");
- }
- }
- /// Ditto
- bool getBool(string section, string option, bool fallback) @safe pure const
- {
- try
- {
- return getBool(section, option);
- }
- catch (NoSectionException e)
- {
- return fallback;
- }
- catch (NoOptionException e)
- {
- return fallback;
- }
- catch (ConvException e)
- {
- return fallback;
- }
- }
- /*
- string[string] items(string section)
- {
- }*/
- ///
- /// Remove the specified `option` from the specified `section`.
- ///
- /// Params:
- /// section = The section to remove from.
- /// option = The option to remove from section.
- ///
- /// Retruns:
- /// `true` if option existed, false otherwise.
- ///
- /// Throws:
- /// - NoSectionException if the specified section doesn't exist.
- ///
- bool removeOption(string section, string option) @safe pure
- {
- if ((section in m_sections) is null)
- {
- throw new NoSectionException(section);
- }
- if (option in m_sections[section])
- {
- m_sections[section].remove(option);
- return true;
- }
- return false;
- }
- ///
- @safe pure unittest
- {
- import std.exception : assertThrown;
- auto conf = new ConfigParser();
- conf.addSection("Default");
- conf.set("Default", "exists", "true");
- assertThrown!NoSectionException(conf.removeOption("void", "false"));
- assert(false == conf.removeOption("Default", "void"));
- assert(true == conf.removeOption("Default", "exists"));
- }
- ///
- /// Remove the specified `section` from the config.
- ///
- /// Params:
- /// section = The section to remove.
- ///
- /// Returns:
- /// `true` if the section existed, `false` otherwise.
- ///
- bool removeSection(string section) @safe pure
- {
- if (section in m_sections)
- {
- m_sections.remove(section);
- return true;
- }
- return false;
- }
- ///
- @safe pure unittest
- {
- auto conf = new ConfigParser();
- conf.addSection("Exists");
- assert(false == conf.removeSection("DoesNotExist"));
- assert(true == conf.removeSection("Exists"));
- }
- ///
- /// If the given $(I section) exists, set the given $(I option) to the
- /// specified $(I value).
- ///
- /// Throws:
- /// - NoSectionException if the $(I section) does $(B not) exist.
- ///
- void set(string section, string option, string value) @safe pure
- {
- import std.string : toLower;
- if (false == this.hasSection(section))
- {
- throw new NoSectionException(section);
- }
- const lowercaseOption = toLower(option);
- m_sections[section][lowercaseOption] = value;
- }
- ///
- @safe pure unittest
- {
- import std.exception : assertThrown;
- auto conf = new ConfigParser();
- assertThrown!NoSectionException(conf.set("Section", "option", "value"));
- conf.addSection("Section");
- conf.set("Section", "option", "value");
- assert(conf.get("Section", "option") == "value");
- }
- ///
- /// Write a representation of the configuration to the
- /// provided *file*.
- ///
- /// This representation can be parsed by future calls to
- /// `read`. This does **not** close the file after writing.
- ///
- /// Params:
- /// file = An open file which was opened in text mode.
- /// spaceAroundDelimiters = The delimiters between keys and
- /// values are surrounded by spaces.
- ///
- /// Note: Comments from the original file are not preserved when
- /// writing the configuration back.
- ///
- void write(ref File file, bool spaceAroundDelimiters = true) @safe const
- {
- const del = spaceAroundDelimiters ? " = " : "=";
- foreach(const section, const options; m_sections)
- {
- file.writefln("[%s]", section);
- foreach(const option, const value; options)
- {
- file.writefln("%s%s%s", option, del, value);
- }
- }
- }
- ///
- @safe unittest
- {
- import std.file : remove;
- import std.stdio : File;
- auto writer = new ConfigParser();
- writer.addSection("general");
- writer.addSection("GUI");
- writer.set("GUI", "WINDOW_WIDTH", "848");
- writer.set("GUI", "WINDOW_HEIGHT", "480");
- auto file = File("test.ini", "w+");
- scope(exit) remove(file.name);
- writer.write(file);
- file.rewind();
- auto reader = new ConfigParser();
- reader.read(file);
- assert(reader.hasSection("general"), "reader does not contain general section");
- assert(reader.hasSection("GUI"), "reader does not contain GUI section");
- assert(reader.get("GUI", "WINDOW_WIDTH") == "848", "reader GUI.WINDOW_WIDTH is not 848");
- assert(reader.getInt("GUI", "WINDOW_WIDTH") == 848, "reader GUI.WINDOW_WIDTH is not 848 (int)");
- assert(reader.get("GUI", "WINDOW_HEIGHT") == "480", "reader GUI.WINDOW_HEIGHT is not 480");
- assert(reader.getInt("GUI", "WINDOW_HEIGHT") == 480, "reader GUI.WINDOW_HEIGHT is not 480 (int)");
- }
- ///
- /// Write a representation of the configuration to the
- /// specified *filename*.
- ///
- /// This representation can be parsed by future calls to
- /// `read`. This does **not** close the file after writing.
- ///
- /// Params:
- /// filename = The name of the file to write to.
- /// spaceAroundDelimiters = The delimiters between keys and
- /// values are surrounded by spaces.
- ///
- /// Note: Comments from the original file are not preserved when
- /// writing the configuration back.
- ///
- void write(string filename, bool spaceAroundDelimiters = true) @safe const
- {
- auto file = File(filename, "w+");
- write(file, spaceAroundDelimiters);
- }
- ///
- @safe unittest
- {
- import std.file : remove;
- enum kFilename = __PRETTY_FUNCTION__ ~ ".ini";
- auto writer = new ConfigParser();
- writer.addSection("general");
- writer.addSection("output");
- writer.set("general", "featureX", "true");
- writer.set("output", "featureX", "false");
- writer.write(kFilename);
- scope(exit) remove(kFilename);
- auto reader = new ConfigParser();
- reader.read(kFilename);
- assert(reader.hasSection("general"), "reader does not contain 'general' section");
- assert(reader.hasSection("output"), "reader does not contain 'output' section");
- assert(reader.getBool("general", "featureX") == true, "reader general.featureX is not true (bool)");
- assert(reader.getBool("output", "featureX") == false, "reader output.featureX is not false (bool)");
- }
- ///
- /// Write a representation of the configuration to the
- /// provided *buffer*.
- ///
- /// This representation can be parsed by future calls to
- /// `read`.
- ///
- /// Params:
- /// buffer = An OutputRange to write to (i.e. std.OutBuffer).
- /// spaceAroundDelimiters = The delimiters between keys and
- /// values are surrounded by spaces.
- ///
- /// Note: Comments from the original file are not preserved when
- /// writing the configuration back.
- ///
- void write(T)(T buffer, bool spaceAroundDelimiters = true) @safe pure const
- if (isOutputRange!(T, string))
- {
- import std.format : format;
- const del = spaceAroundDelimiters ? " = " : "=";
- foreach(const section, const options; m_sections)
- {
- buffer.put(format!"[%s]\n"(section));
- foreach(const option, const value; options)
- {
- buffer.put(format!"%s%s%s\n"(option, del, value));
- }
- }
- }
- ///
- @safe pure unittest
- {
- import std.outbuffer : OutBuffer;
- OutBuffer buffer = new OutBuffer();
- ConfigParser writer = new ConfigParser();
- writer.addSection("general");
- writer.addSection("output");
- writer.set("general", "featureX", "false");
- writer.set("output", "featureX", "true");
- writer.write(buffer);
- ConfigParser reader = new ConfigParser();
- reader.readString(buffer.toString());
- assert(reader.hasSection("general"), "reader does not contain 'general' section.");
- assert(reader.hasSection("output"), "reader does not contain 'output' section.");
- assert(reader.getBool("general", "featureX") == false);
- assert(reader.getBool("output", "featureX") == true);
- }
- private:
- void parseSectionHeader(const ref string line) @safe pure
- {
- import std.array : appender, assocArray;
- auto sectionHeader = appender!string;
- /* presume that the last character is ] */
- sectionHeader.reserve(line.length - 1);
- string popped = line[1 .. $];
- foreach(const c; popped)
- {
- if (c != ']')
- {
- sectionHeader.put(c);
- }
- else
- {
- break;
- }
- }
- m_currentSection = sectionHeader.data();
- if (m_currentSection in m_sections && m_strict)
- {
- throw new DuplicateSectionException(m_currentSection);
- }
- try
- {
- this.addSection(m_currentSection);
- }
- catch (DuplicateSectionException)
- {
- /* no-op - just making sure the section exists. */
- }
- }
- void parseLine(const ref string line) @safe pure
- {
- import std.string : indexOfAny, toLower, strip;
- const idx = line.indexOfAny(m_delimiters);
- if (-1 == idx)
- {
- return;
- }
- const option = line[0 .. idx].dup.strip.toLower;
- const string value = line[idx + 1 .. $].dup.strip;
- if (option in m_sections[m_currentSection] && m_strict)
- {
- throw new DuplicateOptionException(option, m_currentSection);
- }
- m_sections[m_currentSection][option] = value;
- }
- }
|