diff --git a/arkenv/build.gradle.kts b/arkenv/build.gradle.kts index 6e3f4fa..c221bc8 100644 --- a/arkenv/build.gradle.kts +++ b/arkenv/build.gradle.kts @@ -1,6 +1,6 @@ plugins { base - kotlin("jvm") version "1.5.10" + kotlin("jvm") version "1.5.21" id("org.jetbrains.dokka") version "1.5.0" id("java-test-fixtures") signing diff --git a/arkenv/src/main/kotlin/com/apurebase/arkenv/ArkenvBuilder.kt b/arkenv/src/main/kotlin/com/apurebase/arkenv/ArkenvBuilder.kt index e0b3e44..76dcaaa 100644 --- a/arkenv/src/main/kotlin/com/apurebase/arkenv/ArkenvBuilder.kt +++ b/arkenv/src/main/kotlin/com/apurebase/arkenv/ArkenvBuilder.kt @@ -9,12 +9,12 @@ import com.apurebase.arkenv.util.key * [Arkenv] configuration builder which controls features and other settings. * @param installAdvancedFeatures whether to install the profile and placeholder feature. */ -class ArkenvBuilder(installAdvancedFeatures: Boolean = true) { +class ArkenvBuilder(installAdvancedFeatures: Boolean = true) : ArkenvConfiguration { /** * A common prefix that is applied to all argument names. */ - var prefix: String? = null + override var prefix: String? = null /** * Whether data should be cleared before parsing. diff --git a/arkenv/src/main/kotlin/com/apurebase/arkenv/ArkenvConfiguration.kt b/arkenv/src/main/kotlin/com/apurebase/arkenv/ArkenvConfiguration.kt new file mode 100644 index 0000000..8746086 --- /dev/null +++ b/arkenv/src/main/kotlin/com/apurebase/arkenv/ArkenvConfiguration.kt @@ -0,0 +1,5 @@ +package com.apurebase.arkenv + +internal interface ArkenvConfiguration { + var prefix: String? +} \ No newline at end of file diff --git a/arkenv/src/main/kotlin/com/apurebase/arkenv/argument/ArgumentNameProcessor.kt b/arkenv/src/main/kotlin/com/apurebase/arkenv/argument/ArgumentNameProcessor.kt index b67c715..f9b1fba 100644 --- a/arkenv/src/main/kotlin/com/apurebase/arkenv/argument/ArgumentNameProcessor.kt +++ b/arkenv/src/main/kotlin/com/apurebase/arkenv/argument/ArgumentNameProcessor.kt @@ -1,5 +1,7 @@ package com.apurebase.arkenv.argument +import com.apurebase.arkenv.ArkenvBuilder +import com.apurebase.arkenv.ArkenvConfiguration import com.apurebase.arkenv.argument.ArkenvArgumentNamingStrategy.* import com.apurebase.arkenv.util.isAdvancedName import com.apurebase.arkenv.util.mapRelaxed @@ -10,6 +12,13 @@ internal class ArgumentNameProcessor( private val prefix: String?, private val namingStrategy: ArkenvArgumentNamingStrategy ) { + internal companion object { + fun get(moduleConfiguration: ArkenvConfiguration?, configuration: ArkenvBuilder): ArgumentNameProcessor { + val prefix = moduleConfiguration?.prefix ?: configuration.prefix + val namingStrategy = configuration.namingStrategy + return ArgumentNameProcessor(prefix, namingStrategy) + } + } /** * Processes the [argument]'s names, potentially updating and adding. @@ -20,8 +29,18 @@ internal class ArgumentNameProcessor( argument.names = getNames(argument.names, property.name) } + /** + * Processes a single name. + * @param name the name to process. + * @return the processed name. + */ + fun process(name: String) = name + .ensureStartsWithDash() + .prefix(prefix ?: "") + .mapRelaxed() + private fun getNames(argumentNames: List, propName: String): List = - getNameList(argumentNames, propName).map(::processName) + getNameList(argumentNames, propName).map(::process) private fun getNameList(argumentNames: List, propName: String): List { return when (namingStrategy) { @@ -32,11 +51,6 @@ internal class ArgumentNameProcessor( } } - private fun processName(name: String) = name - .ensureStartsWithDash() - .prefix(prefix ?: "") - .mapRelaxed() - private fun String.ensureStartsWithDash() = if (!startsWith("-")) "--$this" else this diff --git a/arkenv/src/main/kotlin/com/apurebase/arkenv/argument/ArkenvDelegateLoader.kt b/arkenv/src/main/kotlin/com/apurebase/arkenv/argument/ArkenvDelegateLoader.kt index 196370e..2679bd2 100644 --- a/arkenv/src/main/kotlin/com/apurebase/arkenv/argument/ArkenvDelegateLoader.kt +++ b/arkenv/src/main/kotlin/com/apurebase/arkenv/argument/ArkenvDelegateLoader.kt @@ -8,8 +8,7 @@ class ArkenvDelegateLoader( private val argument: Argument, private val arkenv: Arkenv ) { - private val argumentNameProcessor = ArgumentNameProcessor( - arkenv.configuration.prefix, arkenv.configuration.namingStrategy) + private val argumentNameProcessor = ArgumentNameProcessor.get(null, arkenv.configuration) operator fun provideDelegate(thisRef: Arkenv, prop: KProperty<*>): ReadOnlyProperty { argumentNameProcessor.processArgumentNames(argument, prop) diff --git a/arkenv/src/main/kotlin/com/apurebase/arkenv/argument/ArkenvSimpleArgument.kt b/arkenv/src/main/kotlin/com/apurebase/arkenv/argument/ArkenvSimpleArgument.kt index 9a42c1f..529b8fd 100644 --- a/arkenv/src/main/kotlin/com/apurebase/arkenv/argument/ArkenvSimpleArgument.kt +++ b/arkenv/src/main/kotlin/com/apurebase/arkenv/argument/ArkenvSimpleArgument.kt @@ -34,10 +34,8 @@ internal class ArkenvSimpleArgument( this.arkenv = arkenv this.property = property if (!isInitialized) { - val prefix = moduleConfiguration?.prefix ?: arkenv.configuration.prefix - val namingStrategy = arkenv.configuration.namingStrategy - val argumentNameProcessor = ArgumentNameProcessor(prefix, namingStrategy) - argumentNameProcessor.processArgumentNames(argument, property) + val nameProcessor = ArgumentNameProcessor.get(moduleConfiguration, arkenv.configuration) + nameProcessor.processArgumentNames(argument, property) isInitialized = true } } diff --git a/arkenv/src/main/kotlin/com/apurebase/arkenv/module/ArkenvModule.kt b/arkenv/src/main/kotlin/com/apurebase/arkenv/module/ArkenvModule.kt index 7f03666..64d519d 100644 --- a/arkenv/src/main/kotlin/com/apurebase/arkenv/module/ArkenvModule.kt +++ b/arkenv/src/main/kotlin/com/apurebase/arkenv/module/ArkenvModule.kt @@ -16,7 +16,7 @@ interface ArkenvModule : ReadOnlyProperty { fun initialize(arkenvParser: ArkenvParser<*>) { if (module == null) { - module = arkenvParser.createInstance(kClass) + module = arkenvParser.createInstance(kClass, configuration) } arkenvParser.parse(module!!, configuration) } diff --git a/arkenv/src/main/kotlin/com/apurebase/arkenv/module/ArkenvModuleConfiguration.kt b/arkenv/src/main/kotlin/com/apurebase/arkenv/module/ArkenvModuleConfiguration.kt index 7c73667..facc393 100644 --- a/arkenv/src/main/kotlin/com/apurebase/arkenv/module/ArkenvModuleConfiguration.kt +++ b/arkenv/src/main/kotlin/com/apurebase/arkenv/module/ArkenvModuleConfiguration.kt @@ -1,8 +1,10 @@ package com.apurebase.arkenv.module +import com.apurebase.arkenv.ArkenvConfiguration + /** * Module configuration value class. */ class ArkenvModuleConfiguration( - var prefix: String? = null -) + override var prefix: String? = null +) : ArkenvConfiguration diff --git a/arkenv/src/main/kotlin/com/apurebase/arkenv/parse/ArkenvParser.kt b/arkenv/src/main/kotlin/com/apurebase/arkenv/parse/ArkenvParser.kt index 25c68aa..18e8e1c 100644 --- a/arkenv/src/main/kotlin/com/apurebase/arkenv/parse/ArkenvParser.kt +++ b/arkenv/src/main/kotlin/com/apurebase/arkenv/parse/ArkenvParser.kt @@ -1,13 +1,13 @@ package com.apurebase.arkenv.parse -import com.apurebase.arkenv.Arkenv -import com.apurebase.arkenv.ArkenvBuilder +import com.apurebase.arkenv.* +import com.apurebase.arkenv.ArkenvConfiguration import com.apurebase.arkenv.ArkenvMapper import com.apurebase.arkenv.ParsingException +import com.apurebase.arkenv.argument.ArgumentNameProcessor import com.apurebase.arkenv.argument.ArkenvArgument import com.apurebase.arkenv.module.ArkenvModule import com.apurebase.arkenv.module.ArkenvModuleConfiguration -import com.apurebase.arkenv.util.toSnakeCase import kotlin.reflect.KClass import kotlin.reflect.KFunction import kotlin.reflect.KParameter @@ -43,7 +43,7 @@ class ArkenvParser( * @throws ParsingException when parsing was unsuccessful. */ fun parseClass(): T { - val instance = createInstance(kClass) + val instance = createInstance(kClass, null) parse(instance) return instance } @@ -54,28 +54,29 @@ class ArkenvParser( arkenv.parsePostLoad() } - internal fun createInstance(kClass: KClass): R { + internal fun createInstance(kClass: KClass, configuration: ArkenvConfiguration?): R { val constructor = kClass.constructors.firstOrNull() ?: throw ParsingException(className, IllegalStateException("No valid constructor found")) - return parseConstructor(constructor) + return parseConstructor(constructor, configuration) } - private fun parseConstructor(constructor: KFunction): R = try { - val constructorArgs = parseConstructorArgs(constructor.parameters) + private fun parseConstructor(constructor: KFunction, configuration: ArkenvConfiguration?): R = try { + val constructorArgs = parseConstructorArgs(constructor.parameters, configuration) constructor.callBy(constructorArgs) } catch (ex: IllegalArgumentException) { throw ParsingException(className, ex) } - private fun parseConstructorArgs(parameters: Collection): Map { + private fun parseConstructorArgs(parameters: Collection, configuration: ArkenvConfiguration?): Map { + val nameProcessor = ArgumentNameProcessor.get(configuration, arkenv.configuration) return parameters .filterNot { it.name.isNullOrBlank() } - .mapNotNull(::parseConstructorParameter) + .mapNotNull { parseConstructorParameter(it, nameProcessor) } .toMap() } - private fun parseConstructorParameter(parameter: KParameter): Pair? { - val name = parameter.name!!.toSnakeCase() + private fun parseConstructorParameter(parameter: KParameter, nameProcessor: ArgumentNameProcessor): Pair? { + val name = nameProcessor.process(parameter.name!!) val value = arkenv.getOrNull(name) return if (parameter.isOptional && value == null) null else { diff --git a/arkenv/src/test/kotlin/com/apurebase/arkenv/ParseClassTests.kt b/arkenv/src/test/kotlin/com/apurebase/arkenv/ParseClassTests.kt index 66eee06..5a93b78 100644 --- a/arkenv/src/test/kotlin/com/apurebase/arkenv/ParseClassTests.kt +++ b/arkenv/src/test/kotlin/com/apurebase/arkenv/ParseClassTests.kt @@ -1,6 +1,7 @@ package com.apurebase.arkenv import com.apurebase.arkenv.feature.ProfileFeature +import com.apurebase.arkenv.module.module import com.apurebase.arkenv.test.Expected import com.apurebase.arkenv.util.argument import com.apurebase.arkenv.util.parse @@ -124,4 +125,53 @@ class ParseClassTests { // Assert expectThat(config) { get { headless }.isFalse() } } + + @Test fun `module by class is parsed`() { + // Arrange + val expectedCountry = "DK" + class SubConfig(val country: String) { + val port: Int by argument() + } + class Config { + val subModule by module() + } + + // Act + val config = Arkenv.parse(arrayOf("--country", expectedCountry)) + + // Assert + expectThat(config.subModule) { + get { port } isEqualTo Expected.port + get { country } isEqualTo expectedCountry + } + } + + @Test fun `prefix applied to constructor`() { + // Arrange + val expectedPort = 199 + class PrefixConstructor(val port: Int) + + // Act + val config = Arkenv.parse(arrayOf("--database-port", expectedPort.toString())) { prefix = "database" } + + // Assert + expectThat(config).get { port } isEqualTo expectedPort + } + + @Test fun `common prefix per module`() { + // Arrange + val prefix = "database" + val expectedPort = 199 + class Nested(val port: Int) + + class Config { + val database: Nested by module { this.prefix = "database" } + } + + // Act + val config = Arkenv.parse(arrayOf("--$prefix-port", expectedPort.toString())) + + // Assert + expectThat(config.database).get { port } isEqualTo expectedPort + } } diff --git a/gradle.properties b/gradle.properties index 082706a..3be1c6b 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,5 @@ group=com.apurebase -version=3.3.2 +version=3.3.3 # Versions junitVersion=5.7.1