From 9784cd7265e285a7f0c4d9a1b12919258b72e6a4 Mon Sep 17 00:00:00 2001 From: Daniel Chao Date: Wed, 5 Feb 2025 09:18:23 -0800 Subject: [PATCH] Turn CLI commands into objects, self register subcommands (#935) Usages of `RootCommand` no longer need to initialize a new instance, nor register subcommands. --- pkl-cli/src/main/kotlin/org/pkl/cli/Main.kt | 20 +- .../org/pkl/cli/commands/AnalyzeCommand.kt | 64 +++-- .../cli/commands/DownloadPackageCommand.kt | 10 +- .../org/pkl/cli/commands/EvalCommand.kt | 20 +- .../org/pkl/cli/commands/ProjectCommand.kt | 218 +++++++++--------- .../org/pkl/cli/commands/ReplCommand.kt | 10 +- .../org/pkl/cli/commands/RootCommand.kt | 22 +- .../org/pkl/cli/commands/ServerCommand.kt | 6 +- .../org/pkl/cli/commands/TestCommand.kt | 6 +- .../test/kotlin/org/pkl/cli/CliMainTest.kt | 33 +-- .../kotlin/org/pkl/cli/CliTestRunnerTest.kt | 16 +- .../main/kotlin/org/pkl/codegen/java/Main.kt | 26 +-- .../kotlin/org/pkl/codegen/kotlin/Main.kt | 20 +- pkl-doc/src/main/kotlin/org/pkl/doc/Main.kt | 4 +- .../test/kotlin/org/pkl/doc/CliMainTest.kt | 7 +- 15 files changed, 229 insertions(+), 253 deletions(-) diff --git a/pkl-cli/src/main/kotlin/org/pkl/cli/Main.kt b/pkl-cli/src/main/kotlin/org/pkl/cli/Main.kt index 958d297b..155d4c5a 100644 --- a/pkl-cli/src/main/kotlin/org/pkl/cli/Main.kt +++ b/pkl-cli/src/main/kotlin/org/pkl/cli/Main.kt @@ -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) { - 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) } } diff --git a/pkl-cli/src/main/kotlin/org/pkl/cli/commands/AnalyzeCommand.kt b/pkl-cli/src/main/kotlin/org/pkl/cli/commands/AnalyzeCommand.kt index 63c44545..312b8ce3 100644 --- a/pkl-cli/src/main/kotlin/org/pkl/cli/commands/AnalyzeCommand.kt +++ b/pkl-cli/src/main/kotlin/org/pkl/cli/commands/AnalyzeCommand.kt @@ -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 = "", - 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 = "", + 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() } } diff --git a/pkl-cli/src/main/kotlin/org/pkl/cli/commands/DownloadPackageCommand.kt b/pkl-cli/src/main/kotlin/org/pkl/cli/commands/DownloadPackageCommand.kt index 26fdaafe..3f1846d0 100644 --- a/pkl-cli/src/main/kotlin/org/pkl/cli/commands/DownloadPackageCommand.kt +++ b/pkl-cli/src/main/kotlin/org/pkl/cli/commands/DownloadPackageCommand.kt @@ -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() } diff --git a/pkl-cli/src/main/kotlin/org/pkl/cli/commands/EvalCommand.kt b/pkl-cli/src/main/kotlin/org/pkl/cli/commands/EvalCommand.kt index 73cdebe0..d73a69b0 100644 --- a/pkl-cli/src/main/kotlin/org/pkl/cli/commands/EvalCommand.kt +++ b/pkl-cli/src/main/kotlin/org/pkl/cli/commands/EvalCommand.kt @@ -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 = "", - 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 = "", 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 = "", - 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 = "", - 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() } diff --git a/pkl-cli/src/main/kotlin/org/pkl/cli/commands/ProjectCommand.kt b/pkl-cli/src/main/kotlin/org/pkl/cli/commands/ProjectCommand.kt index 1738cf2d..53039546 100644 --- a/pkl-cli/src/main/kotlin/org/pkl/cli/commands/ProjectCommand.kt +++ b/pkl-cli/src/main/kotlin/org/pkl/cli/commands/ProjectCommand.kt @@ -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 by - argument("", "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 by - argument("", "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 = "" - ) - .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 by + argument("", "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 by + argument("", "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 = "", + ) + .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() } } diff --git a/pkl-cli/src/main/kotlin/org/pkl/cli/commands/ReplCommand.kt b/pkl-cli/src/main/kotlin/org/pkl/cli/commands/ReplCommand.kt index 2723c986..301ff323 100644 --- a/pkl-cli/src/main/kotlin/org/pkl/cli/commands/ReplCommand.kt +++ b/pkl-cli/src/main/kotlin/org/pkl/cli/commands/ReplCommand.kt @@ -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() { diff --git a/pkl-cli/src/main/kotlin/org/pkl/cli/commands/RootCommand.kt b/pkl-cli/src/main/kotlin/org/pkl/cli/commands/RootCommand.kt index 578a9875..5041504c 100644 --- a/pkl-cli/src/main/kotlin/org/pkl/cli/commands/RootCommand.kt +++ b/pkl-cli/src/main/kotlin/org/pkl/cli/commands/RootCommand.kt @@ -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, + ) } } diff --git a/pkl-cli/src/main/kotlin/org/pkl/cli/commands/ServerCommand.kt b/pkl-cli/src/main/kotlin/org/pkl/cli/commands/ServerCommand.kt index cd777b5f..86941b0f 100644 --- a/pkl-cli/src/main/kotlin/org/pkl/cli/commands/ServerCommand.kt +++ b/pkl-cli/src/main/kotlin/org/pkl/cli/commands/ServerCommand.kt @@ -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() { diff --git a/pkl-cli/src/main/kotlin/org/pkl/cli/commands/TestCommand.kt b/pkl-cli/src/main/kotlin/org/pkl/cli/commands/TestCommand.kt index ea273b1e..b4111ab8 100644 --- a/pkl-cli/src/main/kotlin/org/pkl/cli/commands/TestCommand.kt +++ b/pkl-cli/src/main/kotlin/org/pkl/cli/commands/TestCommand.kt @@ -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 by argument(name = "", 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() } diff --git a/pkl-cli/src/test/kotlin/org/pkl/cli/CliMainTest.kt b/pkl-cli/src/test/kotlin/org/pkl/cli/CliMainTest.kt index 8ee7119a..f9cf1a7f 100644 --- a/pkl-cli/src/test/kotlin/org/pkl/cli/CliMainTest.kt +++ b/pkl-cli/src/test/kotlin/org/pkl/cli/CliMainTest.kt @@ -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 """"") + assertThatCode { RootCommand.parse(arrayOf("eval")) } + .hasMessage("""Missing argument """"") } // 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().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 { cmd.parse(arrayOf("eval", "file:my file.txt")) } + val ex = + assertThrows { RootCommand.parse(arrayOf("eval", "file:my file.txt")) } assertThat(ex.message).contains("URI `file:my file.txt` has invalid syntax") } diff --git a/pkl-cli/src/test/kotlin/org/pkl/cli/CliTestRunnerTest.kt b/pkl-cli/src/test/kotlin/org/pkl/cli/CliTestRunnerTest.kt index 65aac748..00211639 100644 --- a/pkl-cli/src/test/kotlin/org/pkl/cli/CliTestRunnerTest.kt +++ b/pkl-cli/src/test/kotlin/org/pkl/cli/CliTestRunnerTest.kt @@ -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 { CliTestRunner(CliBaseOptions(), CliTestOptions()).run() } - val e2 = - assertThrows { - val rootCommand = - RootCommand("pkl", Release.current().versionInfo(), "").subcommands(EvalCommand("")) - rootCommand.parse(listOf("eval")) - } + val e2 = assertThrows { RootCommand.parse(listOf("eval")) } assertThat(e1).hasMessageContaining("Missing argument \"\"") assertThat(e1.message!!.replace("test", "eval")).isEqualTo(e2.helpMessage()) } diff --git a/pkl-codegen-java/src/main/kotlin/org/pkl/codegen/java/Main.kt b/pkl-codegen-java/src/main/kotlin/org/pkl/codegen/java/Main.kt index 6b578088..80271d6f 100644 --- a/pkl-codegen-java/src/main/kotlin/org/pkl/codegen/java/Main.kt +++ b/pkl-codegen-java/src/main/kotlin/org/pkl/codegen/java/Main.kt @@ -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) { - 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 = "", - 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 = "", - 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() } diff --git a/pkl-codegen-kotlin/src/main/kotlin/org/pkl/codegen/kotlin/Main.kt b/pkl-codegen-kotlin/src/main/kotlin/org/pkl/codegen/kotlin/Main.kt index 5673fa83..68b3642a 100644 --- a/pkl-codegen-kotlin/src/main/kotlin/org/pkl/codegen/kotlin/Main.kt +++ b/pkl-codegen-kotlin/src/main/kotlin/org/pkl/codegen/kotlin/Main.kt @@ -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) { - 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 = "", - 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 = "", - 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() } diff --git a/pkl-doc/src/main/kotlin/org/pkl/doc/Main.kt b/pkl-doc/src/main/kotlin/org/pkl/doc/Main.kt index 6743b0fe..44485a15 100644 --- a/pkl-doc/src/main/kotlin/org/pkl/doc/Main.kt +++ b/pkl-doc/src/main/kotlin/org/pkl/doc/Main.kt @@ -34,10 +34,10 @@ import org.pkl.core.Release /** Main method for the Pkldoc CLI. */ internal fun main(args: Array) { - cliMain { DocCommand().main(args) } + cliMain { DocCommand.main(args) } } -class DocCommand : +object DocCommand : BaseCommand(name = "pkldoc", helpLink = Release.current().documentation().homepage()) { private val modules: List by diff --git a/pkl-doc/src/test/kotlin/org/pkl/doc/CliMainTest.kt b/pkl-doc/src/test/kotlin/org/pkl/doc/CliMainTest.kt index 6c5eb927..868d93a6 100644 --- a/pkl-doc/src/test/kotlin/org/pkl/doc/CliMainTest.kt +++ b/pkl-doc/src/test/kotlin/org/pkl/doc/CliMainTest.kt @@ -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 { docCommand.parse(arrayOf("foo", "--output-dir", "/tmp")) } + val e = assertThrows { DocCommand.parse(arrayOf("foo", "--output-dir", "/tmp")) } assertThat(e) .hasMessageContaining("must contain at least one module named `doc-package-info.pkl`") }