mirror of
https://github.com/apple/pkl.git
synced 2026-03-20 16:23:57 +01:00
Turn CLI commands into objects, self register subcommands (#935)
Usages of `RootCommand` no longer need to initialize a new instance, nor register subcommands.
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
|
||||
* Copyright © 2024-2025 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.
|
||||
@@ -17,26 +17,10 @@
|
||||
|
||||
package org.pkl.cli
|
||||
|
||||
import com.github.ajalt.clikt.core.subcommands
|
||||
import org.pkl.cli.commands.*
|
||||
import org.pkl.commons.cli.cliMain
|
||||
import org.pkl.core.Release
|
||||
|
||||
/** Main method of the Pkl CLI (command-line evaluator and REPL). */
|
||||
internal fun main(args: Array<String>) {
|
||||
cliMain {
|
||||
val version = Release.current().versionInfo()
|
||||
val helpLink = "${Release.current().documentation().homepage()}pkl-cli/index.html#usage"
|
||||
RootCommand("pkl", version, helpLink)
|
||||
.subcommands(
|
||||
EvalCommand(helpLink),
|
||||
ReplCommand(helpLink),
|
||||
ServerCommand(helpLink),
|
||||
TestCommand(helpLink),
|
||||
ProjectCommand(helpLink),
|
||||
DownloadPackageCommand(helpLink),
|
||||
AnalyzeCommand(helpLink),
|
||||
)
|
||||
.main(args)
|
||||
}
|
||||
cliMain { RootCommand.main(args) }
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
|
||||
* Copyright © 2024-2025 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.
|
||||
@@ -25,42 +25,40 @@ import org.pkl.cli.CliImportAnalyzerOptions
|
||||
import org.pkl.commons.cli.commands.ModulesCommand
|
||||
import org.pkl.commons.cli.commands.single
|
||||
|
||||
class AnalyzeCommand(helpLink: String) :
|
||||
object AnalyzeCommand :
|
||||
NoOpCliktCommand(
|
||||
name = "analyze",
|
||||
help = "Commands related to static analysis",
|
||||
epilog = "For more information, visit $helpLink"
|
||||
epilog = "For more information, visit $helpLink",
|
||||
) {
|
||||
init {
|
||||
subcommands(AnalyzeImportsCommand(helpLink))
|
||||
}
|
||||
|
||||
companion object {
|
||||
class AnalyzeImportsCommand(helpLink: String) :
|
||||
ModulesCommand(
|
||||
name = "imports",
|
||||
helpLink = helpLink,
|
||||
help = "Prints the graph of modules imported by the input module(s)."
|
||||
) {
|
||||
|
||||
private val outputPath: Path? by
|
||||
option(
|
||||
names = arrayOf("-o", "--output-path"),
|
||||
metavar = "<path>",
|
||||
help = "File path where the output file is placed."
|
||||
)
|
||||
.path()
|
||||
.single()
|
||||
|
||||
override fun run() {
|
||||
val options =
|
||||
CliImportAnalyzerOptions(
|
||||
base = baseOptions.baseOptions(modules, projectOptions),
|
||||
outputFormat = baseOptions.format,
|
||||
outputPath = outputPath
|
||||
)
|
||||
CliImportAnalyzer(options).run()
|
||||
}
|
||||
}
|
||||
subcommands(AnalyzeImportsCommand)
|
||||
}
|
||||
}
|
||||
|
||||
object AnalyzeImportsCommand :
|
||||
ModulesCommand(
|
||||
name = "imports",
|
||||
helpLink = helpLink,
|
||||
help = "Prints the graph of modules imported by the input module(s).",
|
||||
) {
|
||||
|
||||
private val outputPath: Path? by
|
||||
option(
|
||||
names = arrayOf("-o", "--output-path"),
|
||||
metavar = "<path>",
|
||||
help = "File path where the output file is placed.",
|
||||
)
|
||||
.path()
|
||||
.single()
|
||||
|
||||
override fun run() {
|
||||
val options =
|
||||
CliImportAnalyzerOptions(
|
||||
base = baseOptions.baseOptions(modules, projectOptions),
|
||||
outputFormat = baseOptions.format,
|
||||
outputPath = outputPath,
|
||||
)
|
||||
CliImportAnalyzer(options).run()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
|
||||
* Copyright © 2024-2025 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.
|
||||
@@ -27,7 +27,7 @@ import org.pkl.commons.cli.commands.ProjectOptions
|
||||
import org.pkl.commons.cli.commands.single
|
||||
import org.pkl.core.packages.PackageUri
|
||||
|
||||
class DownloadPackageCommand(helpLink: String) :
|
||||
object DownloadPackageCommand :
|
||||
BaseCommand(
|
||||
name = "download-package",
|
||||
helpLink = helpLink,
|
||||
@@ -44,7 +44,7 @@ class DownloadPackageCommand(helpLink: String) :
|
||||
$ pkl download-package package://example.com/package1@1.0.0 package://example.com/package2@1.0.0
|
||||
```
|
||||
"""
|
||||
.trimIndent()
|
||||
.trimIndent(),
|
||||
) {
|
||||
private val projectOptions by ProjectOptions()
|
||||
|
||||
@@ -56,7 +56,7 @@ class DownloadPackageCommand(helpLink: String) :
|
||||
private val noTransitive: Boolean by
|
||||
option(
|
||||
names = arrayOf("--no-transitive"),
|
||||
help = "Skip downloading transitive dependencies of a package"
|
||||
help = "Skip downloading transitive dependencies of a package",
|
||||
)
|
||||
.single()
|
||||
.flag()
|
||||
@@ -65,7 +65,7 @@ class DownloadPackageCommand(helpLink: String) :
|
||||
CliPackageDownloader(
|
||||
baseOptions.baseOptions(emptyList(), projectOptions),
|
||||
packageUris,
|
||||
noTransitive
|
||||
noTransitive,
|
||||
)
|
||||
.run()
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
|
||||
* Copyright © 2024-2025 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.
|
||||
@@ -24,17 +24,13 @@ import org.pkl.cli.CliEvaluatorOptions
|
||||
import org.pkl.commons.cli.commands.ModulesCommand
|
||||
import org.pkl.commons.cli.commands.single
|
||||
|
||||
class EvalCommand(helpLink: String) :
|
||||
ModulesCommand(
|
||||
name = "eval",
|
||||
help = "Render pkl module(s)",
|
||||
helpLink = helpLink,
|
||||
) {
|
||||
object EvalCommand :
|
||||
ModulesCommand(name = "eval", help = "Render pkl module(s)", helpLink = helpLink) {
|
||||
private val outputPath: String? by
|
||||
option(
|
||||
names = arrayOf("-o", "--output-path"),
|
||||
metavar = "<path>",
|
||||
help = "File path where the output file is placed."
|
||||
help = "File path where the output file is placed.",
|
||||
)
|
||||
.single()
|
||||
|
||||
@@ -43,7 +39,7 @@ class EvalCommand(helpLink: String) :
|
||||
names = arrayOf("--module-output-separator"),
|
||||
metavar = "<string>",
|
||||
help =
|
||||
"Separator to use when multiple module outputs are written to the same file. (default: ---)"
|
||||
"Separator to use when multiple module outputs are written to the same file. (default: ---)",
|
||||
)
|
||||
.single()
|
||||
.default("---")
|
||||
@@ -52,7 +48,7 @@ class EvalCommand(helpLink: String) :
|
||||
option(
|
||||
names = arrayOf("-x", "--expression"),
|
||||
metavar = "<expression>",
|
||||
help = "Expression to be evaluated within the module."
|
||||
help = "Expression to be evaluated within the module.",
|
||||
)
|
||||
.single()
|
||||
|
||||
@@ -60,7 +56,7 @@ class EvalCommand(helpLink: String) :
|
||||
option(
|
||||
names = arrayOf("-m", "--multiple-file-output-path"),
|
||||
metavar = "<path>",
|
||||
help = "Directory where a module's multiple file output is placed."
|
||||
help = "Directory where a module's multiple file output is placed.",
|
||||
)
|
||||
.single()
|
||||
.validate {
|
||||
@@ -81,7 +77,7 @@ class EvalCommand(helpLink: String) :
|
||||
outputFormat = baseOptions.format,
|
||||
moduleOutputSeparator = moduleOutputSeparator,
|
||||
multipleFileOutputPath = multipleFileOutputPath,
|
||||
expression = expression ?: CliEvaluatorOptions.defaults.expression
|
||||
expression = expression ?: CliEvaluatorOptions.defaults.expression,
|
||||
)
|
||||
CliEvaluator(options).run()
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
|
||||
* Copyright © 2024-2025 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.
|
||||
@@ -31,119 +31,117 @@ import org.pkl.commons.cli.commands.BaseCommand
|
||||
import org.pkl.commons.cli.commands.TestOptions
|
||||
import org.pkl.commons.cli.commands.single
|
||||
|
||||
class ProjectCommand(helpLink: String) :
|
||||
private const val NEWLINE = '\u0085'
|
||||
|
||||
object ProjectCommand :
|
||||
NoOpCliktCommand(
|
||||
name = "project",
|
||||
help = "Run commands related to projects",
|
||||
epilog = "For more information, visit $helpLink"
|
||||
epilog = "For more information, visit $helpLink",
|
||||
) {
|
||||
init {
|
||||
subcommands(ResolveCommand(helpLink), PackageCommand(helpLink))
|
||||
}
|
||||
|
||||
companion object {
|
||||
class ResolveCommand(helpLink: String) :
|
||||
BaseCommand(
|
||||
name = "resolve",
|
||||
helpLink = helpLink,
|
||||
help =
|
||||
"""
|
||||
Resolve dependencies for project(s)
|
||||
|
||||
This command takes the `dependencies` of `PklProject`s, and writes the
|
||||
resolved versions to `PklProject.deps.json` files.
|
||||
|
||||
Examples:
|
||||
|
||||
```
|
||||
# Search the current working directory for a project, and resolve its dependencies.
|
||||
$ pkl project resolve
|
||||
|
||||
# Resolve dependencies for all projects within the `packages/` directory.
|
||||
$ pkl project resolve packages/*/
|
||||
```
|
||||
""",
|
||||
) {
|
||||
private val projectDirs: List<Path> by
|
||||
argument("<dir>", "The project directories to resolve dependencies for").path().multiple()
|
||||
|
||||
override fun run() {
|
||||
CliProjectResolver(baseOptions.baseOptions(emptyList()), projectDirs).run()
|
||||
}
|
||||
}
|
||||
|
||||
private const val NEWLINE = '\u0085'
|
||||
|
||||
class PackageCommand(helpLink: String) :
|
||||
BaseCommand(
|
||||
name = "package",
|
||||
helpLink = helpLink,
|
||||
help =
|
||||
"""
|
||||
Verify package(s), and prepare package artifacts to be published.
|
||||
|
||||
This command runs a project's api tests, as defined by `apiTests` in `PklProject`.
|
||||
Additionally, it verifies that all imports resolve to paths that are local to the project.
|
||||
|
||||
Finally, this command writes the following artifacts into the output directory specified by the output path.
|
||||
|
||||
- `name@version` - dependency metadata$NEWLINE
|
||||
- `name@version.sha256` - dependency metadata's SHA-256 checksum$NEWLINE
|
||||
- `name@version.zip` - package archive$NEWLINE
|
||||
- `name@version.zip.sha256` - package archive's SHA-256 checksum
|
||||
|
||||
The output path option accepts the following placeholders:
|
||||
|
||||
- %{name}: The display name of the package$NEWLINE
|
||||
- %{version}: The version of the package
|
||||
|
||||
If a project has local project dependencies, the depended upon project directories must also
|
||||
be included as arguments to this command.
|
||||
|
||||
Examples:
|
||||
|
||||
```
|
||||
# Search the current working directory for a project, and package it.
|
||||
$ pkl project package
|
||||
|
||||
# Package all projects within the `packages/` directory.
|
||||
$ pkl project package packages/*/
|
||||
```
|
||||
"""
|
||||
.trimIndent(),
|
||||
) {
|
||||
private val testOptions by TestOptions()
|
||||
|
||||
private val projectDirs: List<Path> by
|
||||
argument("<dir>", "The project directories to package").path().multiple()
|
||||
|
||||
private val outputPath: String by
|
||||
option(
|
||||
names = arrayOf("--output-path"),
|
||||
help = "The directory to write artifacts to",
|
||||
metavar = "<path>"
|
||||
)
|
||||
.single()
|
||||
.default(".out/%{name}@%{version}")
|
||||
|
||||
private val skipPublishCheck: Boolean by
|
||||
option(
|
||||
names = arrayOf("--skip-publish-check"),
|
||||
help = "Skip checking if a package has already been published with different contents",
|
||||
)
|
||||
.single()
|
||||
.flag()
|
||||
|
||||
override fun run() {
|
||||
CliProjectPackager(
|
||||
baseOptions.baseOptions(emptyList()),
|
||||
projectDirs,
|
||||
testOptions.cliTestOptions,
|
||||
outputPath,
|
||||
skipPublishCheck
|
||||
)
|
||||
.run()
|
||||
}
|
||||
}
|
||||
subcommands(ResolveCommand, PackageCommand)
|
||||
}
|
||||
}
|
||||
|
||||
object ResolveCommand :
|
||||
BaseCommand(
|
||||
name = "resolve",
|
||||
helpLink = helpLink,
|
||||
help =
|
||||
"""
|
||||
Resolve dependencies for project(s)
|
||||
|
||||
This command takes the `dependencies` of `PklProject`s, and writes the
|
||||
resolved versions to `PklProject.deps.json` files.
|
||||
|
||||
Examples:
|
||||
|
||||
```
|
||||
# Search the current working directory for a project, and resolve its dependencies.
|
||||
$ pkl project resolve
|
||||
|
||||
# Resolve dependencies for all projects within the `packages/` directory.
|
||||
$ pkl project resolve packages/*/
|
||||
```
|
||||
""",
|
||||
) {
|
||||
private val projectDirs: List<Path> by
|
||||
argument("<dir>", "The project directories to resolve dependencies for").path().multiple()
|
||||
|
||||
override fun run() {
|
||||
CliProjectResolver(baseOptions.baseOptions(emptyList()), projectDirs).run()
|
||||
}
|
||||
}
|
||||
|
||||
object PackageCommand :
|
||||
BaseCommand(
|
||||
name = "package",
|
||||
helpLink = helpLink,
|
||||
help =
|
||||
"""
|
||||
Verify package(s), and prepare package artifacts to be published.
|
||||
|
||||
This command runs a project's api tests, as defined by `apiTests` in `PklProject`.
|
||||
Additionally, it verifies that all imports resolve to paths that are local to the project.
|
||||
|
||||
Finally, this command writes the following artifacts into the output directory specified by the output path.
|
||||
|
||||
- `name@version` - dependency metadata$NEWLINE
|
||||
- `name@version.sha256` - dependency metadata's SHA-256 checksum$NEWLINE
|
||||
- `name@version.zip` - package archive$NEWLINE
|
||||
- `name@version.zip.sha256` - package archive's SHA-256 checksum
|
||||
|
||||
The output path option accepts the following placeholders:
|
||||
|
||||
- %{name}: The display name of the package$NEWLINE
|
||||
- %{version}: The version of the package
|
||||
|
||||
If a project has local project dependencies, the depended upon project directories must also
|
||||
be included as arguments to this command.
|
||||
|
||||
Examples:
|
||||
|
||||
```
|
||||
# Search the current working directory for a project, and package it.
|
||||
$ pkl project package
|
||||
|
||||
# Package all projects within the `packages/` directory.
|
||||
$ pkl project package packages/*/
|
||||
```
|
||||
"""
|
||||
.trimIndent(),
|
||||
) {
|
||||
private val testOptions by TestOptions()
|
||||
|
||||
private val projectDirs: List<Path> by
|
||||
argument("<dir>", "The project directories to package").path().multiple()
|
||||
|
||||
private val outputPath: String by
|
||||
option(
|
||||
names = arrayOf("--output-path"),
|
||||
help = "The directory to write artifacts to",
|
||||
metavar = "<path>",
|
||||
)
|
||||
.single()
|
||||
.default(".out/%{name}@%{version}")
|
||||
|
||||
private val skipPublishCheck: Boolean by
|
||||
option(
|
||||
names = arrayOf("--skip-publish-check"),
|
||||
help = "Skip checking if a package has already been published with different contents",
|
||||
)
|
||||
.single()
|
||||
.flag()
|
||||
|
||||
override fun run() {
|
||||
CliProjectPackager(
|
||||
baseOptions.baseOptions(emptyList()),
|
||||
projectDirs,
|
||||
testOptions.cliTestOptions,
|
||||
outputPath,
|
||||
skipPublishCheck,
|
||||
)
|
||||
.run()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
|
||||
* Copyright © 2024-2025 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.
|
||||
@@ -21,12 +21,8 @@ import org.pkl.cli.CliRepl
|
||||
import org.pkl.commons.cli.commands.BaseCommand
|
||||
import org.pkl.commons.cli.commands.ProjectOptions
|
||||
|
||||
class ReplCommand(helpLink: String) :
|
||||
BaseCommand(
|
||||
name = "repl",
|
||||
help = "Start a REPL session",
|
||||
helpLink = helpLink,
|
||||
) {
|
||||
object ReplCommand :
|
||||
BaseCommand(name = "repl", help = "Start a REPL session", helpLink = helpLink) {
|
||||
private val projectOptions by ProjectOptions()
|
||||
|
||||
override fun run() {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
|
||||
* Copyright © 2024-2025 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.
|
||||
@@ -17,16 +17,20 @@ package org.pkl.cli.commands
|
||||
|
||||
import com.github.ajalt.clikt.core.NoOpCliktCommand
|
||||
import com.github.ajalt.clikt.core.context
|
||||
import com.github.ajalt.clikt.core.subcommands
|
||||
import com.github.ajalt.clikt.parameters.options.versionOption
|
||||
import org.pkl.core.Release
|
||||
|
||||
class RootCommand(name: String, version: String, helpLink: String) :
|
||||
internal val helpLink = "${Release.current().documentation.homepage}pkl-cli/index.html#usage"
|
||||
|
||||
object RootCommand :
|
||||
NoOpCliktCommand(
|
||||
name = name,
|
||||
name = "pkl",
|
||||
printHelpOnEmptyArgs = true,
|
||||
epilog = "For more information, visit $helpLink",
|
||||
) {
|
||||
init {
|
||||
versionOption(version, names = setOf("-v", "--version"), message = { it })
|
||||
versionOption(Release.current().versionInfo, names = setOf("-v", "--version"), message = { it })
|
||||
|
||||
context {
|
||||
correctionSuggestor = { given, possible ->
|
||||
@@ -35,5 +39,15 @@ class RootCommand(name: String, version: String, helpLink: String) :
|
||||
} else possible
|
||||
}
|
||||
}
|
||||
|
||||
subcommands(
|
||||
EvalCommand,
|
||||
ReplCommand,
|
||||
ServerCommand,
|
||||
TestCommand,
|
||||
ProjectCommand,
|
||||
DownloadPackageCommand,
|
||||
AnalyzeCommand,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
|
||||
* Copyright © 2024-2025 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.
|
||||
@@ -19,11 +19,11 @@ import com.github.ajalt.clikt.core.CliktCommand
|
||||
import org.pkl.cli.CliServer
|
||||
import org.pkl.commons.cli.CliBaseOptions
|
||||
|
||||
class ServerCommand(helpLink: String) :
|
||||
object ServerCommand :
|
||||
CliktCommand(
|
||||
name = "server",
|
||||
help = "Run as a server that communicates over standard input/output",
|
||||
epilog = "For more information, visit $helpLink"
|
||||
epilog = "For more information, visit $helpLink",
|
||||
) {
|
||||
|
||||
override fun run() {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
|
||||
* Copyright © 2024-2025 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.
|
||||
@@ -26,7 +26,7 @@ import org.pkl.commons.cli.commands.BaseOptions
|
||||
import org.pkl.commons.cli.commands.ProjectOptions
|
||||
import org.pkl.commons.cli.commands.TestOptions
|
||||
|
||||
class TestCommand(helpLink: String) :
|
||||
object TestCommand :
|
||||
BaseCommand(name = "test", help = "Run tests within the given module(s)", helpLink = helpLink) {
|
||||
val modules: List<URI> by
|
||||
argument(name = "<modules>", help = "Module paths or URIs to evaluate.")
|
||||
@@ -40,7 +40,7 @@ class TestCommand(helpLink: String) :
|
||||
override fun run() {
|
||||
CliTestRunner(
|
||||
options = baseOptions.baseOptions(modules, projectOptions),
|
||||
testOptions = testOptions.cliTestOptions
|
||||
testOptions = testOptions.cliTestOptions,
|
||||
)
|
||||
.run()
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
|
||||
* Copyright © 2024-2025 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.
|
||||
@@ -16,7 +16,6 @@
|
||||
package org.pkl.cli
|
||||
|
||||
import com.github.ajalt.clikt.core.BadParameterValue
|
||||
import com.github.ajalt.clikt.core.subcommands
|
||||
import java.nio.file.Path
|
||||
import kotlin.io.path.createDirectory
|
||||
import kotlin.io.path.createSymbolicLinkPointingTo
|
||||
@@ -27,35 +26,34 @@ import org.junit.jupiter.api.assertThrows
|
||||
import org.junit.jupiter.api.condition.DisabledOnOs
|
||||
import org.junit.jupiter.api.condition.OS
|
||||
import org.junit.jupiter.api.io.TempDir
|
||||
import org.pkl.cli.commands.AnalyzeCommand
|
||||
import org.pkl.cli.commands.EvalCommand
|
||||
import org.pkl.cli.commands.RootCommand
|
||||
import org.pkl.commons.writeString
|
||||
import org.pkl.core.Release
|
||||
|
||||
class CliMainTest {
|
||||
|
||||
private val evalCmd = EvalCommand("")
|
||||
private val analyzeCommand = AnalyzeCommand("")
|
||||
private val cmd = RootCommand("pkl", "pkl version 1", "").subcommands(evalCmd, analyzeCommand)
|
||||
|
||||
@Test
|
||||
fun `duplicate CLI option produces meaningful error message`(@TempDir tempDir: Path) {
|
||||
val inputFile = tempDir.resolve("test.pkl").writeString("").toString()
|
||||
|
||||
assertThatCode {
|
||||
cmd.parse(arrayOf("eval", "--output-path", "path1", "--output-path", "path2", inputFile))
|
||||
RootCommand.parse(
|
||||
arrayOf("eval", "--output-path", "path1", "--output-path", "path2", inputFile)
|
||||
)
|
||||
}
|
||||
.hasMessage("Invalid value for \"--output-path\": Option cannot be repeated")
|
||||
|
||||
assertThatCode {
|
||||
cmd.parse(arrayOf("eval", "-o", "path1", "--output-path", "path2", inputFile))
|
||||
RootCommand.parse(arrayOf("eval", "-o", "path1", "--output-path", "path2", inputFile))
|
||||
}
|
||||
.hasMessage("Invalid value for \"--output-path\": Option cannot be repeated")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `eval requires at least one file`() {
|
||||
assertThatCode { cmd.parse(arrayOf("eval")) }.hasMessage("""Missing argument "<modules>"""")
|
||||
assertThatCode { RootCommand.parse(arrayOf("eval")) }
|
||||
.hasMessage("""Missing argument "<modules>"""")
|
||||
}
|
||||
|
||||
// Can't reliably create symlinks on Windows.
|
||||
@@ -76,7 +74,7 @@ class CliMainTest {
|
||||
val inputFile = tempDir.resolve("test.pkl").writeString(code).toString()
|
||||
val outputFile = makeSymdir(tempDir, "out", "linkOut").resolve("test.pkl").toString()
|
||||
|
||||
assertThatCode { cmd.parse(arrayOf("eval", inputFile, "-o", outputFile)) }
|
||||
assertThatCode { RootCommand.parse(arrayOf("eval", inputFile, "-o", outputFile)) }
|
||||
.doesNotThrowAnyException()
|
||||
}
|
||||
|
||||
@@ -87,22 +85,24 @@ class CliMainTest {
|
||||
val error =
|
||||
"""Invalid value for "--multiple-file-output-path": Option is mutually exclusive with -o, --output-path and -x, --expression."""
|
||||
|
||||
assertThatCode { cmd.parse(arrayOf("eval", "-m", testOut, "-x", "x", testIn)) }
|
||||
assertThatCode { RootCommand.parse(arrayOf("eval", "-m", testOut, "-x", "x", testIn)) }
|
||||
.hasMessage(error)
|
||||
|
||||
assertThatCode { cmd.parse(arrayOf("eval", "-m", testOut, "-o", "/tmp/test", testIn)) }
|
||||
assertThatCode { RootCommand.parse(arrayOf("eval", "-m", testOut, "-o", "/tmp/test", testIn)) }
|
||||
.hasMessage(error)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `showing version works`() {
|
||||
assertThatCode { cmd.parse(arrayOf("--version")) }.hasMessage("pkl version 1")
|
||||
assertThatCode { RootCommand.parse(arrayOf("--version")) }
|
||||
.hasMessage(Release.current().versionInfo)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `file paths get parsed into URIs`(@TempDir tempDir: Path) {
|
||||
cmd.parse(arrayOf("eval", makeInput(tempDir, "my file.txt")))
|
||||
RootCommand.parse(arrayOf("eval", makeInput(tempDir, "my file.txt")))
|
||||
|
||||
val evalCmd = RootCommand.registeredSubcommands().filterIsInstance<EvalCommand>().first()
|
||||
val modules = evalCmd.baseOptions.baseOptions(evalCmd.modules).normalizedSourceModules
|
||||
assertThat(modules).hasSize(1)
|
||||
assertThat(modules[0].path).endsWith("my file.txt")
|
||||
@@ -110,7 +110,8 @@ class CliMainTest {
|
||||
|
||||
@Test
|
||||
fun `invalid URIs are not accepted`() {
|
||||
val ex = assertThrows<BadParameterValue> { cmd.parse(arrayOf("eval", "file:my file.txt")) }
|
||||
val ex =
|
||||
assertThrows<BadParameterValue> { RootCommand.parse(arrayOf("eval", "file:my file.txt")) }
|
||||
|
||||
assertThat(ex.message).contains("URI `file:my file.txt` has invalid syntax")
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
|
||||
* Copyright © 2024-2025 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.
|
||||
@@ -16,18 +16,16 @@
|
||||
package org.pkl.cli
|
||||
|
||||
import com.github.ajalt.clikt.core.MissingArgument
|
||||
import com.github.ajalt.clikt.core.subcommands
|
||||
import java.io.StringWriter
|
||||
import java.io.Writer
|
||||
import java.net.URI
|
||||
import java.nio.file.Path
|
||||
import java.util.Locale
|
||||
import java.util.*
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.assertj.core.api.Assertions.assertThatCode
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.assertThrows
|
||||
import org.junit.jupiter.api.io.TempDir
|
||||
import org.pkl.cli.commands.EvalCommand
|
||||
import org.pkl.cli.commands.RootCommand
|
||||
import org.pkl.commons.cli.CliBaseOptions
|
||||
import org.pkl.commons.cli.CliException
|
||||
@@ -35,7 +33,6 @@ import org.pkl.commons.cli.CliTestOptions
|
||||
import org.pkl.commons.readString
|
||||
import org.pkl.commons.toUri
|
||||
import org.pkl.commons.writeString
|
||||
import org.pkl.core.Release
|
||||
|
||||
class CliTestRunnerTest {
|
||||
@Test
|
||||
@@ -381,7 +378,7 @@ class CliTestRunnerTest {
|
||||
val opts =
|
||||
CliBaseOptions(
|
||||
sourceModules = listOf(input.toUri(), input2.toUri()),
|
||||
settings = URI("pkl:settings")
|
||||
settings = URI("pkl:settings"),
|
||||
)
|
||||
val testOpts = CliTestOptions(junitDir = tempDir)
|
||||
val runner = CliTestRunner(opts, testOpts, noopWriter, noopWriter)
|
||||
@@ -391,12 +388,7 @@ class CliTestRunnerTest {
|
||||
@Test
|
||||
fun `no source modules specified has same message as pkl eval`() {
|
||||
val e1 = assertThrows<CliException> { CliTestRunner(CliBaseOptions(), CliTestOptions()).run() }
|
||||
val e2 =
|
||||
assertThrows<MissingArgument> {
|
||||
val rootCommand =
|
||||
RootCommand("pkl", Release.current().versionInfo(), "").subcommands(EvalCommand(""))
|
||||
rootCommand.parse(listOf("eval"))
|
||||
}
|
||||
val e2 = assertThrows<MissingArgument> { RootCommand.parse(listOf("eval")) }
|
||||
assertThat(e1).hasMessageContaining("Missing argument \"<modules>\"")
|
||||
assertThat(e1.message!!.replace("test", "eval")).isEqualTo(e2.helpMessage())
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
|
||||
* Copyright © 2024-2025 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.
|
||||
@@ -28,10 +28,10 @@ import org.pkl.core.Release
|
||||
|
||||
/** Main method for the Java code generator CLI. */
|
||||
internal fun main(args: Array<String>) {
|
||||
cliMain { PklJavaCodegenCommand().main(args) }
|
||||
cliMain { PklJavaCodegenCommand.main(args) }
|
||||
}
|
||||
|
||||
class PklJavaCodegenCommand :
|
||||
object PklJavaCodegenCommand :
|
||||
ModulesCommand(
|
||||
name = "pkl-codegen-java",
|
||||
helpLink = Release.current().documentation().homepage(),
|
||||
@@ -43,7 +43,7 @@ class PklJavaCodegenCommand :
|
||||
option(
|
||||
names = arrayOf("-o", "--output-dir"),
|
||||
metavar = "<path>",
|
||||
help = "The directory where generated source code is placed."
|
||||
help = "The directory where generated source code is placed.",
|
||||
)
|
||||
.path()
|
||||
.default(defaults.outputDir)
|
||||
@@ -52,7 +52,7 @@ class PklJavaCodegenCommand :
|
||||
option(
|
||||
names = arrayOf("--indent"),
|
||||
metavar = "<chars>",
|
||||
help = "The characters to use for indenting generated source code."
|
||||
help = "The characters to use for indenting generated source code.",
|
||||
)
|
||||
.default(defaults.indent)
|
||||
|
||||
@@ -61,21 +61,21 @@ class PklJavaCodegenCommand :
|
||||
names = arrayOf("--generate-getters"),
|
||||
help =
|
||||
"Whether to generate public getter methods and " +
|
||||
"private final fields instead of public final fields."
|
||||
"private final fields instead of public final fields.",
|
||||
)
|
||||
.flag()
|
||||
|
||||
private val generateJavadoc: Boolean by
|
||||
option(
|
||||
names = arrayOf("--generate-javadoc"),
|
||||
help = "Whether to preserve Pkl doc comments by generating corresponding Javadoc comments."
|
||||
help = "Whether to preserve Pkl doc comments by generating corresponding Javadoc comments.",
|
||||
)
|
||||
.flag()
|
||||
|
||||
private val generateSpringBoot: Boolean by
|
||||
option(
|
||||
names = arrayOf("--generate-spring-boot"),
|
||||
help = "Whether to generate config classes for use with Spring Boot."
|
||||
help = "Whether to generate config classes for use with Spring Boot.",
|
||||
)
|
||||
.flag()
|
||||
|
||||
@@ -83,7 +83,7 @@ class PklJavaCodegenCommand :
|
||||
option(
|
||||
names = arrayOf("--params-annotation"),
|
||||
help =
|
||||
"Fully qualified name of the annotation type to use for annotating constructor parameters with their name."
|
||||
"Fully qualified name of the annotation type to use for annotating constructor parameters with their name.",
|
||||
)
|
||||
.defaultLazy(
|
||||
"`none` if `--generate-spring-boot` is set, `org.pkl.config.java.mapper.Named` otherwise"
|
||||
@@ -100,13 +100,13 @@ class PklJavaCodegenCommand :
|
||||
The specified annotation type must be annotated with `@java.lang.annotation.Target(ElementType.TYPE_USE)`
|
||||
or the generated code may not compile.
|
||||
"""
|
||||
.trimIndent()
|
||||
.trimIndent(),
|
||||
)
|
||||
|
||||
private val implementSerializable: Boolean by
|
||||
option(
|
||||
names = arrayOf("--implement-serializable"),
|
||||
help = "Whether to generate classes that implement java.io.Serializable."
|
||||
help = "Whether to generate classes that implement java.io.Serializable.",
|
||||
)
|
||||
.flag()
|
||||
|
||||
@@ -121,7 +121,7 @@ class PklJavaCodegenCommand :
|
||||
With this option, you can override or modify the default names, renaming entire
|
||||
classes or just their packages.
|
||||
"""
|
||||
.trimIndent()
|
||||
.trimIndent(),
|
||||
)
|
||||
.associate()
|
||||
|
||||
@@ -137,7 +137,7 @@ class PklJavaCodegenCommand :
|
||||
paramsAnnotation = if (paramsAnnotation == "none") null else paramsAnnotation,
|
||||
nonNullAnnotation = nonNullAnnotation,
|
||||
implementSerializable = implementSerializable,
|
||||
renames = renames
|
||||
renames = renames,
|
||||
)
|
||||
CliJavaCodeGenerator(options).run()
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
|
||||
* Copyright © 2024-2025 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.
|
||||
@@ -31,10 +31,10 @@ import org.pkl.core.Release
|
||||
|
||||
/** Main method for the Kotlin code generator CLI. */
|
||||
internal fun main(args: Array<String>) {
|
||||
cliMain { PklKotlinCodegenCommand().main(args) }
|
||||
cliMain { PklKotlinCodegenCommand.main(args) }
|
||||
}
|
||||
|
||||
class PklKotlinCodegenCommand :
|
||||
object PklKotlinCodegenCommand :
|
||||
ModulesCommand(
|
||||
name = "pkl-codegen-kotlin",
|
||||
helpLink = Release.current().documentation().homepage(),
|
||||
@@ -46,7 +46,7 @@ class PklKotlinCodegenCommand :
|
||||
option(
|
||||
names = arrayOf("-o", "--output-dir"),
|
||||
metavar = "<path>",
|
||||
help = "The directory where generated source code is placed."
|
||||
help = "The directory where generated source code is placed.",
|
||||
)
|
||||
.path()
|
||||
.default(defaults.outputDir)
|
||||
@@ -55,28 +55,28 @@ class PklKotlinCodegenCommand :
|
||||
option(
|
||||
names = arrayOf("--indent"),
|
||||
metavar = "<chars>",
|
||||
help = "The characters to use for indenting generated source code."
|
||||
help = "The characters to use for indenting generated source code.",
|
||||
)
|
||||
.default(defaults.indent)
|
||||
|
||||
private val generateKdoc: Boolean by
|
||||
option(
|
||||
names = arrayOf("--generate-kdoc"),
|
||||
help = "Whether to preserve Pkl doc comments by generating corresponding KDoc comments."
|
||||
help = "Whether to preserve Pkl doc comments by generating corresponding KDoc comments.",
|
||||
)
|
||||
.flag()
|
||||
|
||||
private val generateSpringboot: Boolean by
|
||||
option(
|
||||
names = arrayOf("--generate-spring-boot"),
|
||||
help = "Whether to generate config classes for use with Spring Boot."
|
||||
help = "Whether to generate config classes for use with Spring Boot.",
|
||||
)
|
||||
.flag()
|
||||
|
||||
private val implementSerializable: Boolean by
|
||||
option(
|
||||
names = arrayOf("--implement-serializable"),
|
||||
help = "Whether to generate classes that implement java.io.Serializable."
|
||||
help = "Whether to generate classes that implement java.io.Serializable.",
|
||||
)
|
||||
.flag()
|
||||
|
||||
@@ -91,7 +91,7 @@ class PklKotlinCodegenCommand :
|
||||
With this option, you can override or modify the default names, renaming entire
|
||||
classes or just their packages.
|
||||
"""
|
||||
.trimIndent()
|
||||
.trimIndent(),
|
||||
)
|
||||
.associate()
|
||||
|
||||
@@ -104,7 +104,7 @@ class PklKotlinCodegenCommand :
|
||||
generateKdoc = generateKdoc,
|
||||
generateSpringBootConfig = generateSpringboot,
|
||||
implementSerializable = implementSerializable,
|
||||
renames = renames
|
||||
renames = renames,
|
||||
)
|
||||
CliKotlinCodeGenerator(options).run()
|
||||
}
|
||||
|
||||
@@ -34,10 +34,10 @@ import org.pkl.core.Release
|
||||
|
||||
/** Main method for the Pkldoc CLI. */
|
||||
internal fun main(args: Array<String>) {
|
||||
cliMain { DocCommand().main(args) }
|
||||
cliMain { DocCommand.main(args) }
|
||||
}
|
||||
|
||||
class DocCommand :
|
||||
object DocCommand :
|
||||
BaseCommand(name = "pkldoc", helpLink = Release.current().documentation().homepage()) {
|
||||
|
||||
private val modules: List<URI> by
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright © 2024 Apple Inc. and the Pkl project authors. All rights reserved.
|
||||
* Copyright © 2024-2025 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.
|
||||
@@ -21,12 +21,9 @@ import org.junit.jupiter.api.assertThrows
|
||||
import org.pkl.commons.cli.CliException
|
||||
|
||||
class CliMainTest {
|
||||
|
||||
private val docCommand = DocCommand()
|
||||
|
||||
@Test
|
||||
fun `CLI run test`() {
|
||||
val e = assertThrows<CliException> { docCommand.parse(arrayOf("foo", "--output-dir", "/tmp")) }
|
||||
val e = assertThrows<CliException> { DocCommand.parse(arrayOf("foo", "--output-dir", "/tmp")) }
|
||||
assertThat(e)
|
||||
.hasMessageContaining("must contain at least one module named `doc-package-info.pkl`")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user