Redesign CommandDescriptor

This commit is contained in:
Him188 2020-05-15 00:57:50 +08:00
parent c5d681a024
commit ef83281e97
5 changed files with 89 additions and 106 deletions

View File

@ -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
}

View File

@ -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> {

View File

@ -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
}
}

View File

@ -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)

View File

@ -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.