From 40a20ca3ff30e9cfd0fe6176aea6f15bb7c82878 Mon Sep 17 00:00:00 2001 From: Mike Bierlee Date: Sat, 8 Oct 2022 23:46:34 +0300 Subject: [PATCH] Add java properties config factory --- README.md | 2 +- source/mirage/java.d | 125 ++++++++++++++++++++++++++++++++++++++ source/mirage/json.d | 16 ++++- source/mirage/package.d | 1 + testfiles/java.properties | 3 + 5 files changed, 145 insertions(+), 2 deletions(-) create mode 100644 source/mirage/java.d create mode 100644 testfiles/java.properties diff --git a/README.md b/README.md index a5d310c..a653e93 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ Licensed under the terms of the MIT license - See [LICENSE.txt](LICENSE.txt) Toolkit for loading and using application configuration from various formats. Features: -- Load from various file formats such as JSON; +- Load from various file formats such as JSON and Java properties; - Environment variable substitution; - Internal configuration substitution (Value in config replaced by other path in config); - Parse configuration from string or JSONValue instead of from disk. diff --git a/source/mirage/java.d b/source/mirage/java.d new file mode 100644 index 0000000..6da63cf --- /dev/null +++ b/source/mirage/java.d @@ -0,0 +1,125 @@ +/** + * Utilities for loading Java properties 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.java; + +import mirage.config : ConfigFactory, ConfigDictionary, ConfigNode, ValueNode, ObjectNode, ConfigCreationException; + +import std.string : lineSplitter, strip, startsWith, split; +import std.array : array; +import std.exception : enforce; +import std.conv : to; + +/** + * Creates configuration files from Java properties. + */ +class JavaPropertiesFactory : ConfigFactory { + /** + * Parse configuration from the given Java properties string. + * + * Params: + * contents = Text contents of the config to be parsed. + * Returns: The parsed configuration. + */ + override ConfigDictionary parseConfig(string contents) { + enforce!ConfigCreationException(contents !is null, "Contents cannot be null."); + auto lines = contents.lineSplitter().array; + auto properties = new ConfigDictionary(); + foreach (size_t index, string line; lines) { + auto trimmedLine = line.strip; + if (trimmedLine.length == 0 || trimmedLine.startsWith('#')) { + continue; + } + + auto parts = trimmedLine.split('='); + enforce!ConfigCreationException(parts.length <= 2, "Line has too many equals signs and cannot be parsed (L" ~ index + .to!string ~ "): " ~ trimmedLine); + enforce!ConfigCreationException(parts.length == 2, "Missing value assignment (L" ~ index.to!string ~ "): " ~ trimmedLine); + properties.set(parts[0], parts[1]); + } + + return properties; + } +} + +/** + * Parse Java properties from the given Java properties string. + + * Params: + * json = Text contents of the config to be parsed. + * Returns: The parsed configuration. + */ +ConfigDictionary parseJavaProperties(const string properties) { + return new JavaPropertiesFactory().parseConfig(properties); +} + +/** + * Load a Java properties file from disk. + * + * Params: + * filePath = Path to the Java properties file. + * Returns: The loaded configuration. + */ +ConfigDictionary loadJavaProperties(const string filePath) { + return new JavaPropertiesFactory().loadFile(filePath); +} + +version (unittest) { + import std.exception : assertThrown; + import std.process : environment; + + @("Parse java properties") + unittest { + auto config = parseJavaProperties(" + # I have a comment + bla=one + di.bla=two + "); + + assert(config.get("bla") == "one"); + assert(config.get("di.bla") == "two"); + } + + @("Parse java properties file") + unittest { + auto config = loadJavaProperties("testfiles/java.properties"); + assert(config.get("bla") == "one"); + assert(config.get("di.bla") == "two"); + } + + @("Fail to parse when there are too many equals signs") + unittest { + assertThrown!ConfigCreationException(parseJavaProperties("one=two=three")); + } + + @("Fail to parse when value assignment is missing") + unittest { + assertThrown!ConfigCreationException(parseJavaProperties("answertolife")); + } + + @("Substitute env vars") + unittest { + environment["MIRAGE_TEST_ENVY"] = "Much"; + auto config = parseJavaProperties("envy=$MIRAGE_TEST_ENVY"); + + assert(config.get("envy") == "Much"); + } + + @("Use value from other key") + unittest { + auto config = parseJavaProperties(" + one=money + two=${one} + "); + + assert(config.get("two") == "money"); + } +} diff --git a/source/mirage/json.d b/source/mirage/json.d index df4b937..33b5644 100644 --- a/source/mirage/json.d +++ b/source/mirage/json.d @@ -196,7 +196,7 @@ version (unittest) { assert(config.get("taxNumber") == null); } - @("Substitute env vars in JSON") + @("Substitute env vars") unittest { environment["MIRAGE_TEST_APP_NAME"] = "Unittest"; environment["MIRAGE_TEST_HOSTNAME"] = "wonkeyhost"; @@ -208,4 +208,18 @@ version (unittest) { assert(config.get("server.port") == "8118"); assert(config.get("app") == "Unittest server - built with love"); } + + @("Use value from other key") + unittest { + string json = " + { + \"one\": \"Groot\", + \"two\": \"${one}\", + } + "; + + auto config = parseJsonConfig(json); + + assert(config.get("two") == "Groot"); + } } diff --git a/source/mirage/package.d b/source/mirage/package.d index cf145ec..554d096 100644 --- a/source/mirage/package.d +++ b/source/mirage/package.d @@ -11,3 +11,4 @@ module mirage; public import mirage.config; public import mirage.json; +public import mirage.java; \ No newline at end of file diff --git a/testfiles/java.properties b/testfiles/java.properties new file mode 100644 index 0000000..aed0f9d --- /dev/null +++ b/testfiles/java.properties @@ -0,0 +1,3 @@ + # I have a comment + bla=one +di.bla=two \ No newline at end of file