Skip to content
This repository was archived by the owner on Oct 14, 2024. It is now read-only.

Commit ddca1b5

Browse files
authored
Introduce CommandFactory to aggregate commands (#75)
* Introduce CommandFactory to aggregate commands This makes it easier to invoke any command by known key rather than just creating one-off kts scripts * Remove internal name * Print to err * API dump
1 parent e167305 commit ddca1b5

File tree

9 files changed

+184
-13
lines changed

9 files changed

+184
-13
lines changed

api/kotlin-cli-util.api

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,31 +32,74 @@ public final class slack/cli/CliktExtensionsKt {
3232
public static synthetic fun projectDirOption$default (Lcom/github/ajalt/clikt/core/CliktCommand;[Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lcom/github/ajalt/clikt/parameters/options/OptionDelegate;
3333
}
3434

35+
public abstract interface class slack/cli/CommandFactory {
36+
public abstract fun create ()Lcom/github/ajalt/clikt/core/CliktCommand;
37+
public abstract fun getDescription ()Ljava/lang/String;
38+
public abstract fun getKey ()Ljava/lang/String;
39+
}
40+
41+
public final class slack/cli/CommandFactoryKt {
42+
public static final fun runCommand ([Ljava/lang/String;Z)V
43+
public static synthetic fun runCommand$default ([Ljava/lang/String;ZILjava/lang/Object;)V
44+
}
45+
3546
public final class slack/cli/Toml {
3647
public static final field INSTANCE Lslack/cli/Toml;
3748
public final fun parseVersion (Ljava/io/File;)Ljava/util/Map;
3849
}
3950

4051
public final class slack/cli/gradle/GradleProjectFlattenerCli : com/github/ajalt/clikt/core/CliktCommand {
52+
public static final field DESCRIPTION Ljava/lang/String;
4153
public fun <init> ()V
4254
public fun run ()V
4355
}
4456

57+
public final class slack/cli/gradle/GradleProjectFlattenerCli$Factory : slack/cli/CommandFactory {
58+
public fun <init> ()V
59+
public fun create ()Lcom/github/ajalt/clikt/core/CliktCommand;
60+
public fun getDescription ()Ljava/lang/String;
61+
public fun getKey ()Ljava/lang/String;
62+
}
63+
4564
public final class slack/cli/gradle/GradleSettingsVerifierCli : com/github/ajalt/clikt/core/CliktCommand {
65+
public static final field DESCRIPTION Ljava/lang/String;
4666
public fun <init> ()V
4767
public fun run ()V
4868
}
4969

70+
public final class slack/cli/gradle/GradleSettingsVerifierCli$Factory : slack/cli/CommandFactory {
71+
public fun <init> ()V
72+
public fun create ()Lcom/github/ajalt/clikt/core/CliktCommand;
73+
public fun getDescription ()Ljava/lang/String;
74+
public fun getKey ()Ljava/lang/String;
75+
}
76+
5077
public final class slack/cli/lint/LintBaselineMergerCli : com/github/ajalt/clikt/core/CliktCommand {
78+
public static final field DESCRIPTION Ljava/lang/String;
5179
public fun <init> ()V
5280
public fun run ()V
5381
}
5482

83+
public final class slack/cli/lint/LintBaselineMergerCli$Factory : slack/cli/CommandFactory {
84+
public fun <init> ()V
85+
public fun create ()Lcom/github/ajalt/clikt/core/CliktCommand;
86+
public fun getDescription ()Ljava/lang/String;
87+
public fun getKey ()Ljava/lang/String;
88+
}
89+
5590
public final class slack/cli/sarif/MergeSarifReports : com/github/ajalt/clikt/core/CliktCommand {
91+
public static final field DESCRIPTION Ljava/lang/String;
5692
public fun <init> ()V
5793
public fun run ()V
5894
}
5995

96+
public final class slack/cli/sarif/MergeSarifReports$Factory : slack/cli/CommandFactory {
97+
public fun <init> ()V
98+
public fun create ()Lcom/github/ajalt/clikt/core/CliktCommand;
99+
public fun getDescription ()Ljava/lang/String;
100+
public fun getKey ()Ljava/lang/String;
101+
}
102+
60103
public final class slack/cli/shellsentry/AnalysisResult {
61104
public fun <init> (Ljava/lang/String;Ljava/lang/String;Lslack/cli/shellsentry/RetrySignal;ILkotlin/jvm/functions/Function1;)V
62105
public final fun component1 ()Ljava/lang/String;
@@ -181,10 +224,18 @@ public final class slack/cli/shellsentry/ShellSentry$Companion {
181224
}
182225

183226
public final class slack/cli/shellsentry/ShellSentryCli : com/github/ajalt/clikt/core/CliktCommand {
227+
public static final field DESCRIPTION Ljava/lang/String;
184228
public fun <init> ()V
185229
public fun run ()V
186230
}
187231

232+
public final class slack/cli/shellsentry/ShellSentryCli$Factory : slack/cli/CommandFactory {
233+
public fun <init> ()V
234+
public fun create ()Lcom/github/ajalt/clikt/core/CliktCommand;
235+
public fun getDescription ()Ljava/lang/String;
236+
public fun getKey ()Ljava/lang/String;
237+
}
238+
188239
public final class slack/cli/shellsentry/ShellSentryConfig {
189240
public fun <init> ()V
190241
public fun <init> (ILjava/lang/String;Ljava/util/List;I)V

build.gradle.kts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ plugins {
2828
alias(libs.plugins.moshix)
2929
alias(libs.plugins.kotlin.serialization)
3030
alias(libs.plugins.retry)
31+
alias(libs.plugins.ksp)
3132
}
3233

3334
spotless {
@@ -98,7 +99,9 @@ if (System.getenv("CI") != null) {
9899
}
99100

100101
dependencies {
102+
ksp(libs.autoService.ksp)
101103
api(libs.clikt)
104+
implementation(libs.autoService.annotations)
102105
implementation(libs.tikxml.htmlEscape)
103106
implementation(libs.kotlinx.serialization.core)
104107
implementation(libs.xmlutil.serialization)

gradle/libs.versions.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
[versions]
22
kotlin = "1.9.21"
33
kotlinx-serialization = "1.6.2"
4+
ksp = "1.9.21-1.0.15"
45
ktfmt = "0.46"
56
jvmTarget = "17"
67
moshix = "0.25.1"
@@ -16,11 +17,14 @@ mavenPublish = { id = "com.vanniktech.maven.publish", version = "0.25.3" }
1617
moshix = { id = "dev.zacsweers.moshix", version.ref = "moshix" }
1718
kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
1819
kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
20+
ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" }
1921
spotless = { id = "com.diffplug.spotless", version = "6.23.2" }
2022
binaryCompatibilityValidator = { id = "org.jetbrains.kotlinx.binary-compatibility-validator", version = "0.13.2" }
2123
retry = { id = "org.gradle.test-retry", version = "1.5.7" }
2224

2325
[libraries]
26+
autoService-annotations = "com.google.auto.service:auto-service-annotations:1.1.1"
27+
autoService-ksp = "dev.zacsweers.autoservice:auto-service-ksp:1.1.0"
2428
bugsnag = "com.bugsnag:bugsnag:3.7.1"
2529
clikt = "com.github.ajalt.clikt:clikt:4.2.1"
2630
kotlinShell = "eu.jrie.jetbrains:kotlin-shell-core:0.2.1"
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/*
2+
* Copyright (C) 2023 Slack Technologies, LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package slack.cli
17+
18+
import com.github.ajalt.clikt.core.CliktCommand
19+
import java.util.ServiceLoader
20+
import kotlin.system.exitProcess
21+
22+
/** Marker interface for a [CliktCommand] that can be aggregated and loaded via service loader. */
23+
public interface CommandFactory {
24+
public val key: String
25+
public val description: String
26+
27+
public fun create(): CliktCommand
28+
}
29+
30+
/**
31+
* Primary entry point to run any command registered via [CommandFactory]. First argument should be
32+
* the command key and remaining arguments are passed to the created CLI.
33+
*/
34+
public fun runCommand(args: Array<String>, exitOnError: Boolean = true) {
35+
val commands = ServiceLoader.load(CommandFactory::class.java).associateBy { it.key }
36+
37+
if (args.isEmpty()) {
38+
System.err.println("Usage: <command> <args>")
39+
System.err.println("Available commands:")
40+
commands.forEach { (key, factory) -> System.err.println(" $key: ${factory.description}") }
41+
if (exitOnError) {
42+
exitProcess(1)
43+
}
44+
}
45+
46+
val command = args[0]
47+
val commandArgs =
48+
when (args.size) {
49+
1 -> emptyArray()
50+
else -> args.sliceArray(1..args.lastIndex)
51+
}
52+
53+
commands[command]?.create()?.main(commandArgs) ?: error("Unknown command: '$command'")
54+
}

src/main/kotlin/slack/cli/gradle/GradleProjectFlattenerCli.kt

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import com.github.ajalt.clikt.parameters.options.flag
2121
import com.github.ajalt.clikt.parameters.options.option
2222
import com.github.ajalt.clikt.parameters.options.required
2323
import com.github.ajalt.clikt.parameters.types.path
24+
import com.google.auto.service.AutoService
2425
import java.io.File
2526
import kotlin.io.path.ExperimentalPathApi
2627
import kotlin.io.path.appendLines
@@ -30,6 +31,7 @@ import kotlin.io.path.exists
3031
import kotlin.io.path.isDirectory
3132
import kotlin.io.path.readText
3233
import kotlin.io.path.relativeTo
34+
import slack.cli.CommandFactory
3335
import slack.cli.dryRunOption
3436
import slack.cli.projectDirOption
3537

@@ -42,12 +44,21 @@ import slack.cli.projectDirOption
4244
*
4345
* It's recommended to run `./gradlew clean` first before running this script to minimize work.
4446
*/
45-
public class GradleProjectFlattenerCli :
46-
CliktCommand(
47-
help =
47+
public class GradleProjectFlattenerCli : CliktCommand(help = DESCRIPTION) {
48+
49+
private companion object {
50+
const val DESCRIPTION =
4851
"A CLI that flattens all gradle projects in a given directory to be top level while " +
4952
"preserving their original project paths."
50-
) {
53+
}
54+
55+
@AutoService(CommandFactory::class)
56+
public class Factory : CommandFactory {
57+
override val key: String = "flatten-gradle-projects"
58+
override val description: String = DESCRIPTION
59+
60+
override fun create(): CliktCommand = GradleProjectFlattenerCli()
61+
}
5162

5263
private val projectDir by projectDirOption()
5364

src/main/kotlin/slack/cli/gradle/GradleSettingsVerifierCli.kt

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import com.github.ajalt.clikt.core.CliktCommand
1919
import com.github.ajalt.clikt.parameters.options.option
2020
import com.github.ajalt.clikt.parameters.options.required
2121
import com.github.ajalt.clikt.parameters.types.path
22+
import com.google.auto.service.AutoService
2223
import java.io.File
2324
import kotlin.io.path.ExperimentalPathApi
2425
import kotlin.io.path.exists
@@ -27,11 +28,23 @@ import kotlin.io.path.name
2728
import kotlin.io.path.readText
2829
import kotlin.io.path.relativeTo
2930
import kotlin.system.exitProcess
31+
import slack.cli.CommandFactory
3032
import slack.cli.projectDirOption
3133

3234
/** A CLI that verifies a given settings file has only valid projects. */
33-
public class GradleSettingsVerifierCli :
34-
CliktCommand(help = "A CLI that verifies a given settings file has only valid projects.") {
35+
public class GradleSettingsVerifierCli : CliktCommand(help = DESCRIPTION) {
36+
37+
private companion object {
38+
const val DESCRIPTION = "A CLI that verifies a given settings file has only valid projects."
39+
}
40+
41+
@AutoService(CommandFactory::class)
42+
public class Factory : CommandFactory {
43+
override val key: String = "verify-gradle-settings"
44+
override val description: String = DESCRIPTION
45+
46+
override fun create(): CliktCommand = GradleSettingsVerifierCli()
47+
}
3548

3649
private val projectDir by projectDirOption()
3750

src/main/kotlin/slack/cli/lint/LintBaselineMergerCli.kt

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import com.github.ajalt.clikt.parameters.options.flag
2020
import com.github.ajalt.clikt.parameters.options.option
2121
import com.github.ajalt.clikt.parameters.options.required
2222
import com.github.ajalt.clikt.parameters.types.path
23+
import com.google.auto.service.AutoService
2324
import com.tickaroo.tikxml.converter.htmlescape.StringEscapeUtils
2425
import io.github.detekt.sarif4k.ArtifactContent
2526
import io.github.detekt.sarif4k.ArtifactLocation
@@ -57,11 +58,23 @@ import kotlinx.serialization.json.Json
5758
import kotlinx.serialization.serializer
5859
import nl.adaptivity.xmlutil.serialization.XML
5960
import nl.adaptivity.xmlutil.serialization.XmlSerialName
61+
import slack.cli.CommandFactory
6062
import slack.cli.projectDirOption
6163
import slack.cli.skipBuildAndCacheDirs
6264

6365
/** A CLI that merges lint baseline xml files into one. */
64-
public class LintBaselineMergerCli : CliktCommand("Merges multiple lint baselines into one") {
66+
public class LintBaselineMergerCli : CliktCommand(DESCRIPTION) {
67+
private companion object {
68+
const val DESCRIPTION = "Merges multiple lint baselines into one"
69+
}
70+
71+
@AutoService(CommandFactory::class)
72+
public class Factory : CommandFactory {
73+
override val key: String = "merge-lint-baselines"
74+
override val description: String = DESCRIPTION
75+
76+
override fun create(): CliktCommand = LintBaselineMergerCli()
77+
}
6578

6679
private val projectDir by projectDirOption()
6780

src/main/kotlin/slack/cli/sarif/MergeSarifReports.kt

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import com.github.ajalt.clikt.parameters.options.flag
2222
import com.github.ajalt.clikt.parameters.options.option
2323
import com.github.ajalt.clikt.parameters.options.required
2424
import com.github.ajalt.clikt.parameters.types.path
25+
import com.google.auto.service.AutoService
2526
import io.github.detekt.sarif4k.SarifSchema210
2627
import io.github.detekt.sarif4k.SarifSerializer
2728
import java.nio.file.Path
@@ -33,13 +34,19 @@ import kotlin.io.path.readText
3334
import kotlin.io.path.relativeTo
3435
import kotlin.io.path.writeText
3536
import kotlin.system.exitProcess
37+
import slack.cli.CommandFactory
3638
import slack.cli.projectDirOption
3739
import slack.cli.skipBuildAndCacheDirs
3840

39-
public class MergeSarifReports :
40-
CliktCommand(
41-
help = "Merges all matching sarif reports into a single file for ease of uploading."
42-
) {
41+
public class MergeSarifReports : CliktCommand(help = DESCRIPTION) {
42+
43+
@AutoService(CommandFactory::class)
44+
public class Factory : CommandFactory {
45+
override val key: String = "merge-sarif-reports"
46+
override val description: String = DESCRIPTION
47+
48+
override fun create(): CliktCommand = MergeSarifReports()
49+
}
4350

4451
private val projectDir by projectDirOption()
4552
private val outputFile by option("--output-file").path().required()
@@ -358,5 +365,7 @@ public class MergeSarifReports :
358365

359366
private companion object {
360367
private const val SRC_ROOT = "%SRCROOT%"
368+
const val DESCRIPTION =
369+
"Merges all matching sarif reports into a single file for ease of uploading."
361370
}
362371
}

src/main/kotlin/slack/cli/shellsentry/ShellSentryCli.kt

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,9 @@ import com.github.ajalt.clikt.parameters.arguments.multiple
2121
import com.github.ajalt.clikt.parameters.options.flag
2222
import com.github.ajalt.clikt.parameters.options.option
2323
import com.github.ajalt.clikt.parameters.types.path
24+
import com.google.auto.service.AutoService
2425
import org.jetbrains.annotations.TestOnly
26+
import slack.cli.CommandFactory
2527
import slack.cli.projectDirOption
2628

2729
/**
@@ -33,8 +35,19 @@ import slack.cli.projectDirOption
3335
* $ ./<binary> --bugsnag-key=1234 --verbose --configurationFile config.json ./gradlew build
3436
* ```
3537
*/
36-
public class ShellSentryCli :
37-
CliktCommand("Executes a command with Bugsnag tracing and retries as needed.") {
38+
public class ShellSentryCli : CliktCommand(DESCRIPTION) {
39+
40+
private companion object {
41+
const val DESCRIPTION = "Executes a command with Bugsnag tracing and retries as needed."
42+
}
43+
44+
@AutoService(CommandFactory::class)
45+
public class Factory : CommandFactory {
46+
override val key: String = "shell-sentry"
47+
override val description: String = DESCRIPTION
48+
49+
override fun create(): CliktCommand = ShellSentryCli()
50+
}
3851

3952
internal val projectDir by projectDirOption()
4053

0 commit comments

Comments
 (0)