Skip to content

Commit 35182ff

Browse files
committed
Add test coverage
1 parent fa4fde4 commit 35182ff

File tree

28 files changed

+1846
-178
lines changed

28 files changed

+1846
-178
lines changed

docs/modules/pkl-cli/pages/index.adoc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -974,7 +974,7 @@ False values: `false`, `f`, `0`, `no`, `n`, `off`
974974
|`Int8`, `Int16`, `Int32`, `UInt`, `UInt8`, `UInt16`, `UInt32`
975975
|Behaves like `Int` but enforces the type's bounds check
976976

977-
|xref:language-reference:index.adoc#union-types[Union] of xref:language-reference:index.adoc#string-literal-types[string literals]
977+
|xref:language-reference:index.adoc#union-types[Union] of xref:language-reference:index.adoc#string-literal-types[string literals] or a xref:language-reference:index.adoc#type-aliases[type alias] to one
978978
|Behaves like `String` but only admits specific values.
979979

980980
|`List<Element>`, `Set<Element>`

pkl-cli/src/main/kotlin/org/pkl/cli/CliCommandRunner.kt

Lines changed: 71 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright © 2025 Apple Inc. and the Pkl project authors. All rights reserved.
2+
* Copyright © 2025-2026 Apple Inc. and the Pkl project authors. All rights reserved.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -37,6 +37,7 @@ import org.pkl.core.CommandSpec
3737
import org.pkl.core.EvaluatorBuilder
3838
import org.pkl.core.FileOutput
3939
import org.pkl.core.ModuleSource.uri
40+
import org.pkl.core.PklException
4041
import org.pkl.core.util.IoUtils
4142

4243
class CliCommandRunner
@@ -64,14 +65,20 @@ constructor(
6465
val evaluator = builder.build()
6566
evaluator.use {
6667
evaluator.evaluateCommand(uri(normalizedSourceModule)) { spec ->
67-
val root = SynthesizedRunCommand(spec, this, options.sourceModules.first().toString())
68-
root.subcommands(
69-
CompletionCommand(
70-
name = "shell-completion",
71-
help = "Generate a completion script for the given shell",
68+
try {
69+
val root = SynthesizedRunCommand(spec, this, options.sourceModules.first().toString())
70+
root.subcommands(
71+
CompletionCommand(
72+
name = "shell-completion",
73+
help = "Generate a completion script for the given shell",
74+
)
7275
)
73-
)
74-
root.main(args)
76+
root.parse(args)
77+
} catch (e: PklException) {
78+
throw e
79+
} catch (e: Exception) {
80+
throw e.message?.let { PklException(it, e) } ?: PklException(e)
81+
}
7582
}
7683
}
7784
}
@@ -109,7 +116,6 @@ constructor(
109116
checkPathSpec(pathSpec)
110117
val resolvedPath = outputDir.resolve(pathSpec).normalize()
111118
val realPath = if (resolvedPath.exists()) resolvedPath.toRealPath() else resolvedPath
112-
// FIXME: should we validate against options.normalizedRootDir?
113119
val previousOutput = writtenFiles[realPath]
114120
if (previousOutput != null) {
115121
throw CliException(
@@ -149,39 +155,6 @@ constructor(
149155

150156
override fun help(context: Context): String = spec.description ?: ""
151157

152-
override fun run() {
153-
if (currentContext.invokedSubcommand is CompletionCommand) return
154-
155-
val opts =
156-
registeredOptions()
157-
.mapNotNull {
158-
val opt = it as? OptionWithValues<*, *, *> ?: return@mapNotNull null
159-
return@mapNotNull if (
160-
it.names.contains("--help") ||
161-
(opt.value as? List<*>)?.isEmpty() ?: false ||
162-
(opt.value as? Map<*, *>)?.isEmpty() ?: false
163-
)
164-
null
165-
else it.names.first().trimStart('-') to opt.value
166-
}
167-
.toMap() +
168-
registeredArguments()
169-
.mapNotNull { it as? ArgumentDelegate<*> }
170-
.associateBy({ it.name }, { it.value })
171-
172-
val state = spec.apply.apply(opts, currentContext.obj as CommandSpec.State?)
173-
currentContext.obj = state
174-
175-
if (currentContext.invokedSubcommand != null) return
176-
if (spec.subcommands.isNotEmpty() && spec.noOp) {
177-
throw PrintHelpMessage(currentContext, true, 1)
178-
}
179-
180-
val result = state.evaluate()
181-
runner.writeOutput(result.outputBytes)
182-
runner.writeMultipleFileOutput(result.outputFiles)
183-
}
184-
185158
private fun registerFlag(flag: CommandSpec.Flag) {
186159
val names =
187160
if (flag.shortName == null) arrayOf("--${flag.name}")
@@ -204,12 +177,7 @@ constructor(
204177
else convertValue(it.second, type.valueType, "value"),
205178
)
206179
}
207-
.multiple(
208-
default =
209-
(flag.defaultValue?.let { default ->
210-
(default as Map<*, *>).entries.toList().map { Pair(it.key!!, it.value!!) }
211-
} ?: emptyList())
212-
)
180+
.multiple()
213181
.toMap()
214182
}
215183
)
@@ -227,6 +195,39 @@ constructor(
227195
}
228196
)
229197
}
198+
199+
override fun run() {
200+
if (currentContext.invokedSubcommand is CompletionCommand) return
201+
202+
val opts =
203+
registeredOptions()
204+
.mapNotNull {
205+
val opt = it as? OptionWithValues<*, *, *> ?: return@mapNotNull null
206+
return@mapNotNull if (
207+
it.names.contains("--help") ||
208+
(opt.value as? List<*>)?.isEmpty() ?: false ||
209+
(opt.value as? Map<*, *>)?.isEmpty() ?: false
210+
)
211+
null
212+
else it.names.first().trimStart('-') to opt.value
213+
}
214+
.toMap() +
215+
registeredArguments()
216+
.mapNotNull { it as? ArgumentDelegate<*> }
217+
.associateBy({ it.name }, { it.value })
218+
219+
val state = spec.apply.apply(opts, currentContext.obj as CommandSpec.State?)
220+
currentContext.obj = state
221+
222+
if (currentContext.invokedSubcommand != null) return
223+
if (spec.subcommands.isNotEmpty() && spec.noOp) {
224+
throw PrintHelpMessage(currentContext, true, 1)
225+
}
226+
227+
val result = state.evaluate()
228+
runner.writeOutput(result.outputBytes)
229+
runner.writeMultipleFileOutput(result.outputFiles)
230+
}
230231
}
231232
}
232233

