diff --git a/README.md b/README.md index e119e91d..7d0ce9ad 100644 --- a/README.md +++ b/README.md @@ -159,7 +159,7 @@ compile(group = "com.github.uchuhimo.konf", name = "konf", version = "master-SNA ```kotlin object ServerSpec : ConfigSpec() { val host by optional("0.0.0.0") - val port by required() + val tcpPort by required() } ``` @@ -201,34 +201,34 @@ compile(group = "com.github.uchuhimo.konf", name = "konf", version = "master-SNA ```yaml server: host: 0.0.0.0 - port: 8080 + tcp_port: 8080 ``` - in `server.json`: ```json { "server": { "host": "0.0.0.0", - "port": 8080 + "tcp_port": 8080 } } ``` - in system environment: ```bash SERVER_HOST=0.0.0.0 - SERVER_PORT=8080 + SERVER_TCPPORT=8080 ``` - in command line for system properties: ```bash - -Dserver.host=0.0.0.0 -Dserver.port=8080 + -Dserver.host=0.0.0.0 -Dserver.tcp_port=8080 ``` 4. Retrieve values from config with type-safe APIs: ```kotlin - data class Server(val host: String, val port: Int) { + data class Server(val host: String, val tcpPort: Int) { fun start() {} } - val server = Server(config[ServerSpec.host], config[ServerSpec.port]) + val server = Server(config[ServerSpec.host], config[ServerSpec.tcpPort]) server.start() ``` @@ -260,7 +260,7 @@ If the config spec is binding with single class, you can declare config spec as class Server { companion object : ConfigSpec("server") { val host by optional("0.0.0.0") - val port by required() + val tcpPort by required() } } ``` @@ -299,11 +299,11 @@ There are three kinds of item: - Required item. Required item doesn't have default value, thus must be set with value before retrieved in config. Define a required item with description: ```kotlin - val port by required(description = "port of server") + val tcpPort by required(description = "port of server") ``` Or omit the description: ```kotlin - val port by required() + val tcpPort by required() ``` - Optional item. Optional item has default value, thus can be safely retrieved before setting. Define an optional item: ```kotlin @@ -312,7 +312,7 @@ There are three kinds of item: Description can be omitted. - Lazy item. Lazy item also has default value, however, the default value is not a constant, it is evaluated from thunk every time when retrieved. Define a lazy item: ```kotlin - val nextPort by lazy { config -> config[port] + 1 } + val nextPort by lazy { config -> config[tcpPort] + 1 } ``` You can also define config spec in Java, with a more verbose API (compared to Kotlin version in "quick start"): @@ -324,7 +324,7 @@ public class ServerSpec { public static final OptionalItem host = new OptionalItem(spec, "host", "0.0.0.0") {}; - public static final RequiredItem port = new RequiredItem(spec, "port") {}; + public static final RequiredItem tcpPort = new RequiredItem(spec, "tcpPort") {}; } ``` @@ -418,37 +418,37 @@ config.validateRequired() Associate item with value (type-safe API): ```kotlin -config[Server.port] = 80 +config[Server.tcpPort] = 80 ``` Find item with specified name, and associate it with value (unsafe API): ```kotlin -config["server.port"] = 80 +config["server.tcpPort"] = 80 ``` Discard associated value of item: ```kotlin -config.unset(Server.port) +config.unset(Server.tcpPort) ``` Discard associated value of item with specified name: ```kotlin -config.unset("server.port") +config.unset("server.tcpPort") ``` Associate item with lazy thunk (type-safe API): ```kotlin -config.lazySet(Server.port) { it[basePort] + 1 } +config.lazySet(Server.tcpPort) { it[basePort] + 1 } ``` Find item with specified name, and associate it with lazy thunk (unsafe API): ```kotlin -config.lazySet("server.port") { it[basePort] + 1 } +config.lazySet("server.tcpPort") { it[basePort] + 1 } ``` ### Export value in config as property @@ -456,7 +456,7 @@ config.lazySet("server.port") { it[basePort] + 1 } Export a read-write property from value in config: ```kotlin -var port by config.property(Server.port) +var port by config.property(Server.tcpPort) port = 9090 check(port == 9090) ``` @@ -464,7 +464,7 @@ check(port == 9090) Export a read-only property from value in config: ```kotlin -val port by config.property(Server.port) +val port by config.property(Server.tcpPort) check(port == 9090) ``` @@ -472,19 +472,19 @@ check(port == 9090) ```kotlin val config = Config { addSpec(Server) } -config[Server.port] = 1000 +config[Server.tcpPort] = 1000 // fork from parent config val childConfig = config.withLayer("child") // child config inherit values from parent config -check(childConfig[Server.port] == 1000) +check(childConfig[Server.tcpPort] == 1000) // modifications in parent config affect values in child config -config[Server.port] = 2000 -check(config[Server.port] == 2000) -check(childConfig[Server.port] == 2000) +config[Server.tcpPort] = 2000 +check(config[Server.tcpPort] == 2000) +check(childConfig[Server.tcpPort] == 2000) // modifications in child config don't affect values in parent config -childConfig[Server.port] = 3000 -check(config[Server.port] == 2000) -check(childConfig[Server.port] == 3000) +childConfig[Server.tcpPort] = 3000 +check(config[Server.tcpPort] == 2000) +check(childConfig[Server.tcpPort] == 3000) ``` ## Load values from source @@ -752,6 +752,7 @@ These features include: - `FAIL_ON_UNKNOWN_PATH`: feature that determines what happens when unknown paths appear in the source. If enabled, an exception is thrown when loading from the source to indicate it contains unknown paths. This feature is disabled by default. - `LOAD_KEYS_CASE_INSENSITIVELY`: feature that determines whether loading keys from sources case-insensitively. This feature is disabled by default except for system environment. +- `LOAD_KEYS_AS_LITTLE_CAMEL_CASE`: feature that determines whether loading keys from sources as little camel case. This feature is enabled by default. - `OPTIONAL_SOURCE_BY_DEFAULT`: feature that determines whether sources are optional by default. This feature is disabled by default. - `SUBSTITUTE_SOURCE_BEFORE_LOADED`: feature that determines whether sources should be substituted before loaded into config. This feature is enabled by default. diff --git a/konf-all/src/snippet/java/com/uchuhimo/konf/snippet/ServerInJava.java b/konf-all/src/snippet/java/com/uchuhimo/konf/snippet/ServerInJava.java index 5bfe00da..f69807ee 100644 --- a/konf-all/src/snippet/java/com/uchuhimo/konf/snippet/ServerInJava.java +++ b/konf-all/src/snippet/java/com/uchuhimo/konf/snippet/ServerInJava.java @@ -20,22 +20,22 @@ public class ServerInJava { private String host; - private Integer port; + private Integer tcpPort; - public ServerInJava(String host, Integer port) { + public ServerInJava(String host, Integer tcpPort) { this.host = host; - this.port = port; + this.tcpPort = tcpPort; } public ServerInJava(Config config) { - this(config.get(ServerSpecInJava.host), config.get(ServerSpecInJava.port)); + this(config.get(ServerSpecInJava.host), config.get(ServerSpecInJava.tcpPort)); } public String getHost() { return host; } - public Integer getPort() { - return port; + public Integer getTcpPort() { + return tcpPort; } } diff --git a/konf-all/src/snippet/java/com/uchuhimo/konf/snippet/ServerSpecInJava.java b/konf-all/src/snippet/java/com/uchuhimo/konf/snippet/ServerSpecInJava.java index cc1f8dd9..ba605109 100644 --- a/konf-all/src/snippet/java/com/uchuhimo/konf/snippet/ServerSpecInJava.java +++ b/konf-all/src/snippet/java/com/uchuhimo/konf/snippet/ServerSpecInJava.java @@ -26,5 +26,5 @@ public class ServerSpecInJava { public static final OptionalItem host = new OptionalItem(spec, "host", "0.0.0.0") {}; - public static final RequiredItem port = new RequiredItem(spec, "port") {}; + public static final RequiredItem tcpPort = new RequiredItem(spec, "tcpPort") {}; } diff --git a/konf-all/src/snippet/kotlin/com/uchuhimo/konf/snippet/Config.kt b/konf-all/src/snippet/kotlin/com/uchuhimo/konf/snippet/Config.kt index 4c526326..5b2616ce 100644 --- a/konf-all/src/snippet/kotlin/com/uchuhimo/konf/snippet/Config.kt +++ b/konf-all/src/snippet/kotlin/com/uchuhimo/konf/snippet/Config.kt @@ -37,22 +37,22 @@ fun main(args: Array) { config.contains("server.host") // or "server.host" in config - config[Server.port] = 80 - config["server.port"] = 80 + config[Server.tcpPort] = 80 + config["server.tcpPort"] = 80 config.containsRequired() config.validateRequired() - config.unset(Server.port) - config.unset("server.port") + config.unset(Server.tcpPort) + config.unset("server.tcpPort") val basePort by ConfigSpec("server").required() - config.lazySet(Server.port) { it[basePort] + 1 } - config.lazySet("server.port") { it[basePort] + 1 } + config.lazySet(Server.tcpPort) { it[basePort] + 1 } + config.lazySet("server.tcpPort") { it[basePort] + 1 } run { - var port by config.property(Server.port) + var port by config.property(Server.tcpPort) port = 9090 check(port == 9090) } run { - val port by config.property(Server.port) + val port by config.property(Server.tcpPort) check(port == 9090) } } diff --git a/konf-all/src/snippet/kotlin/com/uchuhimo/konf/snippet/Export.kt b/konf-all/src/snippet/kotlin/com/uchuhimo/konf/snippet/Export.kt index 98a72b3b..410cff1e 100644 --- a/konf-all/src/snippet/kotlin/com/uchuhimo/konf/snippet/Export.kt +++ b/konf-all/src/snippet/kotlin/com/uchuhimo/konf/snippet/Export.kt @@ -23,7 +23,7 @@ import com.uchuhimo.konf.source.json.toJson fun main(args: Array) { val config = Config { addSpec(Server) } - config[Server.port] = 1000 + config[Server.tcpPort] = 1000 run { val map = config.toMap() } @@ -38,5 +38,5 @@ fun main(args: Array) { val newConfig = Config { addSpec(Server) }.from.json.file(file) - check(config == newConfig) + check(config.toMap() == newConfig.toMap()) } diff --git a/konf-all/src/snippet/kotlin/com/uchuhimo/konf/snippet/Fork.kt b/konf-all/src/snippet/kotlin/com/uchuhimo/konf/snippet/Fork.kt index 46ce9ee4..9502da79 100644 --- a/konf-all/src/snippet/kotlin/com/uchuhimo/konf/snippet/Fork.kt +++ b/konf-all/src/snippet/kotlin/com/uchuhimo/konf/snippet/Fork.kt @@ -20,17 +20,17 @@ import com.uchuhimo.konf.Config fun main(args: Array) { val config = Config { addSpec(Server) } - config[Server.port] = 1000 + config[Server.tcpPort] = 1000 // fork from parent config val childConfig = config.withLayer("child") // child config inherit values from parent config - check(childConfig[Server.port] == 1000) + check(childConfig[Server.tcpPort] == 1000) // modifications in parent config affect values in child config - config[Server.port] = 2000 - check(config[Server.port] == 2000) - check(childConfig[Server.port] == 2000) + config[Server.tcpPort] = 2000 + check(config[Server.tcpPort] == 2000) + check(childConfig[Server.tcpPort] == 2000) // modifications in child config don't affect values in parent config - childConfig[Server.port] = 3000 - check(config[Server.port] == 2000) - check(childConfig[Server.port] == 3000) + childConfig[Server.tcpPort] = 3000 + check(config[Server.tcpPort] == 2000) + check(childConfig[Server.tcpPort] == 3000) } diff --git a/konf-all/src/snippet/kotlin/com/uchuhimo/konf/snippet/QuickStart.kt b/konf-all/src/snippet/kotlin/com/uchuhimo/konf/snippet/QuickStart.kt index 6c80fe9f..d1a02505 100644 --- a/konf-all/src/snippet/kotlin/com/uchuhimo/konf/snippet/QuickStart.kt +++ b/konf-all/src/snippet/kotlin/com/uchuhimo/konf/snippet/QuickStart.kt @@ -26,7 +26,7 @@ import java.io.File object ServerSpec : ConfigSpec() { val host by optional("0.0.0.0") - val port by required() + val tcpPort by required() } fun main(args: Array) { @@ -36,7 +36,7 @@ fun main(args: Array) { """ server: host: 127.0.0.1 - port: 8080 + tcp_port: 8080 """.trimIndent() ) file.deleteOnExit() @@ -60,7 +60,7 @@ fun main(args: Array) { .from.env() .from.systemProperties() } - val server = Server(config[ServerSpec.host], config[ServerSpec.port]) + val server = Server(config[ServerSpec.host], config[ServerSpec.tcpPort]) server.start() run { val server = Config() diff --git a/konf-all/src/snippet/kotlin/com/uchuhimo/konf/snippet/Serialize.kt b/konf-all/src/snippet/kotlin/com/uchuhimo/konf/snippet/Serialize.kt index e6fdd112..a328d911 100644 --- a/konf-all/src/snippet/kotlin/com/uchuhimo/konf/snippet/Serialize.kt +++ b/konf-all/src/snippet/kotlin/com/uchuhimo/konf/snippet/Serialize.kt @@ -22,7 +22,7 @@ import java.io.ObjectOutputStream fun main(args: Array) { val config = Config { addSpec(Server) } - config[Server.port] = 1000 + config[Server.tcpPort] = 1000 val map = config.toMap() val newMap = createTempFile().run { ObjectOutputStream(outputStream()).use { @@ -36,5 +36,5 @@ fun main(args: Array) { val newConfig = Config { addSpec(Server) }.from.map.kv(newMap) - check(config == newConfig) + check(config.toMap() == newConfig.toMap()) } diff --git a/konf-all/src/snippet/kotlin/com/uchuhimo/konf/snippet/Server.kt b/konf-all/src/snippet/kotlin/com/uchuhimo/konf/snippet/Server.kt index 84dc6c24..fcfa6b90 100644 --- a/konf-all/src/snippet/kotlin/com/uchuhimo/konf/snippet/Server.kt +++ b/konf-all/src/snippet/kotlin/com/uchuhimo/konf/snippet/Server.kt @@ -19,14 +19,14 @@ package com.uchuhimo.konf.snippet import com.uchuhimo.konf.Config import com.uchuhimo.konf.ConfigSpec -data class Server(val host: String, val port: Int) { - constructor(config: Config) : this(config[Server.host], config[Server.port]) +data class Server(val host: String, val tcpPort: Int) { + constructor(config: Config) : this(config[Server.host], config[Server.tcpPort]) fun start() {} companion object : ConfigSpec("server") { val host by optional("0.0.0.0", description = "host IP of server") - val port by required(description = "port of server") - val nextPort by lazy { config -> config[port] + 1 } + val tcpPort by required(description = "port of server") + val nextPort by lazy { config -> config[tcpPort] + 1 } } } diff --git a/konf-all/src/snippet/resources/server.json b/konf-all/src/snippet/resources/server.json index 06eb20d1..054d9763 100644 --- a/konf-all/src/snippet/resources/server.json +++ b/konf-all/src/snippet/resources/server.json @@ -1,6 +1,6 @@ { "server": { "host": "127.0.0.1", - "port": 8080 + "tcp_port": 8080 } } \ No newline at end of file diff --git a/konf-all/src/test/kotlin/com/uchuhimo/konf/source/QuickStartSpec.kt b/konf-all/src/test/kotlin/com/uchuhimo/konf/source/QuickStartSpec.kt index 810d7978..6a263316 100644 --- a/konf-all/src/test/kotlin/com/uchuhimo/konf/source/QuickStartSpec.kt +++ b/konf-all/src/test/kotlin/com/uchuhimo/konf/source/QuickStartSpec.kt @@ -39,7 +39,7 @@ object QuickStartSpec : Spek({ it("should load all values") { assertThat( config.toMap(), - equalTo(mapOf("server.host" to "127.0.0.1", "server.port" to 8080)) + equalTo(mapOf("server.host" to "127.0.0.1", "server.tcpPort" to 8080)) ) } } @@ -55,7 +55,7 @@ object QuickStartSpec : Spek({ it("should load all values") { assertThat( config.toMap(), - equalTo(mapOf("server.host" to "127.0.0.1", "server.port" to 8080)) + equalTo(mapOf("server.host" to "127.0.0.1", "server.tcpPort" to 8080)) ) } } @@ -70,7 +70,7 @@ object QuickStartSpec : Spek({ it("should load all values") { assertThat( config.toMap(), - equalTo(mapOf("server.host" to "127.0.0.1", "server.port" to 8080)) + equalTo(mapOf("server.host" to "127.0.0.1", "server.tcpPort" to 8080)) ) } } @@ -85,7 +85,7 @@ object QuickStartSpec : Spek({ } val server = config.toValue() it("should load all values") { - assertThat(server, equalTo(Server(host = "127.0.0.1", port = 8080))) + assertThat(server, equalTo(Server(host = "127.0.0.1", tcpPort = 8080))) } } on("cast source to value") { @@ -99,7 +99,7 @@ object QuickStartSpec : Spek({ } val server = source.toValue() it("should load all values") { - assertThat(server, equalTo(Server(host = "127.0.0.1", port = 8080))) + assertThat(server, equalTo(Server(host = "127.0.0.1", tcpPort = 8080))) } } }) @@ -111,7 +111,7 @@ private fun useFile(block: () -> T): T { """ server: host: 127.0.0.1 - port: 8080 + tcp_port: 8080 """.trimIndent() ) try { diff --git a/konf-core/src/main/kotlin/com/uchuhimo/konf/source/DefaultLoaders.kt b/konf-core/src/main/kotlin/com/uchuhimo/konf/source/DefaultLoaders.kt index 123b86be..b6d877c6 100644 --- a/konf-core/src/main/kotlin/com/uchuhimo/konf/source/DefaultLoaders.kt +++ b/konf-core/src/main/kotlin/com/uchuhimo/konf/source/DefaultLoaders.kt @@ -119,6 +119,9 @@ class DefaultLoaders( @JvmOverloads fun env(nested: Boolean = true): Config = config.withSource(EnvProvider.env(nested).orMapped()) + @JvmOverloads + fun envMap(map: Map, nested: Boolean = true): Config = config.withSource(EnvProvider.envMap(map, nested).orMapped()) + /** * Returns a child config containing values from system properties. * diff --git a/konf-core/src/main/kotlin/com/uchuhimo/konf/source/MergedSource.kt b/konf-core/src/main/kotlin/com/uchuhimo/konf/source/MergedSource.kt index 036c5f01..82a41090 100644 --- a/konf-core/src/main/kotlin/com/uchuhimo/konf/source/MergedSource.kt +++ b/konf-core/src/main/kotlin/com/uchuhimo/konf/source/MergedSource.kt @@ -35,6 +35,10 @@ class MergedSource(val facade: Source, val fallback: Source) : Source { Collections.unmodifiableMap(facade.features) ) + override fun disabled(feature: Feature): Source = MergedSource(facade.disabled(feature), fallback) + + override fun enabled(feature: Feature): Source = MergedSource(facade.enabled(feature), fallback) + override fun substituted(root: Source, enabled: Boolean, errorWhenUndefined: Boolean): Source { val substitutedFacade = facade.substituted(root, enabled, errorWhenUndefined) val substitutedFallback = fallback.substituted(root, enabled, errorWhenUndefined) @@ -65,6 +69,16 @@ class MergedSource(val facade: Source, val fallback: Source) : Source { } } + override fun normalized(lowercased: Boolean, littleCamelCased: Boolean): Source { + val normalizedFacade = facade.normalized(lowercased, littleCamelCased) + val normalizedFallback = fallback.normalized(lowercased, littleCamelCased) + if (normalizedFacade === facade && normalizedFallback === fallback) { + return this + } else { + return MergedSource(normalizedFacade, normalizedFallback) + } + } + override fun getNodeOrNull(path: Path, lowercased: Boolean, littleCamelCased: Boolean): TreeNode? { val facadeNode = facade.getNodeOrNull(path, lowercased, littleCamelCased) val fallbackNode = fallback.getNodeOrNull(path, lowercased, littleCamelCased) @@ -78,4 +92,30 @@ class MergedSource(val facade: Source, val fallback: Source) : Source { fallbackNode } } + + override fun getOrNull(path: Path): Source? { + return if (path.isEmpty()) { + this + } else { + val subFacade = facade.getOrNull(path) + val subFallback = fallback.getOrNull(path) + if (subFacade != null) { + if (subFallback != null) { + MergedSource(subFacade, subFallback) + } else { + subFacade + } + } else { + subFallback + } + } + } + + override fun withPrefix(prefix: Path): Source { + return if (prefix.isEmpty()) { + this + } else { + MergedSource(facade.withPrefix(prefix), fallback.withPrefix(prefix)) + } + } } diff --git a/konf-core/src/main/kotlin/com/uchuhimo/konf/source/MultiSource.kt b/konf-core/src/main/kotlin/com/uchuhimo/konf/source/MultiSource.kt index 3cd0bc09..98a63e1b 100644 --- a/konf-core/src/main/kotlin/com/uchuhimo/konf/source/MultiSource.kt +++ b/konf-core/src/main/kotlin/com/uchuhimo/konf/source/MultiSource.kt @@ -48,6 +48,10 @@ class MultiSource(val sources: Collection) : Source { throw NotImplementedError() } + override fun normalized(lowercased: Boolean, littleCamelCased: Boolean): Source { + throw NotImplementedError() + } + override fun getNodeOrNull(path: Path, lowercased: Boolean, littleCamelCased: Boolean): TreeNode? { val nodes = sources.map { it.getNodeOrNull(path, lowercased, littleCamelCased) diff --git a/konf-core/src/main/kotlin/com/uchuhimo/konf/source/Source.kt b/konf-core/src/main/kotlin/com/uchuhimo/konf/source/Source.kt index 3ebe9cd6..da5fbfed 100644 --- a/konf-core/src/main/kotlin/com/uchuhimo/konf/source/Source.kt +++ b/konf-core/src/main/kotlin/com/uchuhimo/konf/source/Source.kt @@ -154,23 +154,51 @@ interface Source { * `null` otherwise */ fun getOrNull(path: Path): Source? { - if (path.isEmpty()) { - return this + return if (path.isEmpty()) { + this } else { - return normalized().tree.getOrNull(normalizedPath(path))?.let { + getTreeOrNull(tree, normalizedPath(path))?.let { Source(info = info, tree = it, features = features) } } } + private fun getTreeOrNull(tree: TreeNode, path: Path): TreeNode? { + return if (path.isEmpty()) { + tree + } else { + val key = normalizedKey(path.first()) + val rest = path.drop(1) + var result: TreeNode? = null + for ((childKey, child) in tree.children) { + if (key == normalizedKey(childKey)) { + result = child + break + } + } + result?.let { getTreeOrNull(it, rest) } + } + } + + private fun normalizedKey(key: String): String { + var currentKey = key + if (isEnabled(Feature.LOAD_KEYS_AS_LITTLE_CAMEL_CASE)) { + currentKey = currentKey.toLittleCamelCase() + } + if (isEnabled(Feature.LOAD_KEYS_CASE_INSENSITIVELY)) { + currentKey = currentKey.toLowerCase() + } + return currentKey + } + private fun normalizedPath(path: Path, lowercased: Boolean = false, littleCamelCased: Boolean = true): Path { var currentPath = path - if (lowercased || isEnabled(Feature.LOAD_KEYS_CASE_INSENSITIVELY)) { - currentPath = currentPath.map { it.toLowerCase() } - } if (littleCamelCased && isEnabled(Feature.LOAD_KEYS_AS_LITTLE_CAMEL_CASE)) { currentPath = currentPath.map { it.toLittleCamelCase() } } + if (lowercased || isEnabled(Feature.LOAD_KEYS_CASE_INSENSITIVELY)) { + currentPath = currentPath.map { it.toLowerCase() } + } return currentPath } @@ -309,8 +337,8 @@ interface Source { fun normalized(lowercased: Boolean = false, littleCamelCased: Boolean = true): Source { var currentSource = this - currentSource = currentSource.lowercased(lowercased) currentSource = currentSource.littleCamelCased(littleCamelCased) + currentSource = currentSource.lowercased(lowercased) return currentSource } @@ -613,13 +641,13 @@ internal fun Config.loadItem(item: Item<*>, path: Path, source: Source): Boolean internal fun load(config: Config, source: Source): Source { var currentSource = source - currentSource = currentSource.substituted( - enabled = config.isEnabled(Feature.SUBSTITUTE_SOURCE_BEFORE_LOADED) - ) currentSource = currentSource.normalized( lowercased = config.isEnabled(Feature.LOAD_KEYS_CASE_INSENSITIVELY), littleCamelCased = config.isEnabled(Feature.LOAD_KEYS_AS_LITTLE_CAMEL_CASE) ) + currentSource = currentSource.substituted( + enabled = config.isEnabled(Feature.SUBSTITUTE_SOURCE_BEFORE_LOADED) + ) config.lock { for (item in config) { config.loadItem(item, config.pathOf(item), currentSource) diff --git a/konf-core/src/main/kotlin/com/uchuhimo/konf/source/env/EnvProvider.kt b/konf-core/src/main/kotlin/com/uchuhimo/konf/source/env/EnvProvider.kt index dedd1472..e0db8c52 100644 --- a/konf-core/src/main/kotlin/com/uchuhimo/konf/source/env/EnvProvider.kt +++ b/konf-core/src/main/kotlin/com/uchuhimo/konf/source/env/EnvProvider.kt @@ -34,9 +34,12 @@ object EnvProvider { * @return a new source from system environment */ @JvmOverloads - fun env(nested: Boolean = true): Source { + fun env(nested: Boolean = true): Source = envMap(System.getenv(), nested) + + @JvmOverloads + fun envMap(map: Map, nested: Boolean = true): Source { return FlatSource( - System.getenv().mapKeys { (key, _) -> + map.mapKeys { (key, _) -> if (nested) key.replace('_', '.') else key }.filter { (key, _) -> key.matches(validEnv) diff --git a/konf-core/src/test/kotlin/com/uchuhimo/konf/source/DefaultLoadersSpec.kt b/konf-core/src/test/kotlin/com/uchuhimo/konf/source/DefaultLoadersSpec.kt index c1746434..3fa95236 100644 --- a/konf-core/src/test/kotlin/com/uchuhimo/konf/source/DefaultLoadersSpec.kt +++ b/konf-core/src/test/kotlin/com/uchuhimo/konf/source/DefaultLoadersSpec.kt @@ -48,6 +48,12 @@ object DefaultLoadersSpec : SubjectSpek({ val item = DefaultLoadersConfig.type given("a loader") { + on("load from environment-like map") { + val config = subject.envMap(mapOf("SOURCE_TEST_TYPE" to "env")) + it("should return a config which contains value from environment-like map") { + assertThat(config[item], equalTo("env")) + } + } on("load from system environment") { val config = subject.env() it("should return a config which contains value from system environment") {