diff --git a/backend/mirai-console/src/main/java/net/mamoe/mirai/console/command/JCommandManager.java b/backend/mirai-console/src/main/java/net/mamoe/mirai/console/command/JCommandManager.java
index 58d922319..74ad92fa9 100644
--- a/backend/mirai-console/src/main/java/net/mamoe/mirai/console/command/JCommandManager.java
+++ b/backend/mirai-console/src/main/java/net/mamoe/mirai/console/command/JCommandManager.java
@@ -1,6 +1,7 @@
package net.mamoe.mirai.console.command;
import kotlin.NotImplementedError;
+import kotlin.coroutines.Continuation;
import kotlin.coroutines.EmptyCoroutineContext;
import kotlinx.coroutines.BuildersKt;
import kotlinx.coroutines.CoroutineScope;
@@ -18,7 +19,7 @@ import java.util.concurrent.CompletableFuture;
/**
* Java 适配的 {@link CommandManagerKt}
*/
-@SuppressWarnings("unused")
+@SuppressWarnings({"unused", "RedundantSuppression"})
public final class JCommandManager {
private JCommandManager() {
throw new NotImplementedError();
@@ -101,14 +102,17 @@ public final class JCommandManager {
CommandManagerKt.unregisterAllCommands(owner);
}
+
/**
* 解析并执行一个指令
*
* @param args 接受 {@link String} 或 {@link Message} , 其他对象将会被 {@link Object#toString()}
- * @see CommandExecuteResult
+ * @return 成功执行的指令, 在无匹配指令时返回 null
+ * @throws CommandExecutionException 当 {@link Command#onCommand(CommandSender, Object[], Continuation)} 抛出异常时包装并附带相关指令信息抛出
* @see #executeCommandAsync(CoroutineScope, CommandSender, Object...)
*/
- public static CommandExecuteResult executeCommand(final @NotNull CommandSender sender, final @NotNull Object... args) throws InterruptedException {
+ @Nullable
+ public static Command executeCommand(final @NotNull CommandSender sender, final @NotNull Object... args) throws CommandExecutionException, InterruptedException {
Objects.requireNonNull(sender, "sender");
Objects.requireNonNull(args, "args");
for (Object arg : args) {
@@ -123,10 +127,11 @@ public final class JCommandManager {
*
* @param scope 协程作用域 (用于管理协程生命周期). 一般填入 {@link JavaPlugin} 实例.
* @param args 接受 {@link String} 或 {@link Message} , 其他对象将会被 {@link Object#toString()}
- * @see CommandExecuteResult
+ * @return 成功执行的指令, 在无匹配指令时返回 null
* @see #executeCommand(CommandSender, Object...)
*/
- public static CompletableFuture executeCommandAsync(final @NotNull CoroutineScope scope, final @NotNull CommandSender sender, final @NotNull Object... args) {
+ @NotNull
+ public static CompletableFuture<@Nullable Command> executeCommandAsync(final @NotNull CoroutineScope scope, final @NotNull CommandSender sender, final @NotNull Object... args) {
Objects.requireNonNull(sender, "sender");
Objects.requireNonNull(args, "args");
Objects.requireNonNull(scope, "scope");
@@ -136,4 +141,46 @@ public final class JCommandManager {
return FutureKt.future(scope, EmptyCoroutineContext.INSTANCE, CoroutineStart.DEFAULT, (sc, completion) -> CommandManagerKt.executeCommand(sender, args, completion));
}
+
+
+ /**
+ * 解析并执行一个指令, 获取详细的指令参数等信息.
+ *
+ * 执行过程中产生的异常将不会直接抛出, 而会包装为 {@link CommandExecuteResult.ExecutionException}
+ *
+ * @param args 接受 {@link String} 或 {@link Message} , 其他对象将会被 {@link Object#toString()}
+ * @return 执行结果
+ * @see #executeCommandDetailedAsync(CoroutineScope, CommandSender, Object...)
+ */
+ @NotNull
+ public static CommandExecuteResult executeCommandDetailed(final @NotNull CommandSender sender, final @NotNull Object... args) throws InterruptedException {
+ Objects.requireNonNull(sender, "sender");
+ Objects.requireNonNull(args, "args");
+ for (Object arg : args) {
+ Objects.requireNonNull(arg, "element of args");
+ }
+
+ return BuildersKt.runBlocking(EmptyCoroutineContext.INSTANCE, (scope, completion) -> CommandManagerKt.executeCommandDetailed(sender, args, completion));
+ }
+
+ /**
+ * 异步 (在 Kotlin 协程线程池) 解析并执行一个指令, 获取详细的指令参数等信息
+ *
+ * @param scope 协程作用域 (用于管理协程生命周期). 一般填入 {@link JavaPlugin} 实例.
+ * @param args 接受 {@link String} 或 {@link Message} , 其他对象将会被 {@link Object#toString()}
+ * @return 执行结果
+ * @see #executeCommandDetailed(CommandSender, Object...)
+ */
+ @NotNull
+ public static CompletableFuture<@NotNull CommandExecuteResult>
+ executeCommandDetailedAsync(final @NotNull CoroutineScope scope, final @NotNull CommandSender sender, final @NotNull Object... args) {
+ Objects.requireNonNull(sender, "sender");
+ Objects.requireNonNull(args, "args");
+ Objects.requireNonNull(scope, "scope");
+ for (Object arg : args) {
+ Objects.requireNonNull(arg, "element of args");
+ }
+
+ return FutureKt.future(scope, EmptyCoroutineContext.INSTANCE, CoroutineStart.DEFAULT, (sc, completion) -> CommandManagerKt.executeCommandDetailed(sender, args, completion));
+ }
}
diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandExecuteResult.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandExecuteResult.kt
new file mode 100644
index 000000000..a9ebce45a
--- /dev/null
+++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandExecuteResult.kt
@@ -0,0 +1,146 @@
+@file:Suppress("unused")
+
+package net.mamoe.mirai.console.command
+
+import net.mamoe.mirai.console.command.CommandExecuteResult.CommandExecuteStatus
+import net.mamoe.mirai.message.data.Message
+import kotlin.contracts.contract
+
+/**
+ * 指令的执行返回
+ *
+ * @see CommandExecuteStatus
+ */
+sealed class CommandExecuteResult {
+ /** 指令最终执行状态 */
+ abstract val status: CommandExecuteStatus
+
+ /** 指令执行时发生的错误 (如果有) */
+ abstract val exception: Throwable?
+
+ /** 尝试执行的指令 (如果匹配到) */
+ abstract val command: Command?
+
+ /** 尝试执行的指令名 (如果匹配到) */
+ abstract val commandName: String?
+
+ /** 基础分割后的实际参数列表, 元素类型可能为 [Message] 或 [String] */
+ abstract val args: Array?
+
+ // abstract val to allow smart casting
+
+ /** 指令执行成功 */
+ class Success(
+ /** 尝试执行的指令 */
+ override val command: Command,
+ /** 尝试执行的指令名 */
+ override val commandName: String,
+ /** 基础分割后的实际参数列表, 元素类型可能为 [Message] 或 [String] */
+ override val args: Array
+ ) : CommandExecuteResult() {
+ /** 指令执行时发生的错误, 总是 `null` */
+ override val exception: Nothing? get() = null
+
+ /** 指令最终执行状态, 总是 [CommandExecuteStatus.SUCCESSFUL] */
+ override val status: CommandExecuteStatus get() = CommandExecuteStatus.SUCCESSFUL
+ }
+
+ /** 指令执行过程出现了错误 */
+ class ExecutionException(
+ /** 指令执行时发生的错误 */
+ override val exception: Throwable,
+ /** 尝试执行的指令 */
+ override val command: Command,
+ /** 尝试执行的指令名 */
+ override val commandName: String,
+ /** 基础分割后的实际参数列表, 元素类型可能为 [Message] 或 [String] */
+ override val args: Array
+ ) : CommandExecuteResult() {
+ /** 指令最终执行状态, 总是 [CommandExecuteStatus.EXECUTION_EXCEPTION] */
+ override val status: CommandExecuteStatus get() = CommandExecuteStatus.EXECUTION_EXCEPTION
+ }
+
+ /** 没有匹配的指令 */
+ class CommandNotFound(
+ /** 尝试执行的指令名 */
+ override val commandName: String
+ ) : CommandExecuteResult() {
+ /** 指令执行时发生的错误, 总是 `null` */
+ override val exception: Nothing? get() = null
+
+ /** 尝试执行的指令, 总是 `null` */
+ override val command: Nothing? get() = null
+
+ /** 基础分割后的实际参数列表, 元素类型可能为 [Message] 或 [String] */
+ override val args: Nothing? get() = null
+
+ /** 指令最终执行状态, 总是 [CommandExecuteStatus.COMMAND_NOT_FOUND] */
+ override val status: CommandExecuteStatus get() = CommandExecuteStatus.COMMAND_NOT_FOUND
+ }
+
+ /**
+ * 指令的执行状态
+ */
+ enum class CommandExecuteStatus {
+ /** 指令执行成功 */
+ SUCCESSFUL,
+
+ /** 指令执行过程出现了错误 */
+ EXECUTION_EXCEPTION,
+
+ /** 没有匹配的指令 */
+ COMMAND_NOT_FOUND
+ }
+}
+
+
+@Suppress("RemoveRedundantQualifierName")
+typealias CommandExecuteStatus = CommandExecuteResult.CommandExecuteStatus
+
+/**
+ * 当 [this] 为 [CommandExecuteResult.Success] 时返回 `true`
+ */
+@JvmSynthetic
+fun CommandExecuteResult.isSuccess(): Boolean {
+ contract {
+ returns(true) implies (this@isSuccess is CommandExecuteResult.Success)
+ returns(false) implies (this@isSuccess !is CommandExecuteResult.Success)
+ }
+ return this is CommandExecuteResult.Success
+}
+
+/**
+ * 当 [this] 为 [CommandExecuteResult.ExecutionException] 时返回 `true`
+ */
+@JvmSynthetic
+fun CommandExecuteResult.isExecutionException(): Boolean {
+ contract {
+ returns(true) implies (this@isExecutionException is CommandExecuteResult.ExecutionException)
+ returns(false) implies (this@isExecutionException !is CommandExecuteResult.ExecutionException)
+ }
+ return this is CommandExecuteResult.ExecutionException
+}
+
+/**
+ * 当 [this] 为 [CommandExecuteResult.ExecutionException] 时返回 `true`
+ */
+@JvmSynthetic
+fun CommandExecuteResult.isCommandNotFound(): Boolean {
+ contract {
+ returns(true) implies (this@isCommandNotFound is CommandExecuteResult.CommandNotFound)
+ returns(false) implies (this@isCommandNotFound !is CommandExecuteResult.CommandNotFound)
+ }
+ return this is CommandExecuteResult.CommandNotFound
+}
+
+/**
+ * 当 [this] 为 [CommandExecuteResult.ExecutionException] 或 [CommandExecuteResult.CommandNotFound] 时返回 `true`
+ */
+@JvmSynthetic
+fun CommandExecuteResult.isFailure(): Boolean {
+ contract {
+ returns(true) implies (this@isFailure !is CommandExecuteResult.Success)
+ returns(false) implies (this@isFailure is CommandExecuteResult.Success)
+ }
+ return this !is CommandExecuteResult.Success
+}
\ No newline at end of file
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 56d980cda..bda174d01 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
@@ -7,7 +7,10 @@
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
-@file:Suppress("NOTHING_TO_INLINE", "unused")
+@file:Suppress(
+ "NOTHING_TO_INLINE", "unused", "INVISIBLE_MEMBER", "INVISIBLE_REFERENCE", "RESULT_CLASS_IN_RETURN_TYPE",
+ "MemberVisibilityCanBePrivate"
+)
@file:JvmName("CommandManagerKt")
package net.mamoe.mirai.console.command
@@ -18,7 +21,6 @@ import kotlinx.coroutines.Job
import net.mamoe.mirai.console.plugin.Plugin
import net.mamoe.mirai.message.data.Message
import net.mamoe.mirai.message.data.MessageChain
-import net.mamoe.mirai.message.data.SingleMessage
import net.mamoe.mirai.utils.MiraiInternalAPI
/**
@@ -126,104 +128,119 @@ fun Command.unregister(): Boolean = InternalCommandManager.modifyLock.withLock {
InternalCommandManager.registeredCommands.remove(this)
}
-//// executing
+//// executing without detailed result (faster)
/**
* 解析并执行一个指令
*
- * Java 调用方式: ` CommandManager.executeCommand(Command)`
- *
* @param messages 接受 [String] 或 [Message], 其他对象将会被 [Any.toString]
- * @see CommandExecuteResult
+ *
+ * @return 成功执行的指令, 在无匹配指令时返回 `null`
+ * @throws CommandExecutionException 当 [Command.onCommand] 抛出异常时包装并附带相关指令信息抛出
*
* @see JCommandManager.executeCommand Java 方法
*/
-suspend fun CommandSender.executeCommand(vararg messages: Any): CommandExecuteResult {
- if (messages.isEmpty()) return CommandExecuteResult(
- status = CommandExecuteStatus.EMPTY_COMMAND
- )
- return executeCommandInternal(
- messages,
- messages[0].let { if (it is SingleMessage) it.toString() else it.toString().substringBefore(' ') })
+suspend fun CommandSender.executeCommand(vararg messages: Any): Command? {
+ if (messages.isEmpty()) return null
+ return executeCommandInternal(messages, messages[0].toString().substringBefore(' '))
}
-@JvmSynthetic
-internal inline fun List.dropToTypedArray(n: Int): Array = Array(size - n) { this[n + it] }
-
/**
* 解析并执行一个指令
- * @see CommandExecuteResult
+ *
+ * @return 成功执行的指令, 在无匹配指令时返回 `null`
+ * @throws CommandExecutionException 当 [Command.onCommand] 抛出异常时包装并附带相关指令信息抛出
*
* @see JCommandManager.executeCommand Java 方法
*/
-suspend fun CommandSender.executeCommand(message: MessageChain): CommandExecuteResult {
- if (message.isEmpty()) return CommandExecuteResult(
- status = CommandExecuteStatus.EMPTY_COMMAND
- )
+@Throws(CommandExecutionException::class)
+suspend fun CommandSender.executeCommand(message: MessageChain): Command? {
+ if (message.isEmpty()) return null
return executeCommandInternal(message, message[0].toString())
}
-@JvmSynthetic
-internal suspend inline fun CommandSender.executeCommandInternal(
- messages: Any,
- commandName: String
-): CommandExecuteResult {
- val command = InternalCommandManager.matchCommand(commandName) ?: return CommandExecuteResult(
- status = CommandExecuteStatus.COMMAND_NOT_FOUND,
- commandName = commandName
- )
- val rawInput = messages.flattenCommandComponents()
- kotlin.runCatching {
- command.onCommand(this, rawInput.dropToTypedArray(1))
- }.onFailure {
- return CommandExecuteResult(
- status = CommandExecuteStatus.FAILED,
- commandName = commandName,
- command = command,
- exception = it
- )
- }
- return CommandExecuteResult(
- status = CommandExecuteStatus.SUCCESSFUL,
- commandName = commandName,
- command = command
- )
+
+/**
+ * 在 [executeCommand] 中, [Command.onCommand] 抛出异常时包装的异常.
+ */
+class CommandExecutionException(
+ /**
+ * 执行过程发生异常的指令
+ */
+ val command: Command,
+ /**
+ * 匹配到的指令名
+ */
+ val name: String,
+ /**
+ * 基础分割后的实际参数列表, 元素类型可能为 [Message] 或 [String]
+ */
+ val args: Array,
+ cause: Throwable
+) : RuntimeException(
+ "Exception while executing command '${command.primaryName}' with args ${args.joinToString { "'$it'" }}",
+ cause
+) {
+ override fun toString(): String =
+ "CommandExecutionException(command=$command, name='$name', args=${args.contentToString()})"
+}
+
+
+//// execution with detailed result
+
+/**
+ * 解析并执行一个指令, 获取详细的指令参数等信息
+ *
+ * @param messages 接受 [String] 或 [Message], 其他对象将会被 [Any.toString]
+ *
+ * @return 执行结果
+ *
+ * @see JCommandManager.executeCommandDetailed Java 方法
+ */
+suspend fun CommandSender.executeCommandDetailed(vararg messages: Any): CommandExecuteResult {
+ if (messages.isEmpty()) return CommandExecuteResult.CommandNotFound("")
+ return executeCommandDetailedInternal(messages, messages[0].toString().substringBefore(' '))
}
/**
- * 命令的执行返回
+ * 解析并执行一个指令, 获取详细的指令参数等信息
*
- * @param status 命令最终执行状态
- * @param exception 命令执行时发生的错误(如果有)
- * @param command 尝试执行的命令 (status = SUCCESSFUL | FAILED)
- * @param commandName 尝试执行的命令的名字 (status != EMPTY_COMMAND)
+ * 执行过程中产生的异常将不会直接抛出, 而会包装为 [CommandExecuteResult.ExecutionException]
*
+ * @return 执行结果
*
- * @see CommandExecuteStatus
+ * @see JCommandManager.executeCommandDetailed Java 方法
*/
-class CommandExecuteResult(
- val status: CommandExecuteStatus,
- val exception: Throwable? = null,
- val command: Command? = null,
- val commandName: String? = null
-) {
- /**
- * 命令的执行状态
- *
- * 当为 [SUCCESSFUL] 的时候,代表命令执行成功
- *
- * 当为 [FAILED] 的时候, 代表命令执行出现了错误
- *
- * 当为 [COMMAND_NOT_FOUND] 的时候,代表没有匹配的命令
- *
- * 当为 [EMPTY_COMMAND] 的时候, 代表尝试执行 ""
- *
- */
- enum class CommandExecuteStatus {
- SUCCESSFUL, FAILED, COMMAND_NOT_FOUND, EMPTY_COMMAND
- }
-
+suspend fun CommandSender.executeCommandDetailed(messages: MessageChain): CommandExecuteResult {
+ if (messages.isEmpty()) return CommandExecuteResult.CommandNotFound("")
+ return executeCommandDetailedInternal(messages, messages[0].toString())
}
-@Suppress("RemoveRedundantQualifierName")
-typealias CommandExecuteStatus = CommandExecuteResult.CommandExecuteStatus
+@JvmSynthetic
+internal suspend inline fun CommandSender.executeCommandDetailedInternal(
+ messages: Any,
+ commandName: String
+): CommandExecuteResult {
+ val command =
+ InternalCommandManager.matchCommand(commandName) ?: return CommandExecuteResult.CommandNotFound(commandName)
+ val args = messages.flattenCommandComponents().dropToTypedArray(1)
+ kotlin.runCatching {
+ command.onCommand(this, args)
+ }.fold(
+ onSuccess = {
+ return CommandExecuteResult.Success(
+ commandName = commandName,
+ command = command,
+ args = args
+ )
+ },
+ onFailure = {
+ return CommandExecuteResult.ExecutionException(
+ commandName = commandName,
+ command = command,
+ exception = it,
+ args = args
+ )
+ }
+ )
+}
diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/internal.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/internal.kt
index e88f5a486..1552786d8 100644
--- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/internal.kt
+++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/internal.kt
@@ -156,4 +156,33 @@ internal fun Group.fuzzySearchMember(nameCardTarget: String): Member? {
return this.members.fuzzySearchOnly(nameCardTarget) {
it.nameCard
}
-}
\ No newline at end of file
+}
+
+
+//// internal
+
+@JvmSynthetic
+internal inline fun List.dropToTypedArray(n: Int): Array = Array(size - n) { this[n + it] }
+
+@JvmSynthetic
+@Throws(CommandExecutionException::class)
+internal suspend inline fun CommandSender.executeCommandInternal(
+ messages: Any,
+ commandName: String
+): Command? {
+ val command = InternalCommandManager.matchCommand(commandName) ?: return null
+ val rawInput = messages.flattenCommandComponents()
+
+ val loweredArgs = rawInput.dropToTypedArray(1)
+
+ kotlin.runCatching {
+ command.onCommand(this, loweredArgs)
+ }.fold(
+ onSuccess = {
+ return command
+ },
+ onFailure = {
+ throw CommandExecutionException(command, commandName, loweredArgs, it)
+ }
+ )
+}
diff --git a/frontend/mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure/MiraiConsolePureLoader.kt b/frontend/mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure/MiraiConsolePureLoader.kt
index ad142caa5..77a55e1e9 100644
--- a/frontend/mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure/MiraiConsolePureLoader.kt
+++ b/frontend/mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure/MiraiConsolePureLoader.kt
@@ -22,9 +22,8 @@ package net.mamoe.mirai.console.pure
import net.mamoe.mirai.console.MiraiConsoleInitializer
import net.mamoe.mirai.console.command.CommandExecuteStatus
import net.mamoe.mirai.console.command.ConsoleCommandSender
-import net.mamoe.mirai.console.command.executeCommand
+import net.mamoe.mirai.console.command.executeCommandDetailed
import net.mamoe.mirai.message.data.Message
-import net.mamoe.mirai.message.data.PlainText
import net.mamoe.mirai.utils.DefaultLogger
import kotlin.concurrent.thread
@@ -48,14 +47,11 @@ internal fun startConsoleThread() {
while (true) {
val next = MiraiConsoleFrontEndPure.requestInput("")
consoleLogger.debug("INPUT> $next")
- val result = ConsoleCS.executeCommand(PlainText(next))
+ val result = ConsoleCS.executeCommandDetailed(next)
when (result.status) {
CommandExecuteStatus.SUCCESSFUL -> {
}
- CommandExecuteStatus.EMPTY_COMMAND -> {
- }
- CommandExecuteStatus.FAILED -> {
- consoleLogger.error("An error occurred while executing the command: $next", result.exception)
+ CommandExecuteStatus.EXECUTION_EXCEPTION -> {
}
CommandExecuteStatus.COMMAND_NOT_FOUND -> {
consoleLogger.warning("Unknown command: ${result.commandName}")