CommandExecuteResult for executeCommand

This commit is contained in:
Him188 2020-08-23 00:34:34 +08:00
parent cdc9f8f613
commit cd60a60d97
14 changed files with 168 additions and 130 deletions

View File

@ -0,0 +1,74 @@
# mirai-console backend
欢迎来到 mirai-console 后端开发文档。
## 包结构
- `net.mamoe.mirai.console.`
- `command`:指令模块:[Command]
- `data`:存储模块:[PluginData], [PluginConfig], [PluginDataStorage]
- `event`Console 实现的事件.
- `plugin`:插件模块:[Plugin], [PluginLoader], [JvmPlugin]
- `util`:工具类:[Annotations], [BotManager], [ConsoleInput], [JavaPluginScheduler]
- `internal`:内部实现
## 基础
### `Plugin` 模块
Console 支持拥有强扩展性的插件加载器。内建 JVM 插件支持 ([JarPluginLoader])。
#### [插件加载器 `PluginLoader`][PluginLoader]
Console 本身是一套高扩展性的「框架」,必然拥有通用的 [插件加载器][PluginLoader]。
Console 内置 [JarPluginLoader],支持加载使用 Kotlin、 Java或其他 JVM 平台编程语言并打包为 jar 的插件 (详见下文 `JvmPlugin`)。
扩展的 [插件加载器][PluginLoader] 可以由一个特别的 [JVM 插件][JvmPlugin] 提供。在启动时, Console 首先加载那些提供扩展 [插件加载器][PluginLoader] 的插件. 并允许它们 [注册扩展加载器]。
#### [`Plugin`][Plugin]
所有 Console 插件都必须实现 [`Plugin`][Plugin] 接口。
虽然 Console 是 JVM 平台程序, 但也拥有支持其他平台的插件管理系统。
[`Plugin`][Plugin] 可在相应 [插件加载器 `PluginLoader`][PluginLoader] 的帮助下,成为任何语言实现的插件与 Console 建立联系的桥梁。
#### [JVM 插件][JvmPlugin]
#### 实现 Kotlin 插件
添加一个类
#### 实现 Java 插件
[Plugin]: src/main/kotlin/net/mamoe/mirai/console/plugin/Plugin.kt
[PluginDescription]: src/main/kotlin/net/mamoe/mirai/console/plugin/description.kt
[PluginLoader]: src/main/kotlin/net/mamoe/mirai/console/plugin/PluginLoader.kt
[JarPluginLoader]: src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/JarPluginLoader.kt
[JvmPlugin]: src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/JvmPlugin.kt
[AbstractJvmPlugin]: src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/AbstractJvmPlugin.kt
[KotlinPlugin]: src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/KotlinPlugin.kt
[JavaPlugin]: src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/JavaPlugin.kt
[PluginData]: src/main/kotlin/net/mamoe/mirai/console/data/PluginData.kt
[PluginConfig]: src/main/kotlin/net/mamoe/mirai/console/data/PluginConfig.kt
[PluginDataStorage]: src/main/kotlin/net/mamoe/mirai/console/data/PluginDataStorage.kt
[MiraiConsole]: src/main/kotlin/net/mamoe/mirai/console/MiraiConsole.kt
[MiraiConsoleImplementation]: src/main/kotlin/net/mamoe/mirai/console/MiraiConsoleImplementation.kt
<!--[MiraiConsoleFrontEnd]: src/main/kotlin/net/mamoe/mirai/console/MiraiConsoleFrontEnd.kt-->
[Command]: src/main/kotlin/net/mamoe/mirai/console/command/Command.kt
[CompositeCommand]: src/main/kotlin/net/mamoe/mirai/console/command/CompositeCommand.kt
[SimpleCommand]: src/main/kotlin/net/mamoe/mirai/console/command/SimpleCommand.kt
[RawCommand]: src/main/kotlin/net/mamoe/mirai/console/command/RawCommand.kt
[CommandManager]: src/main/kotlin/net/mamoe/mirai/console/command/CommandManager.kt
[BotManager]: src/main/kotlin/net/mamoe/mirai/console/util/BotManager.kt
[Annotations]: src/main/kotlin/net/mamoe/mirai/console/util/Annotations.kt
[ConsoleInput]: src/main/kotlin/net/mamoe/mirai/console/util/ConsoleInput.kt
[JavaPluginScheduler]: src/main/kotlin/net/mamoe/mirai/console/plugin/jvm/JavaPluginScheduler.kt
[注册扩展加载器]: src/main/kotlin/net/mamoe/mirai/console/plugin/PluginManager.kt#L49-L51

