Add INI config factory

This commit is contained in:
Mike Bierlee 2022-10-13 01:56:27 +03:00
parent aaa6512af4
commit 9eac0515a1
7 changed files with 173 additions and 10 deletions

View file

@ -56,6 +56,7 @@ The following file formats are currently supported:
| _any below_ | _any below_ | `mirage` | `loadConfig`<sup>**</sup> | _(N/A)_ | | | _any below_ | _any below_ | `mirage` | `loadConfig`<sup>**</sup> | _(N/A)_ | |
| JSON | .json | `mirage.json` | `loadJsonConfig` | `parseJsonConfig`<sup>***</sup> | `JsonConfigFactory` | | JSON | .json | `mirage.json` | `loadJsonConfig` | `parseJsonConfig`<sup>***</sup> | `JsonConfigFactory` |
| Java | .properties | `mirage.java` | `loadJavaProperties` | `parseJavaProperties` | `JavaPropertiesFactory` | | Java | .properties | `mirage.java` | `loadJavaProperties` | `parseJavaProperties` | `JavaPropertiesFactory` |
| INI | .ini | `mirage.ini` | `loadIniConfig` | `parseIniConfig` | `IniConfigFactory` |
<sup>\*</sup> _Any loader or parser can be imported from the `mirage` package since they are all publicly imported._ <sup>\*</sup> _Any loader or parser can be imported from the `mirage` package since they are all publicly imported._
<sup>\*\*</sup> _Loads files based on their extension. If the file does not use one of the extensions in the table, you must use a specific loader._ <sup>\*\*</sup> _Loads files based on their extension. If the file does not use one of the extensions in the table, you must use a specific loader._

24
TODO.md
View file

@ -1,12 +1,16 @@
# TODO # TODO
* Add tutorial - Add tutorial
* Config loading - Config loading
* Config parsing - Config parsing
* Config manip - Config manip
* Env and config var substitution - Env and config var substitution
* Escaping - Escaping
* Java properties - Java properties
* Add unicode escaping - Add unicode escaping
* Support multi-line values with backslash - Support multi-line values with backslash
* Add escaping of key/value separator = and : - Add escaping of key/value separator = and :
- INI config
- Case insensitive properties and sections
- Escape characters
- Support multi-line values with backslash

View file

