mirror of
https://github.com/mamoe/mirai.git
synced 2025-03-13 06:30:13 +08:00
Rework CompositeCommand, SimpleCommand, RawCommand;
Add ConsoleExperimentalAPI annotation; Add CommandParserContextAware;
This commit is contained in:
parent
32e77ca420
commit
e25a818942
@ -40,6 +40,7 @@ kotlin {
|
||||
|
||||
useExperimentalAnnotation("net.mamoe.mirai.utils.MiraiInternalAPI")
|
||||
useExperimentalAnnotation("net.mamoe.mirai.utils.MiraiExperimentalAPI")
|
||||
useExperimentalAnnotation("net.mamoe.mirai.console.utils.ConsoleExperimentalAPI")
|
||||
useExperimentalAnnotation("kotlin.ExperimentalUnsignedTypes")
|
||||
useExperimentalAnnotation("kotlin.experimental.ExperimentalTypeInference")
|
||||
useExperimentalAnnotation("kotlin.contracts.ExperimentalContracts")
|
||||
|
@ -22,8 +22,8 @@ import net.mamoe.mirai.console.plugin.PluginLoader
|
||||
import net.mamoe.mirai.console.plugin.center.CuiPluginCenter
|
||||
import net.mamoe.mirai.console.plugin.center.PluginCenter
|
||||
import net.mamoe.mirai.console.plugin.jvm.JarPluginLoader
|
||||
import net.mamoe.mirai.console.utils.ConsoleExperimentalAPI
|
||||
import net.mamoe.mirai.utils.DefaultLogger
|
||||
import net.mamoe.mirai.utils.MiraiExperimentalAPI
|
||||
import net.mamoe.mirai.utils.MiraiLogger
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.io.File
|
||||
@ -64,7 +64,7 @@ interface MiraiConsole : CoroutineScope {
|
||||
|
||||
val pluginCenter: PluginCenter
|
||||
|
||||
@MiraiExperimentalAPI
|
||||
@ConsoleExperimentalAPI
|
||||
fun newLogger(identity: String?): MiraiLogger
|
||||
|
||||
companion object INSTANCE : MiraiConsole by MiraiConsoleInternal
|
||||
@ -102,7 +102,7 @@ internal object MiraiConsoleInternal : CoroutineScope, IMiraiConsole, MiraiConso
|
||||
override val rootDir: File get() = instance.rootDir
|
||||
override val frontEnd: MiraiConsoleFrontEnd get() = instance.frontEnd
|
||||
|
||||
@MiraiExperimentalAPI
|
||||
@ConsoleExperimentalAPI
|
||||
override val mainLogger: MiraiLogger
|
||||
get() = instance.mainLogger
|
||||
override val coroutineContext: CoroutineContext get() = instance.coroutineContext
|
||||
@ -114,7 +114,7 @@ internal object MiraiConsoleInternal : CoroutineScope, IMiraiConsole, MiraiConso
|
||||
DefaultLogger = { identity -> this.newLogger(identity) }
|
||||
}
|
||||
|
||||
@MiraiExperimentalAPI
|
||||
@ConsoleExperimentalAPI
|
||||
override fun newLogger(identity: String?): MiraiLogger = frontEnd.loggerFor(identity)
|
||||
|
||||
internal fun initialize() {
|
||||
|
@ -9,11 +9,11 @@
|
||||
|
||||
package net.mamoe.mirai.console.command
|
||||
|
||||
import net.mamoe.mirai.utils.MiraiExperimentalAPI
|
||||
import net.mamoe.mirai.console.utils.ConsoleExperimentalAPI
|
||||
|
||||
interface BuiltInCommand : Command
|
||||
|
||||
@MiraiExperimentalAPI
|
||||
@ConsoleExperimentalAPI
|
||||
object BuiltInCommands
|
||||
|
||||
/*
|
||||
|
@ -11,6 +11,7 @@
|
||||
|
||||
package net.mamoe.mirai.console.command
|
||||
|
||||
import net.mamoe.mirai.console.command.internal.isValidSubName
|
||||
import net.mamoe.mirai.message.data.SingleMessage
|
||||
|
||||
/**
|
||||
@ -45,10 +46,28 @@ interface Command {
|
||||
|
||||
/**
|
||||
* @param args 指令参数. 可能是 [SingleMessage] 或 [String]. 且已经以 ' ' 分割.
|
||||
*/
|
||||
*/ // TODO: 2020/6/28 Java-friendly bridges
|
||||
suspend fun CommandSender.onCommand(args: Array<out Any>)
|
||||
}
|
||||
|
||||
/**
|
||||
* [Command] 的基础实现
|
||||
*/
|
||||
abstract class AbstractCommand @JvmOverloads constructor(
|
||||
final override val owner: CommandOwner,
|
||||
vararg names: String,
|
||||
description: String = "<no description available>",
|
||||
final override val permission: CommandPermission = CommandPermission.Default,
|
||||
final override val prefixOptional: Boolean = false
|
||||
) : Command {
|
||||
final override val description = description.trimIndent()
|
||||
final override val names: Array<out String> =
|
||||
names.map(String::trim).filterNot(String::isEmpty).map(String::toLowerCase).also { list ->
|
||||
list.firstOrNull { !it.isValidSubName() }?.let { error("Invalid name: $it") }
|
||||
}.toTypedArray()
|
||||
|
||||
}
|
||||
|
||||
suspend inline fun Command.onCommand(sender: CommandSender, args: Array<out Any>) = sender.run { onCommand(args) }
|
||||
|
||||
/**
|
||||
|
@ -7,70 +7,66 @@
|
||||
* https://github.com/mamoe/mirai/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
@file:Suppress("EXPOSED_SUPER_CLASS", "NOTHING_TO_INLINE")
|
||||
@file:Suppress(
|
||||
"EXPOSED_SUPER_CLASS",
|
||||
"NOTHING_TO_INLINE",
|
||||
"unused",
|
||||
"WRONG_MODIFIER_TARGET",
|
||||
"WRONG_MODIFIER_CONTAINING_DECLARATION"
|
||||
)
|
||||
|
||||
package net.mamoe.mirai.console.command
|
||||
|
||||
import net.mamoe.mirai.console.command.description.CommandArgParser
|
||||
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.console.command.internal.CompositeCommandImpl
|
||||
import net.mamoe.mirai.console.command.internal.isValidSubName
|
||||
import net.mamoe.mirai.console.command.description.*
|
||||
import net.mamoe.mirai.console.command.internal.AbstractReflectionCommand
|
||||
import net.mamoe.mirai.console.command.internal.CompositeCommandSubCommandAnnotationResolver
|
||||
import net.mamoe.mirai.console.utils.ConsoleExperimentalAPI
|
||||
import kotlin.annotation.AnnotationRetention.RUNTIME
|
||||
import kotlin.annotation.AnnotationTarget.FUNCTION
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
|
||||
/**
|
||||
* 功能最集中的 Commend
|
||||
* 只支持有sub的指令
|
||||
* 例:
|
||||
* /mute add
|
||||
* /mute remove
|
||||
* /mute addandremove (sub is case insensitive, lowercase are recommend)
|
||||
* /mute add and remove('add and remove' consider as a sub)
|
||||
* 复合指令.
|
||||
*/
|
||||
@ConsoleExperimentalAPI
|
||||
abstract class CompositeCommand @JvmOverloads constructor(
|
||||
final override val owner: CommandOwner,
|
||||
owner: CommandOwner,
|
||||
vararg names: String,
|
||||
description: String = "no description available",
|
||||
final override val permission: CommandPermission = CommandPermission.Default,
|
||||
final override val prefixOptional: Boolean = false,
|
||||
permission: CommandPermission = CommandPermission.Default,
|
||||
prefixOptional: Boolean = false,
|
||||
overrideContext: CommandParserContext = EmptyCommandParserContext
|
||||
) : Command, CompositeCommandImpl() {
|
||||
final override val description = description.trimIndent()
|
||||
final override val names: Array<out String> =
|
||||
names.map(String::trim).filterNot(String::isEmpty).map(String::toLowerCase).also { list ->
|
||||
list.firstOrNull { !it.isValidSubName() }?.let { error("Name is not valid: $it") }
|
||||
}.toTypedArray()
|
||||
|
||||
) : Command, AbstractReflectionCommand(owner, names, description, permission, prefixOptional),
|
||||
CommandParserContextAware {
|
||||
/**
|
||||
* [CommandArgParser] 的环境
|
||||
*/
|
||||
val context: CommandParserContext = CommandParserContext.Builtins + overrideContext
|
||||
final override val context: CommandParserContext = CommandParserContext.Builtins + overrideContext
|
||||
|
||||
final override val usage: String get() = super.usage
|
||||
/**
|
||||
* 标记一个函数为子指令, 当 [value] 为空时使用函数名.
|
||||
* @param value 子指令名
|
||||
*/
|
||||
@Retention(RUNTIME)
|
||||
@Target(FUNCTION)
|
||||
protected annotation class SubCommand(vararg val value: String)
|
||||
|
||||
/** 指定子指令要求的权限 */
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
@Target(AnnotationTarget.FUNCTION)
|
||||
annotation class Permission(val permission: KClass<out CommandPermission>)
|
||||
|
||||
/** 标记一个函数为子指令, 当 [names] 为空时使用函数名. */
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
@Target(AnnotationTarget.FUNCTION)
|
||||
annotation class SubCommand(vararg val names: String)
|
||||
@Retention(RUNTIME)
|
||||
@Target(FUNCTION)
|
||||
protected annotation class Permission(val value: KClass<out CommandPermission>)
|
||||
|
||||
/** 指令描述 */
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
@Target(AnnotationTarget.FUNCTION)
|
||||
annotation class Description(val description: String)
|
||||
@Retention(RUNTIME)
|
||||
@Target(FUNCTION)
|
||||
protected annotation class Description(val value: String)
|
||||
|
||||
/** 参数名, 将参与构成 [usage] */
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
@Retention(RUNTIME)
|
||||
@Target(AnnotationTarget.VALUE_PARAMETER)
|
||||
annotation class Name(val name: String)
|
||||
protected annotation class Name(val value: String)
|
||||
|
||||
public override suspend fun CommandSender.onDefault(rawArgs: Array<out Any>) {
|
||||
override suspend fun CommandSender.onDefault(rawArgs: Array<out Any>) {
|
||||
sendMessage(usage)
|
||||
}
|
||||
|
||||
@ -79,4 +75,7 @@ abstract class CompositeCommand @JvmOverloads constructor(
|
||||
defaultSubCommand.onCommand(this, args)
|
||||
}
|
||||
}
|
||||
|
||||
final override val subCommandAnnotationResolver: SubCommandAnnotationResolver
|
||||
get() = CompositeCommandSubCommandAnnotationResolver
|
||||
}
|
||||
|
@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright 2020 Mamoe Technologies and contributors.
|
||||
*
|
||||
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
|
||||
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
|
||||
*
|
||||
* https://github.com/mamoe/mirai/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
@file:Suppress(
|
||||
"EXPOSED_SUPER_CLASS",
|
||||
"NOTHING_TO_INLINE",
|
||||
"unused",
|
||||
"WRONG_MODIFIER_TARGET",
|
||||
"WRONG_MODIFIER_CONTAINING_DECLARATION"
|
||||
)
|
||||
|
||||
package net.mamoe.mirai.console.command
|
||||
|
||||
import net.mamoe.mirai.console.command.description.CommandParserContext
|
||||
import net.mamoe.mirai.console.command.description.CommandParserContextAware
|
||||
import net.mamoe.mirai.console.command.description.EmptyCommandParserContext
|
||||
import net.mamoe.mirai.console.command.internal.AbstractReflectionCommand
|
||||
import net.mamoe.mirai.console.command.internal.SimpleCommandSubCommandAnnotationResolver
|
||||
|
||||
abstract class SimpleCommand @JvmOverloads constructor(
|
||||
owner: CommandOwner,
|
||||
vararg names: String,
|
||||
description: String = "no description available",
|
||||
permission: CommandPermission = CommandPermission.Default,
|
||||
prefixOptional: Boolean = false,
|
||||
overrideContext: CommandParserContext = EmptyCommandParserContext
|
||||
) : Command, AbstractReflectionCommand(owner, names, description, permission, prefixOptional),
|
||||
CommandParserContextAware {
|
||||
|
||||
/**
|
||||
* 标注指令处理器
|
||||
*/
|
||||
protected annotation class Handler
|
||||
|
||||
final override val context: CommandParserContext
|
||||
get() = TODO("Not yet implemented")
|
||||
|
||||
final override suspend fun CommandSender.onCommand(args: Array<out Any>) {
|
||||
|
||||
}
|
||||
|
||||
final override val subCommandAnnotationResolver: SubCommandAnnotationResolver
|
||||
get() = SimpleCommandSubCommandAnnotationResolver
|
||||
}
|
@ -14,10 +14,10 @@ package net.mamoe.mirai.console.command.description
|
||||
import net.mamoe.mirai.Bot
|
||||
import net.mamoe.mirai.console.command.CommandSender
|
||||
import net.mamoe.mirai.console.command.description.CommandParserContext.ParserPair
|
||||
import net.mamoe.mirai.console.utils.ConsoleExperimentalAPI
|
||||
import net.mamoe.mirai.contact.Friend
|
||||
import net.mamoe.mirai.contact.Group
|
||||
import net.mamoe.mirai.contact.Member
|
||||
import net.mamoe.mirai.utils.MiraiExperimentalAPI
|
||||
import kotlin.internal.LowPriorityInOverloadResolution
|
||||
import kotlin.reflect.KClass
|
||||
import kotlin.reflect.full.isSubclassOf
|
||||
@ -57,6 +57,16 @@ interface CommandParserContext {
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 拥有 [CommandParserContext] 的类
|
||||
*/
|
||||
interface CommandParserContextAware {
|
||||
/**
|
||||
* [CommandArgParser] 的环境
|
||||
*/
|
||||
val context: CommandParserContext
|
||||
}
|
||||
|
||||
object EmptyCommandParserContext : CommandParserContext by CustomCommandParserContext(listOf())
|
||||
|
||||
/**
|
||||
@ -151,7 +161,7 @@ class CommandParserContextBuilder : MutableList<ParserPair<*>> by mutableListOf(
|
||||
/**
|
||||
* 添加一个指令解析器
|
||||
*/
|
||||
@MiraiExperimentalAPI
|
||||
@ConsoleExperimentalAPI
|
||||
@JvmSynthetic
|
||||
inline infix fun <reified T : Any> add(
|
||||
crossinline parser: CommandArgParser<*>.(s: String) -> T
|
||||
@ -160,7 +170,7 @@ class CommandParserContextBuilder : MutableList<ParserPair<*>> by mutableListOf(
|
||||
/**
|
||||
* 添加一个指令解析器
|
||||
*/
|
||||
@MiraiExperimentalAPI
|
||||
@ConsoleExperimentalAPI
|
||||
@JvmSynthetic
|
||||
@LowPriorityInOverloadResolution
|
||||
inline infix fun <reified T : Any> add(
|
||||
|
@ -7,11 +7,10 @@
|
||||
* https://github.com/mamoe/mirai/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
@file:Suppress("unused")
|
||||
@file:Suppress("unused", "INVISIBLE_REFERENCE", "INVISIBLE_MEMBER")
|
||||
|
||||
package net.mamoe.mirai.console.command.description
|
||||
|
||||
import net.mamoe.mirai.console.command.Command
|
||||
import net.mamoe.mirai.console.command.CompositeCommand
|
||||
import java.lang.reflect.Parameter
|
||||
import kotlin.reflect.KClass
|
||||
@ -19,7 +18,8 @@ import kotlin.reflect.KClass
|
||||
internal fun Parameter.toCommandParam(): CommandParam<*> {
|
||||
val name = getAnnotation(CompositeCommand.Name::class.java)
|
||||
return CommandParam(
|
||||
name?.name ?: this.name ?: throw IllegalArgumentException("Cannot construct CommandParam from a unnamed param"),
|
||||
name?.value ?: this.name
|
||||
?: throw IllegalArgumentException("Cannot construct CommandParam from a unnamed param"),
|
||||
this.type.kotlin
|
||||
)
|
||||
}
|
||||
@ -34,7 +34,7 @@ internal data class CommandParam<T : Any>(
|
||||
*/
|
||||
val name: String,
|
||||
/**
|
||||
* 参数类型. 将从 [CommandDescriptor.context] 中寻找 [CommandArgParser] 解析.
|
||||
* 参数类型. 将从 [CompositeCommand.context] 中寻找 [CommandArgParser] 解析.
|
||||
*/
|
||||
val type: KClass<T> // exact type
|
||||
) {
|
||||
@ -51,8 +51,6 @@ internal data class CommandParam<T : Any>(
|
||||
* 覆盖的 [CommandArgParser].
|
||||
*
|
||||
* 如果非 `null`, 将不会从 [CommandParserContext] 寻找 [CommandArgParser]
|
||||
*
|
||||
* @see Command.parserFor
|
||||
*/
|
||||
val overrideParser: CommandArgParser<T>? get() = _overrideParser
|
||||
}
|
||||
|
@ -1,267 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020 Mamoe Technologies and contributors.
|
||||
*
|
||||
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
|
||||
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
|
||||
*
|
||||
* https://github.com/mamoe/mirai/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
@file:Suppress("NOTHING_TO_INLINE", "MemberVisibilityCanBePrivate")
|
||||
|
||||
package net.mamoe.mirai.console.command.internal
|
||||
|
||||
import net.mamoe.mirai.console.command.*
|
||||
import net.mamoe.mirai.console.command.description.CommandParam
|
||||
import net.mamoe.mirai.message.data.PlainText
|
||||
import net.mamoe.mirai.message.data.SingleMessage
|
||||
import kotlin.reflect.KAnnotatedElement
|
||||
import kotlin.reflect.KClass
|
||||
import kotlin.reflect.full.*
|
||||
|
||||
internal abstract class CompositeCommandImpl : Command {
|
||||
@JvmField
|
||||
@Suppress("PropertyName")
|
||||
internal var _usage: String = "<command build failed>"
|
||||
|
||||
override val usage: String // initialized by subCommand reflection
|
||||
get() = _usage
|
||||
|
||||
internal abstract suspend fun CommandSender.onDefault(rawArgs: Array<out Any>)
|
||||
|
||||
internal val defaultSubCommand: DefaultSubCommandDescriptor by lazy {
|
||||
DefaultSubCommandDescriptor(
|
||||
"",
|
||||
CommandPermission.Default,
|
||||
onCommand = block2 { sender: CommandSender, args: Array<out Any> ->
|
||||
sender.onDefault(args)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
internal val subCommands: Array<SubCommandDescriptor> by lazy {
|
||||
this@CompositeCommandImpl as CompositeCommand
|
||||
|
||||
val buildUsage = StringBuilder(this.description).append(": \n")
|
||||
|
||||
this::class.declaredFunctions.filter { it.hasAnnotation<CompositeCommand.SubCommand>() }
|
||||
.also { subCommandFunctions ->
|
||||
// overloading not yet supported
|
||||
val overloadFunction = subCommandFunctions.groupBy { it.name }.entries.firstOrNull { it.value.size > 1 }
|
||||
if (overloadFunction != null) {
|
||||
error("Sub command overloading is not yet supported. (at ${this::class.qualifiedNameOrTip}.${overloadFunction.key})")
|
||||
}
|
||||
}.map { function ->
|
||||
val notStatic = !function.hasAnnotation<JvmStatic>()
|
||||
val overridePermission = function.findAnnotation<CompositeCommand.Permission>()//optional
|
||||
val subDescription =
|
||||
function.findAnnotation<CompositeCommand.Description>()?.description ?: "<no description available>"
|
||||
|
||||
fun KClass<*>.isValidReturnType(): Boolean {
|
||||
return when (this) {
|
||||
Boolean::class, Void::class, Unit::class, Nothing::class -> true
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
check((function.returnType.classifier as? KClass<*>)?.isValidReturnType() == true) {
|
||||
error("Return type of sub command ${function.name} must be one of the following: kotlin.Boolean, java.lang.Boolean, kotlin.Unit (including implicit), kotlin.Nothing, boolean or void (at ${this::class.qualifiedNameOrTip}.${function.name})")
|
||||
}
|
||||
|
||||
check(!function.returnType.isMarkedNullable) {
|
||||
error("Return type of sub command ${function.name} must not be marked nullable in Kotlin, and must be marked with @NotNull or @NonNull explicitly in Java. (at ${this::class.qualifiedNameOrTip}.${function.name})")
|
||||
}
|
||||
|
||||
val parameters = function.parameters.toMutableList()
|
||||
|
||||
if (notStatic) parameters.removeAt(0) // instance
|
||||
|
||||
var hasSenderParam = false
|
||||
check(parameters.isNotEmpty()) {
|
||||
"Parameters of sub command ${function.name} must not be empty. (Must have CommandSender as its receiver or first parameter or absent, followed by naturally typed params) (at ${this::class.qualifiedNameOrTip}.${function.name})"
|
||||
}
|
||||
|
||||
parameters.forEach { param ->
|
||||
check(!param.isVararg) {
|
||||
"Parameter $param must not be vararg. (at ${this::class.qualifiedNameOrTip}.${function.name}.$param)"
|
||||
}
|
||||
}
|
||||
|
||||
(parameters.first()).let { receiver ->
|
||||
if ((receiver.type.classifier as? KClass<*>)?.isSubclassOf(CommandSender::class) == true) {
|
||||
hasSenderParam = true
|
||||
parameters.removeAt(0)
|
||||
}
|
||||
}
|
||||
|
||||
val commandName =
|
||||
function.findAnnotation<CompositeCommand.SubCommand>()!!.names
|
||||
.let { namesFromAnnotation ->
|
||||
if (namesFromAnnotation.isNotEmpty()) {
|
||||
namesFromAnnotation
|
||||
} else arrayOf(function.name)
|
||||
}.also { names ->
|
||||
names.forEach {
|
||||
check(it.isValidSubName()) {
|
||||
"Name of sub command ${function.name} is invalid"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//map parameter
|
||||
val params = parameters.map { param ->
|
||||
buildUsage.append("/$primaryName ")
|
||||
|
||||
if (param.isOptional) error("optional parameters are not yet supported. (at ${this::class.qualifiedNameOrTip}.${function.name}.$param)")
|
||||
|
||||
val argName = param.findAnnotation<CompositeCommand.Name>()?.name ?: param.name ?: "unknown"
|
||||
buildUsage.append("<").append(argName).append("> ").append(" ")
|
||||
CommandParam(
|
||||
argName,
|
||||
(param.type.classifier as? KClass<*>)
|
||||
?: throw IllegalArgumentException("unsolved type reference from param " + param.name + ". (at ${this::class.qualifiedNameOrTip}.${function.name}.$param)")
|
||||
)
|
||||
}.toTypedArray()
|
||||
|
||||
buildUsage.append(subDescription).append("\n")
|
||||
|
||||
SubCommandDescriptor(
|
||||
commandName,
|
||||
params,
|
||||
subDescription,
|
||||
overridePermission?.permission?.getInstance() ?: permission,
|
||||
onCommand = block { sender: CommandSender, args: Array<out Any> ->
|
||||
val result = if (notStatic) {
|
||||
if (hasSenderParam) {
|
||||
function.isSuspend
|
||||
function.callSuspend(this, sender, *args)
|
||||
} else function.callSuspend(this, *args)
|
||||
} else {
|
||||
if (hasSenderParam) {
|
||||
function.callSuspend(sender, *args)
|
||||
} else function.callSuspend(*args)
|
||||
}
|
||||
|
||||
checkNotNull(result) { "sub command return value is null (at ${this::class.qualifiedName}.${function.name})" }
|
||||
|
||||
result as? Boolean ?: true // Unit, void is considered as true.
|
||||
}
|
||||
)
|
||||
}.toTypedArray().also {
|
||||
_usage = buildUsage.toString()
|
||||
}
|
||||
}
|
||||
|
||||
private fun block(block: suspend (CommandSender, Array<out Any>) -> Boolean): suspend (CommandSender, Array<out Any>) -> Boolean {
|
||||
return block
|
||||
}
|
||||
|
||||
private fun block2(block: suspend (CommandSender, Array<out Any>) -> Unit): suspend (CommandSender, Array<out Any>) -> Unit {
|
||||
return block
|
||||
}
|
||||
|
||||
internal val bakedCommandNameToSubDescriptorArray: Map<Array<String>, SubCommandDescriptor> by lazy {
|
||||
kotlin.run {
|
||||
val map = LinkedHashMap<Array<String>, SubCommandDescriptor>(subCommands.size * 2)
|
||||
for (descriptor in subCommands) {
|
||||
for (name in descriptor.bakedSubNames) {
|
||||
map[name] = descriptor
|
||||
}
|
||||
}
|
||||
map.toSortedMap(Comparator { o1, o2 -> o1!!.contentHashCode() - o2!!.contentHashCode() })
|
||||
}
|
||||
}
|
||||
|
||||
internal class DefaultSubCommandDescriptor(
|
||||
val description: String,
|
||||
val permission: CommandPermission,
|
||||
val onCommand: suspend (sender: CommandSender, rawArgs: Array<out Any>) -> Unit
|
||||
)
|
||||
|
||||
internal inner class SubCommandDescriptor(
|
||||
val names: Array<out String>,
|
||||
val params: Array<CommandParam<*>>,
|
||||
val description: String,
|
||||
val permission: CommandPermission,
|
||||
val onCommand: suspend (sender: CommandSender, parsedArgs: Array<out Any>) -> Boolean
|
||||
) {
|
||||
internal suspend inline fun parseAndExecute(
|
||||
sender: CommandSender,
|
||||
argsWithSubCommandNameNotRemoved: Array<out Any>
|
||||
) {
|
||||
if (!onCommand(sender, parseArgs(sender, argsWithSubCommandNameNotRemoved, names.size))) {
|
||||
sender.sendMessage(usage)
|
||||
}
|
||||
}
|
||||
|
||||
@JvmField
|
||||
internal val bakedSubNames: Array<Array<String>> = names.map { it.bakeSubName() }.toTypedArray()
|
||||
private fun parseArgs(sender: CommandSender, rawArgs: Array<out Any>, offset: Int): Array<out Any> {
|
||||
this@CompositeCommandImpl as CompositeCommand
|
||||
require(rawArgs.size >= offset + this.params.size) { "No enough args. Required ${params.size}, but given ${rawArgs.size - offset}" }
|
||||
|
||||
return Array(this.params.size) { index ->
|
||||
val param = params[index]
|
||||
val rawArg = rawArgs[offset + index]
|
||||
when (rawArg) {
|
||||
is String -> context[param.type]?.parse(rawArg, sender)
|
||||
is SingleMessage -> context[param.type]?.parse(rawArg, sender)
|
||||
else -> throw IllegalArgumentException("Illegal argument type: ${rawArg::class.qualifiedName}")
|
||||
} ?: error("Cannot find a parser for $rawArg")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param rawArgs 元素类型必须为 [SingleMessage] 或 [String], 且已经经过扁平化处理. 否则抛出异常 [IllegalArgumentException]
|
||||
*/
|
||||
internal fun matchSubCommand(rawArgs: Array<out Any>): SubCommandDescriptor? {
|
||||
val maxCount = rawArgs.size - 1
|
||||
var cur = 0
|
||||
bakedCommandNameToSubDescriptorArray.forEach { (name, descriptor) ->
|
||||
if (name.size != cur) {
|
||||
if (cur++ == maxCount) return null
|
||||
}
|
||||
if (name.contentEqualsOffset(rawArgs, length = cur)) {
|
||||
return descriptor
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
internal fun <T> Array<T>.contentEqualsOffset(other: Array<out Any>, length: Int): Boolean {
|
||||
repeat(length) { index ->
|
||||
if (other[index].toString() != this[index]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
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()
|
||||
|
||||
internal fun Any.flattenCommandComponents(): ArrayList<Any> {
|
||||
val list = ArrayList<Any>()
|
||||
when (this) {
|
||||
is PlainText -> this.content.splitToSequence(' ').filterNot { it.isBlank() }
|
||||
.forEach { list.add(it) }
|
||||
is CharSequence -> this.splitToSequence(' ').filterNot { it.isBlank() }.forEach { list.add(it) }
|
||||
is SingleMessage -> list.add(this)
|
||||
is Array<*> -> this.forEach { if (it != null) list.addAll(it.flattenCommandComponents()) }
|
||||
is Iterable<*> -> this.forEach { if (it != null) list.addAll(it.flattenCommandComponents()) }
|
||||
else -> list.add(this.toString())
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
internal inline fun <reified T : Annotation> KAnnotatedElement.hasAnnotation(): Boolean =
|
||||
findAnnotation<T>() != null
|
||||
|
||||
internal inline fun <T : Any> KClass<out T>.getInstance(): T {
|
||||
return this.objectInstance ?: this.createInstance()
|
||||
}
|
||||
|
||||
internal val KClass<*>.qualifiedNameOrTip: String get() = this.qualifiedName ?: "<anonymous class>"
|
@ -0,0 +1,311 @@
|
||||
/*
|
||||
* Copyright 2020 Mamoe Technologies and contributors.
|
||||
*
|
||||
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
|
||||
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
|
||||
*
|
||||
* https://github.com/mamoe/mirai/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
@file:Suppress("NOTHING_TO_INLINE", "MemberVisibilityCanBePrivate", "INVISIBLE_REFERENCE", "INVISIBLE_MEMBER")
|
||||
|
||||
package net.mamoe.mirai.console.command.internal
|
||||
|
||||
import net.mamoe.mirai.console.command.*
|
||||
import net.mamoe.mirai.console.command.description.CommandParam
|
||||
import net.mamoe.mirai.console.command.description.CommandParserContext
|
||||
import net.mamoe.mirai.console.command.description.CommandParserContextAware
|
||||
import net.mamoe.mirai.message.data.PlainText
|
||||
import net.mamoe.mirai.message.data.SingleMessage
|
||||
import kotlin.reflect.KAnnotatedElement
|
||||
import kotlin.reflect.KClass
|
||||
import kotlin.reflect.KFunction
|
||||
import kotlin.reflect.full.*
|
||||
|
||||
internal object CompositeCommandSubCommandAnnotationResolver :
|
||||
AbstractReflectionCommand.SubCommandAnnotationResolver {
|
||||
override fun hasAnnotation(function: KFunction<*>) = function.hasAnnotation<CompositeCommand.SubCommand>()
|
||||
override fun getSubCommandNames(function: KFunction<*>): Array<out String> =
|
||||
function.findAnnotation<CompositeCommand.SubCommand>()!!.value
|
||||
}
|
||||
|
||||
internal object SimpleCommandSubCommandAnnotationResolver :
|
||||
AbstractReflectionCommand.SubCommandAnnotationResolver {
|
||||
override fun hasAnnotation(function: KFunction<*>) = function.hasAnnotation<SimpleCommand.Handler>()
|
||||
override fun getSubCommandNames(function: KFunction<*>): Array<out String> = arrayOf("")
|
||||
}
|
||||
|
||||
internal abstract class AbstractReflectionCommand @JvmOverloads constructor(
|
||||
owner: CommandOwner,
|
||||
names: Array<out String>,
|
||||
description: String = "<no description available>",
|
||||
permission: CommandPermission = CommandPermission.Default,
|
||||
prefixOptional: Boolean = false
|
||||
) : Command, AbstractCommand(
|
||||
owner,
|
||||
names = *names,
|
||||
description = description,
|
||||
permission = permission,
|
||||
prefixOptional = prefixOptional
|
||||
), CommandParserContextAware {
|
||||
internal abstract val subCommandAnnotationResolver: SubCommandAnnotationResolver
|
||||
|
||||
@JvmField
|
||||
@Suppress("PropertyName")
|
||||
internal var _usage: String = "<command build failed>"
|
||||
|
||||
final override val usage: String // initialized by subCommand reflection
|
||||
get() = _usage
|
||||
|
||||
abstract suspend fun CommandSender.onDefault(rawArgs: Array<out Any>)
|
||||
|
||||
internal val defaultSubCommand: DefaultSubCommandDescriptor by lazy {
|
||||
DefaultSubCommandDescriptor(
|
||||
"",
|
||||
CommandPermission.Default,
|
||||
onCommand = block2 { sender: CommandSender, args: Array<out Any> ->
|
||||
sender.onDefault(args)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
interface SubCommandAnnotationResolver {
|
||||
fun hasAnnotation(function: KFunction<*>): Boolean
|
||||
fun getSubCommandNames(function: KFunction<*>): Array<out String>
|
||||
}
|
||||
|
||||
internal val subCommands: Array<SubCommandDescriptor> by lazy {
|
||||
this::class.declaredFunctions.filter { subCommandAnnotationResolver.hasAnnotation(it) }
|
||||
.also { subCommandFunctions ->
|
||||
// overloading not yet supported
|
||||
val overloadFunction = subCommandFunctions.groupBy { it.name }.entries.firstOrNull { it.value.size > 1 }
|
||||
if (overloadFunction != null) {
|
||||
error("Sub command overloading is not yet supported. (at ${this::class.qualifiedNameOrTip}.${overloadFunction.key})")
|
||||
}
|
||||
}.map { function ->
|
||||
createSubCommand(function, context)
|
||||
}.toTypedArray().also {
|
||||
_usage = it.firstOrNull()?.usage ?: description
|
||||
}
|
||||
}
|
||||
|
||||
internal val bakedCommandNameToSubDescriptorArray: Map<Array<String>, SubCommandDescriptor> by lazy {
|
||||
kotlin.run {
|
||||
val map = LinkedHashMap<Array<String>, SubCommandDescriptor>(subCommands.size * 2)
|
||||
for (descriptor in subCommands) {
|
||||
for (name in descriptor.bakedSubNames) {
|
||||
map[name] = descriptor
|
||||
}
|
||||
}
|
||||
map.toSortedMap(Comparator { o1, o2 -> o1!!.contentHashCode() - o2!!.contentHashCode() })
|
||||
}
|
||||
}
|
||||
|
||||
internal class DefaultSubCommandDescriptor(
|
||||
val description: String,
|
||||
val permission: CommandPermission,
|
||||
val onCommand: suspend (sender: CommandSender, rawArgs: Array<out Any>) -> Unit
|
||||
)
|
||||
|
||||
internal class SubCommandDescriptor(
|
||||
val names: Array<out String>,
|
||||
val params: Array<CommandParam<*>>,
|
||||
val description: String,
|
||||
val permission: CommandPermission,
|
||||
val onCommand: suspend (sender: CommandSender, parsedArgs: Array<out Any>) -> Boolean,
|
||||
val context: CommandParserContext,
|
||||
val usage: String
|
||||
) {
|
||||
internal suspend inline fun parseAndExecute(
|
||||
sender: CommandSender,
|
||||
argsWithSubCommandNameNotRemoved: Array<out Any>
|
||||
) {
|
||||
if (!onCommand(sender, parseArgs(sender, argsWithSubCommandNameNotRemoved, names.size))) {
|
||||
sender.sendMessage(usage)
|
||||
}
|
||||
}
|
||||
|
||||
@JvmField
|
||||
internal val bakedSubNames: Array<Array<String>> = names.map { it.bakeSubName() }.toTypedArray()
|
||||
private fun parseArgs(sender: CommandSender, rawArgs: Array<out Any>, offset: Int): Array<out Any> {
|
||||
require(rawArgs.size >= offset + this.params.size) { "No enough args. Required ${params.size}, but given ${rawArgs.size - offset}" }
|
||||
|
||||
return Array(this.params.size) { index ->
|
||||
val param = params[index]
|
||||
val rawArg = rawArgs[offset + index]
|
||||
when (rawArg) {
|
||||
is String -> context[param.type]?.parse(rawArg, sender)
|
||||
is SingleMessage -> context[param.type]?.parse(rawArg, sender)
|
||||
else -> throw IllegalArgumentException("Illegal argument type: ${rawArg::class.qualifiedName}")
|
||||
} ?: error("Cannot find a parser for $rawArg")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param rawArgs 元素类型必须为 [SingleMessage] 或 [String], 且已经经过扁平化处理. 否则抛出异常 [IllegalArgumentException]
|
||||
*/
|
||||
internal fun matchSubCommand(rawArgs: Array<out Any>): SubCommandDescriptor? {
|
||||
val maxCount = rawArgs.size - 1
|
||||
var cur = 0
|
||||
bakedCommandNameToSubDescriptorArray.forEach { (name, descriptor) ->
|
||||
if (name.size != cur) {
|
||||
if (cur++ == maxCount) return null
|
||||
}
|
||||
if (name.contentEqualsOffset(rawArgs, length = cur)) {
|
||||
return descriptor
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
internal fun <T> Array<T>.contentEqualsOffset(other: Array<out Any>, length: Int): Boolean {
|
||||
repeat(length) { index ->
|
||||
if (other[index].toString() != this[index]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
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()
|
||||
|
||||
internal fun Any.flattenCommandComponents(): ArrayList<Any> {
|
||||
val list = ArrayList<Any>()
|
||||
when (this) {
|
||||
is PlainText -> this.content.splitToSequence(' ').filterNot { it.isBlank() }
|
||||
.forEach { list.add(it) }
|
||||
is CharSequence -> this.splitToSequence(' ').filterNot { it.isBlank() }.forEach { list.add(it) }
|
||||
is SingleMessage -> list.add(this)
|
||||
is Array<*> -> this.forEach { if (it != null) list.addAll(it.flattenCommandComponents()) }
|
||||
is Iterable<*> -> this.forEach { if (it != null) list.addAll(it.flattenCommandComponents()) }
|
||||
else -> list.add(this.toString())
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
internal inline fun <reified T : Annotation> KAnnotatedElement.hasAnnotation(): Boolean =
|
||||
findAnnotation<T>() != null
|
||||
|
||||
internal inline fun <T : Any> KClass<out T>.getInstance(): T {
|
||||
return this.objectInstance ?: this.createInstance()
|
||||
}
|
||||
|
||||
internal val KClass<*>.qualifiedNameOrTip: String get() = this.qualifiedName ?: "<anonymous class>"
|
||||
|
||||
internal fun AbstractReflectionCommand.createSubCommand(
|
||||
function: KFunction<*>,
|
||||
context: CommandParserContext
|
||||
): AbstractReflectionCommand.SubCommandDescriptor {
|
||||
val notStatic = !function.hasAnnotation<JvmStatic>()
|
||||
val overridePermission = function.findAnnotation<CompositeCommand.Permission>()//optional
|
||||
val subDescription =
|
||||
function.findAnnotation<CompositeCommand.Description>()?.value ?: "<no description available>"
|
||||
|
||||
fun KClass<*>.isValidReturnType(): Boolean {
|
||||
return when (this) {
|
||||
Boolean::class, Void::class, Unit::class, Nothing::class -> true
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
check((function.returnType.classifier as? KClass<*>)?.isValidReturnType() == true) {
|
||||
error("Return type of sub command ${function.name} must be one of the following: kotlin.Boolean, java.lang.Boolean, kotlin.Unit (including implicit), kotlin.Nothing, boolean or void (at ${this::class.qualifiedNameOrTip}.${function.name})")
|
||||
}
|
||||
|
||||
check(!function.returnType.isMarkedNullable) {
|
||||
error("Return type of sub command ${function.name} must not be marked nullable in Kotlin, and must be marked with @NotNull or @NonNull explicitly in Java. (at ${this::class.qualifiedNameOrTip}.${function.name})")
|
||||
}
|
||||
|
||||
val parameters = function.parameters.toMutableList()
|
||||
|
||||
if (notStatic) parameters.removeAt(0) // instance
|
||||
|
||||
var hasSenderParam = false
|
||||
check(parameters.isNotEmpty()) {
|
||||
"Parameters of sub command ${function.name} must not be empty. (Must have CommandSender as its receiver or first parameter or absent, followed by naturally typed params) (at ${this::class.qualifiedNameOrTip}.${function.name})"
|
||||
}
|
||||
|
||||
parameters.forEach { param ->
|
||||
check(!param.isVararg) {
|
||||
"Parameter $param must not be vararg. (at ${this::class.qualifiedNameOrTip}.${function.name}.$param)"
|
||||
}
|
||||
}
|
||||
|
||||
(parameters.first()).let { receiver ->
|
||||
if ((receiver.type.classifier as? KClass<*>)?.isSubclassOf(CommandSender::class) == true) {
|
||||
hasSenderParam = true
|
||||
parameters.removeAt(0)
|
||||
}
|
||||
}
|
||||
|
||||
val commandName =
|
||||
function.findAnnotation<CompositeCommand.SubCommand>()!!.value
|
||||
.let { namesFromAnnotation ->
|
||||
if (namesFromAnnotation.isNotEmpty()) {
|
||||
namesFromAnnotation
|
||||
} else arrayOf(function.name)
|
||||
}.also { names ->
|
||||
names.forEach {
|
||||
check(it.isValidSubName()) {
|
||||
"Name of sub command ${function.name} is invalid"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val buildUsage = StringBuilder(this.description).append(": \n")
|
||||
|
||||
//map parameter
|
||||
val params = parameters.map { param ->
|
||||
buildUsage.append("/$primaryName ")
|
||||
|
||||
if (param.isOptional) error("optional parameters are not yet supported. (at ${this::class.qualifiedNameOrTip}.${function.name}.$param)")
|
||||
|
||||
val argName = param.findAnnotation<CompositeCommand.Name>()?.value ?: param.name ?: "unknown"
|
||||
buildUsage.append("<").append(argName).append("> ").append(" ")
|
||||
CommandParam(
|
||||
argName,
|
||||
(param.type.classifier as? KClass<*>)
|
||||
?: throw IllegalArgumentException("unsolved type reference from param " + param.name + ". (at ${this::class.qualifiedNameOrTip}.${function.name}.$param)")
|
||||
)
|
||||
}.toTypedArray()
|
||||
|
||||
buildUsage.append(subDescription).append("\n")
|
||||
|
||||
return AbstractReflectionCommand.SubCommandDescriptor(
|
||||
commandName,
|
||||
params,
|
||||
subDescription,
|
||||
overridePermission?.value?.getInstance() ?: permission,
|
||||
onCommand = block { sender: CommandSender, args: Array<out Any> ->
|
||||
val result = if (notStatic) {
|
||||
if (hasSenderParam) {
|
||||
function.isSuspend
|
||||
function.callSuspend(this, sender, *args)
|
||||
} else function.callSuspend(this, *args)
|
||||
} else {
|
||||
if (hasSenderParam) {
|
||||
function.callSuspend(sender, *args)
|
||||
} else function.callSuspend(*args)
|
||||
}
|
||||
|
||||
checkNotNull(result) { "sub command return value is null (at ${this::class.qualifiedName}.${function.name})" }
|
||||
|
||||
result as? Boolean ?: true // Unit, void is considered as true.
|
||||
},
|
||||
context = context,
|
||||
usage = buildUsage.toString()
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
private fun block(block: suspend (CommandSender, Array<out Any>) -> Boolean): suspend (CommandSender, Array<out Any>) -> Boolean {
|
||||
return block
|
||||
}
|
||||
|
||||
private fun block2(block: suspend (CommandSender, Array<out Any>) -> Unit): suspend (CommandSender, Array<out Any>) -> Unit {
|
||||
return block
|
||||
}
|
@ -10,7 +10,7 @@
|
||||
package net.mamoe.mirai.console.plugin
|
||||
|
||||
import net.mamoe.mirai.console.plugin.jvm.JvmPlugin
|
||||
import net.mamoe.mirai.utils.MiraiExperimentalAPI
|
||||
import net.mamoe.mirai.console.utils.ConsoleExperimentalAPI
|
||||
import java.io.File
|
||||
|
||||
/**
|
||||
@ -38,7 +38,7 @@ inline val <P : Plugin> P.safeLoader: PluginLoader<P, PluginDescription>
|
||||
*
|
||||
* @see JvmPlugin
|
||||
*/
|
||||
@MiraiExperimentalAPI("classname is subject to change")
|
||||
@ConsoleExperimentalAPI("classname is subject to change")
|
||||
interface PluginFileExtensions {
|
||||
/**
|
||||
* 数据目录
|
||||
|
@ -7,7 +7,7 @@
|
||||
* https://github.com/mamoe/mirai/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
@file:OptIn(MiraiExperimentalAPI::class)
|
||||
@file:OptIn(ConsoleExperimentalAPI::class)
|
||||
|
||||
package net.mamoe.mirai.console.plugin.center
|
||||
|
||||
@ -19,8 +19,8 @@ import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.UnstableDefault
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlinx.serialization.json.JsonConfiguration
|
||||
import net.mamoe.mirai.console.utils.ConsoleExperimentalAPI
|
||||
import net.mamoe.mirai.console.utils.retryCatching
|
||||
import net.mamoe.mirai.utils.MiraiExperimentalAPI
|
||||
import java.io.File
|
||||
|
||||
@OptIn(UnstableDefault::class)
|
||||
|
@ -11,10 +11,10 @@ package net.mamoe.mirai.console.plugin.center
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import net.mamoe.mirai.utils.MiraiExperimentalAPI
|
||||
import net.mamoe.mirai.console.utils.ConsoleExperimentalAPI
|
||||
import java.io.File
|
||||
|
||||
@MiraiExperimentalAPI
|
||||
@ConsoleExperimentalAPI
|
||||
interface PluginCenter {
|
||||
|
||||
@Serializable
|
||||
|
@ -16,7 +16,7 @@ import net.mamoe.mirai.console.plugin.PluginLoadException
|
||||
import net.mamoe.mirai.console.plugin.internal.JvmPluginInternal
|
||||
import net.mamoe.mirai.console.plugin.internal.PluginsLoader
|
||||
import net.mamoe.mirai.console.setting.SettingStorage
|
||||
import net.mamoe.mirai.utils.MiraiExperimentalAPI
|
||||
import net.mamoe.mirai.console.utils.ConsoleExperimentalAPI
|
||||
import net.mamoe.mirai.utils.MiraiLogger
|
||||
import net.mamoe.yamlkt.Yaml
|
||||
import java.io.File
|
||||
@ -32,7 +32,7 @@ object JarPluginLoader : AbstractFilePluginLoader<JvmPlugin, JvmPluginDescriptio
|
||||
MiraiConsole.newLogger(JarPluginLoader::class.simpleName!!)
|
||||
}
|
||||
|
||||
@MiraiExperimentalAPI
|
||||
@ConsoleExperimentalAPI
|
||||
val settingStorage: SettingStorage by lazy { TODO() }
|
||||
|
||||
override val coroutineContext: CoroutineContext by lazy {
|
||||
|
@ -1,6 +1,6 @@
|
||||
package net.mamoe.mirai.console.setting
|
||||
|
||||
import net.mamoe.mirai.utils.MiraiExperimentalAPI
|
||||
import net.mamoe.mirai.console.utils.ConsoleExperimentalAPI
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
interface SettingStorage {
|
||||
@ -9,11 +9,11 @@ interface SettingStorage {
|
||||
|
||||
fun <T : Setting> load(holder: SettingHolder, settingClass: Class<T>): T
|
||||
|
||||
@MiraiExperimentalAPI
|
||||
@ConsoleExperimentalAPI
|
||||
fun store(holder: SettingHolder, setting: Setting)
|
||||
}
|
||||
|
||||
@MiraiExperimentalAPI
|
||||
@ConsoleExperimentalAPI
|
||||
interface SettingHolder {
|
||||
val name: String
|
||||
}
|
@ -16,7 +16,7 @@ import kotlinx.serialization.KSerializer
|
||||
import kotlinx.serialization.StringFormat
|
||||
import net.mamoe.mirai.console.setting.internal.map
|
||||
import net.mamoe.mirai.console.setting.internal.setValueBySerializer
|
||||
import net.mamoe.mirai.utils.MiraiExperimentalAPI
|
||||
import net.mamoe.mirai.console.utils.ConsoleExperimentalAPI
|
||||
import kotlin.reflect.KProperty
|
||||
|
||||
/**
|
||||
@ -145,7 +145,7 @@ interface StringValue : PrimitiveValue<String>
|
||||
//// endregion PrimitiveValues CODEGEN ////
|
||||
|
||||
|
||||
@MiraiExperimentalAPI
|
||||
@ConsoleExperimentalAPI
|
||||
interface CompositeValue<T> : Value<T>
|
||||
|
||||
|
||||
|
@ -1,8 +1,38 @@
|
||||
package net.mamoe.mirai.console.utils
|
||||
|
||||
import kotlin.annotation.AnnotationTarget.*
|
||||
|
||||
/**
|
||||
* 表明这个 API 是为了让 Java 使用者调用更方便. Kotlin 使用者不应该使用这些 API.
|
||||
*/
|
||||
@RequiresOptIn(level = RequiresOptIn.Level.ERROR)
|
||||
@Target(AnnotationTarget.PROPERTY, AnnotationTarget.FUNCTION, AnnotationTarget.TYPE, AnnotationTarget.CLASS)
|
||||
@Target(PROPERTY, FUNCTION, TYPE, CLASS)
|
||||
internal annotation class JavaFriendlyAPI
|
||||
|
||||
/**
|
||||
* 标记为一个仅供 mirai-console 内部使用的 API.
|
||||
*
|
||||
* 这些 API 可能会在任意时刻更改, 且不会发布任何预警.
|
||||
* 非常不建议在发行版本中使用这些 API.
|
||||
*/
|
||||
@Retention(AnnotationRetention.SOURCE)
|
||||
@RequiresOptIn(level = RequiresOptIn.Level.ERROR)
|
||||
@Target(CLASS, TYPEALIAS, FUNCTION, PROPERTY, FIELD, CONSTRUCTOR, CLASS, FUNCTION, PROPERTY)
|
||||
@MustBeDocumented
|
||||
annotation class ConsoleInternalAPI(
|
||||
val message: String = ""
|
||||
)
|
||||
|
||||
/**
|
||||
* 标记一个实验性的 API.
|
||||
*
|
||||
* 这些 API 不具有稳定性, 且可能会在任意时刻更改.
|
||||
* 不建议在发行版本中使用这些 API.
|
||||
*/
|
||||
@Retention(AnnotationRetention.SOURCE)
|
||||
@RequiresOptIn(level = RequiresOptIn.Level.WARNING)
|
||||
@Target(CLASS, TYPEALIAS, FUNCTION, PROPERTY, FIELD, CONSTRUCTOR)
|
||||
@MustBeDocumented
|
||||
annotation class ConsoleExperimentalAPI(
|
||||
val message: String = ""
|
||||
)
|
||||
|
@ -36,6 +36,7 @@ object TestCompositeCommand : CompositeCommand(
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
object TestSimpleCommand : RawCommand(owner, "testSimple", "tsS") {
|
||||
override suspend fun CommandSender.onCommand(args: Array<out Any>) {
|
||||
Testing.ok(args)
|
||||
@ -63,7 +64,7 @@ internal class TestCommand {
|
||||
assertEquals(1, ConsoleCommandOwner.instance.registeredCommands.size)
|
||||
|
||||
assertEquals(1, InternalCommandManager.registeredCommands.size)
|
||||
assertEquals(1, InternalCommandManager.requiredPrefixCommandMap.size)
|
||||
assertEquals(2, InternalCommandManager.requiredPrefixCommandMap.size)
|
||||
} finally {
|
||||
TestCompositeCommand.unregister()
|
||||
}
|
||||
@ -112,11 +113,11 @@ internal class TestCommand {
|
||||
@Test
|
||||
fun `executing command by string command`() = runBlocking {
|
||||
TestCompositeCommand.register()
|
||||
val result = withTesting<Array<String>> {
|
||||
assertNotNull(sender.executeCommand("testComposite", "test"))
|
||||
val result = withTesting<Int> {
|
||||
assertNotNull(sender.executeCommand("/testComposite", "mute 1"))
|
||||
}
|
||||
|
||||
assertEquals("test", result.single())
|
||||
assertEquals(1, result)
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -16,6 +16,7 @@ kotlin {
|
||||
languageSettings.progressiveMode = true
|
||||
languageSettings.useExperimentalAnnotation("net.mamoe.mirai.utils.MiraiInternalAPI")
|
||||
languageSettings.useExperimentalAnnotation("net.mamoe.mirai.utils.MiraiExperimentalAPI")
|
||||
languageSettings.useExperimentalAnnotation("net.mamoe.mirai.console.utils.ConsoleExperimentalAPI")
|
||||
languageSettings.useExperimentalAnnotation("kotlin.ExperimentalUnsignedTypes")
|
||||
languageSettings.useExperimentalAnnotation("kotlin.experimental.ExperimentalTypeInference")
|
||||
languageSettings.useExperimentalAnnotation("kotlin.contracts.ExperimentalContracts")
|
||||
|
Loading…
Reference in New Issue
Block a user