View File

@ -55,7 +55,7 @@ public sealed class CommandExecuteResult {
}
/** 指令执行过程出现了错误 */
public class ExecutionException(
public class ExecutionFailed(
/** 指令执行时发生的错误 */
public override val exception: Throwable,
/** 尝试执行的指令 */
@ -122,7 +122,6 @@ public sealed class CommandExecuteResult {
}
}
@Suppress("RemoveRedundantQualifierName")
public typealias CommandExecuteStatus = CommandExecuteResult.CommandExecuteStatus
@ -139,19 +138,19 @@ public fun CommandExecuteResult.isSuccess(): Boolean {
}
/**
* [this] [CommandExecuteResult.ExecutionException] 时返回 `true`
* [this] [CommandExecuteResult.ExecutionFailed] 时返回 `true`
*/
@JvmSynthetic
public fun CommandExecuteResult.isExecutionException(): Boolean {
contract {
returns(true) implies (this@isExecutionException is CommandExecuteResult.ExecutionException)
returns(false) implies (this@isExecutionException !is CommandExecuteResult.ExecutionException)
returns(true) implies (this@isExecutionException is CommandExecuteResult.ExecutionFailed)
returns(false) implies (this@isExecutionException !is CommandExecuteResult.ExecutionFailed)
}
return this is CommandExecuteResult.ExecutionException
return this is CommandExecuteResult.ExecutionFailed
}
/**
* [this] [CommandExecuteResult.ExecutionException] 时返回 `true`
* [this] [CommandExecuteResult.ExecutionFailed] 时返回 `true`
*/
@JvmSynthetic
public fun CommandExecuteResult.isPermissionDenied(): Boolean {
@ -163,7 +162,7 @@ public fun CommandExecuteResult.isPermissionDenied(): Boolean {
}
/**
* [this] [CommandExecuteResult.ExecutionException] 时返回 `true`
* [this] [CommandExecuteResult.ExecutionFailed] 时返回 `true`
*/
@JvmSynthetic
public fun CommandExecuteResult.isCommandNotFound(): Boolean {
@ -175,7 +174,7 @@ public fun CommandExecuteResult.isCommandNotFound(): Boolean {
}
/**
* [this] [CommandExecuteResult.ExecutionException] [CommandExecuteResult.CommandNotFound] 时返回 `true`
* [this] [CommandExecuteResult.ExecutionFailed] [CommandExecuteResult.CommandNotFound] 时返回 `true`
*/
@JvmSynthetic
public fun CommandExecuteResult.isFailure(): Boolean {

View File

@ -12,9 +12,10 @@
package net.mamoe.mirai.console.command
import net.mamoe.mirai.console.command.Command.Companion.primaryName
import net.mamoe.mirai.console.command.CommandManager.INSTANCE.executeCommand
/**
* [executeCommand] , [Command.onCommand] 抛出异常时包装的异常.
* [CommandManager.executeCommand] , [Command.onCommand] 抛出异常时包装的异常.
*/
public class CommandExecutionException(
/**

View File

@ -15,12 +15,7 @@
package net.mamoe.mirai.console.command
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import net.mamoe.kjbb.JvmBlockingBridge
import net.mamoe.mirai.console.command.CommandManagerImpl.unregisterAllCommands
import net.mamoe.mirai.console.plugin.Plugin
import net.mamoe.mirai.console.util.ConsoleExperimentalAPI
import net.mamoe.mirai.message.data.Message
import net.mamoe.mirai.message.data.MessageChain
@ -79,27 +74,6 @@ public interface CommandManager {
*/
public fun Command.isRegistered(): Boolean
/**
* 解析并执行一个指令. 将会检查指令权限, 在无权限时抛出
*
* @param messages 接受 [String] [Message], 其他对象将会被 [Any.toString]
*
* @return 成功执行的指令, 在无匹配指令时返回 `null`
* @throws CommandExecutionException [Command.onCommand] 抛出异常时包装并附带相关指令信息抛出
*/
@JvmBlockingBridge
public suspend fun CommandSender.executeCommand(vararg messages: Any): Command?
/**
* 解析并执行一个指令
*
* @return 成功执行的指令, 在无匹配指令时返回 `null`
* @throws CommandExecutionException [Command.onCommand] 抛出异常时包装并附带相关指令信息抛出
*/
@Throws(CommandExecutionException::class)
@JvmBlockingBridge
public suspend fun CommandSender.executeCommand(message: MessageChain): Command?
/**
* 执行一个指令
*
@ -127,20 +101,18 @@ public interface CommandManager {
*
* @return 执行结果
*/
@ConsoleExperimentalAPI
@JvmBlockingBridge
public suspend fun CommandSender.executeCommandDetailed(vararg messages: Any): CommandExecuteResult
public suspend fun CommandSender.executeCommand(vararg messages: Any): CommandExecuteResult
/**
* 解析并执行一个指令, 获取详细的指令参数等信息
*
* 执行过程中产生的异常将不会直接抛出, 而会包装为 [CommandExecuteResult.ExecutionException]
* 执行过程中产生的异常将不会直接抛出, 而会包装为 [CommandExecuteResult.ExecutionFailed]
*
* @return 执行结果
*/
@ConsoleExperimentalAPI
@JvmBlockingBridge
public suspend fun CommandSender.executeCommandDetailed(messages: MessageChain): CommandExecuteResult
public suspend fun CommandSender.executeCommand(messages: MessageChain): CommandExecuteResult
public companion object INSTANCE : CommandManager by CommandManagerImpl {
// TODO: 2020/8/20 https://youtrack.jetbrains.com/issue/KT-41191
@ -152,54 +124,19 @@ public interface CommandManager {
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) }
override suspend fun CommandSender.executeCommand(message: MessageChain): Command? =
CommandManagerImpl.run { executeCommand(message) }
override suspend fun Command.execute(
sender: CommandSender,
args: MessageChain,
checkPermission: Boolean
): Unit =
CommandManagerImpl.run { execute(sender, args = args, checkPermission = checkPermission) }
): Unit = CommandManagerImpl.run { execute(sender, args = args, checkPermission = checkPermission) }
override suspend fun Command.execute(sender: CommandSender, vararg args: Any, checkPermission: Boolean): Unit =
CommandManagerImpl.run { execute(sender, args = args, checkPermission = checkPermission) }
@ConsoleExperimentalAPI
override suspend fun CommandSender.executeCommandDetailed(vararg messages: Any): CommandExecuteResult =
CommandManagerImpl.run { executeCommandDetailed(*messages) }
override suspend fun CommandSender.executeCommand(vararg messages: Any): CommandExecuteResult =
CommandManagerImpl.run { executeCommand(*messages) }
@ConsoleExperimentalAPI
override suspend fun CommandSender.executeCommandDetailed(messages: MessageChain): CommandExecuteResult =
CommandManagerImpl.run { executeCommandDetailed(messages) }
override suspend fun CommandSender.executeCommand(messages: MessageChain): CommandExecuteResult =
CommandManagerImpl.run { executeCommand(messages) }
}
}
/**
* 指令的所有者.
* @see PluginCommandOwner
*/
public open class CommandOwner
/**
* 插件指令所有者. 插件只能通过 [PluginCommandOwner] 管理指令.
*/
public class PluginCommandOwner(
public val plugin: Plugin
) : CommandOwner() {
init {
if (plugin is CoroutineScope) { // JVM Plugin
plugin.coroutineContext[Job]?.invokeOnCompletion {
this.unregisterAllCommands()
}
}
}
}
/**
* 代表控制台所有者. 所有的 mirai-console 内建的指令都属于 [ConsoleCommandOwner].
*/
internal object ConsoleCommandOwner : CommandOwner()
}

View File

@ -10,20 +10,26 @@
package net.mamoe.mirai.console.command
import kotlinx.atomicfu.locks.withLock
import kotlinx.coroutines.CoroutineExceptionHandler
import kotlinx.coroutines.CoroutineScope
import net.mamoe.mirai.console.MiraiConsole
import net.mamoe.mirai.console.command.Command.Companion.primaryName
import net.mamoe.mirai.console.internal.command.*
import net.mamoe.mirai.console.util.ConsoleExperimentalAPI
import net.mamoe.mirai.console.internal.command.executeCommandInternal
import net.mamoe.mirai.console.internal.command.flattenCommandComponents
import net.mamoe.mirai.console.internal.command.intersectsIgnoringCase
import net.mamoe.mirai.event.Listener
import net.mamoe.mirai.event.subscribeAlways
import net.mamoe.mirai.message.MessageEvent
import net.mamoe.mirai.message.data.MessageChain
import net.mamoe.mirai.message.data.MessageContent
import net.mamoe.mirai.message.data.content
import net.mamoe.mirai.utils.MiraiLogger
import java.util.concurrent.locks.ReentrantLock
internal object CommandManagerImpl : CommandManager, CoroutineScope by CoroutineScope(MiraiConsole.job) {
private val logger: MiraiLogger by lazy {
MiraiConsole.newLogger("command")
}
@JvmField
internal val registeredCommands: MutableList<Command> = mutableListOf()
@ -49,11 +55,31 @@ internal object CommandManagerImpl : CommandManager, CoroutineScope by Coroutine
internal val commandListener: Listener<MessageEvent> by lazy {
subscribeAlways(
coroutineContext = CoroutineExceptionHandler { _, throwable ->
logger.error(throwable)
},
concurrency = Listener.ConcurrencyKind.CONCURRENT,
priority = Listener.EventPriority.HIGH
) {
if (this.toCommandSender().executeCommand(message) != null) {
intercept()
val sender = this.toCommandSender()
when (val result = sender.executeCommand(message)) {
is CommandExecuteResult.PermissionDenied -> {
if (!result.command.prefixOptional) {
sender.sendMessage("权限不足")
intercept()
}
}
is CommandExecuteResult.Success -> {
intercept()
}
is CommandExecuteResult.ExecutionFailed -> {
sender.catchExecutionException(result.exception)
intercept()
}
is CommandExecuteResult.CommandNotFound -> {
// noop
}
}
}
}
@ -114,17 +140,6 @@ internal object CommandManagerImpl : CommandManager, CoroutineScope by Coroutine
override fun Command.isRegistered(): Boolean = this in registeredCommands
//// executing without detailed result (faster)
override suspend fun CommandSender.executeCommand(vararg messages: Any): Command? {
if (messages.isEmpty()) return null
return matchAndExecuteCommandInternal(messages, messages[0].toString().substringBefore(' '))
}
override suspend fun CommandSender.executeCommand(message: MessageChain): Command? {
if (message.isEmpty()) return null
val msg = message.filterIsInstance<MessageContent>()
return matchAndExecuteCommandInternal(msg, msg[0].content.substringBefore(' '))
}
override suspend fun Command.execute(sender: CommandSender, args: MessageChain, checkPermission: Boolean) {
sender.executeCommandInternal(
this,
@ -144,15 +159,14 @@ internal object CommandManagerImpl : CommandManager, CoroutineScope by Coroutine
}
//// execution with detailed result
@ConsoleExperimentalAPI
override suspend fun CommandSender.executeCommandDetailed(vararg messages: Any): CommandExecuteResult {
override suspend fun CommandSender.executeCommand(vararg messages: Any): CommandExecuteResult {
if (messages.isEmpty()) return CommandExecuteResult.CommandNotFound("")
return executeCommandDetailedInternal(messages, messages[0].toString().substringBefore(' '))
return executeCommandInternal(messages, messages[0].toString().substringBefore(' '))
}
@ConsoleExperimentalAPI
override suspend fun CommandSender.executeCommandDetailed(messages: MessageChain): CommandExecuteResult {
if (messages.isEmpty()) return CommandExecuteResult.CommandNotFound("")
return executeCommandDetailedInternal(messages, messages[0].toString())
override suspend fun CommandSender.executeCommand(messages: MessageChain): CommandExecuteResult {
val msg = messages.filterIsInstance<MessageContent>()
if (msg.isEmpty()) return CommandExecuteResult.CommandNotFound("")
return executeCommandInternal(msg, msg[0].toString().substringBefore(' '))
}
}

View File

@ -0,0 +1,23 @@
/*
* Copyright 2019-2020 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 with Mamoe Exceptions 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AFFERO GENERAL PUBLIC LICENSE version 3 with Mamoe Exceptions license that can be found via the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
package net.mamoe.mirai.console.command
/**
* 指令的所有者. 目前仅作为标识作用.
*
* @see CommandManager.unregisterAllCommands 取消注册所有属于一个 [CommandOwner] 的指令
* @see CommandManager.registeredCommands 获取已经注册了的属于一个 [CommandOwner] 的指令列表.
*/
public interface CommandOwner
/**
* 代表控制台所有者. 所有的 mirai-console 内建的指令都属于 [ConsoleCommandOwner].
*/
internal object ConsoleCommandOwner : CommandOwner

View File

@ -107,20 +107,6 @@ internal fun Group.fuzzySearchMember(nameCardTarget: String): Member? {
@JvmSynthetic
internal inline fun <reified T> List<T>.dropToTypedArray(n: Int): Array<T> = Array(size - n) { this[n + it] }
@JvmSynthetic
@Throws(CommandExecutionException::class)
internal suspend inline fun CommandSender.matchAndExecuteCommandInternal(
messages: Any,
commandName: String
): Command? {
val command = CommandManagerImpl.matchCommand(
commandName
) ?: return null
this.executeCommandInternal(command, messages.flattenCommandComponents().dropToTypedArray(1), commandName, true)
return command
}
@JvmSynthetic
@Throws(CommandExecutionException::class)
internal suspend inline fun CommandSender.executeCommandInternal(
@ -149,7 +135,7 @@ internal suspend inline fun CommandSender.executeCommandInternal(
@JvmSynthetic
internal suspend inline fun CommandSender.executeCommandDetailedInternal(
internal suspend fun CommandSender.executeCommandInternal(
messages: Any,
commandName: String
): CommandExecuteResult {
@ -171,7 +157,7 @@ internal suspend inline fun CommandSender.executeCommandDetailedInternal(
)
},
onFailure = {
return CommandExecuteResult.ExecutionException(
return CommandExecuteResult.ExecutionFailed(
commandName = commandName,
command = command,
exception = it,

View File

@ -48,10 +48,10 @@ internal inline fun <reified T : PluginData> newPluginDataInstanceUsingReflectio
}
private fun isReferenceArray(rootClass: KClass<Any>): Boolean = rootClass.java.isArray
internal fun isReferenceArray(rootClass: KClass<Any>): Boolean = rootClass.java.isArray
@Suppress("UNCHECKED_CAST")
private fun KType.kclass() = when (val t = classifier) {
internal fun KType.kclass() = when (val t = classifier) {
is KClass<*> -> t
else -> error("Only KClass supported as classifier, got $t")
} as KClass<Any>

View File

@ -23,7 +23,7 @@ import java.util.concurrent.locks.ReentrantLock
internal object PluginManagerImpl : PluginManager {
override val pluginsPath: Path = MiraiConsole.rootPath.resolve("plugins").apply { mkdir() }
override val pluginsDataPath = MiraiConsole.rootPath.resolve("data").apply { mkdir() }
override val pluginsDataPath: Path = MiraiConsole.rootPath.resolve("data").apply { mkdir() }
@Suppress("ObjectPropertyName")
private val _pluginLoaders: MutableList<PluginLoader<*, *>> = mutableListOf()

View File

@ -11,6 +11,7 @@
package net.mamoe.mirai.console.plugin
import net.mamoe.mirai.console.command.CommandOwner
import net.mamoe.mirai.console.plugin.jvm.JvmPlugin
import net.mamoe.mirai.console.util.ConsoleExperimentalAPI
import java.io.File
@ -25,7 +26,7 @@ import java.nio.file.Path
*
* @see PluginLoader 插件加载器
*/
public interface Plugin {
public interface Plugin : CommandOwner {
/**
* 判断此插件是否已启用
*

View File

@ -38,6 +38,7 @@ import kotlin.reflect.KClass
*/
public interface JvmPlugin : Plugin, CoroutineScope,
PluginFileExtensions, ResourceContainer, AutoSavePluginDataHolder {
/** 日志 */
public val logger: MiraiLogger

View File

@ -123,8 +123,10 @@ internal class TestCommand {
}
@Test
fun `test throw Exception`() = runBlocking {
assertEquals(null, sender.executeCommand(""))
fun `test throw Exception`() = assertTrue {
runBlocking {
sender.executeCommand("").isFailure()
}
}
@Test

View File

@ -17,7 +17,7 @@
*/
object Versions {
const val core = "1.2.1"
const val core = "1.2.2"
const val console = "1.0-M2-1"
const val consoleGraphical = "0.0.7"
const val consoleTerminal = "0.1.0"

View File

@ -15,7 +15,7 @@ import net.mamoe.mirai.console.command.BuiltInCommands
import net.mamoe.mirai.console.command.Command.Companion.primaryName
import net.mamoe.mirai.console.command.CommandExecuteStatus
import net.mamoe.mirai.console.command.CommandManager
import net.mamoe.mirai.console.command.CommandManager.INSTANCE.executeCommandDetailed
import net.mamoe.mirai.console.command.CommandManager.INSTANCE.executeCommand
import net.mamoe.mirai.console.util.ConsoleInternalAPI
import net.mamoe.mirai.console.util.requestInput
import net.mamoe.mirai.utils.DefaultLogger
@ -62,7 +62,7 @@ internal fun startupConsoleThread() {
continue
}
// consoleLogger.debug("INPUT> $next")
val result = ConsoleCommandSenderImpl.executeCommandDetailed(next)
val result = ConsoleCommandSenderImpl.executeCommand(next)
when (result.status) {
CommandExecuteStatus.SUCCESSFUL -> {
}