From 42e93e64d01b25a8bdbc1cef086ff423ba37ea31 Mon Sep 17 00:00:00 2001 From: Mike Bierlee Date: Tue, 11 Oct 2022 20:49:32 +0300 Subject: [PATCH] Support semicolons as comments in keyvalue config --- source/mirage/java.d | 7 ++-- source/mirage/keyvalue.d | 69 ++++++++++++++++++++++++++++++---------- 2 files changed, 58 insertions(+), 18 deletions(-) diff --git a/source/mirage/java.d b/source/mirage/java.d index 4305238..9e84684 100644 --- a/source/mirage/java.d +++ b/source/mirage/java.d @@ -12,12 +12,15 @@ module mirage.java; import mirage.config : ConfigDictionary; -import mirage.keyvalue : KeyValueConfigFactory; +import mirage.keyvalue : KeyValueConfigFactory, SupportHashtagComments, SupportSemicolonComments; /** * Creates configuration files from Java properties. */ -class JavaPropertiesFactory : KeyValueConfigFactory { +class JavaPropertiesFactory : KeyValueConfigFactory!( + SupportHashtagComments.yes, + SupportSemicolonComments.no +) { } /** diff --git a/source/mirage/keyvalue.d b/source/mirage/keyvalue.d index 3d1a00b..cef42f2 100644 --- a/source/mirage/keyvalue.d +++ b/source/mirage/keyvalue.d @@ -17,12 +17,19 @@ import std.string : lineSplitter, strip, startsWith, split, indexOf; import std.array : array; import std.exception : enforce; import std.conv : to; +import std.typecons : Flag; + +alias SupportHashtagComments = Flag!"supportHashtagComment"; +alias SupportSemicolonComments = Flag!"supportSemicolonComments"; /** * A generic reusable key/value config factory that can be configured to parse * the specifics of certain key/value formats. */ -class KeyValueConfigFactory : ConfigFactory { +class KeyValueConfigFactory( + SupportHashtagComments supportHashtagComments = SupportHashtagComments.no, + SupportSemicolonComments supportSemicolonComments = SupportSemicolonComments.no +) : ConfigFactory { /** * Parse a configuration file following the configured key/value conventions. * @@ -35,14 +42,25 @@ class KeyValueConfigFactory : ConfigFactory { auto lines = contents.lineSplitter().array; auto properties = new ConfigDictionary(); foreach (size_t index, string line; lines) { - auto normalizedLine = line.strip; - if (normalizedLine.length == 0 || normalizedLine.startsWith('#')) { - continue; + auto normalizedLine = line; + + if (supportHashtagComments) { + auto commentPosition = normalizedLine.indexOf('#'); + if (commentPosition >= 0) { + normalizedLine = normalizedLine[0 .. commentPosition]; + } } - auto commentPosition = normalizedLine.indexOf('#'); - if (commentPosition >= 0) { - normalizedLine = normalizedLine[0 .. commentPosition]; + if (supportSemicolonComments) { + auto commentPosition = normalizedLine.indexOf(';'); + if (commentPosition >= 0) { + normalizedLine = normalizedLine[0 .. commentPosition]; + } + } + + normalizedLine = normalizedLine.strip; + if (normalizedLine.length == 0) { + continue; } auto parts = normalizedLine.split('='); @@ -60,10 +78,12 @@ version (unittest) { import std.exception : assertThrown; import std.process : environment; + class TestKeyValueConfigFactory : KeyValueConfigFactory!() { + } + @("Parse standard key/value config") unittest { - auto config = new KeyValueConfigFactory().parseConfig(" - # I have a comment + auto config = new TestKeyValueConfigFactory().parseConfig(" bla=one di.bla=two "); @@ -72,29 +92,43 @@ version (unittest) { assert(config.get("di.bla") == "two"); } + @("Parse and ignore comments") + unittest { + auto config = new KeyValueConfigFactory!(SupportHashtagComments.yes, + SupportSemicolonComments.yes + )().parseConfig(" + # this is a comment + ; this is another comment + iamset=true + "); + + assert(config.get!bool("iamset")); + } + @("Fail to parse when there are too many equals signs") unittest { - assertThrown!ConfigCreationException(new KeyValueConfigFactory() + assertThrown!ConfigCreationException(new TestKeyValueConfigFactory() .parseConfig("one=two=three")); } @("Fail to parse when value assignment is missing") unittest { - assertThrown!ConfigCreationException(new KeyValueConfigFactory().parseConfig( - "answertolife")); + assertThrown!ConfigCreationException(new TestKeyValueConfigFactory() + .parseConfig( + "answertolife")); } @("Substitute env vars") unittest { environment["MIRAGE_TEST_ENVY"] = "Much"; - auto config = new KeyValueConfigFactory().parseConfig("envy=$MIRAGE_TEST_ENVY"); + auto config = new TestKeyValueConfigFactory().parseConfig("envy=$MIRAGE_TEST_ENVY"); assert(config.get("envy") == "Much"); } @("Use value from other key") unittest { - auto config = new KeyValueConfigFactory().parseConfig(" + auto config = new TestKeyValueConfigFactory().parseConfig(" one=money two=${one} "); @@ -104,7 +138,7 @@ version (unittest) { @("Values and keys are trimmed") unittest { - auto config = new KeyValueConfigFactory().parseConfig(" + auto config = new TestKeyValueConfigFactory().parseConfig(" one = money "); @@ -113,10 +147,13 @@ version (unittest) { @("Remove end-of-line comments") unittest { - auto config = new KeyValueConfigFactory().parseConfig(" + auto config = new KeyValueConfigFactory!(SupportHashtagComments.yes, + SupportSemicolonComments.yes)().parseConfig(" server=localhost #todo: change me. default=localhost when not set. + port=9876; I think this port = right? "); assert(config.get("server") == "localhost"); + assert(config.get("port") == "9876"); } }