This commit is contained in:
Him188 2020-05-26 13:06:45 +08:00
parent c3f7101830
commit f73f064d1a
2 changed files with 119 additions and 43 deletions

View File

@ -12,13 +12,8 @@
package net.mamoe.mirai.console.command package net.mamoe.mirai.console.command
import net.mamoe.mirai.console.command.description.* import net.mamoe.mirai.console.command.description.*
import net.mamoe.mirai.console.command.description.CommandParam
import net.mamoe.mirai.console.command.description.CommandParserContext
import net.mamoe.mirai.console.command.description.EmptyCommandParserContext
import net.mamoe.mirai.console.command.description.plus
import net.mamoe.mirai.message.data.PlainText import net.mamoe.mirai.message.data.PlainText
import net.mamoe.mirai.message.data.SingleMessage import net.mamoe.mirai.message.data.SingleMessage
import java.lang.Exception
import kotlin.reflect.KAnnotatedElement import kotlin.reflect.KAnnotatedElement
import kotlin.reflect.KClass import kotlin.reflect.KClass
import kotlin.reflect.full.* import kotlin.reflect.full.*
@ -29,20 +24,37 @@ import kotlin.reflect.full.*
* @see register 注册这个指令 * @see register 注册这个指令
*/ */
interface Command { interface Command {
/**
* 指令名. 需要至少有一个元素. 所有元素都不能带有空格
*/
val names: Array<out String> val names: Array<out String>
fun getPrimaryName():String = names[0]
val usage: String val usage: String
val description: String val description: String
/**
* 指令权限
*/
val permission: CommandPermission val permission: CommandPermission
/**
* `true` 时表示 [指令前缀][CommandPrefix] 可选
*/
val prefixOptional: Boolean val prefixOptional: Boolean
val owner: CommandOwner val owner: CommandOwner
/**
* @param args 指令参数. 可能是 [SingleMessage] [String]. 且已经以 ' ' 分割.
*/
suspend fun onCommand(sender: CommandSender, args: Array<out Any>) suspend fun onCommand(sender: CommandSender, args: Array<out Any>)
} }
/**
* 主要指令名. [Command.names] 的第一个元素.
*/
val Command.primaryName: String get() = names[0]
/** /**
* 功能最集中的Commend * 功能最集中的Commend
* 支持且只支持有sub的指令 * 支持且只支持有sub的指令
@ -55,20 +67,26 @@ interface Command {
abstract class CompositeCommand @JvmOverloads constructor( abstract class CompositeCommand @JvmOverloads constructor(
override val owner: CommandOwner, override val owner: CommandOwner,
vararg names: String, vararg names: String,
override val description: String = "no description available", description: String = "no description available",
override val permission: CommandPermission = CommandPermission.Default, override val permission: CommandPermission = CommandPermission.Default,
override val prefixOptional: Boolean = false, override val prefixOptional: Boolean = false,
overrideContext: CommandParserContext = EmptyCommandParserContext overrideContext: CommandParserContext = EmptyCommandParserContext
) : Command { ) : Command {
override val description = description.trimIndent()
class IllegalParameterException(message:String): Exception(message)
override val names: Array<out String> = override val names: Array<out String> =
names.map(String::trim).filterNot(String::isEmpty).map(String::toLowerCase).toTypedArray() names.map(String::trim).filterNot(String::isEmpty).map(String::toLowerCase).also { list ->
list.firstOrNull { !it.isValidSubName() }?.let {
error("Name is not valid: $it")
}
}.toTypedArray()
/**
* [CommandArgParser] 的环境
*/
val context: CommandParserContext = CommandParserContext.Builtins + overrideContext val context: CommandParserContext = CommandParserContext.Builtins + overrideContext
override lateinit var usage: String override var usage: String = "<command build failed>" // initialized by subCommand reflection
internal set
/** 指定子指令要求的权限 */ /** 指定子指令要求的权限 */
@Retention(AnnotationRetention.RUNTIME) @Retention(AnnotationRetention.RUNTIME)
@ -108,6 +126,9 @@ abstract class CompositeCommand @JvmOverloads constructor(
) )
} }
class IllegalParameterException internal constructor(message: String) : Exception(message)
internal val subCommands: Array<SubCommandDescriptor> by lazy { internal val subCommands: Array<SubCommandDescriptor> by lazy {
val buildUsage = StringBuilder(this.description).append(": \n") val buildUsage = StringBuilder(this.description).append(": \n")
@ -121,38 +142,38 @@ abstract class CompositeCommand @JvmOverloads constructor(
println() println()
} }
val notStatic = function.findAnnotation<JvmStatic>()==null val notStatic = function.findAnnotation<JvmStatic>() == null
val overridePermission = function.findAnnotation<Permission>()//optional val overridePermission = function.findAnnotation<Permission>()//optional
val subDescription = function.findAnnotation<Description>()?.description?:"no description available" val subDescription = function.findAnnotation<Description>()?.description ?: "no description available"
if((function.returnType.classifier as? KClass<*>)?.isSubclassOf(Boolean::class) != true){ if ((function.returnType.classifier as? KClass<*>)?.isSubclassOf(Boolean::class) != true) {
throw IllegalParameterException("Return Type of SubCommand must be Boolean") throw IllegalParameterException("Return Type of SubCommand must be Boolean")
} }
val parameter = function.parameters.toMutableList() val parameter = function.parameters.toMutableList()
if (parameter.isEmpty()){ if (parameter.isEmpty()) {
throw IllegalParameterException("First parameter (receiver for kotlin) for sub commend " + function.name + " from " + this.getPrimaryName() + " should be <out CommandSender>") throw IllegalParameterException("First parameter (receiver for kotlin) for sub commend " + function.name + " from " + this.primaryName + " should be <out CommandSender>")
} }
if(notStatic){ if (notStatic) {
parameter.removeAt(0) parameter.removeAt(0)
} }
(parameter.removeAt(0)).let {receiver -> (parameter.removeAt(0)).let { receiver ->
if ( if (
receiver.isVararg || receiver.isVararg ||
((receiver.type.classifier as? KClass<*>).also { print(it) } ((receiver.type.classifier as? KClass<*>).also { print(it) }
?.isSubclassOf(CommandSender::class) != true) ?.isSubclassOf(CommandSender::class) != true)
) { ) {
throw IllegalParameterException("First parameter (receiver for kotlin) for sub commend " + function.name + " from " + this.getPrimaryName() + " should be <out CommandSender>") throw IllegalParameterException("First parameter (receiver for kotlin) for sub commend " + function.name + " from " + this.primaryName + " should be <out CommandSender>")
} }
} }
val commandName = function.findAnnotation<SubCommand>()!!.name.map { val commandName = function.findAnnotation<SubCommand>()!!.name.map {
if(!it.isValidSubName()){ if (!it.isValidSubName()) {
error("SubName $it is not valid") error("SubName $it is not valid")
} }
it it
@ -160,20 +181,22 @@ abstract class CompositeCommand @JvmOverloads constructor(
//map parameter //map parameter
val parms = parameter.map { val parms = parameter.map {
buildUsage.append("/" + getPrimaryName() + " ") buildUsage.append("/$primaryName ")
if(it.isVararg){ if (it.isVararg) {
throw IllegalParameterException("parameter for sub commend " + function.name + " from " + this.getPrimaryName() + " should not be var arg") throw IllegalParameterException("parameter for sub commend " + function.name + " from " + this.primaryName + " should not be var arg")
} }
if(it.isOptional){ if (it.isOptional) {
throw IllegalParameterException("parameter for sub commend " + function.name + " from " + this.getPrimaryName() + " should not be var optional") throw IllegalParameterException("parameter for sub commend " + function.name + " from " + this.primaryName + " should not be var optional")
} }
val argName = it.findAnnotation<Name>()?.name?:it.name?:"unknown" val argName = it.findAnnotation<Name>()?.name ?: it.name ?: "unknown"
buildUsage.append("<").append(argName).append("> ").append(" ") buildUsage.append("<").append(argName).append("> ").append(" ")
CommandParam( CommandParam(
argName, argName,
(it.type.classifier as? KClass<*>)?: throw IllegalParameterException("unsolved type reference from param " + it.name + " in " + function.name + " from " + this.getPrimaryName())) (it.type.classifier as? KClass<*>)
?: throw IllegalParameterException("unsolved type reference from param " + it.name + " in " + function.name + " from " + this.primaryName)
)
}.toTypedArray() }.toTypedArray()
buildUsage.append(subDescription).append("\n") buildUsage.append(subDescription).append("\n")
@ -184,9 +207,9 @@ abstract class CompositeCommand @JvmOverloads constructor(
subDescription, subDescription,
overridePermission?.permission?.getInstance() ?: permission, overridePermission?.permission?.getInstance() ?: permission,
onCommand = block { sender: CommandSender, args: Array<out Any> -> onCommand = block { sender: CommandSender, args: Array<out Any> ->
if(notStatic) { if (notStatic) {
function.callSuspend(this,sender, *args) as Boolean function.callSuspend(this, sender, *args) as Boolean
}else{ } else {
function.callSuspend(sender, *args) as Boolean function.callSuspend(sender, *args) as Boolean
} }
} }
@ -317,7 +340,7 @@ internal fun Any.flattenCommandComponents(): ArrayList<Any> {
internal inline fun <reified T : Annotation> KAnnotatedElement.hasAnnotation(): Boolean = internal inline fun <reified T : Annotation> KAnnotatedElement.hasAnnotation(): Boolean =
findAnnotation<T>() != null findAnnotation<T>() != null
internal inline fun <T:Any> KClass<out T>.getInstance():T { internal inline fun <T : Any> KClass<out T>.getInstance(): T {
return this.objectInstance ?: this.createInstance() return this.objectInstance ?: this.createInstance()
} }

View File

@ -8,34 +8,67 @@
*/ */
@file:Suppress("NOTHING_TO_INLINE", "unused") @file:Suppress("NOTHING_TO_INLINE", "unused")
@file:JvmName("CommandManager") @file:JvmName("CommandManagerKt")
package net.mamoe.mirai.console.command package net.mamoe.mirai.console.command
import kotlinx.atomicfu.locks.withLock import kotlinx.atomicfu.locks.withLock
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.runBlocking
import net.mamoe.mirai.console.plugin.Plugin import net.mamoe.mirai.console.plugin.Plugin
import net.mamoe.mirai.message.data.Message import net.mamoe.mirai.message.data.Message
import net.mamoe.mirai.message.data.MessageChain import net.mamoe.mirai.message.data.MessageChain
import net.mamoe.mirai.message.data.SingleMessage import net.mamoe.mirai.message.data.SingleMessage
import net.mamoe.mirai.utils.MiraiInternalAPI
/**
* 指令的所有者.
* @see PluginCommandOwner
*/
sealed class CommandOwner sealed class CommandOwner
@MiraiInternalAPI
object TestCommandOwner : CommandOwner() object TestCommandOwner : CommandOwner()
abstract class PluginCommandOwner(val plugin: Plugin) : CommandOwner() /**
* 插件指令所有者. 插件只能通过 [PluginCommandOwner] 管理指令.
*/
abstract class PluginCommandOwner(val plugin: Plugin) : CommandOwner() {
init {
if (plugin is CoroutineScope) { // JVM Plugin
plugin.coroutineContext[Job]?.invokeOnCompletion {
this.unregisterAllCommands()
}
}
}
}
// 由前端实现 /**
* 代表控制台所有者. 所有的 mirai-console 内建的指令都属于 [ConsoleCommandOwner].
*
* 由前端实现
*/
internal abstract class ConsoleCommandOwner : CommandOwner() internal abstract class ConsoleCommandOwner : CommandOwner()
/** /**
* 获取已经注册了的指令列表 * 获取已经注册了的属于这个 [CommandOwner] 的指令列表.
* @see JCommandManager.getRegisteredCommands Java 方法
*/ */
val CommandOwner.registeredCommands: List<Command> get() = InternalCommandManager.registeredCommands.filter { it.owner == this } val CommandOwner.registeredCommands: List<Command> get() = InternalCommandManager.registeredCommands.filter { it.owner == this }
/**
* 指令前缀, '/'
* @see JCommandManager.getCommandPrefix Java 方法
*/
@get:JvmName("getCommandPrefix") @get:JvmName("getCommandPrefix")
val CommandPrefix: String val CommandPrefix: String
get() = InternalCommandManager.COMMAND_PREFIX get() = InternalCommandManager.COMMAND_PREFIX
/**
* 取消注册所有属于 [this] 的指令
* @see JCommandManager.unregisterAllCommands Java 方法
*/
fun CommandOwner.unregisterAllCommands() { fun CommandOwner.unregisterAllCommands() {
for (registeredCommand in registeredCommands) { for (registeredCommand in registeredCommands) {
registeredCommand.unregister() registeredCommand.unregister()
@ -43,10 +76,25 @@ fun CommandOwner.unregisterAllCommands() {
} }
/** /**
* 注册一个指令. 若此指令已经注册或有已经注册的指令与 [SubCommandDescriptor] 重名, 返回 `false` * 注册一个指令.
*
* @param override 是否覆盖重名指令.
*
* 若原有指令 P, [Command.names] 'a', 'b', 'c'.
* 新指令 Q, [Command.names] 'b', 将会覆盖原指令 A 注册的 'b'.
*
* 即注册完成后, 'a' 'c' 将会解析到指令 P, 'b' 会解析到指令 Q.
*
* @return
* 若已有重名指令, [override] `false`, 返回 `false`;
* 若已有重名指令, [override] `true`, 覆盖原有指令并返回 `true`.
*
*/ */
fun Command.register(): Boolean = InternalCommandManager.modifyLock.withLock { @JvmOverloads
if (findDuplicate() != null) return false fun Command.register(override: Boolean = false): Boolean = InternalCommandManager.modifyLock.withLock {
if (!override) {
if (findDuplicate() != null) return false
}
InternalCommandManager.registeredCommands.add(this@register) InternalCommandManager.registeredCommands.add(this@register)
if (this.prefixOptional) { if (this.prefixOptional) {
for (name in this.names) { for (name in this.names) {
@ -54,6 +102,7 @@ fun Command.register(): Boolean = InternalCommandManager.modifyLock.withLock {
} }
} else { } else {
for (name in this.names) { for (name in this.names) {
InternalCommandManager.optionalPrefixCommandMap.remove(name) // ensure resolution consistency
InternalCommandManager.requiredPrefixCommandMap[name] = this InternalCommandManager.requiredPrefixCommandMap[name] = this
} }
} }
@ -61,13 +110,13 @@ fun Command.register(): Boolean = InternalCommandManager.modifyLock.withLock {
} }
/** /**
* 查找是否有重名的指令. 返回重名的指令. * 查找并返回重名的指令. 返回重名指令.
*/ */
fun Command.findDuplicate(): Command? = fun Command.findDuplicate(): Command? =
InternalCommandManager.registeredCommands.firstOrNull { it.names intersects this.names } InternalCommandManager.registeredCommands.firstOrNull { it.names intersects this.names }
/** /**
* 取消注册这个指令. 若指令未注册, 返回 `false` * 取消注册这个指令. 若指令未注册, 返回 `false`.
*/ */
fun Command.unregister(): Boolean = InternalCommandManager.modifyLock.withLock { fun Command.unregister(): Boolean = InternalCommandManager.modifyLock.withLock {
InternalCommandManager.registeredCommands.remove(this) InternalCommandManager.registeredCommands.remove(this)
@ -78,6 +127,8 @@ fun Command.unregister(): Boolean = InternalCommandManager.modifyLock.withLock {
/** /**
* 解析并执行一个指令 * 解析并执行一个指令
* *
* Java 调用方式: `<static> CommandManager.executeCommand(Command)`
*
* @param messages 接受 [String] [Message], 其他对象将会被 [Any.toString] * @param messages 接受 [String] [Message], 其他对象将会被 [Any.toString]
* @return 是否成功解析到指令. 返回 `false` 代表无任何指令匹配 * @return 是否成功解析到指令. 返回 `false` 代表无任何指令匹配
*/ */
@ -88,6 +139,7 @@ suspend fun CommandSender.executeCommand(vararg messages: Any): Boolean {
messages[0].let { if (it is SingleMessage) it.toString() else it.toString().substringBefore(' ') }) messages[0].let { if (it is SingleMessage) it.toString() else it.toString().substringBefore(' ') })
} }
@JvmSynthetic
internal inline fun <reified T> List<T>.dropToTypedArray(n: Int): Array<T> = Array(size - n) { this[n + it] } internal inline fun <reified T> List<T>.dropToTypedArray(n: Int): Array<T> = Array(size - n) { this[n + it] }
/** /**
@ -99,6 +151,7 @@ suspend fun CommandSender.executeCommand(message: MessageChain): Boolean {
return executeCommandInternal(message, message[0].toString()) return executeCommandInternal(message, message[0].toString())
} }
@JvmSynthetic
internal suspend inline fun CommandSender.executeCommandInternal( internal suspend inline fun CommandSender.executeCommandInternal(
messages: Any, messages: Any,
commandName: String commandName: String