diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandManager.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandManager.kt index 0c5f51381..b907012ac 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandManager.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandManager.kt @@ -146,6 +146,7 @@ public interface CommandManager { override fun Command.findDuplicate(): Command? = CommandManagerImpl.run { findDuplicate() } override fun Command.unregister(): Boolean = CommandManagerImpl.run { unregister() } override fun Command.isRegistered(): Boolean = CommandManagerImpl.run { isRegistered() } + override val commandPrefix: String get() = CommandManagerImpl.commandPrefix override suspend fun CommandSender.executeCommand(vararg messages: Any): Command? = CommandManagerImpl.run { executeCommand(*messages) } @@ -196,4 +197,4 @@ public abstract class PluginCommandOwner( /** * 代表控制台所有者. 所有的 mirai-console 内建的指令都属于 [ConsoleCommandOwner]. */ -public object ConsoleCommandOwner : CommandOwner() +internal object ConsoleCommandOwner : CommandOwner() diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/description/CompositeCommand.CommandParam.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/description/CompositeCommand.CommandParam.kt index bd328a643..6c8ee69f0 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/description/CompositeCommand.CommandParam.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/description/CompositeCommand.CommandParam.kt @@ -15,9 +15,9 @@ import net.mamoe.mirai.console.command.CompositeCommand import java.lang.reflect.Parameter import kotlin.reflect.KClass -internal fun Parameter.toCommandParam(): CommandParam<*> { +internal fun Parameter.toCommandParam(): CommandParameter<*> { val name = getAnnotation(CompositeCommand.Name::class.java) - return CommandParam( + return CommandParameter( name?.value ?: this.name ?: throw IllegalArgumentException("Cannot construct CommandParam from a unnamed param"), this.type.kotlin @@ -28,7 +28,7 @@ internal fun Parameter.toCommandParam(): CommandParam<*> { * 指令形式参数. * @see toCommandParam */ -internal data class CommandParam( +internal data class CommandParameter( /** * 参数名. 不允许重复. */ diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/command/CompositeCommandInternal.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/command/CompositeCommandInternal.kt index 2c42a32ab..bea3f95a7 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/command/CompositeCommandInternal.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/command/CompositeCommandInternal.kt @@ -12,10 +12,9 @@ package net.mamoe.mirai.console.internal.command import net.mamoe.mirai.console.command.* -import net.mamoe.mirai.console.command.Command.Companion.primaryName import net.mamoe.mirai.console.command.description.CommandArgumentContext import net.mamoe.mirai.console.command.description.CommandArgumentContextAware -import net.mamoe.mirai.console.command.description.CommandParam +import net.mamoe.mirai.console.command.description.CommandParameter import net.mamoe.mirai.message.data.PlainText import net.mamoe.mirai.message.data.SingleMessage import kotlin.reflect.KAnnotatedElement @@ -90,7 +89,7 @@ internal abstract class AbstractReflectionCommand @JvmOverloads constructor( }.map { function -> createSubCommand(function, context) }.toTypedArray().also { - _usage = it.firstOrNull()?.usage ?: description + _usage = it.createUsage(this) }.also { checkSubCommand(it) } } @@ -112,16 +111,16 @@ internal abstract class AbstractReflectionCommand @JvmOverloads constructor( val onCommand: suspend (sender: CommandSender, rawArgs: Array) -> Unit ) - internal class SubCommandDescriptor( + internal inner class SubCommandDescriptor( val names: Array, - val params: Array>, + val params: Array>, val description: String, val permission: CommandPermission, val onCommand: suspend (sender: CommandSender, parsedArgs: Array) -> Boolean, - val context: CommandArgumentContext, - val usage: String + val context: CommandArgumentContext ) { - internal suspend inline fun parseAndExecute( + val usage: String = createUsage(this@AbstractReflectionCommand) + internal suspend fun parseAndExecute( sender: CommandSender, argsWithSubCommandNameNotRemoved: Array, removeSubName: Boolean @@ -159,7 +158,7 @@ internal abstract class AbstractReflectionCommand @JvmOverloads constructor( * @param rawArgs 元素类型必须为 [SingleMessage] 或 [String], 且已经经过扁平化处理. 否则抛出异常 [IllegalArgumentException] */ internal fun matchSubCommand(rawArgs: Array): SubCommandDescriptor? { - val maxCount = rawArgs.size - 1 + val maxCount = rawArgs.size var cur = 0 bakedCommandNameToSubDescriptorArray.forEach { (name, descriptor) -> if (name.size != cur) { @@ -209,6 +208,29 @@ internal inline fun KClass.getInstance(): T { internal val KClass<*>.qualifiedNameOrTip: String get() = this.qualifiedName ?: "" +internal fun Array.createUsage(baseCommand: AbstractReflectionCommand): String = + buildString { + appendLine(baseCommand.description) + appendLine() + + for (subCommandDescriptor in this@createUsage) { + appendLine(subCommandDescriptor.usage) + } + }.trimEnd() + +internal fun AbstractReflectionCommand.SubCommandDescriptor.createUsage(baseCommand: AbstractReflectionCommand): String = + buildString { + if (!baseCommand.prefixOptional) { + append(CommandManager.commandPrefix) + } + append(names.first()) + append(" ") + append(params.joinToString(" ") { "<${it.name}>" }) + append(" ") + append(description) + appendLine() + }.trimEnd() + internal fun AbstractReflectionCommand.createSubCommand( function: KFunction<*>, context: CommandArgumentContext @@ -216,7 +238,7 @@ internal fun AbstractReflectionCommand.createSubCommand( val notStatic = !function.hasAnnotation() val overridePermission = function.findAnnotation()//optional val subDescription = - function.findAnnotation()?.value ?: "" + function.findAnnotation()?.value ?: "" fun KClass<*>.isValidReturnType(): Boolean { return when (this) { @@ -269,26 +291,20 @@ internal fun AbstractReflectionCommand.createSubCommand( } } - val buildUsage = StringBuilder(this.description).append(": \n") - //map parameter val params = parameters.map { param -> - buildUsage.append("/$primaryName ") if (param.isOptional) error("optional parameters are not yet supported. (at ${this::class.qualifiedNameOrTip}.${function.name}.$param)") - val argName = param.findAnnotation()?.value ?: param.name ?: "unknown" - buildUsage.append("<").append(argName).append("> ").append(" ") - CommandParam( - argName, + val paramName = param.findAnnotation()?.value ?: param.name ?: "unknown" + CommandParameter( + paramName, (param.type.classifier as? KClass<*>) ?: throw IllegalArgumentException("unsolved type reference from param " + param.name + ". (at ${this::class.qualifiedNameOrTip}.${function.name}.$param)") ) }.toTypedArray() - buildUsage.append(subDescription).append("\n") - - return AbstractReflectionCommand.SubCommandDescriptor( + return SubCommandDescriptor( commandName, params, subDescription, @@ -309,7 +325,6 @@ internal fun AbstractReflectionCommand.createSubCommand( result as? Boolean ?: true // Unit, void is considered as true. }, - context = context, - usage = buildUsage.toString() + context = context ) } \ No newline at end of file diff --git a/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/TestMiraiConosle.kt b/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/TestMiraiConosle.kt index bf7ea38f8..8242e9b2a 100644 --- a/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/TestMiraiConosle.kt +++ b/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/TestMiraiConosle.kt @@ -15,6 +15,7 @@ import kotlinx.coroutines.suspendCancellableCoroutine import kotlinx.coroutines.withTimeout import net.mamoe.mirai.Bot import net.mamoe.mirai.console.MiraiConsoleImplementation.Companion.start +import net.mamoe.mirai.console.command.CommandManager import net.mamoe.mirai.console.command.ConsoleCommandSender import net.mamoe.mirai.console.plugin.DeferredPluginLoader import net.mamoe.mirai.console.plugin.PluginLoader @@ -54,6 +55,7 @@ fun initTestEnvironment() { override val settingStorageForBuiltIns: SettingStorage get() = MemorySettingStorage() override val coroutineContext: CoroutineContext = SupervisorJob() }.start() + CommandManager } internal object Testing { diff --git a/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/command/TestCommand.kt b/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/command/TestCommand.kt index c7098afa3..aa1d1f0c1 100644 --- a/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/command/TestCommand.kt +++ b/backend/mirai-console/src/test/kotlin/net/mamoe/mirai/console/command/TestCommand.kt @@ -27,8 +27,8 @@ import net.mamoe.mirai.console.command.description.CommandArgumentParser import net.mamoe.mirai.console.initTestEnvironment import net.mamoe.mirai.console.internal.command.flattenCommandComponents import net.mamoe.mirai.message.data.Image +import net.mamoe.mirai.message.data.PlainText import net.mamoe.mirai.message.data.SingleMessage -import net.mamoe.mirai.message.data.toMessage import org.junit.jupiter.api.AfterAll import org.junit.jupiter.api.BeforeAll import org.junit.jupiter.api.Test @@ -107,7 +107,7 @@ internal class TestCommand { @Test fun testSimpleArgsSplitting() = runBlocking { assertEquals(arrayOf("test", "ttt", "tt").contentToString(), withTesting> { - TestSimpleCommand.execute(sender, "test ttt tt".toMessage()) + TestSimpleCommand.execute(sender, PlainText("test ttt tt")) }.contentToString()) }