From 6cb00936365b41ae1dee4cd80b1f73fcf5392a86 Mon Sep 17 00:00:00 2001 From: Nariman Abdullin Date: Fri, 15 Dec 2023 16:08:29 +0300 Subject: [PATCH] Updated arguments for `diktat-cli` (#1860) - `reporter` is optional now - `output` is required now when `reporter` is provided - renamed parameters are related to plain output - added a new type `PLAIN_GROUP_BY_FILE` --- README.md | 23 +-- .../diktat/api/DiktatReporterType.kt | 2 +- diktat-cli/diktat-cli.adoc | 89 +++++++++++ .../diktat/cli/DiktatProperties.kt | 145 +++++++++++------- .../ktlint/DiktatReporterFactoryImpl.kt | 20 +-- 5 files changed, 198 insertions(+), 81 deletions(-) create mode 100644 diktat-cli/diktat-cli.adoc diff --git a/README.md b/README.md index 891bb13132..5cf4f720b7 100644 --- a/README.md +++ b/README.md @@ -45,22 +45,15 @@ Main features of diktat are the following: ## Run as CLI-application -### Download and install binaries +[Info](diktat-cli/diktat-cli.adoc) -1. Install KTlint manually: [here](https://github.com/pinterest/ktlint/releases) +### Download binary - **OR** use `curl`: - ```shell - # another option is "brew install ktlint" - - curl -sSLO https://github.com/pinterest/ktlint/releases/download/0.46.1/ktlint && chmod a+x ktlint - ``` - -1. Load diKTat manually: [here](https://github.com/saveourtool/diKTat/releases/download/v1.2.5/diktat-1.2.5.jar) +1. Download diKTat manually: [here](https://github.com/saveourtool/diktat/releases) **OR** use `curl`: - ```console - $ curl -sSLO https://github.com/saveourtool/diKTat/releases/download/v1.2.5/diktat-1.2.5.jar && chmod a+x diktat-1.2.5.jar + ```shell + curl -sSLO https://github.com/saveourtool/diktat/releases/download/v2.0.0/diktat && chmod a+x diktat ``` ### Run diKTat @@ -68,10 +61,10 @@ Main features of diktat are the following: Finally, run KTlint (with diKTat injected) to check your '*.kt' files in 'dir/your/dir': ```console -$ ./ktlint -R diktat.jar --disabled_rules=standard,experimental,test,custom "dir/your/dir/**/*.kt" +$ ./diktat "dir/your/dir/**/*.kt" ``` -To **autofix** all code style violations, use `-F` option. +To **autofix** all code style violations, use `--mode fix` option. ## Run with Maven using diktat-maven-plugin :heavy_exclamation_mark: If you are using **Java 16+**, you need to add `--add-opens java.base/java.util=ALL-UNNAMED --add-opens=java.base/java.lang=ALL-UNNAMED` flag to the JVM. For more information, see: https://github.com/pinterest/ktlint/issues/1195 @@ -81,7 +74,7 @@ This can be done by setting `MAVEN_OPTS` variable: export MAVEN_OPTS="--add-opens java.base/java.util=ALL-UNNAMED --add-opens=java.base/java.lang=ALL-UNNAMED" ``` -This plugin is available since version 0.1.3. You can see how it is configured in our project for self-checks: [pom.xml](pom.xml). +This plugin is available since version 0.1.3. You can see how it is configured in our examples for self-checks: [examples](examples/maven/pom.xml). If you use it and encounter any problems, feel free to open issues on [github](https://github.com/saveourtool/diktat/issues).
diff --git a/diktat-api/src/main/kotlin/com/saveourtool/diktat/api/DiktatReporterType.kt b/diktat-api/src/main/kotlin/com/saveourtool/diktat/api/DiktatReporterType.kt index 142866562a..401fa34e4b 100644 --- a/diktat-api/src/main/kotlin/com/saveourtool/diktat/api/DiktatReporterType.kt +++ b/diktat-api/src/main/kotlin/com/saveourtool/diktat/api/DiktatReporterType.kt @@ -11,8 +11,8 @@ enum class DiktatReporterType( CHECKSTYLE("checkstyle", "xml"), HTML("html", "html"), JSON("json", "json"), - NONE("none", ""), PLAIN("plain", "txt"), + PLAIN_GROUP_BY_FILE("plain-group-by-file", "txt"), SARIF("sarif", "sarif"), ; } diff --git a/diktat-cli/diktat-cli.adoc b/diktat-cli/diktat-cli.adoc new file mode 100644 index 0000000000..e69ba43303 --- /dev/null +++ b/diktat-cli/diktat-cli.adoc @@ -0,0 +1,89 @@ += _diktat-cli_, the command-line client for https://github.com/saveourtool/diktat[_diKTat_] +:toc: + +[#features] +== Features + +* Self-executable JAR in _UNIX Shell_ (requires installed _JAVA_) +* BSD-compatible +* Also works in Windows (_Git Bash_, _Cygwin_, or _MSys2_) via the dedicated _diktat.cmd_ +* Can be used as a regular uber JAR + +[#usage] +== Usage + +[source,bash] +---- +diktat [OPTION]... [FILE]... +---- + +[#options] +== Option summary + +.Options +[cols="1,3"] +|=== +| Command-line switch | Meaning + +| `-c CONFIG`, `--config=CONFIG` +| Specify the location of the YAML configuration file. By default, +`diktat-analysis.yml` in the current directory is used. + +| `-m MODE`, `--mode MODE` +| Mode of `diktat` controls that `diktat` fixes or +only finds any deviations from the code style. + +| `-r REPORTER`, `--reporter=REPORTER` +| The reporter to use to errors to `output`, one of: `plain`, `plain_group_by_file`, +`json`, `sarif`, `checkstyle`, `html`. + +| `-o OUTPUT`, `--output=OUTPUT` +| Redirect the reporter output to a file. +Must be provided when the reporter is provided. + +| `--group-by-file` +| A flag to group found errors by files. + +| `--color COLOR` +| Colorize the output, one of: `BLACK`, `RED`, +`GREEN`, `YELLOW`, `BLUE`, `MAGENTA`, `CYAN`, `LIGHT_GRAY`, +`DARK_GRAY`, `LIGHT_RED`, `LIGHT_GREEN`, `LIGHT_YELLOW`, +`LIGHT_BLUE`, `LIGHT_MAGENTA`, `LIGHT_CYAN`, `WHITE` + +| `-l`, `--log-level` +| Control the log level. + +| `-h`, `--help` +| Display the help text and exit. + +| `-l`, `--license` +| Display the license and exit. + +| `-v`, `--verbose` +|Enable the verbose output. + +| `-V`, `--version` +|Output version information and exit. +|=== + +[#exit-codes] +== Exit codes + +.Exit codes +[cols="1,3"] +|=== +| Exit code | Meaning + +| 0 +| _diKTat_ found no errors in your code + +| 1 +| _diKTat_ reported some errors in your code + +| 2 +| The JVM was not found (probably, you need to set up the JVM explicitly, using +the `JAVA_HOME` environment variable) + +| 3 +| Incompatible _Bash_ version +|=== diff --git a/diktat-cli/src/main/kotlin/com/saveourtool/diktat/cli/DiktatProperties.kt b/diktat-cli/src/main/kotlin/com/saveourtool/diktat/cli/DiktatProperties.kt index 3700ff283c..65bbb05741 100644 --- a/diktat-cli/src/main/kotlin/com/saveourtool/diktat/cli/DiktatProperties.kt +++ b/diktat-cli/src/main/kotlin/com/saveourtool/diktat/cli/DiktatProperties.kt @@ -20,6 +20,7 @@ import java.io.OutputStream import java.nio.file.Path import java.nio.file.Paths +import kotlin.io.path.absolute import kotlin.io.path.createDirectories import kotlin.io.path.exists import kotlin.io.path.inputStream @@ -31,22 +32,22 @@ import kotlinx.cli.default import kotlinx.cli.vararg /** - * @param groupByFileInPlain - * @param colorNameInPlain + * @param reporterType + * @param output + * @param groupByFileInStdout + * @param colorNameInStdout * @param logLevel * @property config path to `diktat-analysis.yml` * @property mode mode of `diktat` - * @property reporterType - * @property output * @property patterns */ data class DiktatProperties( - val config: String, + val config: String?, val mode: DiktatMode, - val reporterType: DiktatReporterType, - val output: String?, - private val groupByFileInPlain: Boolean?, - private val colorNameInPlain: String?, + private val reporterType: DiktatReporterType?, + private val output: String?, + private val groupByFileInStdout: Boolean?, + private val colorNameInStdout: String?, private val logLevel: Level, val patterns: List, ) { @@ -78,19 +79,26 @@ data class DiktatProperties( sourceRootDir: Path, loggingListener: DiktatProcessorListener, ): DiktatRunnerArguments { - val reporterCreationArguments = DiktatReporterCreationArguments( - reporterType = reporterType, - outputStream = getReporterOutput(), - groupByFileInPlain = groupByFileInPlain, - colorNameInPlain = colorNameInPlain, + val stdoutReporterCreationArguments = DiktatReporterCreationArguments( + reporterType = DiktatReporterType.PLAIN, + outputStream = null, sourceRootDir = sourceRootDir, + groupByFileInPlain = groupByFileInStdout, + colorNameInPlain = colorNameInStdout, ) + val reporterCreationArguments = reporterType?.let { + DiktatReporterCreationArguments( + reporterType = it, + outputStream = getRequiredReporterOutput(), + sourceRootDir = sourceRootDir, + ) + } return DiktatRunnerArguments( - configInputStream = Paths.get(config).takeIf { it.exists() }?.inputStream(), + configInputStream = config?.let { Paths.get(it).inputStream() } ?: Paths.get(DIKTAT_ANALYSIS_CONF).takeIf { it.exists() }?.inputStream(), sourceRootDir = sourceRootDir, files = getFiles(sourceRootDir), baselineFile = null, - reporterArgsList = listOf(reporterCreationArguments), + reporterArgsList = listOfNotNull(stdoutReporterCreationArguments, reporterCreationArguments), loggingListener = loggingListener, ) } @@ -99,10 +107,11 @@ data class DiktatProperties( .filter { file -> file.isKotlinCodeOrScript() } .toList() - private fun getReporterOutput(): OutputStream? = output - ?.let { Paths.get(it) } + private fun getRequiredReporterOutput(): OutputStream = output + ?.let { Paths.get(it).absolute() } ?.also { it.parent.createDirectories() } ?.outputStream() + ?: throw IllegalArgumentException("A file for the reporter output is not provided") companion object { /** @@ -119,38 +128,13 @@ data class DiktatProperties( args: Array, ): DiktatProperties { val parser = ArgParser(DIKTAT) - val config: String by parser.option( - type = ArgType.String, - fullName = "config", - shortName = "c", - description = "Specify the location of the YAML configuration file. By default, $DIKTAT_ANALYSIS_CONF in the current directory is used.", - ).default(DIKTAT_ANALYSIS_CONF) - val mode: DiktatMode by parser.option( - type = ArgType.Choice(), - fullName = "mode", - shortName = "m", - description = "Mode of `diktat` controls that `diktat` fixes or only finds any deviations from the code style." - ).default(DiktatMode.CHECK) - val reporterType: DiktatReporterType by parser.reporterType() - val output: String? by parser.option( - type = ArgType.String, - fullName = "output", - shortName = "o", - description = "Redirect the reporter output to a file.", - ) - val groupByFileInPlain: Boolean? by parser.option( - type = ArgType.Boolean, - fullName = "plain-group-by-file", - shortName = null, - description = "A flag for plain reporter" - ) + val config: String? by parser.config() + val mode: DiktatMode by parser.diktatMode() + val reporterType: DiktatReporterType? by parser.reporterType() + val output: String? by parser.output() + val groupByFile: Boolean? by parser.groupByFile() val colorName: String? by parser.colorName(diktatReporterFactory) - val logLevel: Level by parser.option( - type = ArgType.Choice(), - fullName = "log-level", - shortName = "l", - description = "Enable the output with specific level", - ).default(Level.INFO) + val logLevel: Level by parser.logLevel() val patterns: List by parser.argument( type = ArgType.String, description = "A list of files to process by diktat" @@ -187,39 +171,88 @@ data class DiktatProperties( mode = mode, reporterType = reporterType, output = output, - groupByFileInPlain = groupByFileInPlain, - colorNameInPlain = colorName, + groupByFileInStdout = groupByFile, + colorNameInStdout = colorName, logLevel = logLevel, patterns = patterns, ) } /** - * @return a single type of [com.saveourtool.diktat.api.DiktatReporter] as parsed cli arg + * @return a single and optional [String] for location of config as parsed cli arg + */ + private fun ArgParser.config() = option( + type = ArgType.String, + fullName = "config", + shortName = "c", + description = "Specify the location of the YAML configuration file. By default, $DIKTAT_ANALYSIS_CONF in the current directory is used.", + ) + + /** + * @return a single type of [DiktatMode] as parsed cli arg. [DiktatMode.CHECK] is default value + */ + private fun ArgParser.diktatMode() = option( + type = ArgType.Choice(), + fullName = "mode", + shortName = "m", + description = "Mode of `diktat` controls that `diktat` fixes or only finds any deviations from the code style." + ).default(DiktatMode.CHECK) + + /** + * @return a single and optional type of [DiktatReporterType] as parsed cli arg */ private fun ArgParser.reporterType() = option( type = ArgType.Choice(), fullName = "reporter", shortName = "r", - description = "The reporter to use" + description = "The reporter to use to log errors to output." + ) + + /** + * @return a single and optional [String] for output as parsed cli arg + */ + private fun ArgParser.output() = option( + type = ArgType.String, + fullName = "output", + shortName = "o", + description = "Redirect the reporter output to a file. Must be provided when the reporter is provided.", + ) + + /** + * @return an optional flag to enable a grouping errors by files + */ + private fun ArgParser.groupByFile() = option( + type = ArgType.Boolean, + fullName = "group-by-file", + shortName = null, + description = "A flag to group found errors by files." ) - .default(DiktatReporterType.PLAIN) /** * @param diktatReporterFactory * @return a single and optional color name as parsed cli args */ - private fun ArgParser.colorName(diktatReporterFactory: DiktatReporterFactory) = this.option( + private fun ArgParser.colorName(diktatReporterFactory: DiktatReporterFactory) = option( type = ArgType.Choice( choices = diktatReporterFactory.colorNamesInPlain.toList(), toVariant = { it }, variantToString = { it }, ), - fullName = "plain-color", + fullName = "color", shortName = null, description = "Colorize the output.", ) + /** + * @return a single log leve as parser cli args. [Level.INFO] is default value + */ + private fun ArgParser.logLevel() = option( + type = ArgType.Choice(), + fullName = "log-level", + shortName = "l", + description = "Control the log level.", + ).default(Level.INFO) + private fun ArgParser.addOptionAndShowTextWithExit( fullName: String, shortName: String?, diff --git a/diktat-ktlint-engine/src/main/kotlin/com/saveourtool/diktat/ktlint/DiktatReporterFactoryImpl.kt b/diktat-ktlint-engine/src/main/kotlin/com/saveourtool/diktat/ktlint/DiktatReporterFactoryImpl.kt index c572668674..5c6fef1983 100644 --- a/diktat-ktlint-engine/src/main/kotlin/com/saveourtool/diktat/ktlint/DiktatReporterFactoryImpl.kt +++ b/diktat-ktlint-engine/src/main/kotlin/com/saveourtool/diktat/ktlint/DiktatReporterFactoryImpl.kt @@ -37,19 +37,21 @@ class DiktatReporterFactoryImpl : DiktatReporterFactory { override fun invoke( args: DiktatReporterCreationArguments, ): DiktatReporter { - if (args.reporterType == DiktatReporterType.NONE) { - return DiktatReporter.empty - } - val opts = if (args is PlainDiktatReporterCreationArguments) { - buildMap { + val opts = when { + args is PlainDiktatReporterCreationArguments -> buildMap { put("color", args.colorName?.let { true } ?: false) put("color_name", args.colorName ?: Color.DARK_GRAY) args.groupByFile?.let { put("group_by_file", it) } }.mapValues { it.value.toString() } - } else if (args.reporterType == DiktatReporterType.PLAIN) { - mapOf("color_name" to Color.DARK_GRAY.name) - } else { - emptyMap() + args.reporterType == DiktatReporterType.PLAIN -> mapOf( + "color_name" to Color.DARK_GRAY.name, + "group_by_file" to false.toString(), + ) + args.reporterType == DiktatReporterType.PLAIN_GROUP_BY_FILE -> mapOf( + "color_name" to Color.DARK_GRAY.name, + "group_by_file" to true.toString(), + ) + else -> emptyMap() } val reporterProvider = reporterProviders[args.reporterType] ?: throw IllegalArgumentException("Not supported reporter id by ${DiktatBaselineFactoryImpl::class.simpleName}")