mirror of
https://github.com/mamoe/mirai.git
synced 2025-01-10 18:40:15 +08:00
Add docs
This commit is contained in:
parent
c3f7101830
commit
f73f064d1a
@ -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()
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user