@ -21,6 +21,7 @@ import std.typecons : Flag;
import mirage.json : loadJsonConfig; import mirage.json : loadJsonConfig;
import mirage.java : loadJavaProperties; import mirage.java : loadJavaProperties;
import mirage.ini : loadIniConfig;
/** /**
* Used by the ConfigDictionary when something goes wrong when reading configuration. * Used by the ConfigDictionary when something goes wrong when reading configuration.
@ -603,6 +604,10 @@ ConfigDictionary loadConfig(const string configPath) {
return loadJavaProperties(configPath); return loadJavaProperties(configPath);
} }
if (extension == ".ini") {
return loadIniConfig(configPath);
}
throw new ConfigCreationException( throw new ConfigCreationException(
"File extension '" ~ extension ~ "' is not recognized as a supported config file format. Please use a specific function to load it, such as 'loadJsonConfig()'"); "File extension '" ~ extension ~ "' is not recognized as a supported config file format. Please use a specific function to load it, such as 'loadJsonConfig()'");
} }
@ -853,6 +858,11 @@ version (unittest) {
assert(javaProperties.get("name") == "Groot"); assert(javaProperties.get("name") == "Groot");
assert(javaProperties.get("age") == "8728"); assert(javaProperties.get("age") == "8728");
assert(javaProperties.get("taxNumber") == "null"); assert(javaProperties.get("taxNumber") == "null");
auto iniConfig = loadConfig("testfiles/groot.ini");
assert(iniConfig.get("groot.name") == "Groot");
assert(iniConfig.get("groot.age") == "8728");
assert(iniConfig.get("groot.taxNumber") == "null");
} }
@("Whitespace is preserved in values") @("Whitespace is preserved in values")

127
source/mirage/ini.d Normal file
View file

@ -0,0 +1,127 @@
/**
* Utilities for loading INI files.
*
* Authors:
* Mike Bierlee, m.bierlee@lostmoment.com
* Copyright: 2022 Mike Bierlee
* License:
* This software is licensed under the terms of the MIT license.
* The full terms of the license can be found in the LICENSE file.
*/
module mirage.ini;
import mirage.config : ConfigDictionary;
import mirage.keyvalue : KeyValueConfigFactory, SupportHashtagComments, SupportSemicolonComments,
SupportExclamationComments, SupportSections, NormalizeQuotedValues, SupportEqualsSeparator,
SupportColonSeparator, SupportKeysWithoutValues;
/**
* Creates configuration dictionaries from INI files.
*
* Format specifications:
* https://en.wikipedia.org/wiki/INI_file#Format
*/
class IniConfigFactory : KeyValueConfigFactory!(
SupportHashtagComments.yes,
SupportSemicolonComments.yes,
SupportExclamationComments.no,
SupportSections.yes,
NormalizeQuotedValues.yes,
SupportEqualsSeparator.yes,
SupportColonSeparator.yes,
SupportKeysWithoutValues.no
) {
}
/**
* Parse configuration from the given INI config string.
* Params:
* contents = Text contents of the config to be parsed.
* Returns: The parsed configuration.
*/
ConfigDictionary parseIniConfig(const string contents) {
return new IniConfigFactory().parseConfig(contents);
}
/**
* Load a INI configuration file from disk.
*
* Params:
* filePath = Path to the INI configuration file.
* Returns: The loaded configuration.
*/
ConfigDictionary loadIniConfig(const string filePath) {
return new IniConfigFactory().loadFile(filePath);
}
version (unittest) {
import std.process : environment;
@("Parse INI config")
unittest {
auto config = parseIniConfig("
globalSection = yes
[supersection]
thefirst = here
[supersection.sub]
sandwich=maybe tasty
[.way]
advertisement? = nah ; For real, not sponsored!
# Although money would be cool
[back]
to: basics
much = \"very much whitespace\"
many = 'very many whitespace'
");
assert(config.get("globalSection") == "yes");
assert(config.get("supersection.thefirst") == "here");
assert(config.get("supersection.sub.sandwich") == "maybe tasty");
assert(config.get("supersection.sub.way.advertisement?") == "nah");
assert(config.get("back.much") == "very much whitespace");
assert(config.get("back.many") == "very many whitespace");
}
@("Load INI file")
unittest {
auto config = loadIniConfig("testfiles/fuzzy.ini");
assert(config.get("globalSection") == "yes");
assert(config.get("supersection.thefirst") == "here");
assert(config.get("supersection.sub.sandwich") == "maybe tasty");
assert(config.get("supersection.sub.way.advertisement?") == "nah");
assert(config.get("back.much") == "very much whitespace");
assert(config.get("back.many") == "very many whitespace");
}
@("Substitute env vars")
unittest {
environment["MIRAGE_TEST_INI_VAR"] = "I am ini";
auto config = parseIniConfig("
[app]
startInfo = ${MIRAGE_TEST_INI_VAR}
");
assert(config.get("app.startInfo") == "I am ini");
}
@("Use value from other key")
unittest {
auto config = parseIniConfig("
[app]
startInfo = \"Let's get started!\"
[logger]
startInfo = ${app.startInfo}
");
assert(config.get("app.startInfo") == "Let's get started!");
assert(config.get("logger.startInfo") == "Let's get started!");
}
}

View file

@ -10,6 +10,7 @@
module mirage; module mirage;
public import mirage.config; public import mirage.config;
public import mirage.ini;
public import mirage.java; public import mirage.java;
public import mirage.json; public import mirage.json;
public import mirage.keyvalue; public import mirage.keyvalue;

16
testfiles/fuzzy.ini Normal file
View file

@ -0,0 +1,16 @@
globalSection = yes
[supersection]
thefirst = here
[supersection.sub]
sandwich=maybe tasty
[.way]
advertisement? = nah ; For real, not sponsored!
# Although money would be cool
[back]
to: basics
much = "very much whitespace"
many = 'very many whitespace'

4
testfiles/groot.ini Normal file
View file

@ -0,0 +1,4 @@
[groot]
name=Groot
age=8728
taxNumber=null