mirror of
https://github.com/apple/pkl.git
synced 2026-04-22 08:18:32 +02:00
Coalesce pkl format subcommands into the parent command. (#1263)
This commit is contained in:
@@ -733,16 +733,22 @@ pkl shell-completion bash
|
|||||||
pkl shell-completion zsh
|
pkl shell-completion zsh
|
||||||
----
|
----
|
||||||
|
|
||||||
[[command-format-check]]
|
[[command-format]]
|
||||||
=== `pkl format check`
|
=== `pkl format`
|
||||||
|
|
||||||
*Synopsis*: `pkl format check <file-or-dir-path>`
|
*Synopsis*: `pkl format <options> [<paths>]`
|
||||||
|
|
||||||
This command checks for format violations on the given file or directory and print their names to stdout. +
|
This command formats or checks formatting of Pkl files. +
|
||||||
It returns a non-zero status code in case violations are found.
|
Exit codes:
|
||||||
|
|
||||||
|
* 0: No violations found.
|
||||||
|
* 1: An unexpected error happened (ex.: IO error)
|
||||||
|
* 11: Violations were found.
|
||||||
|
|
||||||
If the path is a directory, recursively looks for files with a `.pkl` extension, or files named `PklProject`.
|
If the path is a directory, recursively looks for files with a `.pkl` extension, or files named `PklProject`.
|
||||||
|
|
||||||
|
By default, the input files are formatted, and written to standard out.
|
||||||
|
|
||||||
==== Options
|
==== Options
|
||||||
|
|
||||||
.--grammar-version
|
.--grammar-version
|
||||||
@@ -753,29 +759,22 @@ Select the grammar compatibility version for the formatter.
|
|||||||
New versions are created for each backward incompatible grammar change.
|
New versions are created for each backward incompatible grammar change.
|
||||||
====
|
====
|
||||||
|
|
||||||
[[command-format-apply]]
|
|
||||||
=== `pkl format apply`
|
|
||||||
|
|
||||||
*Synopsis*: `pkl format apply [<options>] <file-or-dir-path>`
|
|
||||||
|
|
||||||
This command formats the given files overwriting them.
|
|
||||||
|
|
||||||
If the path is a directory, recursively looks for files with a `.pkl` extension, or files named `PklProject`.
|
|
||||||
|
|
||||||
==== Options
|
|
||||||
|
|
||||||
.-s, --silent
|
.-s, --silent
|
||||||
[%collapsible]
|
[%collapsible]
|
||||||
====
|
====
|
||||||
Do not write the name of wrongly formatted files to stdout.
|
Skip writing to standard out. Mutually exclusive with `--diff-name-only`.
|
||||||
====
|
====
|
||||||
|
|
||||||
.--grammar-version
|
.-w, --write
|
||||||
[%collapsible]
|
[%collapsible]
|
||||||
====
|
====
|
||||||
Default: `2` (latest version) +
|
Format files in place, overwriting them. Implies `--diff-name-only`.
|
||||||
Select the grammar compatibility version for the formatter.
|
====
|
||||||
New versions are created for each backward incompatible grammar change.
|
|
||||||
|
.--diff-name-only
|
||||||
|
[%collapsible]
|
||||||
|
====
|
||||||
|
Write the path of files with formatting violations to stdout.
|
||||||
====
|
====
|
||||||
|
|
||||||
[[common-options]]
|
[[common-options]]
|
||||||
|
|||||||
@@ -1,59 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright © 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.
|
|
||||||
* 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 java.io.IOException
|
|
||||||
import java.nio.file.Files
|
|
||||||
import java.nio.file.Path
|
|
||||||
import kotlin.io.path.writeText
|
|
||||||
import org.pkl.commons.cli.CliBaseOptions
|
|
||||||
import org.pkl.commons.cli.CliException
|
|
||||||
import org.pkl.formatter.GrammarVersion
|
|
||||||
|
|
||||||
class CliFormatterApply(
|
|
||||||
cliBaseOptions: CliBaseOptions,
|
|
||||||
paths: List<Path>,
|
|
||||||
grammarVersion: GrammarVersion,
|
|
||||||
private val silent: Boolean,
|
|
||||||
) : CliFormatterCommand(cliBaseOptions, paths, grammarVersion) {
|
|
||||||
|
|
||||||
override fun doRun() {
|
|
||||||
var status = 0
|
|
||||||
|
|
||||||
for (path in paths()) {
|
|
||||||
val contents = Files.readString(path)
|
|
||||||
val (formatted, stat) = format(path, contents)
|
|
||||||
status = if (status == 0) stat else status
|
|
||||||
if (stat != 0 || contents == formatted) continue
|
|
||||||
if (!silent) {
|
|
||||||
consoleWriter.write(path.toAbsolutePath().toString())
|
|
||||||
consoleWriter.appendLine()
|
|
||||||
consoleWriter.flush()
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
path.writeText(formatted, Charsets.UTF_8)
|
|
||||||
} catch (e: IOException) {
|
|
||||||
consoleWriter.write("Could not overwrite `$path`: ${e.message}")
|
|
||||||
consoleWriter.appendLine()
|
|
||||||
consoleWriter.flush()
|
|
||||||
status = 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (status != 0) {
|
|
||||||
throw CliException("Formatting violations found.", status)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright © 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.
|
|
||||||
* 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 java.nio.file.Files
|
|
||||||
import java.nio.file.Path
|
|
||||||
import org.pkl.commons.cli.CliBaseOptions
|
|
||||||
import org.pkl.commons.cli.CliException
|
|
||||||
import org.pkl.formatter.GrammarVersion
|
|
||||||
|
|
||||||
class CliFormatterCheck(
|
|
||||||
cliBaseOptions: CliBaseOptions,
|
|
||||||
paths: List<Path>,
|
|
||||||
grammarVersion: GrammarVersion,
|
|
||||||
) : CliFormatterCommand(cliBaseOptions, paths, grammarVersion) {
|
|
||||||
|
|
||||||
override fun doRun() {
|
|
||||||
var status = 0
|
|
||||||
|
|
||||||
for (path in paths()) {
|
|
||||||
val contents = Files.readString(path)
|
|
||||||
val (formatted, stat) = format(path, contents)
|
|
||||||
status = if (status == 0) stat else status
|
|
||||||
if (contents != formatted) {
|
|
||||||
consoleWriter.write(path.toAbsolutePath().toString())
|
|
||||||
consoleWriter.appendLine()
|
|
||||||
consoleWriter.flush()
|
|
||||||
status = 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (status != 0) {
|
|
||||||
throw CliException("Formatting violations found.", status)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -15,48 +15,144 @@
|
|||||||
*/
|
*/
|
||||||
package org.pkl.cli
|
package org.pkl.cli
|
||||||
|
|
||||||
|
import java.io.File
|
||||||
|
import java.io.IOException
|
||||||
import java.io.Writer
|
import java.io.Writer
|
||||||
|
import java.nio.file.Files
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
import kotlin.io.path.ExperimentalPathApi
|
import java.util.stream.Stream
|
||||||
import kotlin.io.path.extension
|
import kotlin.io.path.extension
|
||||||
import kotlin.io.path.isDirectory
|
import kotlin.io.path.isDirectory
|
||||||
import kotlin.io.path.name
|
import kotlin.io.path.name
|
||||||
import kotlin.io.path.walk
|
import kotlin.io.path.writeText
|
||||||
|
import kotlin.math.max
|
||||||
import org.pkl.commons.cli.CliBaseOptions
|
import org.pkl.commons.cli.CliBaseOptions
|
||||||
import org.pkl.commons.cli.CliCommand
|
import org.pkl.commons.cli.CliCommand
|
||||||
|
import org.pkl.commons.cli.CliTestException
|
||||||
|
import org.pkl.core.ModuleSource
|
||||||
|
import org.pkl.core.runtime.VmUtils
|
||||||
|
import org.pkl.core.util.IoUtils
|
||||||
import org.pkl.formatter.Formatter
|
import org.pkl.formatter.Formatter
|
||||||
import org.pkl.formatter.GrammarVersion
|
import org.pkl.formatter.GrammarVersion
|
||||||
import org.pkl.parser.GenericParserError
|
import org.pkl.parser.GenericParserError
|
||||||
|
|
||||||
abstract class CliFormatterCommand
|
class CliFormatterCommand
|
||||||
@JvmOverloads
|
@JvmOverloads
|
||||||
constructor(
|
constructor(
|
||||||
options: CliBaseOptions,
|
private val paths: List<Path>,
|
||||||
protected val paths: List<Path>,
|
private val grammarVersion: GrammarVersion,
|
||||||
protected val grammarVersion: GrammarVersion,
|
private val overwrite: Boolean,
|
||||||
protected val consoleWriter: Writer = System.out.writer(),
|
private val diffNameOnly: Boolean,
|
||||||
) : CliCommand(options) {
|
private val silent: Boolean,
|
||||||
protected fun format(file: Path, contents: String): Pair<String, Int> {
|
private val consoleWriter: Writer = System.out.writer(),
|
||||||
try {
|
private val errWriter: Writer = System.err.writer(),
|
||||||
return Formatter().format(contents, grammarVersion) to 0
|
) : CliCommand(CliBaseOptions()) {
|
||||||
} catch (pe: GenericParserError) {
|
|
||||||
consoleWriter.write("Could not format `$file`: $pe")
|
private fun format(contents: String): String {
|
||||||
consoleWriter.appendLine()
|
return Formatter().format(contents, grammarVersion)
|
||||||
consoleWriter.flush()
|
}
|
||||||
return "" to 1
|
|
||||||
|
private fun writeErr(error: String) {
|
||||||
|
errWriter.write(error)
|
||||||
|
errWriter.appendLine()
|
||||||
|
errWriter.flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun write(message: String) {
|
||||||
|
if (silent) return
|
||||||
|
consoleWriter.write(message)
|
||||||
|
consoleWriter.appendLine()
|
||||||
|
consoleWriter.flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun allSources(): Stream<ModuleSource> {
|
||||||
|
return paths.distinct().stream().flatMap { path ->
|
||||||
|
when {
|
||||||
|
path.toString() == "-" -> Stream.of(ModuleSource.text(IoUtils.readString(System.`in`)))
|
||||||
|
path.isDirectory() ->
|
||||||
|
Files.walk(path)
|
||||||
|
.filter { it.extension == "pkl" || it.name == "PklProject" }
|
||||||
|
.map(ModuleSource::path)
|
||||||
|
else -> Stream.of(ModuleSource.path(path))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalPathApi::class)
|
override fun doRun() {
|
||||||
protected fun paths(): Set<Path> {
|
val status = Status(SUCCESS)
|
||||||
val allPaths = mutableSetOf<Path>()
|
|
||||||
for (path in paths) {
|
handleSources(status)
|
||||||
if (path.isDirectory()) {
|
|
||||||
allPaths.addAll(path.walk().filter { it.extension == "pkl" || it.name == "PklProject" })
|
when (status.status) {
|
||||||
} else {
|
FORMATTING_VIOLATION -> {
|
||||||
allPaths.add(path)
|
// using CliTestException instead of CliException because we want full control on how to
|
||||||
|
// print errors
|
||||||
|
throw CliTestException("", status.status)
|
||||||
|
}
|
||||||
|
ERROR -> {
|
||||||
|
if (!silent) {
|
||||||
|
writeErr("An error occurred during formatting.")
|
||||||
|
}
|
||||||
|
throw CliTestException("", status.status)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleSources(status: Status) {
|
||||||
|
for (source in allSources()) {
|
||||||
|
val path = if (source.uri == VmUtils.REPL_TEXT_URI) Path.of("-") else Path.of(source.uri)
|
||||||
|
try {
|
||||||
|
val contents =
|
||||||
|
if (source.contents != null) {
|
||||||
|
if (overwrite) {
|
||||||
|
writeErr("Cannot write to stdin.")
|
||||||
|
throw CliTestException("", ERROR)
|
||||||
|
}
|
||||||
|
source.contents!!
|
||||||
|
} else {
|
||||||
|
File(source.uri).readText()
|
||||||
|
}
|
||||||
|
|
||||||
|
val formatted = format(contents)
|
||||||
|
if (contents != formatted) {
|
||||||
|
status.update(FORMATTING_VIOLATION)
|
||||||
|
if (diffNameOnly || overwrite) {
|
||||||
|
// if `--diff-name-only` or `-w` is specified, only write file names
|
||||||
|
write(path.toAbsolutePath().toString())
|
||||||
|
}
|
||||||
|
|
||||||
|
if (overwrite) {
|
||||||
|
path.writeText(formatted, Charsets.UTF_8)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!diffNameOnly && !overwrite) {
|
||||||
|
write(formatted)
|
||||||
|
}
|
||||||
|
} catch (pe: GenericParserError) {
|
||||||
|
writeErr("Could not format `$path`: $pe")
|
||||||
|
status.update(ERROR)
|
||||||
|
} catch (e: IOException) {
|
||||||
|
writeErr("IO error while reading `$path`: ${e.message}")
|
||||||
|
status.update(ERROR)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val SUCCESS = 0
|
||||||
|
private const val FORMATTING_VIOLATION = 11
|
||||||
|
private const val ERROR = 1
|
||||||
|
|
||||||
|
private class Status(var status: Int) {
|
||||||
|
fun update(newStatus: Int) {
|
||||||
|
status =
|
||||||
|
when {
|
||||||
|
status == ERROR -> status
|
||||||
|
newStatus == ERROR -> newStatus
|
||||||
|
else -> max(status, newStatus)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return allPaths
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,9 +15,8 @@
|
|||||||
*/
|
*/
|
||||||
package org.pkl.cli.commands
|
package org.pkl.cli.commands
|
||||||
|
|
||||||
|
import com.github.ajalt.clikt.core.CliktCommand
|
||||||
import com.github.ajalt.clikt.core.Context
|
import com.github.ajalt.clikt.core.Context
|
||||||
import com.github.ajalt.clikt.core.NoOpCliktCommand
|
|
||||||
import com.github.ajalt.clikt.core.subcommands
|
|
||||||
import com.github.ajalt.clikt.parameters.arguments.argument
|
import com.github.ajalt.clikt.parameters.arguments.argument
|
||||||
import com.github.ajalt.clikt.parameters.arguments.multiple
|
import com.github.ajalt.clikt.parameters.arguments.multiple
|
||||||
import com.github.ajalt.clikt.parameters.options.default
|
import com.github.ajalt.clikt.parameters.options.default
|
||||||
@@ -26,75 +25,75 @@ import com.github.ajalt.clikt.parameters.options.option
|
|||||||
import com.github.ajalt.clikt.parameters.types.enum
|
import com.github.ajalt.clikt.parameters.types.enum
|
||||||
import com.github.ajalt.clikt.parameters.types.path
|
import com.github.ajalt.clikt.parameters.types.path
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
import org.pkl.cli.CliFormatterApply
|
import org.pkl.cli.CliFormatterCommand
|
||||||
import org.pkl.cli.CliFormatterCheck
|
|
||||||
import org.pkl.cli.commands.FormatterCheckCommand.Companion.grammarVersionHelp
|
|
||||||
import org.pkl.commons.cli.commands.BaseCommand
|
|
||||||
import org.pkl.formatter.GrammarVersion
|
import org.pkl.formatter.GrammarVersion
|
||||||
|
|
||||||
class FormatterCommand : NoOpCliktCommand(name = "format") {
|
class FormatterCommand : CliktCommand(name = "format") {
|
||||||
override fun help(context: Context) = "Run commands related to formatting"
|
override fun help(context: Context) =
|
||||||
|
"""
|
||||||
|
Format or check formatting of Pkl files.
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
|
||||||
|
```
|
||||||
|
# Overwrite all Pkl files inside `my/folder/`, recursively.
|
||||||
|
$ pkl format -w my/folder/
|
||||||
|
|
||||||
|
# Check formatting of all files, printing filenames with formatting violations to stdout.
|
||||||
|
# Exit with exit code `11` if formatting violations were found.
|
||||||
|
$ pkl format --diff-name-only my/folder/
|
||||||
|
|
||||||
|
# Format Pkl code from stdin.
|
||||||
|
$ echo "foo = 1" | pkl format -
|
||||||
|
```
|
||||||
|
"""
|
||||||
|
.trimIndent()
|
||||||
|
|
||||||
override fun helpEpilog(context: Context) = "For more information, visit $helpLink"
|
override fun helpEpilog(context: Context) = "For more information, visit $helpLink"
|
||||||
|
|
||||||
init {
|
|
||||||
subcommands(FormatterCheckCommand(), FormatterApplyCommand())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class FormatterCheckCommand : BaseCommand(name = "check", helpLink = helpLink) {
|
|
||||||
override val helpString: String =
|
|
||||||
"Check if the given files are properly formatted, printing the file name to stdout in case they are not. Returns non-zero in case of failure."
|
|
||||||
|
|
||||||
val paths: List<Path> by
|
val paths: List<Path> by
|
||||||
argument(name = "paths", help = "Files or directory to check.")
|
argument(name = "paths", help = "Files or directory to check. Use `-` to read from stdin.")
|
||||||
.path(mustExist = true, canBeDir = true)
|
.path(mustExist = false, canBeDir = true)
|
||||||
.multiple()
|
.multiple()
|
||||||
|
|
||||||
val grammarVersion: GrammarVersion by
|
val grammarVersion: GrammarVersion by
|
||||||
option(names = arrayOf("--grammar-version"), help = grammarVersionHelp)
|
option(
|
||||||
|
names = arrayOf("--grammar-version"),
|
||||||
|
help =
|
||||||
|
"""
|
||||||
|
The grammar compatibility version to use.$NEWLINE
|
||||||
|
${GrammarVersion.entries.joinToString("$NEWLINE", prefix = " ") {
|
||||||
|
val default = if (it == GrammarVersion.latest()) " `(default)`" else ""
|
||||||
|
"`${it.version}`: ${it.versionSpan}$default"
|
||||||
|
}}
|
||||||
|
"""
|
||||||
|
.trimIndent(),
|
||||||
|
)
|
||||||
.enum<GrammarVersion> { "${it.version}" }
|
.enum<GrammarVersion> { "${it.version}" }
|
||||||
.default(GrammarVersion.latest())
|
.default(GrammarVersion.latest())
|
||||||
|
|
||||||
override fun run() {
|
val overwrite: Boolean by
|
||||||
CliFormatterCheck(baseOptions.baseOptions(emptyList()), paths, grammarVersion).run()
|
option(
|
||||||
}
|
names = arrayOf("-w", "--write"),
|
||||||
|
help = "Format files in place, overwriting them. Implies `---diff-name-only`.",
|
||||||
|
)
|
||||||
|
.flag(default = false)
|
||||||
|
|
||||||
companion object {
|
val diffNameOnly: Boolean by
|
||||||
internal val grammarVersionHelp =
|
option(
|
||||||
"""
|
names = arrayOf("--diff-name-only"),
|
||||||
The grammar compatibility version to use.$NEWLINE
|
help = "Write the path of files with formatting violations to stdout.",
|
||||||
${GrammarVersion.entries.joinToString("$NEWLINE", prefix = " ") {
|
)
|
||||||
val default = if (it == GrammarVersion.latest()) " `(default)`" else ""
|
.flag(default = false)
|
||||||
"`${it.version}`: ${it.versionSpan}$default"
|
|
||||||
}}
|
|
||||||
"""
|
|
||||||
.trimIndent()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class FormatterApplyCommand : BaseCommand(name = "apply", helpLink = helpLink) {
|
|
||||||
override val helpString: String =
|
|
||||||
"Overwrite all the files in place with the formatted version. Returns non-zero in case of failure."
|
|
||||||
|
|
||||||
val paths: List<Path> by
|
|
||||||
argument(name = "paths", help = "Files or directory to format.")
|
|
||||||
.path(mustExist = true, canBeDir = true)
|
|
||||||
.multiple()
|
|
||||||
|
|
||||||
val silent: Boolean by
|
val silent: Boolean by
|
||||||
option(
|
option(
|
||||||
names = arrayOf("-s", "--silent"),
|
names = arrayOf("-s", "--silent"),
|
||||||
help = "Do not write the name of the files that failed formatting to stdout.",
|
help = "Don't write to stdout or stderr. Mutually exclusive with `--diff-name-only`.",
|
||||||
)
|
)
|
||||||
.flag()
|
.flag(default = false)
|
||||||
|
|
||||||
val grammarVersion: GrammarVersion by
|
|
||||||
option(names = arrayOf("--grammar-version"), help = grammarVersionHelp)
|
|
||||||
.enum<GrammarVersion> { "${it.version}" }
|
|
||||||
.default(GrammarVersion.latest())
|
|
||||||
|
|
||||||
override fun run() {
|
override fun run() {
|
||||||
CliFormatterApply(baseOptions.baseOptions(emptyList()), paths, grammarVersion, silent).run()
|
CliFormatterCommand(paths, grammarVersion, overwrite, diffNameOnly, silent).run()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user