mirror of
https://github.com/mamoe/mirai.git
synced 2025-03-23 13:50:12 +08:00
Redesign CommandDescriptor
This commit is contained in:
parent
c5d681a024
commit
ef83281e97
@ -11,12 +11,8 @@
|
||||
|
||||
package net.mamoe.mirai.console.command
|
||||
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import net.mamoe.mirai.console.MiraiConsole
|
||||
import net.mamoe.mirai.console.plugins.PluginBase
|
||||
import net.mamoe.mirai.console.command.CommandDescriptor.SubCommandDescriptor
|
||||
import net.mamoe.mirai.message.data.Message
|
||||
import java.lang.reflect.Member
|
||||
import kotlin.reflect.KProperty
|
||||
|
||||
internal const val FOR_BINARY_COMPATIBILITY = "for binary compatibility"
|
||||
@ -42,14 +38,18 @@ interface Command {
|
||||
*/
|
||||
}
|
||||
|
||||
internal fun Command.matchChild(args: List<Any>): SubCommandDescriptor {
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 指令实际参数列表. 参数顺序与 [Command.descriptor] 的 [CommandDescriptor.params] 相同.
|
||||
* 解析完成的指令实际参数列表. 参数顺序与 [Command.descriptor] 的 [CommandDescriptor.params] 相同.
|
||||
*/
|
||||
class CommandArgs private constructor(
|
||||
@JvmField
|
||||
internal val values: List<Any>,
|
||||
private val fromCommand: Command
|
||||
private val fromCommand: SubCommandDescriptor
|
||||
) : List<Any> by values {
|
||||
/**
|
||||
* 获取第一个类型为 [R] 的参数
|
||||
@ -71,7 +71,7 @@ class CommandArgs private constructor(
|
||||
* @throws NoSuchElementException 找不到这个名称的参数时抛出
|
||||
*/
|
||||
operator fun get(name: String?): Any {
|
||||
val index = fromCommand.descriptor.params.indexOfFirst { it.name == name }
|
||||
val index = fromCommand.params.indexOfFirst { it.name == name }
|
||||
if (index == -1) {
|
||||
throw NoSuchElementException("Cannot find argument named $name")
|
||||
}
|
||||
@ -93,12 +93,12 @@ class CommandArgs private constructor(
|
||||
inline operator fun <reified R : Any> getValue(thisRef: Any?, property: KProperty<*>): R = getReified()
|
||||
|
||||
companion object {
|
||||
fun parseFrom(command: Command, sender: CommandSender, rawArgs: List<Any>): CommandArgs {
|
||||
val params = command.descriptor.params
|
||||
fun parseFrom(command: SubCommandDescriptor, sender: CommandSender, rawArgs: List<Any>): CommandArgs {
|
||||
val params = command.params
|
||||
|
||||
require(rawArgs.size >= params.size) { "No enough rawArgs: required ${params.size}, found only ${rawArgs.size}" }
|
||||
|
||||
command.descriptor.params.asSequence().zip(rawArgs.asSequence()).map { (commandParam, any) ->
|
||||
command.params.asSequence().zip(rawArgs.asSequence()).map { (commandParam, any) ->
|
||||
command.parserFor(commandParam)?.parse(any, sender)
|
||||
?: error("Could not find a parser for param named ${commandParam.name}, typed ${commandParam.type.qualifiedName}")
|
||||
}.toList().let { bakedArgs ->
|
||||
@ -106,44 +106,4 @@ class CommandArgs private constructor(
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inline val Command.fullName get() = descriptor.fullName
|
||||
inline val Command.usage get() = descriptor.usage
|
||||
inline val Command.params get() = descriptor.params
|
||||
inline val Command.description get() = descriptor.description
|
||||
inline val Command.context get() = descriptor.context
|
||||
inline val Command.aliases get() = descriptor.aliases
|
||||
inline val Command.permission get() = descriptor.permission
|
||||
inline val Command.allNames get() = descriptor.allNames
|
||||
|
||||
abstract class PluginCommand(
|
||||
final override val owner: PluginBase,
|
||||
descriptor: CommandDescriptor
|
||||
) : AbstractCommand(descriptor)
|
||||
|
||||
internal abstract class ConsoleCommand(
|
||||
descriptor: CommandDescriptor
|
||||
) : AbstractCommand(descriptor) {
|
||||
final override val owner: MiraiConsole get() = MiraiConsole
|
||||
}
|
||||
|
||||
sealed class AbstractCommand(
|
||||
final override val descriptor: CommandDescriptor
|
||||
) : Command
|
||||
|
||||
|
||||
/**
|
||||
* For Java
|
||||
*/
|
||||
@Suppress("unused")
|
||||
abstract class BlockingCommand(
|
||||
owner: PluginBase,
|
||||
descriptor: CommandDescriptor
|
||||
) : PluginCommand(owner, descriptor) {
|
||||
final override suspend fun CommandSender.onCommand(args: CommandArgs): Boolean {
|
||||
return withContext(Dispatchers.IO) { onCommandBlocking(this@onCommand, args) }
|
||||
}
|
||||
|
||||
abstract fun onCommandBlocking(sender: CommandSender, args: CommandArgs): Boolean
|
||||
}
|
@ -13,7 +13,7 @@ import net.mamoe.mirai.message.data.SingleMessage
|
||||
*/
|
||||
class CommandDescriptor(
|
||||
/**
|
||||
* 子指令列表
|
||||
* 子指令列表. 第一个元素为默认值.
|
||||
*/
|
||||
val subCommands: List<SubCommandDescriptor>,
|
||||
/**
|
||||
@ -25,8 +25,8 @@ class CommandDescriptor(
|
||||
) {
|
||||
/** 子指令描述 */
|
||||
inner class SubCommandDescriptor(
|
||||
/** 为空字符串时代表默认 */
|
||||
val name: String,
|
||||
/** 子指令名, 如 "/mute group add" 中的 "group add". 当表示默认指令时为父指令名. */
|
||||
val subName: String,
|
||||
/** 用法说明 */
|
||||
val usage: String,
|
||||
/** 指令参数列表, 有顺序. */
|
||||
@ -40,8 +40,20 @@ class CommandDescriptor(
|
||||
* @see CommandPermission.or 要求其中一个权限
|
||||
* @see CommandPermission.and 同时要求两个权限
|
||||
*/
|
||||
val permission: CommandPermission = CommandPermission.Default
|
||||
)
|
||||
val permission: CommandPermission = CommandPermission.Default,
|
||||
/** 指令执行器 */
|
||||
val onCommand: suspend (sender: CommandSender, args: CommandArgs) -> Boolean
|
||||
) {
|
||||
init {
|
||||
require(!(subName.startsWith(' ') || subName.endsWith(' '))) { "subName mustn't start or end with a caret" }
|
||||
require(subName.isValidSubName()) { "subName mustn't contain any of $ILLEGAL_SUB_NAME_CHARS" }
|
||||
}
|
||||
|
||||
@JvmField
|
||||
internal val bakedSubNames: Array<Array<String>> =
|
||||
listOf(subName, *aliases).map { it.bakeSubName() }.toTypedArray()
|
||||
internal inline val parent: CommandDescriptor get() = this@CommandDescriptor
|
||||
}
|
||||
|
||||
/**
|
||||
* 指令参数解析器环境.
|
||||
@ -49,6 +61,13 @@ class CommandDescriptor(
|
||||
val context: CommandParserContext = CommandParserContext.Builtins + overrideContext
|
||||
}
|
||||
|
||||
internal val CommandDescriptor.base: CommandDescriptor.SubCommandDescriptor get() = subCommands[0]
|
||||
|
||||
|
||||
internal val ILLEGAL_SUB_NAME_CHARS = "\\/!@#$%^&*()_+-={}[];':\",.<>?`~".toCharArray()
|
||||
internal fun String.isValidSubName(): Boolean = ILLEGAL_SUB_NAME_CHARS.none { it in this }
|
||||
internal fun String.bakeSubName(): Array<String> = split(' ').filterNot { it.isBlank() }.toTypedArray()
|
||||
|
||||
|
||||
/**
|
||||
* 检查指令参数数量是否足够, 类型是否匹配.
|
||||
@ -213,15 +232,16 @@ inline class ParamBlock internal constructor(@PublishedApi internal val list: Mu
|
||||
/// internal
|
||||
|
||||
|
||||
internal fun Any.flattenCommandComponents(): Sequence<String> = when (this) {
|
||||
is Array<*> -> this.asSequence().flatMap {
|
||||
it?.flattenCommandComponents() ?: throw java.lang.IllegalArgumentException("unexpected null value")
|
||||
internal fun Any.flattenCommandComponents(): List<String> {
|
||||
val list = ArrayList<String>()
|
||||
when (this) {
|
||||
is String -> list.addAll(split(' ').filterNot { it.isBlank() })
|
||||
is PlainText -> list.addAll(content.flattenCommandComponents())
|
||||
is SingleMessage -> list.add(this.toString())
|
||||
is MessageChain -> this.asSequence().forEach { list.addAll(it.flattenCommandComponents()) }
|
||||
else -> throw IllegalArgumentException("Illegal component: $this")
|
||||
}
|
||||
is String -> splitToSequence(' ').filterNot { it.isBlank() }
|
||||
is PlainText -> content.flattenCommandComponents()
|
||||
is SingleMessage -> sequenceOf(this.toString())
|
||||
is MessageChain -> this.asSequence().flatMap { it.flattenCommandComponents() }
|
||||
else -> throw IllegalArgumentException("Illegal component: $this")
|
||||
return list
|
||||
}
|
||||
|
||||
internal fun Any.checkFullName(errorHint: String): Array<String> {
|
||||
|
@ -7,7 +7,7 @@ import kotlinx.atomicfu.locks.withLock
|
||||
import net.mamoe.mirai.console.plugins.PluginBase
|
||||
import net.mamoe.mirai.message.data.Message
|
||||
import net.mamoe.mirai.message.data.MessageChain
|
||||
import java.util.*
|
||||
import net.mamoe.mirai.utils.MiraiExperimentalAPI
|
||||
import java.util.concurrent.locks.ReentrantLock
|
||||
|
||||
sealed class CommandOwner
|
||||
@ -30,16 +30,13 @@ fun CommandOwner.unregisterAllCommands() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册一个指令. 若此指令已经注册或有已经注册的指令与 [allNames] 重名, 返回 `false`
|
||||
* 注册一个指令. 若此指令已经注册或有已经注册的指令与 [SubCommandDescriptor] 重名, 返回 `false`
|
||||
*/
|
||||
fun Command.register(): Boolean = InternalCommandManager.modifyLock.withLock {
|
||||
if (findDuplicate() != null) {
|
||||
return false
|
||||
}
|
||||
InternalCommandManager.registeredCommands.add(this@register)
|
||||
for (name in this.allNames) {
|
||||
InternalCommandManager.nameToCommandMap[name] = this@register
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@ -47,25 +44,24 @@ fun Command.register(): Boolean = InternalCommandManager.modifyLock.withLock {
|
||||
* 查找是否有重名的指令. 返回重名的指令.
|
||||
*/
|
||||
fun Command.findDuplicate(): Command? {
|
||||
return InternalCommandManager.nameToCommandMap.entries.firstOrNull { (names, _) ->
|
||||
this.allNames.any { it.contentEquals(names) }
|
||||
}?.value
|
||||
return InternalCommandManager.registeredCommands.firstOrNull {
|
||||
it.descriptor.base.bakedSubNames intersects this.descriptor.base.bakedSubNames
|
||||
}
|
||||
}
|
||||
|
||||
private infix fun <T> Array<T>.intersects(other: Array<T>): Boolean {
|
||||
val max = this.size.coerceAtMost(other.size)
|
||||
for (i in 0 until max) {
|
||||
if (this[i] == other[i]) return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* 取消注册这个指令. 若指令未注册, 返回 `false`
|
||||
*/
|
||||
fun Command.unregister(): Boolean = InternalCommandManager.modifyLock.withLock {
|
||||
if (!InternalCommandManager.registeredCommands.contains(this)) {
|
||||
return false
|
||||
}
|
||||
InternalCommandManager.registeredCommands.remove(this)
|
||||
for (name in this.allNames) {
|
||||
InternalCommandManager.nameToCommandMap.entries.removeIf {
|
||||
it.key.contentEquals(this.fullName)
|
||||
}
|
||||
}
|
||||
return true
|
||||
return InternalCommandManager.registeredCommands.remove(this)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -73,8 +69,11 @@ fun Command.unregister(): Boolean = InternalCommandManager.modifyLock.withLock {
|
||||
* @param args 接受 [String] 或 [Message]
|
||||
* @return 是否成功解析到指令. 返回 `false` 代表无任何指令匹配
|
||||
*/
|
||||
@MiraiExperimentalAPI
|
||||
suspend fun CommandSender.executeCommand(vararg args: Any): Boolean {
|
||||
return args.flattenCommandComponents().toList().executeCommand(this)
|
||||
val command = InternalCommandManager.matchCommand(args[0].toString()) ?: return false
|
||||
|
||||
return args.flattenCommandComponents().executeCommand(this)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -82,15 +81,15 @@ suspend fun CommandSender.executeCommand(vararg args: Any): Boolean {
|
||||
* @return 是否成功解析到指令. 返回 `false` 代表无任何指令匹配
|
||||
*/
|
||||
suspend fun MessageChain.executeAsCommand(sender: CommandSender): Boolean {
|
||||
return this.flattenCommandComponents().toList().executeCommand(sender)
|
||||
return this.flattenCommandComponents().executeCommand(sender)
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查指令参数并直接执行一个指令.
|
||||
*/
|
||||
suspend inline fun CommandSender.execute(command: Command, args: CommandArgs): Boolean = with(command) {
|
||||
checkArgs(args)
|
||||
return this@execute.onCommand(args)
|
||||
suspend inline fun CommandSender.execute(command: CommandDescriptor.SubCommandDescriptor, args: CommandArgs): Boolean {
|
||||
command.checkArgs(args)
|
||||
return command.onCommand(this@execute, args)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -99,19 +98,19 @@ suspend inline fun CommandSender.execute(command: Command, args: CommandArgs): B
|
||||
suspend inline fun Command.execute(sender: CommandSender, args: CommandArgs): Boolean = sender.execute(this, args)
|
||||
|
||||
/**
|
||||
* 解析并执行一个指令.
|
||||
* @param args 接受 [String] 或 [Message]
|
||||
* 核心执行指令
|
||||
*/
|
||||
suspend fun CommandSender.execute(vararg args: Any): Boolean = args.toList().executeCommand(this)
|
||||
|
||||
|
||||
internal suspend fun List<Any>.executeCommand(sender: CommandSender): Boolean {
|
||||
val command = InternalCommandManager.matchCommand(this) ?: return false
|
||||
return command.run {
|
||||
sender.onCommand(
|
||||
internal suspend fun List<Any>.executeCommand(origin: String, sender: CommandSender): Boolean {
|
||||
if (this.isEmpty()) return false
|
||||
val command = InternalCommandManager.matchCommand(origin) ?: return false
|
||||
TODO()
|
||||
/*
|
||||
command.descriptor.subCommands.forEach { sub ->
|
||||
}.run {
|
||||
sender.onDefault(
|
||||
CommandArgs.parseFrom(command, sender, this@executeCommand.drop(command.fullName.size))
|
||||
)
|
||||
}
|
||||
}*/
|
||||
}
|
||||
|
||||
internal infix fun Array<String>.matchesBeginning(list: List<Any>): Boolean {
|
||||
@ -125,18 +124,16 @@ internal object InternalCommandManager {
|
||||
@JvmField
|
||||
internal val registeredCommands: MutableList<Command> = mutableListOf()
|
||||
|
||||
@JvmField
|
||||
internal val nameToCommandMap: TreeMap<Array<String>, Command> = TreeMap(Comparator.comparingInt { it.size })
|
||||
|
||||
@JvmField
|
||||
internal val modifyLock = ReentrantLock()
|
||||
|
||||
internal var _commandPrefix: String = "/"
|
||||
|
||||
internal fun matchCommand(splitted: List<Any>): Command? {
|
||||
nameToCommandMap.entries.forEach {
|
||||
if (it.key matchesBeginning splitted) return it.value
|
||||
internal fun matchCommand(name: String): Command? {
|
||||
return registeredCommands.firstOrNull { command ->
|
||||
command.descriptor.base.bakedSubNames.any {
|
||||
name.startsWith(it[0]) && (name.length <= it[0].length || name[it[0].length] == ' ') // 判断跟随空格
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
@ -41,4 +41,7 @@ fun <T : Any> CommandParam<T>.parserFrom(command: Command): CommandArgParser<T>?
|
||||
fun <T : Any> CommandParam<T>.parserFrom(descriptor: CommandDescriptor): CommandArgParser<T>? =
|
||||
descriptor.parserFor(this)
|
||||
|
||||
fun <T : Any> CommandParam<T>.parserFrom(descriptor: CommandDescriptor.SubCommandDescriptor): CommandArgParser<T>? =
|
||||
descriptor.parserFor(this)
|
||||
|
||||
fun <T : Any> CommandParam<T>.parserFrom(context: CommandParserContext): CommandArgParser<T>? = context.parserFor(this)
|
@ -61,10 +61,13 @@ fun <T : Any> CommandParserContext.parserFor(param: CommandParam<T>): CommandArg
|
||||
param.overrideParser ?: this[param.type]
|
||||
|
||||
fun <T : Any> CommandDescriptor.parserFor(param: CommandParam<T>): CommandArgParser<T>? =
|
||||
param.overrideParser ?: this.context.parserFor(param)
|
||||
this.context.parserFor(param)
|
||||
|
||||
fun <T : Any> CommandDescriptor.SubCommandDescriptor.parserFor(param: CommandParam<T>): CommandArgParser<T>? =
|
||||
this.parent.parserFor(param)
|
||||
|
||||
fun <T : Any> Command.parserFor(param: CommandParam<T>): CommandArgParser<T>? =
|
||||
param.overrideParser ?: this.descriptor.parserFor(param)
|
||||
this.descriptor.parserFor(param)
|
||||
|
||||
/**
|
||||
* 合并两个 [CommandParserContext], [replacer] 将会替换 [this] 中重复的 parser.
|
||||
|
Loading…
Reference in New Issue
Block a user