mirror of
https://github.com/mamoe/mirai.git
synced 2025-01-25 15:40:28 +08:00
API stabilization: Simplify command execution in PluginManager
This commit is contained in:
parent
e9595075e0
commit
27952f537f
@ -9,7 +9,7 @@
|
||||
|
||||
@file:Suppress(
|
||||
"NOTHING_TO_INLINE", "unused", "INVISIBLE_MEMBER", "INVISIBLE_REFERENCE", "RESULT_CLASS_IN_RETURN_TYPE",
|
||||
"MemberVisibilityCanBePrivate"
|
||||
"MemberVisibilityCanBePrivate", "INAPPLICABLE_JVM_NAME"
|
||||
)
|
||||
@file:JvmName("CommandManagerKt")
|
||||
|
||||
@ -17,10 +17,8 @@ package net.mamoe.mirai.console.command
|
||||
|
||||
import net.mamoe.kjbb.JvmBlockingBridge
|
||||
import net.mamoe.mirai.console.internal.command.CommandManagerImpl
|
||||
import net.mamoe.mirai.message.data.Image
|
||||
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.console.internal.command.CommandManagerImpl.executeCommand
|
||||
import net.mamoe.mirai.message.data.*
|
||||
|
||||
/**
|
||||
* 指令管理器
|
||||
@ -63,74 +61,92 @@ public interface CommandManager {
|
||||
*
|
||||
* 注意: [内建指令][BuiltInCommands] 也可以被覆盖.
|
||||
*/
|
||||
@JvmName("registerCommand")
|
||||
public fun Command.register(override: Boolean = false): Boolean
|
||||
|
||||
/**
|
||||
* 查找并返回重名的指令. 返回重名指令.
|
||||
*/
|
||||
@JvmName("findCommandDuplicate")
|
||||
public fun Command.findDuplicate(): Command?
|
||||
|
||||
/**
|
||||
* 取消注册这个指令. 若指令未注册, 返回 `false`.
|
||||
*/
|
||||
@JvmName("unregisterCommand")
|
||||
public fun Command.unregister(): Boolean
|
||||
|
||||
/**
|
||||
* 当 [this] 已经 [注册][register] 后返回 `true`
|
||||
*/
|
||||
@JvmName("isCommandRegistered")
|
||||
public fun Command.isRegistered(): Boolean
|
||||
|
||||
/**
|
||||
* 执行一个指令
|
||||
*
|
||||
* @return 成功执行的指令, 在无匹配指令时返回 `null`
|
||||
* @throws CommandExecutionException 当 [Command.onCommand] 抛出异常时包装并附带相关指令信息抛出
|
||||
* @see executeCommand
|
||||
*/
|
||||
@JvmBlockingBridge
|
||||
@Throws(CommandExecutionException::class)
|
||||
public suspend fun Command.execute(sender: CommandSender, args: MessageChain, checkPermission: Boolean = true)
|
||||
|
||||
/**
|
||||
* 执行一个指令
|
||||
*
|
||||
* @return 成功执行的指令, 在无匹配指令时返回 `null`
|
||||
* @throws CommandExecutionException 当 [Command.onCommand] 抛出异常时包装并附带相关指令信息抛出
|
||||
* @see executeCommand
|
||||
*/
|
||||
@JvmBlockingBridge
|
||||
@Throws(CommandExecutionException::class)
|
||||
public suspend fun Command.execute(sender: CommandSender, vararg args: Any, checkPermission: Boolean = true)
|
||||
|
||||
/**
|
||||
* 解析并执行一个指令, 获取详细的指令参数等信息
|
||||
*
|
||||
* 执行过程中产生的异常将不会直接抛出, 而会包装为 [CommandExecuteResult.ExecutionFailed]
|
||||
* 解析并执行一个指令
|
||||
*
|
||||
* ### 指令解析流程
|
||||
* 1. [messages] 的第一个消息元素的 [内容][Message.contentToString] 被作为指令名, 在已注册指令列表中搜索. (包含 [Command.prefixOptional] 相关的处理)
|
||||
* 1. [message] 的第一个消息元素的 [内容][Message.contentToString] 被作为指令名, 在已注册指令列表中搜索. (包含 [Command.prefixOptional] 相关的处理)
|
||||
* 2. 参数语法分析.
|
||||
* 在当前的实现下, [messages] 被以空格和 [SingleMessage] 分割.
|
||||
* 在当前的实现下, [message] 被以空格和 [SingleMessage] 分割.
|
||||
* 如 "MessageChain("foo bar", [Image], " test")" 被分割为 "foo", "bar", [Image], "test".
|
||||
* 注意: 字符串与消息元素之间不需要空格, 会被强制分割. 如 "bar[mirai:image:]" 会被分割为 "bar" 和 [Image] 类型的消息元素.
|
||||
* 3. 参数解析. 各类型指令实现不同. 详见 [RawCommand], [CompositeCommand], [SimpleCommand]
|
||||
*
|
||||
* @param messages 接受 [String] 或 [Message], 其他对象将会被 [Any.toString]
|
||||
* ### 未来的扩展
|
||||
* 在将来, 参数语法分析过程可能会被扩展, 允许插件自定义处理方式, 因此可能不会简单地使用空格分隔.
|
||||
*
|
||||
* @param message 一条完整的指令. 如 "/managers add 123456.123456"
|
||||
* @param checkPermission 为 `true` 时检查权限
|
||||
*
|
||||
* @return 执行结果
|
||||
*/
|
||||
@JvmBlockingBridge
|
||||
public suspend fun CommandSender.executeCommand(vararg messages: Any): CommandExecuteResult
|
||||
public suspend fun CommandSender.executeCommand(
|
||||
message: Message,
|
||||
checkPermission: Boolean = true
|
||||
): CommandExecuteResult
|
||||
|
||||
/**
|
||||
* 解析并执行一个指令, 获取详细的指令参数等信息
|
||||
* 解析并执行一个指令
|
||||
*
|
||||
* 执行过程中产生的异常将不会直接抛出, 而会包装为 [CommandExecuteResult.ExecutionFailed]
|
||||
* @param message 一条完整的指令. 如 "/managers add 123456.123456"
|
||||
* @param checkPermission 为 `true` 时检查权限
|
||||
*
|
||||
* @return 执行结果
|
||||
* @see executeCommand
|
||||
*/
|
||||
@JvmDefault
|
||||
@JvmBlockingBridge
|
||||
public suspend fun CommandSender.executeCommand(
|
||||
message: String,
|
||||
checkPermission: Boolean = true
|
||||
): CommandExecuteResult = executeCommand(PlainText(message).asMessageChain(), checkPermission)
|
||||
|
||||
/**
|
||||
* 执行一个确切的指令
|
||||
* @see executeCommand 获取更多信息
|
||||
*/
|
||||
@JvmBlockingBridge
|
||||
public suspend fun CommandSender.executeCommand(messages: MessageChain): CommandExecuteResult
|
||||
@JvmName("executeCommand")
|
||||
public suspend fun Command.execute(
|
||||
sender: CommandSender,
|
||||
arguments: Message = EmptyMessageChain,
|
||||
checkPermission: Boolean = true
|
||||
): CommandExecuteResult
|
||||
|
||||
/**
|
||||
* 执行一个确切的指令
|
||||
* @see executeCommand 获取更多信息
|
||||
*/
|
||||
@JvmDefault
|
||||
@JvmBlockingBridge
|
||||
@JvmName("executeCommand")
|
||||
public suspend fun Command.execute(
|
||||
sender: CommandSender,
|
||||
arguments: String = "",
|
||||
checkPermission: Boolean = true
|
||||
): CommandExecuteResult = execute(sender, PlainText(arguments).asMessageChain(), checkPermission)
|
||||
|
||||
public companion object INSTANCE : CommandManager by CommandManagerImpl {
|
||||
// TODO: 2020/8/20 https://youtrack.jetbrains.com/issue/KT-41191
|
||||
@ -142,19 +158,31 @@ 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 val allRegisteredCommands: List<Command>
|
||||
get() = CommandManagerImpl.allRegisteredCommands
|
||||
|
||||
|
||||
override suspend fun Command.execute(
|
||||
sender: CommandSender,
|
||||
args: MessageChain,
|
||||
arguments: Message,
|
||||
checkPermission: Boolean
|
||||
): Unit = CommandManagerImpl.run { execute(sender, args = args, checkPermission = checkPermission) }
|
||||
): CommandExecuteResult =
|
||||
CommandManagerImpl.run { execute(sender, arguments = arguments, checkPermission = checkPermission) }
|
||||
|
||||
override suspend fun Command.execute(sender: CommandSender, vararg args: Any, checkPermission: Boolean): Unit =
|
||||
CommandManagerImpl.run { execute(sender, args = args, checkPermission = checkPermission) }
|
||||
override suspend fun CommandSender.executeCommand(
|
||||
message: String,
|
||||
checkPermission: Boolean
|
||||
): CommandExecuteResult = CommandManagerImpl.run { executeCommand(message, checkPermission) }
|
||||
|
||||
override suspend fun CommandSender.executeCommand(vararg messages: Any): CommandExecuteResult =
|
||||
CommandManagerImpl.run { executeCommand(*messages) }
|
||||
override suspend fun Command.execute(
|
||||
sender: CommandSender,
|
||||
arguments: String,
|
||||
checkPermission: Boolean
|
||||
): CommandExecuteResult = CommandManagerImpl.run { execute(sender, arguments, checkPermission) }
|
||||
|
||||
override suspend fun CommandSender.executeCommand(messages: MessageChain): CommandExecuteResult =
|
||||
CommandManagerImpl.run { executeCommand(messages) }
|
||||
override suspend fun CommandSender.executeCommand(
|
||||
message: Message,
|
||||
checkPermission: Boolean
|
||||
): CommandExecuteResult = CommandManagerImpl.run { executeCommand(message, checkPermission) }
|
||||
}
|
||||
}
|
@ -121,6 +121,7 @@ public interface GroupAwareCommandSender : CommandSender {
|
||||
|
||||
/**
|
||||
* 控制台指令执行者. 代表由控制台执行指令
|
||||
* @see INSTANCE
|
||||
*/
|
||||
// 前端实现
|
||||
public abstract class ConsoleCommandSender internal constructor() : CommandSender {
|
||||
|
@ -18,8 +18,10 @@ import net.mamoe.mirai.console.command.Command.Companion.primaryName
|
||||
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.Message
|
||||
import net.mamoe.mirai.message.data.MessageContent
|
||||
import net.mamoe.mirai.message.data.asMessageChain
|
||||
import net.mamoe.mirai.message.data.content
|
||||
import net.mamoe.mirai.utils.MiraiLogger
|
||||
import java.util.concurrent.locks.ReentrantLock
|
||||
|
||||
@ -137,34 +139,43 @@ internal object CommandManagerImpl : CommandManager, CoroutineScope by Coroutine
|
||||
|
||||
override fun Command.isRegistered(): Boolean = this in registeredCommands
|
||||
|
||||
//// executing without detailed result (faster)
|
||||
override suspend fun Command.execute(sender: CommandSender, args: MessageChain, checkPermission: Boolean) {
|
||||
sender.executeCommandInternal(
|
||||
override suspend fun Command.execute(
|
||||
sender: CommandSender,
|
||||
arguments: Message,
|
||||
checkPermission: Boolean
|
||||
): CommandExecuteResult {
|
||||
return sender.executeCommandInternal(
|
||||
this,
|
||||
args.flattenCommandComponents().toTypedArray(),
|
||||
this.primaryName,
|
||||
arguments.flattenCommandComponents().toTypedArray(),
|
||||
primaryName,
|
||||
checkPermission
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun Command.execute(sender: CommandSender, vararg args: Any, checkPermission: Boolean) {
|
||||
sender.executeCommandInternal(
|
||||
override suspend fun Command.execute(
|
||||
sender: CommandSender,
|
||||
arguments: String,
|
||||
checkPermission: Boolean
|
||||
): CommandExecuteResult {
|
||||
return sender.executeCommandInternal(
|
||||
this,
|
||||
args.flattenCommandComponents().toTypedArray(),
|
||||
this.primaryName,
|
||||
arguments.flattenCommandComponents().toTypedArray(),
|
||||
primaryName,
|
||||
checkPermission
|
||||
)
|
||||
}
|
||||
|
||||
//// execution with detailed result
|
||||
override suspend fun CommandSender.executeCommand(vararg messages: Any): CommandExecuteResult {
|
||||
if (messages.isEmpty()) return CommandExecuteResult.CommandNotFound("")
|
||||
return executeCommandInternal(messages, messages[0].toString().substringBefore(' '))
|
||||
}
|
||||
|
||||
override suspend fun CommandSender.executeCommand(messages: MessageChain): CommandExecuteResult {
|
||||
val msg = messages.filterIsInstance<MessageContent>()
|
||||
override suspend fun CommandSender.executeCommand(
|
||||
message: Message,
|
||||
checkPermission: Boolean
|
||||
): CommandExecuteResult {
|
||||
val msg = message.asMessageChain().filterIsInstance<MessageContent>()
|
||||
if (msg.isEmpty()) return CommandExecuteResult.CommandNotFound("")
|
||||
return executeCommandInternal(msg, msg[0].toString().substringBefore(' '))
|
||||
return executeCommandInternal(msg, msg[0].content.substringBefore(' '), checkPermission)
|
||||
}
|
||||
|
||||
override suspend fun CommandSender.executeCommand(message: String, checkPermission: Boolean): CommandExecuteResult {
|
||||
if (message.isBlank()) return CommandExecuteResult.CommandNotFound("")
|
||||
return executeCommandInternal(message, message.substringBefore(' '), checkPermission)
|
||||
}
|
||||
}
|
@ -10,7 +10,6 @@
|
||||
package net.mamoe.mirai.console.internal.command
|
||||
|
||||
import net.mamoe.mirai.console.command.*
|
||||
import net.mamoe.mirai.console.command.description.CommandArgumentParserException
|
||||
import net.mamoe.mirai.contact.Group
|
||||
import net.mamoe.mirai.contact.Member
|
||||
import kotlin.math.max
|
||||
@ -148,34 +147,11 @@ internal suspend fun CommandSender.executeCommandInternal(
|
||||
args: Array<out Any>,
|
||||
commandName: String,
|
||||
checkPermission: Boolean
|
||||
) {
|
||||
if (checkPermission && !command.testPermission(this)) {
|
||||
throw CommandExecutionException(this, command, commandName, CommandPermissionDeniedException(this, command))
|
||||
}
|
||||
|
||||
kotlin.runCatching {
|
||||
command.onCommand(this, args)
|
||||
}.onFailure {
|
||||
catchExecutionException(it)
|
||||
if (it !is CommandArgumentParserException) {
|
||||
throw CommandExecutionException(this, command, commandName, it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@JvmSynthetic
|
||||
internal suspend fun CommandSender.executeCommandInternal(
|
||||
messages: Any,
|
||||
commandName: String
|
||||
): CommandExecuteResult {
|
||||
val command =
|
||||
CommandManagerImpl.matchCommand(commandName) ?: return CommandExecuteResult.CommandNotFound(commandName)
|
||||
val args = messages.flattenCommandComponents().dropToTypedArray(1)
|
||||
|
||||
if (!command.testPermission(this)) {
|
||||
if (checkPermission && !command.testPermission(this)) {
|
||||
return CommandExecuteResult.PermissionDenied(command, commandName)
|
||||
}
|
||||
|
||||
kotlin.runCatching {
|
||||
command.onCommand(this, args)
|
||||
}.fold(
|
||||
@ -196,3 +172,17 @@ internal suspend fun CommandSender.executeCommandInternal(
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@JvmSynthetic
|
||||
internal suspend fun CommandSender.executeCommandInternal(
|
||||
messages: Any,
|
||||
commandName: String,
|
||||
checkPermission: Boolean
|
||||
): CommandExecuteResult {
|
||||
val command =
|
||||
CommandManagerImpl.matchCommand(commandName) ?: return CommandExecuteResult.CommandNotFound(commandName)
|
||||
val args = messages.flattenCommandComponents().dropToTypedArray(1)
|
||||
|
||||
return executeCommandInternal(command, args, commandName, checkPermission)
|
||||
}
|
||||
|
@ -30,6 +30,7 @@ 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.buildMessageChain
|
||||
import org.junit.jupiter.api.AfterAll
|
||||
import org.junit.jupiter.api.BeforeAll
|
||||
import org.junit.jupiter.api.Test
|
||||
@ -91,7 +92,7 @@ internal class TestCommand {
|
||||
@Test
|
||||
fun testSimpleExecute() = runBlocking {
|
||||
assertEquals(arrayOf("test").contentToString(), withTesting<Array<String>> {
|
||||
TestSimpleCommand.execute(sender, "test")
|
||||
assertSuccess(TestSimpleCommand.execute(sender, "test"))
|
||||
}.contentToString())
|
||||
}
|
||||
|
||||
@ -108,7 +109,7 @@ internal class TestCommand {
|
||||
@Test
|
||||
fun testSimpleArgsSplitting() = runBlocking {
|
||||
assertEquals(arrayOf("test", "ttt", "tt").contentToString(), withTesting<Array<String>> {
|
||||
TestSimpleCommand.execute(sender, PlainText("test ttt tt"))
|
||||
assertSuccess(TestSimpleCommand.execute(sender, PlainText("test ttt tt")))
|
||||
}.contentToString())
|
||||
}
|
||||
|
||||
@ -117,16 +118,20 @@ internal class TestCommand {
|
||||
@Test
|
||||
fun `PlainText and Image args splitting`() = runBlocking {
|
||||
val result = withTesting<Array<Any>> {
|
||||
TestSimpleCommand.execute(sender, "test", image, "tt")
|
||||
assertSuccess(TestSimpleCommand.execute(sender, buildMessageChain {
|
||||
+"test"
|
||||
+image
|
||||
+"tt"
|
||||
}))
|
||||
}
|
||||
assertEquals(arrayOf("test", image, "tt").contentToString(), result.contentToString())
|
||||
assertSame(image, result[1])
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test throw Exception`() = assertTrue {
|
||||
fun `test throw Exception`() {
|
||||
runBlocking {
|
||||
sender.executeCommand("").isFailure()
|
||||
assertTrue(sender.executeCommand("").isFailure())
|
||||
}
|
||||
}
|
||||
|
||||
@ -134,7 +139,7 @@ internal class TestCommand {
|
||||
fun `executing command by string command`() = runBlocking {
|
||||
TestCompositeCommand.register()
|
||||
val result = withTesting<Int> {
|
||||
assertNotNull(sender.executeCommand("/testComposite", "mute 1"))
|
||||
assertSuccess(sender.executeCommand("/testComposite mute 1"))
|
||||
}
|
||||
|
||||
assertEquals(1, result)
|
||||
@ -143,7 +148,7 @@ internal class TestCommand {
|
||||
@Test
|
||||
fun `composite command executing`() = runBlocking {
|
||||
assertEquals(1, withTesting {
|
||||
assertNotNull(TestCompositeCommand.execute(sender, "mute 1"))
|
||||
assertSuccess(TestCompositeCommand.execute(sender, "mute 1"))
|
||||
})
|
||||
}
|
||||
|
||||
@ -209,7 +214,12 @@ internal class TestCommand {
|
||||
|
||||
composite.withRegistration {
|
||||
assertEquals(333, withTesting<MyClass> { execute(sender, "mute 333") }.value)
|
||||
assertEquals(2, withTesting<MyClass> { execute(sender, "mute", image) }.value)
|
||||
assertEquals(2, withTesting<MyClass> {
|
||||
execute(sender, buildMessageChain {
|
||||
+"mute"
|
||||
+image
|
||||
})
|
||||
}.value)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -232,3 +242,7 @@ internal class TestCommand {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal fun assertSuccess(result: CommandExecuteResult) {
|
||||
assertTrue(result.isSuccess(), result.toString())
|
||||
}
|
Loading…
Reference in New Issue
Block a user