mirror of
https://github.com/apple/pkl.git
synced 2026-06-12 00:24:37 +02:00
2b3603b544
ktfmt has much improved how it formats Kotlin code. Unfortunately, this means that whenever we touch a single line in a Kotlin file, we get a _lot_ more changes thanks to ratcheting now picking up this file for formatting. This PR just reformats every single Kotlin file so we don't have to deal with this churn in future PRs that touch Kotlin code.
1290 lines
30 KiB
Kotlin
1290 lines
30 KiB
Kotlin
/*
|
|
* Copyright © 2025-2026 Apple Inc. and the Pkl project authors. All rights reserved.
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* https://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
package org.pkl.cli
|
|
|
|
import com.github.ajalt.clikt.core.CliktError
|
|
import com.github.ajalt.clikt.core.MissingOption
|
|
import com.github.ajalt.clikt.core.PrintCompletionMessage
|
|
import com.github.tomakehurst.wiremock.junit5.WireMockTest
|
|
import java.io.ByteArrayOutputStream
|
|
import java.net.URI
|
|
import java.nio.charset.StandardCharsets
|
|
import java.nio.file.Files
|
|
import java.nio.file.Path
|
|
import kotlin.io.path.ExperimentalPathApi
|
|
import kotlin.io.path.createDirectories
|
|
import kotlin.io.path.createParentDirectories
|
|
import kotlin.io.path.deleteRecursively
|
|
import org.assertj.core.api.Assertions.assertThat
|
|
import org.junit.jupiter.api.AfterAll
|
|
import org.junit.jupiter.api.AfterEach
|
|
import org.junit.jupiter.api.Test
|
|
import org.junit.jupiter.api.assertThrows
|
|
import org.pkl.commons.cli.CliBaseOptions
|
|
import org.pkl.commons.cli.CliException
|
|
import org.pkl.commons.test.FileTestUtils
|
|
import org.pkl.commons.test.PackageServer
|
|
import org.pkl.commons.writeString
|
|
|
|
@OptIn(ExperimentalPathApi::class)
|
|
@WireMockTest(httpsEnabled = true, proxyMode = true)
|
|
class CliCommandRunnerTest {
|
|
private val renderOptions =
|
|
"""
|
|
extends "pkl:Command"
|
|
|
|
options: Options
|
|
|
|
output {
|
|
value = options
|
|
}
|
|
|
|
"""
|
|
.trimIndent()
|
|
|
|
companion object {
|
|
private val packageServer = PackageServer()
|
|
|
|
@AfterAll
|
|
@JvmStatic
|
|
fun afterAll() {
|
|
packageServer.close()
|
|
}
|
|
}
|
|
|
|
// use manually constructed temp dir instead of @TempDir to work around
|
|
// https://forums.developer.apple.com/thread/118358
|
|
private val tempDir: Path = run {
|
|
val baseDir = FileTestUtils.rootProjectDir.resolve("pkl-cli/build/tmp/CliCommandRunnerTest")
|
|
baseDir.createDirectories()
|
|
Files.createTempDirectory(baseDir, null)
|
|
}
|
|
|
|
@AfterEach
|
|
fun afterEach() {
|
|
tempDir.deleteRecursively()
|
|
}
|
|
|
|
private fun writePklFile(fileName: String, contents: String): URI {
|
|
tempDir.resolve(fileName).createParentDirectories()
|
|
return tempDir.resolve(fileName).writeString(contents).toUri()
|
|
}
|
|
|
|
private fun runToStdout(options: CliBaseOptions, args: List<String>): String {
|
|
val outWriter = ByteArrayOutputStream()
|
|
CliCommandRunner(
|
|
options,
|
|
setOf("root-dir"),
|
|
emptySet(),
|
|
args,
|
|
outWriter,
|
|
ByteArrayOutputStream(),
|
|
)
|
|
.run()
|
|
return outWriter.toString(StandardCharsets.UTF_8)
|
|
}
|
|
|
|
@Test
|
|
fun `missing required flag`() {
|
|
val moduleUri =
|
|
writePklFile(
|
|
"cmd.pkl",
|
|
renderOptions +
|
|
"""
|
|
class Options {
|
|
foo: String
|
|
}
|
|
"""
|
|
.trimIndent(),
|
|
)
|
|
|
|
val exc =
|
|
assertThrows<MissingOption> {
|
|
runToStdout(CliBaseOptions(sourceModules = listOf(moduleUri), testMode = true), listOf())
|
|
}
|
|
assertThat(exc.paramName).isEqualTo("--foo")
|
|
}
|
|
|
|
@Test
|
|
fun `primitive flags`() {
|
|
val moduleUri =
|
|
writePklFile(
|
|
"cmd.pkl",
|
|
renderOptions +
|
|
"""
|
|
class Options {
|
|
`number-as-int`: Number
|
|
`number-as-float`: Number
|
|
`number-nullable`: Number?
|
|
`number-default`: Number = 100.0
|
|
`number-default-overridden`: Number = 100.0
|
|
|
|
float: Float
|
|
`float-without-decimals`: Float
|
|
`float-nullable`: Float?
|
|
`float-default`: Float = 100.0
|
|
`float-default-overridden`: Float = 100.0
|
|
|
|
int: Int
|
|
`int-nullable`: Int?
|
|
`int-default`: Int = 100
|
|
`int-default-overridden`: Int = 100
|
|
|
|
int8: Int8
|
|
int16: Int16
|
|
int32: Int32
|
|
uint: UInt
|
|
uint8: UInt8
|
|
uint16: UInt16
|
|
uint32: UInt32
|
|
|
|
boolean: Boolean
|
|
`boolean-nullable`: Boolean?
|
|
`boolean-default`: Boolean = true
|
|
`boolean-default-overridden`: Boolean = false
|
|
|
|
string: String
|
|
`string-nullable`: String?
|
|
`string-default`: String = "default"
|
|
`string-default-overridden`: String = "default"
|
|
|
|
char: Char
|
|
`char-nullable`: Char?
|
|
`char-default`: Char = "a"
|
|
`char-default-overridden`: Char = "b"
|
|
}
|
|
"""
|
|
.trimIndent(),
|
|
)
|
|
val output =
|
|
runToStdout(
|
|
CliBaseOptions(sourceModules = listOf(moduleUri)),
|
|
listOf(
|
|
"--number-as-int=123",
|
|
"--number-as-float=123.0",
|
|
"--number-default-overridden=-200.0",
|
|
"--float=123.456",
|
|
"--float-without-decimals=789",
|
|
"--float-default-overridden=-200",
|
|
"--int=123",
|
|
"--int-default-overridden=-200",
|
|
"--int8=127",
|
|
"--int16=32767",
|
|
"--int32=2147483647",
|
|
"--uint=0",
|
|
"--uint8=255",
|
|
"--uint16=65535",
|
|
"--uint32=4294967295",
|
|
"--boolean=n",
|
|
"--boolean-default-overridden=1",
|
|
"--string=foobar",
|
|
"--string-default-overridden=non-default",
|
|
"--char=X",
|
|
"--char-default-overridden=c",
|
|
),
|
|
)
|
|
assertThat(output)
|
|
.isEqualTo(
|
|
"""
|
|
`number-as-int` = 123
|
|
`number-as-float` = 123.0
|
|
`number-nullable` = null
|
|
`number-default` = 100.0
|
|
`number-default-overridden` = -200.0
|
|
float = 123.456
|
|
`float-without-decimals` = 789.0
|
|
`float-nullable` = null
|
|
`float-default` = 100.0
|
|
`float-default-overridden` = -200.0
|
|
int = 123
|
|
`int-nullable` = null
|
|
`int-default` = 100
|
|
`int-default-overridden` = -200
|
|
int8 = 127
|
|
int16 = 32767
|
|
int32 = 2147483647
|
|
uint = 0
|
|
uint8 = 255
|
|
uint16 = 65535
|
|
uint32 = 4294967295
|
|
boolean = false
|
|
`boolean-nullable` = null
|
|
`boolean-default` = true
|
|
`boolean-default-overridden` = true
|
|
string = "foobar"
|
|
`string-nullable` = null
|
|
`string-default` = "default"
|
|
`string-default-overridden` = "non-default"
|
|
char = "X"
|
|
`char-nullable` = null
|
|
`char-default` = "a"
|
|
`char-default-overridden` = "c"
|
|
|
|
"""
|
|
.trimIndent()
|
|
)
|
|
}
|
|
|
|
@Test
|
|
fun `primitive arguments`() {
|
|
val moduleUri =
|
|
writePklFile(
|
|
"cmd.pkl",
|
|
renderOptions +
|
|
"""
|
|
class Options {
|
|
@Argument
|
|
`number-as-int`: Number
|
|
@Argument
|
|
`number-as-float`: Number
|
|
|
|
@Argument
|
|
float: Float
|
|
@Argument
|
|
`float-without-decimals`: Float
|
|
|
|
@Argument
|
|
int: Int
|
|
|
|
@Argument
|
|
int8: Int8
|
|
@Argument
|
|
int16: Int16
|
|
@Argument
|
|
int32: Int32
|
|
@Argument
|
|
uint: UInt
|
|
@Argument
|
|
uint8: UInt8
|
|
@Argument
|
|
uint16: UInt16
|
|
@Argument
|
|
uint32: UInt32
|
|
|
|
@Argument
|
|
boolean: Boolean
|
|
|
|
@Argument
|
|
string: String
|
|
|
|
@Argument
|
|
char: Char
|
|
}
|
|
"""
|
|
.trimIndent(),
|
|
)
|
|
val output =
|
|
runToStdout(
|
|
CliBaseOptions(sourceModules = listOf(moduleUri)),
|
|
listOf(
|
|
"123",
|
|
"123.0",
|
|
"123.456",
|
|
"789",
|
|
"123",
|
|
"127",
|
|
"32767",
|
|
"2147483647",
|
|
"0",
|
|
"255",
|
|
"65535",
|
|
"4294967295",
|
|
"n",
|
|
"foobar",
|
|
"X",
|
|
),
|
|
)
|
|
assertThat(output)
|
|
.isEqualTo(
|
|
"""
|
|
`number-as-int` = 123
|
|
`number-as-float` = 123.0
|
|
float = 123.456
|
|
`float-without-decimals` = 789.0
|
|
int = 123
|
|
int8 = 127
|
|
int16 = 32767
|
|
int32 = 2147483647
|
|
uint = 0
|
|
uint8 = 255
|
|
uint16 = 65535
|
|
uint32 = 4294967295
|
|
boolean = false
|
|
string = "foobar"
|
|
char = "X"
|
|
|
|
"""
|
|
.trimIndent()
|
|
)
|
|
}
|
|
|
|
@Test
|
|
fun `enum flags`() {
|
|
val moduleUri =
|
|
writePklFile(
|
|
"cmd.pkl",
|
|
renderOptions +
|
|
"""
|
|
typealias MyEnum = "d" | "e" | *"f"
|
|
class Options {
|
|
enum: "a" | "b" | "c"
|
|
`enum-default`: "a" | *"b" | "c"
|
|
`enum-explicit-default`: "a" | "b" | "c" = "c"
|
|
`enum-alias-default`: MyEnum
|
|
`enum-alias-explicit-default`: MyEnum = "e"
|
|
`enum-alias-default-overridden`: MyEnum
|
|
|
|
`enum-single`: "x"
|
|
`enum-single-nullable`: "x"?
|
|
`enum-single-explicit-default`: "x" = "x"
|
|
`enum-single-overridden`: "x"
|
|
}
|
|
"""
|
|
.trimIndent(),
|
|
)
|
|
val output =
|
|
runToStdout(
|
|
CliBaseOptions(sourceModules = listOf(moduleUri)),
|
|
listOf("--enum=a", "--enum-alias-default-overridden=d", "--enum-single-overridden=x"),
|
|
)
|
|
assertThat(output)
|
|
.isEqualTo(
|
|
"""
|
|
enum = "a"
|
|
`enum-default` = "b"
|
|
`enum-explicit-default` = "c"
|
|
`enum-alias-default` = "f"
|
|
`enum-alias-explicit-default` = "e"
|
|
`enum-alias-default-overridden` = "d"
|
|
`enum-single` = "x"
|
|
`enum-single-nullable` = null
|
|
`enum-single-explicit-default` = "x"
|
|
`enum-single-overridden` = "x"
|
|
|
|
"""
|
|
.trimIndent()
|
|
)
|
|
}
|
|
|
|
@Test
|
|
fun `enum args`() {
|
|
val moduleUri =
|
|
writePklFile(
|
|
"cmd.pkl",
|
|
renderOptions +
|
|
"""
|
|
typealias MyEnum = "d" | "e" | *"f"
|
|
class Options {
|
|
@Argument
|
|
enum: "a" | "b" | "c"
|
|
@Argument
|
|
`enum-default`: "a" | *"b" | "c"
|
|
@Argument
|
|
`enum-alias-default`: MyEnum
|
|
}
|
|
"""
|
|
.trimIndent(),
|
|
)
|
|
val output =
|
|
runToStdout(CliBaseOptions(sourceModules = listOf(moduleUri)), listOf("a", "c", "d"))
|
|
assertThat(output)
|
|
.isEqualTo(
|
|
"""
|
|
enum = "a"
|
|
`enum-default` = "c"
|
|
`enum-alias-default` = "d"
|
|
|
|
"""
|
|
.trimIndent()
|
|
)
|
|
}
|
|
|
|
@Test
|
|
fun `collection flags`() {
|
|
val moduleUri =
|
|
writePklFile(
|
|
"cmd.pkl",
|
|
renderOptions +
|
|
"""
|
|
class Options {
|
|
list: List<Number>
|
|
`list-nullable`: List<Number>?
|
|
`list-default`: List<Number> = List(1, 2, 300.0)
|
|
set: Set<Number>
|
|
`set-nullable`: Set<Number>?
|
|
`set-default`: Set<Number> = Set(1, 2, 300.0, 2)
|
|
|
|
`enum-list`: List<"a" | "b" | *"c">
|
|
`enum-set`: Set<"a" | "b" | *"c">
|
|
}
|
|
"""
|
|
.trimIndent(),
|
|
)
|
|
val output =
|
|
runToStdout(
|
|
CliBaseOptions(sourceModules = listOf(moduleUri)),
|
|
listOf(
|
|
"--list=1",
|
|
"--list=0",
|
|
"--list=0.0",
|
|
"--list=1",
|
|
"--set=1",
|
|
"--set=0",
|
|
"--set=0.0",
|
|
"--set=1",
|
|
"--enum-list=a",
|
|
"--enum-list=a",
|
|
"--enum-list=b",
|
|
"--enum-set=a",
|
|
"--enum-set=a",
|
|
"--enum-set=b",
|
|
),
|
|
)
|
|
assertThat(output)
|
|
.isEqualTo(
|
|
"""
|
|
list = List(1, 0, 0.0, 1)
|
|
`list-nullable` = null
|
|
`list-default` = List(1, 2, 300.0)
|
|
set = Set(1, 0, 0.0)
|
|
`set-nullable` = null
|
|
`set-default` = Set(1, 2, 300.0)
|
|
`enum-list` = List("a", "a", "b")
|
|
`enum-set` = Set("a", "b")
|
|
|
|
"""
|
|
.trimIndent()
|
|
)
|
|
}
|
|
|
|
@Test
|
|
fun `sequence args`() {
|
|
val moduleUri =
|
|
writePklFile(
|
|
"cmd.pkl",
|
|
renderOptions +
|
|
"""
|
|
class Options {
|
|
@Argument
|
|
list: List<Number>
|
|
}
|
|
"""
|
|
.trimIndent(),
|
|
)
|
|
val output =
|
|
runToStdout(CliBaseOptions(sourceModules = listOf(moduleUri)), listOf("1", "0", "0.0", "1"))
|
|
assertThat(output)
|
|
.isEqualTo(
|
|
"""
|
|
list = List(1, 0, 0.0, 1)
|
|
|
|
"""
|
|
.trimIndent()
|
|
)
|
|
|
|
val moduleUri2 =
|
|
writePklFile(
|
|
"cmd.pkl",
|
|
renderOptions +
|
|
"""
|
|
class Options {
|
|
@Argument
|
|
set: Set<Number>
|
|
}
|
|
"""
|
|
.trimIndent(),
|
|
)
|
|
val output2 =
|
|
runToStdout(CliBaseOptions(sourceModules = listOf(moduleUri2)), listOf("1", "0", "0.0", "1"))
|
|
assertThat(output2)
|
|
.isEqualTo(
|
|
"""
|
|
set = Set(1, 0, 0.0)
|
|
|
|
"""
|
|
.trimIndent()
|
|
)
|
|
|
|
val moduleUri3 =
|
|
writePklFile(
|
|
"cmd.pkl",
|
|
renderOptions +
|
|
"""
|
|
class Options {
|
|
@Argument
|
|
listing: Listing<Number>
|
|
}
|
|
"""
|
|
.trimIndent(),
|
|
)
|
|
val output3 =
|
|
runToStdout(CliBaseOptions(sourceModules = listOf(moduleUri3)), listOf("1", "0", "0.0", "1"))
|
|
assertThat(output3)
|
|
.isEqualTo(
|
|
"""
|
|
listing {
|
|
1
|
|
0
|
|
0.0
|
|
1
|
|
}
|
|
|
|
"""
|
|
.trimIndent()
|
|
)
|
|
}
|
|
|
|
@Test
|
|
fun `keyval args`() {
|
|
val moduleUri =
|
|
writePklFile(
|
|
"cmd.pkl",
|
|
renderOptions +
|
|
"""
|
|
class Options {
|
|
@Argument
|
|
map: Map<Number, Number>
|
|
}
|
|
"""
|
|
.trimIndent(),
|
|
)
|
|
val output =
|
|
runToStdout(CliBaseOptions(sourceModules = listOf(moduleUri)), listOf("1=0", "0.0=1"))
|
|
assertThat(output)
|
|
.isEqualTo(
|
|
"""
|
|
map = Map(1, 0, 0.0, 1)
|
|
|
|
"""
|
|
.trimIndent()
|
|
)
|
|
|
|
val moduleUri2 =
|
|
writePklFile(
|
|
"cmd.pkl",
|
|
renderOptions +
|
|
"""
|
|
class Options {
|
|
@Argument
|
|
mapping: Mapping<Number, Number>
|
|
}
|
|
"""
|
|
.trimIndent(),
|
|
)
|
|
val output2 =
|
|
runToStdout(CliBaseOptions(sourceModules = listOf(moduleUri2)), listOf("1=0", "0.0=1"))
|
|
assertThat(output2)
|
|
.isEqualTo(
|
|
"""
|
|
mapping {
|
|
[1] = 0
|
|
[0.0] = 1
|
|
}
|
|
|
|
"""
|
|
.trimIndent()
|
|
)
|
|
|
|
val moduleUri3 =
|
|
writePklFile(
|
|
"cmd.pkl",
|
|
renderOptions +
|
|
"""
|
|
class Options {
|
|
@Argument
|
|
pair: Pair<Number, Number>
|
|
}
|
|
"""
|
|
.trimIndent(),
|
|
)
|
|
val output3 = runToStdout(CliBaseOptions(sourceModules = listOf(moduleUri3)), listOf("1=0.0"))
|
|
assertThat(output3)
|
|
.isEqualTo(
|
|
"""
|
|
pair = Pair(1, 0.0)
|
|
|
|
"""
|
|
.trimIndent()
|
|
)
|
|
}
|
|
|
|
@Test
|
|
fun `map flags`() {
|
|
val moduleUri =
|
|
writePklFile(
|
|
"cmd.pkl",
|
|
renderOptions +
|
|
"""
|
|
typealias MyEnum = "a" | "b" | *"c"
|
|
class Options {
|
|
map: Map<Char, Number>
|
|
`map-nullable`: Map<Char, Number>?
|
|
`map-default`: Map<Char, Number> = Map("x", 123, "y", 456.789)
|
|
|
|
`enum-map`: Map<MyEnum, MyEnum>
|
|
}
|
|
"""
|
|
.trimIndent(),
|
|
)
|
|
val output =
|
|
runToStdout(
|
|
CliBaseOptions(sourceModules = listOf(moduleUri)),
|
|
listOf("--map=a=0.0", "--map=b=1", "--enum-map=a=b", "--enum-map=b=c"),
|
|
)
|
|
assertThat(output)
|
|
.isEqualTo(
|
|
"""
|
|
map = Map("a", 0.0, "b", 1)
|
|
`map-nullable` = null
|
|
`map-default` = Map("x", 123, "y", 456.789)
|
|
`enum-map` = Map("a", "b", "b", "c")
|
|
|
|
"""
|
|
.trimIndent()
|
|
)
|
|
}
|
|
|
|
@Test
|
|
fun `mapping flags`() {
|
|
val moduleUri =
|
|
writePklFile(
|
|
"cmd.pkl",
|
|
renderOptions +
|
|
"""
|
|
typealias MyEnum = "a" | "b" | *"c"
|
|
class Options {
|
|
mapping: Mapping<Char, Number>
|
|
`mapping-nullable`: Mapping<Char, Number>?
|
|
`mapping-default`: Mapping<Char, Number> = new { ["x"] = 123; ["y"] = 456.789 }
|
|
|
|
`enum-mapping`: Mapping<MyEnum, MyEnum>
|
|
}
|
|
"""
|
|
.trimIndent(),
|
|
)
|
|
val output =
|
|
runToStdout(
|
|
CliBaseOptions(sourceModules = listOf(moduleUri)),
|
|
listOf("--mapping=a=0.0", "--mapping=b=1", "--enum-mapping=a=b", "--enum-mapping=b=c"),
|
|
)
|
|
assertThat(output)
|
|
.isEqualTo(
|
|
"""
|
|
mapping {
|
|
["a"] = 0.0
|
|
["b"] = 1
|
|
}
|
|
`mapping-nullable` = null
|
|
`mapping-default` {
|
|
["x"] = 123
|
|
["y"] = 456.789
|
|
}
|
|
`enum-mapping` {
|
|
["a"] = "b"
|
|
["b"] = "c"
|
|
}
|
|
|
|
"""
|
|
.trimIndent()
|
|
)
|
|
}
|
|
|
|
@Test
|
|
fun `pair flags`() {
|
|
val moduleUri =
|
|
writePklFile(
|
|
"cmd.pkl",
|
|
renderOptions +
|
|
"""
|
|
typealias MyEnum = "a" | "b" | *"c"
|
|
class Options {
|
|
pair: Pair<Char, Number>
|
|
`pair-nullable`: Pair<Char, Number>?
|
|
`pair-default`: Pair<Char, Number> = Pair("x", 123)
|
|
|
|
`enum-pair`: Pair<MyEnum, MyEnum>
|
|
}
|
|
"""
|
|
.trimIndent(),
|
|
)
|
|
val output =
|
|
runToStdout(
|
|
CliBaseOptions(sourceModules = listOf(moduleUri)),
|
|
listOf("--pair=a=0.0", "--enum-pair=a=b"),
|
|
)
|
|
assertThat(output)
|
|
.isEqualTo(
|
|
"""
|
|
pair = Pair("a", 0.0)
|
|
`pair-nullable` = null
|
|
`pair-default` = Pair("x", 123)
|
|
`enum-pair` = Pair("a", "b")
|
|
|
|
"""
|
|
.trimIndent()
|
|
)
|
|
}
|
|
|
|
@Test
|
|
fun `convert Duration`() {
|
|
val moduleUri =
|
|
writePklFile(
|
|
"cmd.pkl",
|
|
renderOptions +
|
|
"""
|
|
class Options {
|
|
@Argument { convert = module.convertDuration }
|
|
a: Duration
|
|
@Argument { convert = module.convertDuration }
|
|
b: Duration
|
|
@Argument { convert = module.convertDuration }
|
|
c: Duration
|
|
@Argument { convert = module.convertDuration }
|
|
d: Duration
|
|
}
|
|
"""
|
|
.trimIndent(),
|
|
)
|
|
val output =
|
|
runToStdout(
|
|
CliBaseOptions(sourceModules = listOf(moduleUri)),
|
|
listOf("10.h", "10H", "10.5.MS", "10.5d"),
|
|
)
|
|
assertThat(output)
|
|
.isEqualTo(
|
|
"""
|
|
a = 10.h
|
|
b = 10.h
|
|
c = 10.5.ms
|
|
d = 10.5.d
|
|
|
|
"""
|
|
.trimIndent()
|
|
)
|
|
}
|
|
|
|
@Test
|
|
fun `convert DataSize`() {
|
|
val moduleUri =
|
|
writePklFile(
|
|
"cmd.pkl",
|
|
renderOptions +
|
|
"""
|
|
class Options {
|
|
@Argument { convert = module.convertDataSize }
|
|
a: DataSize
|
|
@Argument { convert = module.convertDataSize }
|
|
b: DataSize
|
|
@Argument { convert = module.convertDataSize }
|
|
c: DataSize
|
|
@Argument { convert = module.convertDataSize }
|
|
d: DataSize
|
|
}
|
|
"""
|
|
.trimIndent(),
|
|
)
|
|
val output =
|
|
runToStdout(
|
|
CliBaseOptions(sourceModules = listOf(moduleUri)),
|
|
listOf("10.gb", "10GB", "10.5.MB", "10.5tib"),
|
|
)
|
|
assertThat(output)
|
|
.isEqualTo(
|
|
"""
|
|
a = 10.gb
|
|
b = 10.gb
|
|
c = 10.5.mb
|
|
d = 10.5.tib
|
|
|
|
"""
|
|
.trimIndent()
|
|
)
|
|
}
|
|
|
|
@Test
|
|
fun `convert import`() {
|
|
val moduleUri =
|
|
writePklFile(
|
|
"cmd.pkl",
|
|
"""
|
|
extends "pkl:Command"
|
|
|
|
options: Options
|
|
|
|
output {
|
|
value = (options) {
|
|
fromImport {
|
|
baz = true // assert that imported modules are not forced
|
|
}
|
|
}
|
|
}
|
|
|
|
class Options {
|
|
@Argument { convert = (it) -> new Import{ uri = it } }
|
|
fromImport: Module
|
|
}
|
|
"""
|
|
.trimIndent(),
|
|
)
|
|
|
|
val importUri =
|
|
writePklFile(
|
|
"import.pkl",
|
|
"""
|
|
foo = 1
|
|
bar = "baz"
|
|
baz: Boolean
|
|
"""
|
|
.trimIndent(),
|
|
)
|
|
|
|
val output =
|
|
runToStdout(CliBaseOptions(sourceModules = listOf(moduleUri)), listOf(importUri.toString()))
|
|
assertThat(output)
|
|
.isEqualTo(
|
|
"""
|
|
fromImport {
|
|
foo = 1
|
|
bar = "baz"
|
|
baz = true
|
|
}
|
|
|
|
"""
|
|
.trimIndent()
|
|
)
|
|
}
|
|
|
|
@Test
|
|
fun `transformAll import`() {
|
|
val moduleUri =
|
|
writePklFile(
|
|
"cmd.pkl",
|
|
"""
|
|
extends "pkl:Command"
|
|
|
|
options: Options
|
|
|
|
output {
|
|
value = (options) {
|
|
fromImport {
|
|
baz = true // assert that imported modules are not forced
|
|
}
|
|
}
|
|
}
|
|
|
|
class Options {
|
|
@Flag {
|
|
convert = (it) -> new Import{ uri = it }
|
|
transformAll = (values) -> values.firstOrNull ?? new Import { uri = "./default.pkl" }
|
|
}
|
|
fromImport: Module
|
|
}
|
|
"""
|
|
.trimIndent(),
|
|
)
|
|
|
|
val importUri =
|
|
writePklFile(
|
|
"default.pkl",
|
|
"""
|
|
foo = 1
|
|
bar = "baz"
|
|
baz: Boolean
|
|
"""
|
|
.trimIndent(),
|
|
)
|
|
|
|
val output =
|
|
runToStdout(
|
|
CliBaseOptions(sourceModules = listOf(moduleUri), workingDir = tempDir),
|
|
emptyList(),
|
|
)
|
|
assertThat(output)
|
|
.isEqualTo(
|
|
"""
|
|
fromImport {
|
|
foo = 1
|
|
bar = "baz"
|
|
baz = true
|
|
}
|
|
|
|
"""
|
|
.trimIndent()
|
|
)
|
|
}
|
|
|
|
@Test
|
|
fun `convert glob import`() {
|
|
val moduleUri =
|
|
writePklFile(
|
|
"cmd.pkl",
|
|
"""
|
|
extends "pkl:Command"
|
|
import "base.pkl"
|
|
|
|
options: Options
|
|
|
|
output {
|
|
value = (options) {
|
|
fromGlobImport {
|
|
[[true]] {
|
|
baz = true // assert that imported modules are not forced
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
class Options {
|
|
@Argument { convert = (it) -> new Import { uri = it; glob = true }; multiple = false }
|
|
fromGlobImport: Mapping<String, base>
|
|
}
|
|
"""
|
|
.trimIndent(),
|
|
)
|
|
|
|
val baseImport =
|
|
writePklFile(
|
|
"base.pkl",
|
|
"""
|
|
foo: Int
|
|
bar: String
|
|
baz: Boolean
|
|
"""
|
|
.trimIndent(),
|
|
)
|
|
writePklFile(
|
|
"glob1.pkl",
|
|
"""
|
|
amends "base.pkl"
|
|
foo = 1
|
|
bar = "baz"
|
|
"""
|
|
.trimIndent(),
|
|
)
|
|
writePklFile(
|
|
"glob2.pkl",
|
|
"""
|
|
amends "base.pkl"
|
|
foo = 2
|
|
bar = "qux"
|
|
"""
|
|
.trimIndent(),
|
|
)
|
|
|
|
val importDirUri = baseImport.resolve(".")
|
|
|
|
val output =
|
|
runToStdout(
|
|
CliBaseOptions(sourceModules = listOf(moduleUri)),
|
|
listOf(importDirUri.resolve("./glob*.pkl").toString()),
|
|
)
|
|
assertThat(output.replace(importDirUri.toString(), "file:/<dir>/"))
|
|
.isEqualTo(
|
|
"""
|
|
fromGlobImport {
|
|
["file:/<dir>/glob1.pkl"] {
|
|
foo = 1
|
|
bar = "baz"
|
|
baz = true
|
|
}
|
|
["file:/<dir>/glob2.pkl"] {
|
|
foo = 2
|
|
bar = "qux"
|
|
baz = true
|
|
}
|
|
}
|
|
|
|
"""
|
|
.trimIndent()
|
|
)
|
|
}
|
|
|
|
@Test
|
|
fun `convert that throws`() {
|
|
val moduleUri =
|
|
writePklFile(
|
|
"cmd.pkl",
|
|
renderOptions +
|
|
"""
|
|
class Options {
|
|
@Argument { convert = (it) -> throw("oops!") }
|
|
foo: String
|
|
}
|
|
"""
|
|
.trimIndent(),
|
|
)
|
|
|
|
val exc =
|
|
assertThrows<CliktError> {
|
|
runToStdout(CliBaseOptions(sourceModules = listOf(moduleUri)), listOf("hi"))
|
|
}
|
|
assertThat(exc.message).contains("oops!")
|
|
}
|
|
|
|
@Test
|
|
fun `convert with eval error`() {
|
|
val moduleUri =
|
|
writePklFile(
|
|
"cmd.pkl",
|
|
renderOptions +
|
|
"""
|
|
class Options {
|
|
@Argument { convert = (it) -> it.noSuchMethod() }
|
|
foo: String
|
|
}
|
|
"""
|
|
.trimIndent(),
|
|
)
|
|
|
|
val exc =
|
|
assertThrows<CliktError> {
|
|
runToStdout(CliBaseOptions(sourceModules = listOf(moduleUri)), listOf("hi"))
|
|
}
|
|
assertThat(exc.message).contains("Cannot find method `noSuchMethod` in class `String`.")
|
|
}
|
|
|
|
@Test
|
|
fun `convert with stack overflow`() {
|
|
val moduleUri =
|
|
writePklFile(
|
|
"cmd.pkl",
|
|
renderOptions +
|
|
"""
|
|
const function overflow(it) = overflow(it)
|
|
class Options {
|
|
@Argument { convert = (it) -> overflow(it) }
|
|
foo: String
|
|
}
|
|
"""
|
|
.trimIndent(),
|
|
)
|
|
|
|
val exc =
|
|
assertThrows<CliktError> {
|
|
runToStdout(CliBaseOptions(sourceModules = listOf(moduleUri)), listOf("hi"))
|
|
}
|
|
assertThat(exc.message).contains("A stack overflow occurred.")
|
|
}
|
|
|
|
@Test
|
|
fun `boolean flag`() {
|
|
val moduleUri =
|
|
writePklFile(
|
|
"cmd.pkl",
|
|
renderOptions +
|
|
"""
|
|
class Options {
|
|
@BooleanFlag
|
|
`bool-true`: Boolean
|
|
@BooleanFlag
|
|
`bool-false`: Boolean
|
|
@BooleanFlag
|
|
`bool-nullable`: Boolean?
|
|
@BooleanFlag
|
|
`bool-default-true`: Boolean = true
|
|
@BooleanFlag
|
|
`bool-default-false`: Boolean = false
|
|
}
|
|
"""
|
|
.trimIndent(),
|
|
)
|
|
|
|
val output =
|
|
runToStdout(
|
|
CliBaseOptions(sourceModules = listOf(moduleUri)),
|
|
listOf("--bool-true", "--no-bool-false"),
|
|
)
|
|
assertThat(output)
|
|
.isEqualTo(
|
|
"""
|
|
`bool-true` = true
|
|
`bool-false` = false
|
|
`bool-nullable` = null
|
|
`bool-default-true` = true
|
|
`bool-default-false` = false
|
|
|
|
"""
|
|
.trimIndent()
|
|
)
|
|
}
|
|
|
|
@Test
|
|
fun `boolean flag with bad type`() {
|
|
val moduleUri =
|
|
writePklFile(
|
|
"cmd.pkl",
|
|
renderOptions +
|
|
"""
|
|
class Options {
|
|
@BooleanFlag
|
|
foo: String
|
|
}
|
|
"""
|
|
.trimIndent(),
|
|
)
|
|
|
|
val exc =
|
|
assertThrows<CliException> {
|
|
runToStdout(CliBaseOptions(sourceModules = listOf(moduleUri)), listOf("hi"))
|
|
}
|
|
assertThat(exc.message)
|
|
.contains("Option `foo` with annotation `@BooleanFlag` has invalid type `String`.")
|
|
}
|
|
|
|
@Test
|
|
fun `counted flag`() {
|
|
val moduleUri =
|
|
writePklFile(
|
|
"cmd.pkl",
|
|
renderOptions +
|
|
"""
|
|
class Options {
|
|
@CountedFlag { shortName = "a" }
|
|
int: Int
|
|
@CountedFlag { shortName = "b" }
|
|
int8: Int8
|
|
@CountedFlag { shortName = "c" }
|
|
int16: Int16
|
|
@CountedFlag { shortName = "d" }
|
|
int32: Int32
|
|
@CountedFlag { shortName = "x" }
|
|
uint: UInt
|
|
@CountedFlag { shortName = "y" }
|
|
uint8: UInt8
|
|
@CountedFlag { shortName = "g" }
|
|
uint16: UInt16
|
|
@CountedFlag { shortName = "i" }
|
|
uint32: UInt32
|
|
}
|
|
"""
|
|
.trimIndent(),
|
|
)
|
|
|
|
val output =
|
|
runToStdout(
|
|
CliBaseOptions(sourceModules = listOf(moduleUri)),
|
|
listOf("-abbcccddddxxxxxyyyyyygggggggiiiiiiii"),
|
|
)
|
|
assertThat(output)
|
|
.isEqualTo(
|
|
"""
|
|
int = 1
|
|
int8 = 2
|
|
int16 = 3
|
|
int32 = 4
|
|
uint = 5
|
|
uint8 = 6
|
|
uint16 = 7
|
|
uint32 = 8
|
|
|
|
"""
|
|
.trimIndent()
|
|
)
|
|
}
|
|
|
|
@Test
|
|
fun `test transformAll`() {
|
|
val moduleUri =
|
|
writePklFile(
|
|
"cmd.pkl",
|
|
renderOptions +
|
|
"""
|
|
class Options {
|
|
@Flag {
|
|
multiple = true
|
|
transformAll = (values) -> values.fold(0, (res, acc) -> res + acc)
|
|
}
|
|
foo: Int
|
|
}
|
|
"""
|
|
.trimIndent(),
|
|
)
|
|
|
|
val output =
|
|
runToStdout(
|
|
CliBaseOptions(sourceModules = listOf(moduleUri)),
|
|
listOf("--foo=1", "--foo=5", "--foo=8"),
|
|
)
|
|
assertThat(output)
|
|
.isEqualTo(
|
|
"""
|
|
foo = 14
|
|
|
|
"""
|
|
.trimIndent()
|
|
)
|
|
}
|
|
|
|
@Test
|
|
fun `completion candidates`() {
|
|
val moduleUri =
|
|
writePklFile(
|
|
"cmd.pkl",
|
|
renderOptions +
|
|
"""
|
|
class Options {
|
|
none: String?
|
|
enum: *"a" | "b" | "c"
|
|
@Flag { completionCandidates = "paths" }
|
|
path: String?
|
|
@Flag { completionCandidates { "foo"; "bar"; "baz" } }
|
|
explicit: String?
|
|
@Argument
|
|
enumArg: *"a" | "b" | "c"
|
|
@Argument { completionCandidates = "paths" }
|
|
pathArg: String
|
|
@Argument { completionCandidates { "foo"; "bar"; "baz" } }
|
|
explicitArg: String
|
|
}
|
|
"""
|
|
.trimIndent(),
|
|
)
|
|
val exc =
|
|
assertThrows<PrintCompletionMessage> {
|
|
runToStdout(
|
|
CliBaseOptions(sourceModules = listOf(moduleUri)),
|
|
listOf("a", "foo", "bar", "shell-completion", "bash"),
|
|
)
|
|
}
|
|
assertThat(exc.message)
|
|
.contains(
|
|
"""
|
|
"--none")
|
|
;;
|
|
"--enum")
|
|
COMPREPLY=($(compgen -W 'a b c' -- "${'$'}{word}"))
|
|
;;
|
|
"--path")
|
|
__complete_files "${'$'}{word}"
|
|
;;
|
|
"--explicit")
|
|
COMPREPLY=($(compgen -W 'bar baz foo' -- "${'$'}{word}"))
|
|
;;
|
|
"--help")
|
|
;;
|
|
"enumArg")
|
|
COMPREPLY=($(compgen -W '' -- "${'$'}{word}"))
|
|
;;
|
|
"pathArg")
|
|
__complete_files "${'$'}{word}"
|
|
;;
|
|
"explicitArg")
|
|
COMPREPLY=($(compgen -W 'bar baz foo' -- "${'$'}{word}"))
|
|
;;"""
|
|
)
|
|
}
|
|
}
|