From 20ed10808dade891f83fd1737d4cb140a8ebab9d Mon Sep 17 00:00:00 2001 From: AJ Date: Wed, 18 Sep 2024 19:16:58 -0700 Subject: [PATCH 1/2] Add completion and noop for new command types --- CHANGELOG.md | 3 ++ .../command/SuspendingNoOpCliktCommand.kt | 15 +++++++ .../ajalt/clikt/core/NoOpCliktCommand.kt | 2 +- .../clikt/command/CoreChainedCliktCommand.kt | 1 + .../command/CoreSuspendingNoOpCliktCommand.kt | 15 +++++++ .../clikt/completion/CompletionBuiltins.kt | 39 +++++++++++++++++++ .../command/SuspendingCliktCommandTest.kt | 9 +++++ .../clikt/completion/CompletionTestBase.kt | 34 ++++++++++++++++ 8 files changed, 117 insertions(+), 1 deletion(-) create mode 100644 clikt-mordant/src/commonMain/kotlin/com/github/ajalt/clikt/command/SuspendingNoOpCliktCommand.kt create mode 100644 clikt/src/commonMain/kotlin/com/github/ajalt/clikt/command/CoreSuspendingNoOpCliktCommand.kt diff --git a/CHANGELOG.md b/CHANGELOG.md index a82b3a9f..084c78c2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,9 @@ # Changelog ## Unreleased +### Added +- Added completion commands for suspending and chained commands. ([#553](https://github.com/ajalt/clikt/pull/553)) +- Added no-op suspending commands. ([#554](https://github.com/ajalt/clikt/pull/554)) ## 5.0.0 ### Added diff --git a/clikt-mordant/src/commonMain/kotlin/com/github/ajalt/clikt/command/SuspendingNoOpCliktCommand.kt b/clikt-mordant/src/commonMain/kotlin/com/github/ajalt/clikt/command/SuspendingNoOpCliktCommand.kt new file mode 100644 index 00000000..e492f44d --- /dev/null +++ b/clikt-mordant/src/commonMain/kotlin/com/github/ajalt/clikt/command/SuspendingNoOpCliktCommand.kt @@ -0,0 +1,15 @@ +package com.github.ajalt.clikt.command + +/** + * A [SuspendingCliktCommand] that has a default implementation of + * [run][SuspendingCliktCommand.run] that is a no-op. + */ +abstract class SuspendingNoOpCliktCommand( + /** + * The name of the program to use in the help output. If not given, it is inferred from the + * class name. + */ + name: String? = null, +) : SuspendingCliktCommand(name) { + override suspend fun run() = Unit +} diff --git a/clikt-mordant/src/commonMain/kotlin/com/github/ajalt/clikt/core/NoOpCliktCommand.kt b/clikt-mordant/src/commonMain/kotlin/com/github/ajalt/clikt/core/NoOpCliktCommand.kt index 972536b2..26bb6510 100644 --- a/clikt-mordant/src/commonMain/kotlin/com/github/ajalt/clikt/core/NoOpCliktCommand.kt +++ b/clikt-mordant/src/commonMain/kotlin/com/github/ajalt/clikt/core/NoOpCliktCommand.kt @@ -1,7 +1,7 @@ package com.github.ajalt.clikt.core /** - * A [CoreCliktCommand] that has a default implementation of [CliktCommand.run] that is a no-op. + * A [CliktCommand] that has a default implementation of [CliktCommand.run] that is a no-op. */ open class NoOpCliktCommand( /** diff --git a/clikt/src/commonMain/kotlin/com/github/ajalt/clikt/command/CoreChainedCliktCommand.kt b/clikt/src/commonMain/kotlin/com/github/ajalt/clikt/command/CoreChainedCliktCommand.kt index a2a14380..0327ed60 100644 --- a/clikt/src/commonMain/kotlin/com/github/ajalt/clikt/command/CoreChainedCliktCommand.kt +++ b/clikt/src/commonMain/kotlin/com/github/ajalt/clikt/command/CoreChainedCliktCommand.kt @@ -31,6 +31,7 @@ abstract class CoreChainedCliktCommand( } + /** * Parse the command line and print helpful output if any errors occur. * diff --git a/clikt/src/commonMain/kotlin/com/github/ajalt/clikt/command/CoreSuspendingNoOpCliktCommand.kt b/clikt/src/commonMain/kotlin/com/github/ajalt/clikt/command/CoreSuspendingNoOpCliktCommand.kt new file mode 100644 index 00000000..e4dbf6af --- /dev/null +++ b/clikt/src/commonMain/kotlin/com/github/ajalt/clikt/command/CoreSuspendingNoOpCliktCommand.kt @@ -0,0 +1,15 @@ +package com.github.ajalt.clikt.command + +/** + * A [CoreSuspendingCliktCommand] that has a default implementation of + * [run][CoreSuspendingCliktCommand.run] that is a no-op. + */ +abstract class CoreSuspendingNoOpCliktCommand( + /** + * The name of the program to use in the help output. If not given, it is inferred from the + * class name. + */ + name: String? = null, +) : CoreSuspendingCliktCommand(name) { + override suspend fun run() = Unit +} diff --git a/clikt/src/commonMain/kotlin/com/github/ajalt/clikt/completion/CompletionBuiltins.kt b/clikt/src/commonMain/kotlin/com/github/ajalt/clikt/completion/CompletionBuiltins.kt index 917cbd96..20dbd36b 100644 --- a/clikt/src/commonMain/kotlin/com/github/ajalt/clikt/completion/CompletionBuiltins.kt +++ b/clikt/src/commonMain/kotlin/com/github/ajalt/clikt/completion/CompletionBuiltins.kt @@ -1,5 +1,7 @@ package com.github.ajalt.clikt.completion +import com.github.ajalt.clikt.command.CoreChainedCliktCommand +import com.github.ajalt.clikt.command.CoreSuspendingCliktCommand import com.github.ajalt.clikt.completion.CompletionGenerator.generateCompletionForCommand import com.github.ajalt.clikt.core.BaseCliktCommand import com.github.ajalt.clikt.core.Context @@ -44,3 +46,40 @@ class CompletionCommand( throw PrintCompletionMessage(generateCompletionForCommand(cmd, shell)) } } + +/** + * A [CoreSuspendingCliktCommand] subcommand that will print a completion script for the given shell + * when invoked. + */ +class SuspendingCompletionCommand( + private val help: String = "Generate a tab-complete script for the given shell", + private val epilog: String = "", + name: String = "generate-completion", +) : CoreSuspendingCliktCommand(name) { + override fun help(context: Context): String = help + override fun helpEpilog(context: Context): String = epilog + private val shell by argument("shell").choice(*choices) + override suspend fun run() { + val cmd = currentContext.parent?.command ?: this + throw PrintCompletionMessage(generateCompletionForCommand(cmd, shell)) + } +} + +/** + * A [CoreChainedCliktCommand] subcommand that will print a completion script for the given shell + * when invoked. + */ +class ChainedCompletionCommand( + private val help: String = "Generate a tab-complete script for the given shell", + private val epilog: String = "", + name: String = "generate-completion", +) : CoreChainedCliktCommand(name) { + override fun help(context: Context): String = help + override fun helpEpilog(context: Context): String = epilog + private val shell by argument("shell").choice(*choices) + override fun run(value: T): T { + val cmd = currentContext.parent?.command ?: this + throw PrintCompletionMessage(generateCompletionForCommand(cmd, shell)) + } +} + diff --git a/test/src/commonTest/kotlin/com/github/ajalt/clikt/command/SuspendingCliktCommandTest.kt b/test/src/commonTest/kotlin/com/github/ajalt/clikt/command/SuspendingCliktCommandTest.kt index f2401aeb..8e3b8e9a 100644 --- a/test/src/commonTest/kotlin/com/github/ajalt/clikt/command/SuspendingCliktCommandTest.kt +++ b/test/src/commonTest/kotlin/com/github/ajalt/clikt/command/SuspendingCliktCommandTest.kt @@ -52,4 +52,13 @@ class SuspendingCliktCommandTest { C().test("baz").output shouldBe "baz\n" } + + @Test + @JsName("suspending_noop_command_test") + fun `suspending no-op command test`() = runTest { + class C : SuspendingNoOpCliktCommand() + class Sub : CoreSuspendingNoOpCliktCommand() + + C().subcommands(Sub()).test("sub").output shouldBe "" + } } diff --git a/test/src/commonTest/kotlin/com/github/ajalt/clikt/completion/CompletionTestBase.kt b/test/src/commonTest/kotlin/com/github/ajalt/clikt/completion/CompletionTestBase.kt index b5899999..3a7cbb30 100644 --- a/test/src/commonTest/kotlin/com/github/ajalt/clikt/completion/CompletionTestBase.kt +++ b/test/src/commonTest/kotlin/com/github/ajalt/clikt/completion/CompletionTestBase.kt @@ -1,5 +1,8 @@ package com.github.ajalt.clikt.completion +import com.github.ajalt.clikt.command.ChainedCliktCommand +import com.github.ajalt.clikt.command.SuspendingNoOpCliktCommand +import com.github.ajalt.clikt.command.parse import com.github.ajalt.clikt.core.PrintCompletionMessage import com.github.ajalt.clikt.core.context import com.github.ajalt.clikt.core.subcommands @@ -11,6 +14,7 @@ import com.github.ajalt.clikt.testing.TestCommand import com.github.ajalt.clikt.testing.parse import io.kotest.assertions.throwables.shouldThrow import io.kotest.matchers.string.shouldContain +import kotlinx.coroutines.test.runTest import kotlin.js.JsName import kotlin.test.Test import kotlin.test.assertEquals @@ -130,4 +134,34 @@ abstract class CompletionTestBase(private val shell: String) { message shouldContain shell message shouldContain "foo" } + + @Test + @JsName("suspending_completion_command") + fun `suspending completion command`() = runTest { + class Foo : SuspendingNoOpCliktCommand() + + val message = shouldThrow { + Foo() + .subcommands(SuspendingCompletionCommand(), Foo()) + .parse(listOf("generate-completion", shell)) + }.message + message shouldContain shell + message shouldContain "foo" + } + + @Test + @JsName("chained_completion_command") + fun `chained completion command`() = runTest { + class Foo : ChainedCliktCommand() { + override fun run(value: Unit) = Unit + } + + val message = shouldThrow { + Foo() + .subcommands(ChainedCompletionCommand(), Foo()) + .parse(listOf("generate-completion", shell), Unit) + }.message + message shouldContain shell + message shouldContain "foo" + } } From 75feff7c5122fc3ab107f0ce7e89a50d28fd5d82 Mon Sep 17 00:00:00 2001 From: AJ Date: Thu, 19 Sep 2024 19:14:02 -0700 Subject: [PATCH 2/2] api dump --- clikt-mordant/api/clikt-mordant.api | 7 +++++++ clikt/api/clikt.api | 25 +++++++++++++++++++++++++ 2 files changed, 32 insertions(+) mode change 100755 => 100644 clikt-mordant/api/clikt-mordant.api diff --git a/clikt-mordant/api/clikt-mordant.api b/clikt-mordant/api/clikt-mordant.api old mode 100755 new mode 100644 index c5396ea3..7fe8a7a0 --- a/clikt-mordant/api/clikt-mordant.api +++ b/clikt-mordant/api/clikt-mordant.api @@ -28,6 +28,13 @@ public final class com/github/ajalt/clikt/command/SuspendingCliktCommandKt { public static synthetic fun test$default (Lcom/github/ajalt/clikt/command/SuspendingCliktCommand;[Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;ZLcom/github/ajalt/mordant/rendering/AnsiLevel;IIZZZLkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; } +public abstract class com/github/ajalt/clikt/command/SuspendingNoOpCliktCommand : com/github/ajalt/clikt/command/SuspendingCliktCommand { + public fun ()V + public fun (Ljava/lang/String;)V + public synthetic fun (Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun run (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + public abstract class com/github/ajalt/clikt/core/CliktCommand : com/github/ajalt/clikt/core/CoreCliktCommand { public fun ()V public fun (Ljava/lang/String;)V diff --git a/clikt/api/clikt.api b/clikt/api/clikt.api index a7206d0b..052a8895 100644 --- a/clikt/api/clikt.api +++ b/clikt/api/clikt.api @@ -26,6 +26,22 @@ public final class com/github/ajalt/clikt/command/CoreSuspendingCliktCommandKt { public static final fun parse (Lcom/github/ajalt/clikt/command/CoreSuspendingCliktCommand;[Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } +public abstract class com/github/ajalt/clikt/command/CoreSuspendingNoOpCliktCommand : com/github/ajalt/clikt/command/CoreSuspendingCliktCommand { + public fun ()V + public fun (Ljava/lang/String;)V + public synthetic fun (Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun run (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public final class com/github/ajalt/clikt/completion/ChainedCompletionCommand : com/github/ajalt/clikt/command/CoreChainedCliktCommand { + public fun ()V + public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun help (Lcom/github/ajalt/clikt/core/Context;)Ljava/lang/String; + public fun helpEpilog (Lcom/github/ajalt/clikt/core/Context;)Ljava/lang/String; + public fun run (Ljava/lang/Object;)Ljava/lang/Object; +} + public final class com/github/ajalt/clikt/completion/CompletionBuiltinsKt { public static final fun completionOption (Lcom/github/ajalt/clikt/core/BaseCliktCommand;[Ljava/lang/String;Ljava/lang/String;Z)Lcom/github/ajalt/clikt/core/BaseCliktCommand; public static synthetic fun completionOption$default (Lcom/github/ajalt/clikt/core/BaseCliktCommand;[Ljava/lang/String;Ljava/lang/String;ZILjava/lang/Object;)Lcom/github/ajalt/clikt/core/BaseCliktCommand; @@ -112,6 +128,15 @@ public final class com/github/ajalt/clikt/completion/CompletionGenerator { public final fun generateCompletionForCommand (Lcom/github/ajalt/clikt/core/BaseCliktCommand;Ljava/lang/String;)Ljava/lang/String; } +public final class com/github/ajalt/clikt/completion/SuspendingCompletionCommand : com/github/ajalt/clikt/command/CoreSuspendingCliktCommand { + public fun ()V + public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun help (Lcom/github/ajalt/clikt/core/Context;)Ljava/lang/String; + public fun helpEpilog (Lcom/github/ajalt/clikt/core/Context;)Ljava/lang/String; + public fun run (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + public final class com/github/ajalt/clikt/core/Abort : com/github/ajalt/clikt/core/ProgramResult { public fun ()V }