Skip to content

Commit f7272ec

Browse files
Merge pull request #55 from aPureBase/v3.3.0
V3.3.0
2 parents c72509b + 8517a69 commit f7272ec

File tree

16 files changed

+242
-67
lines changed

16 files changed

+242
-67
lines changed

arkenv-yaml/build.gradle.kts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
plugins {
22
base
33
java
4-
kotlin("jvm") version "1.4.21"
4+
kotlin("jvm") version "1.4.32"
55
id("org.jetbrains.dokka") version "0.10.1"
66
signing
77
}

arkenv/build.gradle.kts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
plugins {
22
base
3-
kotlin("jvm") version "1.4.21"
3+
kotlin("jvm") version "1.4.32"
44
id("org.jetbrains.dokka") version "0.10.1"
55
id("java-test-fixtures")
66
signing

arkenv/src/main/kotlin/com/apurebase/arkenv/ArkenvMapper.kt

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
package com.apurebase.arkenv
22

33
import com.apurebase.arkenv.util.split
4+
import java.io.File
5+
import java.net.URI
6+
import java.net.URL
7+
import java.nio.file.Path
8+
import java.nio.file.Paths
49
import kotlin.reflect.KClass
510

611
/**
@@ -35,6 +40,13 @@ internal object ArkenvMapper {
3540
DoubleArray::class -> split().map(String::toDouble).toDoubleArray()
3641
BooleanArray::class -> split().map(String::toBoolean).toBooleanArray()
3742
ByteArray::class -> split().map(String::toByte).toByteArray()
43+
Path::class -> Paths.get(this)
44+
File::class -> File(this)
45+
URL::class -> URL(this)
46+
URI::class -> URI(this)
47+
IntRange::class -> split("..").let { IntRange(it[0].toInt(), it[1].toInt()) }
48+
LongRange::class -> split("..").let { LongRange(it[0].toLong(), it[1].toLong()) }
49+
CharRange::class -> CharRange(this[0], this[3])
3850
else -> throw UnsupportedMappingException(key, clazz)
3951
}
4052
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package com.apurebase.arkenv.feature.cli
2+
3+
/**
4+
* Parses the command line arguments.
5+
*/
6+
internal class CliArgumentParser {
7+
private val allowedSurroundings = listOf("'", "\"")
8+
private val list = mutableListOf<String>()
9+
private var isReading = false
10+
11+
/**
12+
* Parses the provided [arguments] and returns the accumulated results.
13+
* @param arguments List of raw command line string arguments to parse.
14+
*/
15+
fun parseArguments(arguments: List<String>): List<String> {
16+
arguments.forEach(::parse)
17+
return list
18+
}
19+
20+
private fun parse(value: String) {
21+
when {
22+
isReading -> list[list.lastIndex] = "${list.last()} $value"
23+
else -> list.add(value)
24+
}
25+
26+
when {
27+
isReading && value.endsWith(allowedSurroundings) -> {
28+
list[list.lastIndex] = list.last().removeSurrounding(allowedSurroundings)
29+
isReading = false
30+
}
31+
!isReading && value.startsWith(allowedSurroundings) -> isReading = true
32+
}
33+
}
34+
35+
private fun String.removeSurrounding(list: Iterable<CharSequence>): String =
36+
list.fold(this, String::removeSurrounding)
37+
38+
private fun String.startsWith(list: Iterable<String>): Boolean = list.any(::startsWith)
39+
40+
private fun String.endsWith(list: Iterable<CharSequence>): Boolean = list.any(::endsWith)
41+
}

arkenv/src/main/kotlin/com/apurebase/arkenv/feature/cli/CliFeature.kt

Lines changed: 4 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,11 @@
11
package com.apurebase.arkenv.feature.cli
22

3-
import com.apurebase.arkenv.*
3+
import com.apurebase.arkenv.Arkenv
44
import com.apurebase.arkenv.argument.Argument
55
import com.apurebase.arkenv.argument.ArkenvArgument
66
import com.apurebase.arkenv.feature.ArkenvFeature
7-
import com.apurebase.arkenv.util.*
8-
import com.apurebase.arkenv.util.endsWith
97
import com.apurebase.arkenv.util.mapRelaxed
10-
import com.apurebase.arkenv.util.startsWith
8+
import com.apurebase.arkenv.util.putAll
119
import com.apurebase.arkenv.util.toSnakeCase
1210
import kotlin.collections.set
1311

@@ -22,10 +20,10 @@ class CliFeature : ArkenvFeature {
2220
override fun onLoad(arkenv: Arkenv) {
2321
args = arkenv.argList
2422
args.replaceAll(String::mapRelaxed)
25-
loadCliAssignments().let(arkenv::putAll)
26-
val parsed = parseArguments(args)
23+
val parsed = CliArgumentParser().parseArguments(args)
2724
args.clear()
2825
args.addAll(parsed)
26+
loadCliAssignments().let(arkenv::putAll)
2927
}
3028

3129
override fun onParse(arkenv: Arkenv, delegate: ArkenvArgument<*>): String? =
@@ -66,25 +64,6 @@ class CliFeature : ArkenvFeature {
6664

6765
private fun parseCli(index: Int): String? = args.getOrNull(index + 1)
6866

69-
private fun parseArguments(arguments: List<String>): List<String> {
70-
val list = mutableListOf<String>()
71-
var isReading = false
72-
arguments.forEach { value ->
73-
when {
74-
isReading -> list[list.lastIndex] = "${list.last()} $value"
75-
else -> list.add(value)
76-
}
77-
when {
78-
isReading && value.endsWith(allowedSurroundings) -> {
79-
list[list.lastIndex] = list.last().removeSurrounding(allowedSurroundings)
80-
isReading = false
81-
}
82-
!isReading && value.startsWith(allowedSurroundings) -> isReading = true
83-
}
84-
}
85-
return list
86-
}
87-
8867
private fun findIndex(argument: Argument<*>, arguments: List<String>): Int? = when {
8968
argument.isMainArg -> arguments.size - 2
9069
else -> argument
@@ -105,6 +84,4 @@ class CliFeature : ArkenvFeature {
10584
private fun removeValueArgument(index: Int, isBoolean: Boolean, value: Any?, default: Boolean) {
10685
if (!isBoolean && !default && value != null) args.removeAt(index + 1)
10786
}
108-
109-
private val allowedSurroundings = listOf("'", "\"")
11087
}

arkenv/src/main/kotlin/com/apurebase/arkenv/util/StringUtils.kt

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,20 +6,13 @@ internal fun String.toSnakeCase() = this
66
.toUpperCase()
77
.removePrefixes("_")
88

9-
internal fun String.removePrefixes(prefix: CharSequence): String = this
9+
private fun String.removePrefixes(prefix: CharSequence): String = this
1010
.removePrefix(prefix)
1111
.let {
1212
if (it.startsWith(prefix)) it.removePrefixes(prefix)
1313
else it
1414
}
1515

16-
internal fun String.endsWith(list: Iterable<String>): Boolean = list.any(::endsWith)
17-
18-
internal fun String.startsWith(list: Iterable<String>): Boolean = list.any(::startsWith)
19-
20-
internal fun String.removeSurrounding(list: Iterable<String>): String =
21-
list.fold(this) { acc, s -> acc.removeSurrounding(s) }
22-
2316
internal fun String.isAdvancedName() = startsWith("--")
2417

2518
internal fun String.isSimpleName() = startsWith("-") && !isAdvancedName()

arkenv/src/test/kotlin/com/apurebase/arkenv/LookupTests.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,14 +48,14 @@ class LookupTests {
4848
val ark = Ark().parse("--other", "name")
4949
val key = "left-over"
5050
assertThrows<MissingArgumentException> { ark[key] }
51-
.expectThat { get { message }.isNotNull().contains(key) }
51+
.expectThat { get { message }.isNotNull() contains key }
5252
}
5353

5454
@Test fun `input key should be snake-case formatted`() {
5555
val expected = "app"
5656
MockSystem("CLIENT_DIR" to expected)
5757
Ark().parse().expectThat {
58-
get { get("clientDir") }.isEqualTo(expected)
58+
get { get("clientDir") } isEqualTo expected
5959
}
6060
}
6161

arkenv/src/test/kotlin/com/apurebase/arkenv/MappingTests.kt

Lines changed: 85 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,15 @@ package com.apurebase.arkenv
33
import com.apurebase.arkenv.test.expectThat
44
import com.apurebase.arkenv.test.parse
55
import com.apurebase.arkenv.util.argument
6+
import com.apurebase.arkenv.util.parse
67
import org.junit.jupiter.api.Test
78
import org.junit.jupiter.api.TestInstance
9+
import strikt.api.expectThat
810
import strikt.assertions.isEqualTo
11+
import java.io.File
12+
import java.net.URI
13+
import java.net.URL
14+
import java.nio.file.Path
915

1016
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
1117
internal class MappingTests {
@@ -17,8 +23,8 @@ internal class MappingTests {
1723
val list: List<String> by argument()
1824
val collection: Collection<String> by argument()
1925
}.parse("LIST", input, "COLLECTION", input).expectThat {
20-
get { list }.isEqualTo(output)
21-
get { collection }.isEqualTo(output)
26+
get { list } isEqualTo output
27+
get { collection } isEqualTo output
2228
}
2329
}
2430

@@ -94,6 +100,83 @@ internal class MappingTests {
94100
}
95101
}
96102

103+
@Test fun `Path should map`() {
104+
val expectedPath = "C:\\"
105+
val configuration = object {
106+
val path: Path by argument()
107+
}
108+
109+
Arkenv.parse(configuration, arrayOf("--path", expectedPath))
110+
111+
expectThat(configuration).get { path.toString() } isEqualTo expectedPath
112+
}
113+
114+
@Test fun `File should map`() {
115+
val expectedPath = "C:\\"
116+
val configuration = object {
117+
val file: File by argument()
118+
}
119+
120+
Arkenv.parse(configuration, arrayOf("--file", expectedPath))
121+
122+
expectThat(configuration).get { file.toString() } isEqualTo expectedPath
123+
}
124+
125+
@Test fun url() {
126+
val expectedUrl = "https://arkenv.io/features/mapping/"
127+
val configuration = object {
128+
val url: URL by argument()
129+
}
130+
131+
Arkenv.parse(configuration, arrayOf("--url", expectedUrl))
132+
133+
expectThat(configuration).get { url.toString() } isEqualTo expectedUrl
134+
}
135+
136+
@Test fun uri() {
137+
val expectedUri = "arkenv.io/features/mapping"
138+
val configuration = object {
139+
val uri: URI by argument()
140+
}
141+
142+
Arkenv.parse(configuration, arrayOf("--uri", expectedUri))
143+
144+
expectThat(configuration).get { uri.toString() } isEqualTo expectedUri
145+
}
146+
147+
@Test fun intRange() {
148+
val expectedRange = -5..101
149+
val configuration = object {
150+
val range: IntRange by argument()
151+
}
152+
153+
Arkenv.parse(configuration, arrayOf("--range", "-5..101"))
154+
155+
expectThat(configuration).get { range } isEqualTo expectedRange
156+
}
157+
158+
@Test fun longRange() {
159+
val expectedRange = -5L..101
160+
val configuration = object {
161+
val range: LongRange by argument()
162+
}
163+
164+
Arkenv.parse(configuration, arrayOf("--range", "-5..101"))
165+
166+
expectThat(configuration).get { range } isEqualTo expectedRange
167+
}
168+
169+
@Test fun charRange() {
170+
val expectedRange = CharRange('z', 'a')
171+
val configuration = object {
172+
val range: CharRange by argument()
173+
}
174+
175+
Arkenv.parse(configuration, arrayOf("--range", "z..a"))
176+
177+
expectThat(configuration).get { range } isEqualTo expectedRange
178+
}
179+
97180
private val floatingPointInput = "1.1,29.92,-387.9999"
98181
private val floatingPointOutput = listOf(1.1, 29.92, -387.9999)
99182
private val numericInput = "1,-29,387"
Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,17 @@
11
package com.apurebase.arkenv.feature.cli
22

33
import com.apurebase.arkenv.Arkenv
4-
import com.apurebase.arkenv.util.argument
54
import com.apurebase.arkenv.test.expectThat
65
import com.apurebase.arkenv.test.parse
6+
import com.apurebase.arkenv.util.argument
77
import org.junit.jupiter.api.Test
8+
import strikt.api.expectThat
89
import strikt.assertions.isEqualTo
910

11+
/**
12+
* Verify that command-line assignment style arguments (key=value) are parsed correctly.
13+
* @see CliFeature
14+
*/
1015
class AssignmentTest {
1116

1217
private class Ark : Arkenv() {
@@ -18,29 +23,36 @@ class AssignmentTest {
1823
}
1924

2025
@Test fun `should parse assignment correctly`() {
21-
Ark().parse("int=4").expectThat {
22-
get { int }.isEqualTo(4)
23-
get { bool }.isEqualTo(true)
26+
Ark().parse("int=4") expectThat {
27+
get { int } isEqualTo 4
28+
get { bool } isEqualTo true
2429
}
2530
}
2631

2732
@Test fun `should turn bool off`() {
28-
Ark().parse("int=-1", "bool=false").expectThat {
29-
get { int }.isEqualTo(-1)
30-
get { bool }.isEqualTo(false)
33+
Ark().parse("int=-1", "bool=false") expectThat {
34+
get { int } isEqualTo -1
35+
get { bool } isEqualTo false
3136
}
3237
}
3338

3439
@Test fun `should be able to use complex arg in assignment`() {
35-
Ark().parse("int=0", "complex-arg=false").expectThat {
36-
get { bool }.isEqualTo(false)
40+
Ark().parse("int=0", "complex-arg=false") expectThat {
41+
get { bool } isEqualTo false
3742
}
3843
}
3944

4045
@Test fun `should still allow = as part of other args`() {
41-
Ark().parse("--str", "key=value", "int=1").expectThat {
42-
get { string }.isEqualTo("key=value")
43-
get { int }.isEqualTo(1)
46+
Ark().parse("--str", "key=value", "int=1") expectThat {
47+
get { string } isEqualTo "key=value"
48+
get { int } isEqualTo 1
49+
}
50+
}
51+
52+
@Test fun `single quoted assignment`() {
53+
val ark = Ark().parse("int=0", "'str=test", "expected'")
54+
expectThat(ark) {
55+
get { string } isEqualTo "test expected"
4456
}
4557
}
4658
}

0 commit comments

Comments
 (0)