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
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.SingleMessage
import java.lang.Exception
import kotlin.reflect.KAnnotatedElement
import kotlin.reflect.KClass
import kotlin.reflect.full.*
@ -29,20 +24,37 @@ import kotlin.reflect.full.*
* @see register 注册这个指令
*/
interface Command {
/**
* 指令名. 需要至少有一个元素. 所有元素都不能带有空格
*/
val names: Array<out String>
fun getPrimaryName():String = names[0]
val usage: String
val description: String
/**
* 指令权限
*/
val permission: CommandPermission
/**
* `true` 时表示 [指令前缀][CommandPrefix] 可选
*/
val prefixOptional: Boolean
val owner: CommandOwner
/**
* @param args 指令参数. 可能是 [SingleMessage] [String]. 且已经以 ' ' 分割.
*/
suspend fun onCommand(sender: CommandSender, args: Array<out Any>)
}
/**
* 主要指令名. [Command.names] 的第一个元素.
*/
val Command.primaryName: String get() = names[0]
/**
* 功能最集中的Commend
* 支持且只支持有sub的指令
@ -55,20 +67,26 @@ interface Command {
abstract class CompositeCommand @JvmOverloads constructor(
override val owner: CommandOwner,
vararg names: String,
override val description: String = "no description available",
description: String = "no description available",
override val permission: CommandPermission = CommandPermission.Default,
override val prefixOptional: Boolean = false,
overrideContext: CommandParserContext = EmptyCommandParserContext
) : Command {
class IllegalParameterException(message:String): Exception(message)
override val description = description.trimIndent()
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
override lateinit var usage: String
override var usage: String = "<command build failed>" // initialized by subCommand reflection
internal set
/** 指定子指令要求的权限 */
@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 {
val buildUsage = StringBuilder(this.description).append(": \n")
@ -121,38 +142,38 @@ abstract class CompositeCommand @JvmOverloads constructor(
println()
}
val notStatic = function.findAnnotation<JvmStatic>()==null
val notStatic = function.findAnnotation<JvmStatic>() == null
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")
}
val parameter = function.parameters.toMutableList()
if (parameter.isEmpty()){
throw IllegalParameterException("First parameter (receiver for kotlin) for sub commend " + function.name + " from " + this.getPrimaryName() + " should be <out CommandSender>")
if (parameter.isEmpty()) {
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)).let {receiver ->
(parameter.removeAt(0)).let { receiver ->
if (
receiver.isVararg ||
((receiver.type.classifier as? KClass<*>).also { print(it) }
?.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 {
if(!it.isValidSubName()){
if (!it.isValidSubName()) {
error("SubName $it is not valid")
}
it
@ -160,20 +181,22 @@ abstract class CompositeCommand @JvmOverloads constructor(
//map parameter
val parms = parameter.map {
buildUsage.append("/" + getPrimaryName() + " ")
buildUsage.append("/$primaryName ")
if(it.isVararg){
throw IllegalParameterException("parameter for sub commend " + function.name + " from " + this.getPrimaryName() + " should not be var arg")
if (it.isVararg) {
throw IllegalParameterException("parameter for sub commend " + function.name + " from " + this.primaryName + " should not be var arg")
}
if(it.isOptional){
throw IllegalParameterException("parameter for sub commend " + function.name + " from " + this.getPrimaryName() + " should not be var optional")
if (it.isOptional) {
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(" ")
CommandParam(
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()
buildUsage.append(subDescription).append("\n")
@ -184,9 +207,9 @@ abstract class CompositeCommand @JvmOverloads constructor(
subDescription,
overridePermission?.permission?.getInstance() ?: permission,
onCommand = block { sender: CommandSender, args: Array<out Any> ->
if(notStatic) {
function.callSuspend(this,sender, *args) as Boolean
}else{
if (notStatic) {
function.callSuspend(this, sender, *args) as Boolean
} else {
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 =
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()
}

View File

@ -8,34 +8,67 @@
*/
@file:Suppress("NOTHING_TO_INLINE", "unused")
@file:JvmName("CommandManager")
@file:JvmName("CommandManagerKt")
package net.mamoe.mirai.console.command
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.message.data.Message
import net.mamoe.mirai.message.data.MessageChain
import net.mamoe.mirai.message.data.SingleMessage
import net.mamoe.mirai.utils.MiraiInternalAPI
/**
* 指令的所有者.
* @see PluginCommandOwner
*/
sealed class CommandOwner
@MiraiInternalAPI
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()
/**
* 获取已经注册了的指令列表
* 获取已经注册了的属于这个 [CommandOwner] 的指令列表.
* @see JCommandManager.getRegisteredCommands Java 方法
*/
val CommandOwner.registeredCommands: List<Command> get() = InternalCommandManager.registeredCommands.filter { it.owner == this }
/**
* 指令前缀, '/'
* @see JCommandManager.getCommandPrefix Java 方法
*/
@get:JvmName("getCommandPrefix")
val CommandPrefix: String
get() = InternalCommandManager.COMMAND_PREFIX
/**
* 取消注册所有属于 [this] 的指令
* @see JCommandManager.unregisterAllCommands Java 方法
*/
fun CommandOwner.unregisterAllCommands() {
for (registeredCommand in registeredCommands) {
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 {
if (findDuplicate() != null) return false
@JvmOverloads
fun Command.register(override: Boolean = false): Boolean = InternalCommandManager.modifyLock.withLock {
if (!override) {
if (findDuplicate() != null) return false
}
InternalCommandManager.registeredCommands.add(this@register)
if (this.prefixOptional) {
for (name in this.names) {
@ -54,6 +102,7 @@ fun Command.register(): Boolean = InternalCommandManager.modifyLock.withLock {
}
} else {
for (name in this.names) {
InternalCommandManager.optionalPrefixCommandMap.remove(name) // ensure resolution consistency
InternalCommandManager.requiredPrefixCommandMap[name] = this
}
}
@ -61,13 +110,13 @@ fun Command.register(): Boolean = InternalCommandManager.modifyLock.withLock {
}
/**
* 查找是否有重名的指令. 返回重名的指令.
* 查找并返回重名的指令. 返回重名指令.
*/
fun Command.findDuplicate(): Command? =
InternalCommandManager.registeredCommands.firstOrNull { it.names intersects this.names }
/**
* 取消注册这个指令. 若指令未注册, 返回 `false`
* 取消注册这个指令. 若指令未注册, 返回 `false`.
*/
fun Command.unregister(): Boolean = InternalCommandManager.modifyLock.withLock {
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]
* @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(' ') })
}
@JvmSynthetic
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())
}
@JvmSynthetic
internal suspend inline fun CommandSender.executeCommandInternal(
messages: Any,
commandName: String