Support semicolons as comments in keyvalue config

This commit is contained in:
Mike Bierlee 2022-10-11 20:49:32 +03:00
parent 5a34831b03
commit 42e93e64d0
2 changed files with 58 additions and 18 deletions

View file

@ -12,12 +12,15 @@
module mirage.java; module mirage.java;
import mirage.config : ConfigDictionary; import mirage.config : ConfigDictionary;
import mirage.keyvalue : KeyValueConfigFactory; import mirage.keyvalue : KeyValueConfigFactory, SupportHashtagComments, SupportSemicolonComments;
/** /**
* Creates configuration files from Java properties. * Creates configuration files from Java properties.
*/ */
class JavaPropertiesFactory : KeyValueConfigFactory { class JavaPropertiesFactory : KeyValueConfigFactory!(
SupportHashtagComments.yes,
SupportSemicolonComments.no
) {
} }
/** /**

View file

@ -17,12 +17,19 @@ import std.string : lineSplitter, strip, startsWith, split, indexOf;
import std.array : array; import std.array : array;
import std.exception : enforce; import std.exception : enforce;
import std.conv : to; 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 * A generic reusable key/value config factory that can be configured to parse
* the specifics of certain key/value formats. * 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. * Parse a configuration file following the configured key/value conventions.
* *
@ -35,15 +42,26 @@ class KeyValueConfigFactory : ConfigFactory {
auto lines = contents.lineSplitter().array; auto lines = contents.lineSplitter().array;
auto properties = new ConfigDictionary(); auto properties = new ConfigDictionary();
foreach (size_t index, string line; lines) { foreach (size_t index, string line; lines) {
auto normalizedLine = line.strip; auto normalizedLine = line;
if (normalizedLine.length == 0 || normalizedLine.startsWith('#')) {
continue;
}
if (supportHashtagComments) {
auto commentPosition = normalizedLine.indexOf('#'); auto commentPosition = normalizedLine.indexOf('#');
if (commentPosition >= 0) { if (commentPosition >= 0) {
normalizedLine = normalizedLine[0 .. commentPosition]; 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('='); auto parts = normalizedLine.split('=');
enforce!ConfigCreationException(parts.length <= 2, "Line has too many equals signs and cannot be parsed (L" ~ index enforce!ConfigCreationException(parts.length <= 2, "Line has too many equals signs and cannot be parsed (L" ~ index
@ -60,10 +78,12 @@ version (unittest) {
import std.exception : assertThrown; import std.exception : assertThrown;
import std.process : environment; import std.process : environment;
class TestKeyValueConfigFactory : KeyValueConfigFactory!() {
}
@("Parse standard key/value config") @("Parse standard key/value config")
unittest { unittest {
auto config = new KeyValueConfigFactory().parseConfig(" auto config = new TestKeyValueConfigFactory().parseConfig("
# I have a comment
bla=one bla=one
di.bla=two di.bla=two
"); ");
@ -72,29 +92,43 @@ version (unittest) {
assert(config.get("di.bla") == "two"); 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") @("Fail to parse when there are too many equals signs")
unittest { unittest {
assertThrown!ConfigCreationException(new KeyValueConfigFactory() assertThrown!ConfigCreationException(new TestKeyValueConfigFactory()
.parseConfig("one=two=three")); .parseConfig("one=two=three"));
} }
@("Fail to parse when value assignment is missing") @("Fail to parse when value assignment is missing")
unittest { unittest {
assertThrown!ConfigCreationException(new KeyValueConfigFactory().parseConfig( assertThrown!ConfigCreationException(new TestKeyValueConfigFactory()
.parseConfig(
"answertolife")); "answertolife"));
} }
@("Substitute env vars") @("Substitute env vars")
unittest { unittest {
environment["MIRAGE_TEST_ENVY"] = "Much"; 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"); assert(config.get("envy") == "Much");
} }
@("Use value from other key") @("Use value from other key")
unittest { unittest {
auto config = new KeyValueConfigFactory().parseConfig(" auto config = new TestKeyValueConfigFactory().parseConfig("
one=money one=money
two=${one} two=${one}
"); ");
@ -104,7 +138,7 @@ version (unittest) {
@("Values and keys are trimmed") @("Values and keys are trimmed")
unittest { unittest {
auto config = new KeyValueConfigFactory().parseConfig(" auto config = new TestKeyValueConfigFactory().parseConfig("
one = money one = money
"); ");
@ -113,10 +147,13 @@ version (unittest) {
@("Remove end-of-line comments") @("Remove end-of-line comments")
unittest { 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. 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("server") == "localhost");
assert(config.get("port") == "9876");
} }
} }