@@ -244,7 +245,7 @@ private fun <InT> NullableOption<InT, InT>.flag(flag: CommandSpec.Flag) =
244245
flag.type is CommandSpec.OptionType.Collection ->
245246
when {
246247
it.isEmpty() && flag.type.isRequired -> throw MissingOption(option)
247-
it.isEmpty() && !flag.type.isRequired -> flag.defaultValue
248+
it.isEmpty() && !flag.type.isRequired -> it
248249
else ->
249250
if (
250251
(flag.type as CommandSpec.OptionType.Collection).type ==
@@ -253,9 +254,8 @@ private fun <InT> NullableOption<InT, InT>.flag(flag: CommandSpec.Flag) =
253254
it.toSet()
254255
else it
255256
}
256-
flag.defaultValue != null -> it.lastOrNull() ?: flag.defaultValue
257257
flag.type.isRequired -> it.lastOrNull() ?: throw MissingOption(option)
258-
else -> it
258+
else -> it.lastOrNull()
259259
}
260260
}
261261
else -> throw CliException("Unexpected collection flag value type $valueType")
@@ -300,7 +300,15 @@ fun RawOption.convert(
300300
} else it
301301
return@convert parseFunction.parse(parseArg)
302302
}
303-
else convert { convertValue(it, type, null) }.copy(metavarGetter = { type.toString() })
303+
else
304+
convert {
305+
convertValue(
306+
it,
307+
if (type is CommandSpec.OptionType.Collection) type.valueType else type,
308+
null,
309+
)
310+
}
311+
.copy(metavarGetter = { type.toString() })
304312

305313
@Suppress("DuplicatedCode")
306314
fun RawArgument.convert(
@@ -321,7 +329,14 @@ fun RawArgument.convert(
321329
} else it
322330
return@convert parseFunction.parse(parseArg)
323331
}
324-
else convert { convertValue(it, type, null) }
332+
else
333+
convert {
334+
convertValue(
335+
it,
336+
if (type is CommandSpec.OptionType.Collection) type.valueType else type,
337+
null,
338+
)
339+
}
325340

326341
// helpers for converting primitives/enums
327342

@@ -342,12 +357,12 @@ private val convertValue:
342357
is CommandSpec.OptionType.Primitive ->
343358
when (type.type) {
344359
CommandSpec.OptionType.Primitive.Type.NUMBER ->
345-
value.toIntOrNull() ?: value.toDoubleOrNull()
360+
value.toLongOrNull() ?: value.toDoubleOrNull()
346361
CommandSpec.OptionType.Primitive.Type.FLOAT -> value.toDoubleOrNull()
347362
CommandSpec.OptionType.Primitive.Type.INT -> value.toLongOrNull(this, type)
348363
// ranges based on org.pkl.core.stdlib.math.MathNodes
349364
CommandSpec.OptionType.Primitive.Type.INT8 ->
350-
value.toLongOrNull(this, type, Byte.MIN_VALUE.toLong()..<Byte.MAX_VALUE)
365+
value.toLongOrNull(this, type, Byte.MIN_VALUE.toLong()..Byte.MAX_VALUE)
351366
CommandSpec.OptionType.Primitive.Type.INT16 ->
352367
value.toLongOrNull(this, type, Short.MIN_VALUE.toLong()..Short.MAX_VALUE)
353368
CommandSpec.OptionType.Primitive.Type.INT32 ->
@@ -381,6 +396,6 @@ private val convertValue:
381396
is CommandSpec.OptionType.Enum ->
382397
if (type.choices.contains(value)) value
383398
else fail("invalid choice: $value. (choose from ${type.choices.joinToString()})")
384-
else -> fail("unsupported ${mapPosition?.let { "map $it " }}type $type")
399+
else -> fail("unsupported ${mapPosition?.let { "map $it " } ?: ""}type $type")
385400
} ?: fail("$value is not a valid $type")
386401
}

pkl-cli/src/main/kotlin/org/pkl/cli/Utils.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright © 2025 Apple Inc. and the Pkl project authors. All rights reserved.
2+
* Copyright © 2025-2026 Apple Inc. and the Pkl project authors. All rights reserved.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.

pkl-cli/src/main/kotlin/org/pkl/cli/commands/RootCommand.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright © 2024-2025 Apple Inc. and the Pkl project authors. All rights reserved.
2+
* Copyright © 2024-2026 Apple Inc. and the Pkl project authors. All rights reserved.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.

pkl-cli/src/main/kotlin/org/pkl/cli/commands/RunCommand.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright © 2025 Apple Inc. and the Pkl project authors. All rights reserved.
2+
* Copyright © 2025-2026 Apple Inc. and the Pkl project authors. All rights reserved.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.

0 commit comments

Comments
 (0)