Skip to content

Commit

Permalink
Merge pull request #68 from aPureBase/v3.3.3
Browse files Browse the repository at this point in the history
Support plain class module prefix
  • Loading branch information
AndreasVolkmann authored Aug 23, 2021
2 parents a1e58a0 + a6793f9 commit 8ccfc2e
Show file tree
Hide file tree
Showing 11 changed files with 100 additions and 31 deletions.
2 changes: 1 addition & 1 deletion arkenv/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -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
Expand Down
4 changes: 2 additions & 2 deletions arkenv/src/main/kotlin/com/apurebase/arkenv/ArkenvBuilder.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.apurebase.arkenv

internal interface ArkenvConfiguration {
var prefix: String?
}
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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.
Expand All @@ -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<String>, propName: String): List<String> =
getNameList(argumentNames, propName).map(::processName)
getNameList(argumentNames, propName).map(::process)

private fun getNameList(argumentNames: List<String>, propName: String): List<String> {
return when (namingStrategy) {
Expand All @@ -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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,7 @@ class ArkenvDelegateLoader<T : Any>(
private val argument: Argument<T>,
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<Arkenv, T> {
argumentNameProcessor.processArgumentNames(argument, prop)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,8 @@ internal class ArkenvSimpleArgument<T : Any?>(
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
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ interface ArkenvModule<T : Any> : ReadOnlyProperty<Any, T> {

fun initialize(arkenvParser: ArkenvParser<*>) {
if (module == null) {
module = arkenvParser.createInstance(kClass)
module = arkenvParser.createInstance(kClass, configuration)
}
arkenvParser.parse(module!!, configuration)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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
25 changes: 13 additions & 12 deletions arkenv/src/main/kotlin/com/apurebase/arkenv/parse/ArkenvParser.kt
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -43,7 +43,7 @@ class ArkenvParser<T : Any>(
* @throws ParsingException when parsing was unsuccessful.
*/
fun parseClass(): T {
val instance = createInstance(kClass)
val instance = createInstance(kClass, null)
parse(instance)
return instance
}
Expand All @@ -54,28 +54,29 @@ class ArkenvParser<T : Any>(
arkenv.parsePostLoad()
}

internal fun <R : Any> createInstance(kClass: KClass<R>): R {
internal fun <R : Any> createInstance(kClass: KClass<R>, 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 <R> parseConstructor(constructor: KFunction<R>): R = try {
val constructorArgs = parseConstructorArgs(constructor.parameters)
private fun <R> parseConstructor(constructor: KFunction<R>, 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<KParameter>): Map<KParameter, Any?> {
private fun parseConstructorArgs(parameters: Collection<KParameter>, configuration: ArkenvConfiguration?): Map<KParameter, Any?> {
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<KParameter, Any?>? {
val name = parameter.name!!.toSnakeCase()
private fun parseConstructorParameter(parameter: KParameter, nameProcessor: ArgumentNameProcessor): Pair<KParameter, Any?>? {
val name = nameProcessor.process(parameter.name!!)
val value = arkenv.getOrNull(name)
return if (parameter.isOptional && value == null) null
else {
Expand Down
50 changes: 50 additions & 0 deletions arkenv/src/test/kotlin/com/apurebase/arkenv/ParseClassTests.kt
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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<SubConfig>()
}

// Act
val config = Arkenv.parse<Config>(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<PrefixConstructor>(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<Config>(arrayOf("--$prefix-port", expectedPort.toString()))

// Assert
expectThat(config.database).get { port } isEqualTo expectedPort
}
}
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
group=com.apurebase
version=3.3.2
version=3.3.3

# Versions
junitVersion=5.7.1
Expand Down

0 comments on commit 8ccfc2e

Please sign in to comment.