mirror of
https://github.com/mamoe/mirai.git
synced 2025-01-27 00:30:17 +08:00
Rework command reflection:
- Remove AbstractReflectionCommand - Introduce CommandReflector - Misc improvements
This commit is contained in:
parent
8b75e47f58
commit
4aa996a417
@ -26,8 +26,8 @@ import net.mamoe.mirai.console.command.CommandSender.Companion.asTempCommandSend
|
|||||||
import net.mamoe.mirai.console.command.CommandSender.Companion.toCommandSender
|
import net.mamoe.mirai.console.command.CommandSender.Companion.toCommandSender
|
||||||
import net.mamoe.mirai.console.command.descriptor.CommandArgumentParserException
|
import net.mamoe.mirai.console.command.descriptor.CommandArgumentParserException
|
||||||
import net.mamoe.mirai.console.internal.MiraiConsoleImplementationBridge
|
import net.mamoe.mirai.console.internal.MiraiConsoleImplementationBridge
|
||||||
import net.mamoe.mirai.console.internal.command.qualifiedNameOrTip
|
|
||||||
import net.mamoe.mirai.console.internal.data.castOrNull
|
import net.mamoe.mirai.console.internal.data.castOrNull
|
||||||
|
import net.mamoe.mirai.console.internal.data.qualifiedNameOrTip
|
||||||
import net.mamoe.mirai.console.internal.plugin.rootCauseOrSelf
|
import net.mamoe.mirai.console.internal.plugin.rootCauseOrSelf
|
||||||
import net.mamoe.mirai.console.permission.AbstractPermitteeId
|
import net.mamoe.mirai.console.permission.AbstractPermitteeId
|
||||||
import net.mamoe.mirai.console.permission.Permittee
|
import net.mamoe.mirai.console.permission.Permittee
|
||||||
|
@ -20,11 +20,10 @@ package net.mamoe.mirai.console.command
|
|||||||
import net.mamoe.mirai.console.command.descriptor.*
|
import net.mamoe.mirai.console.command.descriptor.*
|
||||||
import net.mamoe.mirai.console.compiler.common.ResolveContext
|
import net.mamoe.mirai.console.compiler.common.ResolveContext
|
||||||
import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.COMMAND_NAME
|
import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.COMMAND_NAME
|
||||||
import net.mamoe.mirai.console.internal.command.AbstractReflectionCommand
|
import net.mamoe.mirai.console.internal.command.CommandReflector
|
||||||
import net.mamoe.mirai.console.internal.command.CompositeCommandSubCommandAnnotationResolver
|
import net.mamoe.mirai.console.internal.command.SimpleCommandSubCommandAnnotationResolver
|
||||||
import net.mamoe.mirai.console.permission.Permission
|
import net.mamoe.mirai.console.permission.Permission
|
||||||
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
|
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
|
||||||
import net.mamoe.mirai.message.data.MessageChain
|
|
||||||
import kotlin.annotation.AnnotationRetention.RUNTIME
|
import kotlin.annotation.AnnotationRetention.RUNTIME
|
||||||
import kotlin.annotation.AnnotationTarget.FUNCTION
|
import kotlin.annotation.AnnotationTarget.FUNCTION
|
||||||
|
|
||||||
@ -90,13 +89,23 @@ public abstract class CompositeCommand(
|
|||||||
parentPermission: Permission = owner.parentPermission,
|
parentPermission: Permission = owner.parentPermission,
|
||||||
prefixOptional: Boolean = false,
|
prefixOptional: Boolean = false,
|
||||||
overrideContext: CommandArgumentContext = EmptyCommandArgumentContext,
|
overrideContext: CommandArgumentContext = EmptyCommandArgumentContext,
|
||||||
) : Command, AbstractReflectionCommand(owner, primaryName, secondaryNames = secondaryNames, description, parentPermission, prefixOptional),
|
) : Command, AbstractCommand(owner, primaryName, secondaryNames = secondaryNames, description, parentPermission, prefixOptional),
|
||||||
CommandArgumentContextAware {
|
CommandArgumentContextAware {
|
||||||
|
|
||||||
|
private val reflector by lazy { CommandReflector(this, SimpleCommandSubCommandAnnotationResolver) }
|
||||||
|
|
||||||
|
@ExperimentalCommandDescriptors
|
||||||
|
public final override val overloads: List<CommandSignatureVariantFromKFunction> by lazy {
|
||||||
|
reflector.findSubCommands()
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 自动根据带有 [SubCommand] 注解的函数签名生成 [usage]. 也可以被覆盖.
|
* 自动根据带有 [SubCommand] 注解的函数签名生成 [usage]. 也可以被覆盖.
|
||||||
*/
|
*/
|
||||||
public override val usage: String get() = super.usage
|
public override val usage: String by lazy {
|
||||||
|
@OptIn(ExperimentalCommandDescriptors::class)
|
||||||
|
reflector.generateUsage(overloads)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* [CommandValueArgumentParser] 的环境
|
* [CommandValueArgumentParser] 的环境
|
||||||
@ -123,33 +132,6 @@ public abstract class CompositeCommand(
|
|||||||
@Retention(RUNTIME)
|
@Retention(RUNTIME)
|
||||||
@Target(AnnotationTarget.VALUE_PARAMETER)
|
@Target(AnnotationTarget.VALUE_PARAMETER)
|
||||||
protected annotation class Name(val value: String)
|
protected annotation class Name(val value: String)
|
||||||
|
|
||||||
@OptIn(ExperimentalCommandDescriptors::class)
|
|
||||||
override val overloads: List<CommandSignatureVariant> by lazy {
|
|
||||||
subCommands.flatMap { desc ->
|
|
||||||
desc.bakedSubNames.map { names ->
|
|
||||||
CommandSignatureVariantImpl(
|
|
||||||
valueParameters =
|
|
||||||
names.mapIndexed { index, s -> CommandValueParameter.StringConstant("p$index", s) } + desc.params.map {
|
|
||||||
CommandValueParameter.UserDefinedType(it.name, null,
|
|
||||||
isOptional = false,
|
|
||||||
isVararg = false,
|
|
||||||
type = it.type)
|
|
||||||
},
|
|
||||||
onCall = { resolvedCommandCall ->
|
|
||||||
desc.onCommand(resolvedCommandCall.caller, resolvedCommandCall.resolvedValueArguments.drop(names.size))
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override suspend fun CommandSender.onDefault(rawArgs: MessageChain) {
|
|
||||||
sendMessage(usage)
|
|
||||||
}
|
|
||||||
|
|
||||||
internal final override val subCommandAnnotationResolver: SubCommandAnnotationResolver
|
|
||||||
get() = CompositeCommandSubCommandAnnotationResolver
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -12,14 +12,12 @@
|
|||||||
package net.mamoe.mirai.console.command
|
package net.mamoe.mirai.console.command
|
||||||
|
|
||||||
import net.mamoe.mirai.console.command.CommandManager.INSTANCE.executeCommand
|
import net.mamoe.mirai.console.command.CommandManager.INSTANCE.executeCommand
|
||||||
import net.mamoe.mirai.console.command.descriptor.CommandSignatureVariant
|
import net.mamoe.mirai.console.command.descriptor.*
|
||||||
import net.mamoe.mirai.console.command.descriptor.CommandSignatureVariantImpl
|
|
||||||
import net.mamoe.mirai.console.command.descriptor.CommandValueParameter
|
|
||||||
import net.mamoe.mirai.console.command.descriptor.ExperimentalCommandDescriptors
|
|
||||||
import net.mamoe.mirai.console.command.java.JRawCommand
|
import net.mamoe.mirai.console.command.java.JRawCommand
|
||||||
import net.mamoe.mirai.console.compiler.common.ResolveContext
|
import net.mamoe.mirai.console.compiler.common.ResolveContext
|
||||||
import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.COMMAND_NAME
|
import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.COMMAND_NAME
|
||||||
import net.mamoe.mirai.console.internal.command.createOrFindCommandPermission
|
import net.mamoe.mirai.console.internal.command.createOrFindCommandPermission
|
||||||
|
import net.mamoe.mirai.console.internal.data.typeOf0
|
||||||
import net.mamoe.mirai.console.permission.Permission
|
import net.mamoe.mirai.console.permission.Permission
|
||||||
import net.mamoe.mirai.message.data.MessageChain
|
import net.mamoe.mirai.message.data.MessageChain
|
||||||
import net.mamoe.mirai.message.data.MessageChainBuilder
|
import net.mamoe.mirai.message.data.MessageChainBuilder
|
||||||
@ -58,7 +56,10 @@ public abstract class RawCommand(
|
|||||||
|
|
||||||
@ExperimentalCommandDescriptors
|
@ExperimentalCommandDescriptors
|
||||||
override val overloads: List<CommandSignatureVariant> = listOf(
|
override val overloads: List<CommandSignatureVariant> = listOf(
|
||||||
CommandSignatureVariantImpl(listOf(CommandValueParameter.UserDefinedType.createRequired<MessageChain>("args", true))) { call ->
|
CommandSignatureVariantImpl(
|
||||||
|
receiverParameter = CommandReceiverParameter(false, typeOf0<CommandSender>()),
|
||||||
|
valueParameters = listOf(CommandValueParameter.UserDefinedType.createRequired<MessageChain>("args", true))
|
||||||
|
) { call ->
|
||||||
val sender = call.caller
|
val sender = call.caller
|
||||||
val arguments = call.rawValueArguments
|
val arguments = call.rawValueArguments
|
||||||
sender.onCommand(arguments.mapTo(MessageChainBuilder()) { it.value }.build())
|
sender.onCommand(arguments.mapTo(MessageChainBuilder()) { it.value }.build())
|
||||||
|
@ -22,10 +22,13 @@ import net.mamoe.mirai.console.command.descriptor.*
|
|||||||
import net.mamoe.mirai.console.command.java.JSimpleCommand
|
import net.mamoe.mirai.console.command.java.JSimpleCommand
|
||||||
import net.mamoe.mirai.console.compiler.common.ResolveContext
|
import net.mamoe.mirai.console.compiler.common.ResolveContext
|
||||||
import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.COMMAND_NAME
|
import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.COMMAND_NAME
|
||||||
import net.mamoe.mirai.console.internal.command.AbstractReflectionCommand
|
import net.mamoe.mirai.console.internal.command.CommandReflector
|
||||||
|
import net.mamoe.mirai.console.internal.command.IllegalCommandDeclarationException
|
||||||
import net.mamoe.mirai.console.internal.command.SimpleCommandSubCommandAnnotationResolver
|
import net.mamoe.mirai.console.internal.command.SimpleCommandSubCommandAnnotationResolver
|
||||||
import net.mamoe.mirai.console.permission.Permission
|
import net.mamoe.mirai.console.permission.Permission
|
||||||
import net.mamoe.mirai.message.data.MessageChain
|
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
|
||||||
|
import kotlin.annotation.AnnotationTarget.FUNCTION
|
||||||
|
import kotlin.annotation.AnnotationTarget.VALUE_PARAMETER
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 简单的, 支持参数自动解析的指令.
|
* 简单的, 支持参数自动解析的指令.
|
||||||
@ -58,47 +61,41 @@ public abstract class SimpleCommand(
|
|||||||
parentPermission: Permission = owner.parentPermission,
|
parentPermission: Permission = owner.parentPermission,
|
||||||
prefixOptional: Boolean = false,
|
prefixOptional: Boolean = false,
|
||||||
overrideContext: CommandArgumentContext = EmptyCommandArgumentContext,
|
overrideContext: CommandArgumentContext = EmptyCommandArgumentContext,
|
||||||
) : Command, AbstractReflectionCommand(owner, primaryName, secondaryNames = secondaryNames, description, parentPermission, prefixOptional),
|
) : Command, AbstractCommand(owner, primaryName, secondaryNames = secondaryNames, description, parentPermission, prefixOptional),
|
||||||
CommandArgumentContextAware {
|
CommandArgumentContextAware {
|
||||||
|
|
||||||
|
private val reflector by lazy { CommandReflector(this, SimpleCommandSubCommandAnnotationResolver) }
|
||||||
|
|
||||||
@ExperimentalCommandDescriptors
|
@ExperimentalCommandDescriptors
|
||||||
override val overloads: List<CommandSignatureVariant> by lazy {
|
public final override val overloads: List<CommandSignatureVariantFromKFunction> by lazy {
|
||||||
CommandSignatureVariantImpl(
|
reflector.findSubCommands().also {
|
||||||
valueParameters = subCommands.single().params.map {
|
if (it.isEmpty())
|
||||||
CommandValueParameter.UserDefinedType(it.name, null, isOptional = false, isVararg = false, type = it.type)
|
throw IllegalCommandDeclarationException(this, "SimpleCommand must have at least one subcommand, whereas zero present.")
|
||||||
}
|
}
|
||||||
) { call ->
|
|
||||||
val sender = call.caller
|
|
||||||
subCommands.single().onCommand(sender, call.resolvedValueArguments)
|
|
||||||
}.let { listOf(it) }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 自动根据带有 [Handler] 注解的函数签名生成 [usage]. 也可以被覆盖.
|
* 自动根据带有 [Handler] 注解的函数签名生成 [usage]. 也可以被覆盖.
|
||||||
*/
|
*/
|
||||||
public override val usage: String get() = super.usage
|
public override val usage: String by lazy {
|
||||||
|
@OptIn(ExperimentalCommandDescriptors::class)
|
||||||
|
reflector.generateUsage(overloads)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 标注指令处理器
|
* 标注指令处理器
|
||||||
*/
|
*/
|
||||||
|
@Target(FUNCTION)
|
||||||
protected annotation class Handler
|
protected annotation class Handler
|
||||||
|
|
||||||
|
/** 参数名, 将参与构成 [usage] */
|
||||||
|
@ConsoleExperimentalApi("Classname might change")
|
||||||
|
@Target(VALUE_PARAMETER)
|
||||||
|
protected annotation class Name(val value: String)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 指令参数环境. 默认为 [CommandArgumentContext.Builtins] `+` `overrideContext`
|
* 指令参数环境. 默认为 [CommandArgumentContext.Builtins] `+` `overrideContext`
|
||||||
*/
|
*/
|
||||||
public override val context: CommandArgumentContext = CommandArgumentContext.Builtins + overrideContext
|
public override val context: CommandArgumentContext = CommandArgumentContext.Builtins + overrideContext
|
||||||
|
|
||||||
internal override fun checkSubCommand(subCommands: Array<SubCommandDescriptor>) {
|
|
||||||
super.checkSubCommand(subCommands)
|
|
||||||
check(subCommands.size == 1) { "There can only be exactly one function annotated with Handler at this moment as overloading is not yet supported." }
|
|
||||||
}
|
|
||||||
|
|
||||||
@Deprecated("prohibited", level = DeprecationLevel.HIDDEN)
|
|
||||||
internal override suspend fun CommandSender.onDefault(rawArgs: MessageChain) {
|
|
||||||
sendMessage(usage)
|
|
||||||
}
|
|
||||||
|
|
||||||
internal final override val subCommandAnnotationResolver: SubCommandAnnotationResolver
|
|
||||||
get() = SimpleCommandSubCommandAnnotationResolver
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9,6 +9,7 @@
|
|||||||
|
|
||||||
package net.mamoe.mirai.console.command.descriptor
|
package net.mamoe.mirai.console.command.descriptor
|
||||||
|
|
||||||
|
import net.mamoe.mirai.console.command.CommandSender
|
||||||
import net.mamoe.mirai.console.command.descriptor.ArgumentAcceptance.Companion.isAcceptable
|
import net.mamoe.mirai.console.command.descriptor.ArgumentAcceptance.Companion.isAcceptable
|
||||||
import net.mamoe.mirai.console.command.descriptor.CommandValueParameter.UserDefinedType.Companion.createOptional
|
import net.mamoe.mirai.console.command.descriptor.CommandValueParameter.UserDefinedType.Companion.createOptional
|
||||||
import net.mamoe.mirai.console.command.descriptor.CommandValueParameter.UserDefinedType.Companion.createRequired
|
import net.mamoe.mirai.console.command.descriptor.CommandValueParameter.UserDefinedType.Companion.createRequired
|
||||||
@ -18,6 +19,7 @@ import net.mamoe.mirai.console.internal.data.classifierAsKClassOrNull
|
|||||||
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
|
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
|
||||||
import net.mamoe.mirai.console.util.safeCast
|
import net.mamoe.mirai.console.util.safeCast
|
||||||
import kotlin.reflect.KClass
|
import kotlin.reflect.KClass
|
||||||
|
import kotlin.reflect.KFunction
|
||||||
import kotlin.reflect.KType
|
import kotlin.reflect.KType
|
||||||
import kotlin.reflect.full.isSubtypeOf
|
import kotlin.reflect.full.isSubtypeOf
|
||||||
import kotlin.reflect.typeOf
|
import kotlin.reflect.typeOf
|
||||||
@ -27,13 +29,23 @@ import kotlin.reflect.typeOf
|
|||||||
*/
|
*/
|
||||||
@ExperimentalCommandDescriptors
|
@ExperimentalCommandDescriptors
|
||||||
public interface CommandSignatureVariant {
|
public interface CommandSignatureVariant {
|
||||||
|
@ConsoleExperimentalApi
|
||||||
|
public val receiverParameter: CommandReceiverParameter<out CommandSender>?
|
||||||
|
|
||||||
public val valueParameters: List<CommandValueParameter<*>>
|
public val valueParameters: List<CommandValueParameter<*>>
|
||||||
|
|
||||||
public suspend fun call(resolvedCommandCall: ResolvedCommandCall)
|
public suspend fun call(resolvedCommandCall: ResolvedCommandCall)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ConsoleExperimentalApi
|
||||||
@ExperimentalCommandDescriptors
|
@ExperimentalCommandDescriptors
|
||||||
public class CommandSignatureVariantImpl(
|
public interface CommandSignatureVariantFromKFunction : CommandSignatureVariant {
|
||||||
|
public val originFunction: KFunction<*>
|
||||||
|
}
|
||||||
|
|
||||||
|
@ExperimentalCommandDescriptors
|
||||||
|
public open class CommandSignatureVariantImpl(
|
||||||
|
override val receiverParameter: CommandReceiverParameter<out CommandSender>?,
|
||||||
override val valueParameters: List<CommandValueParameter<*>>,
|
override val valueParameters: List<CommandValueParameter<*>>,
|
||||||
private val onCall: suspend CommandSignatureVariantImpl.(resolvedCommandCall: ResolvedCommandCall) -> Unit,
|
private val onCall: suspend CommandSignatureVariantImpl.(resolvedCommandCall: ResolvedCommandCall) -> Unit,
|
||||||
) : CommandSignatureVariant {
|
) : CommandSignatureVariant {
|
||||||
@ -42,25 +54,46 @@ public class CommandSignatureVariantImpl(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ConsoleExperimentalApi
|
||||||
|
@ExperimentalCommandDescriptors
|
||||||
|
public open class CommandSignatureVariantFromKFunctionImpl(
|
||||||
|
override val receiverParameter: CommandReceiverParameter<out CommandSender>?,
|
||||||
|
override val valueParameters: List<CommandValueParameter<*>>,
|
||||||
|
override val originFunction: KFunction<*>,
|
||||||
|
private val onCall: suspend CommandSignatureVariantFromKFunctionImpl.(resolvedCommandCall: ResolvedCommandCall) -> Unit,
|
||||||
|
) : CommandSignatureVariantFromKFunction {
|
||||||
|
override suspend fun call(resolvedCommandCall: ResolvedCommandCall) {
|
||||||
|
return onCall(resolvedCommandCall)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Inherited instances must be [CommandValueParameter]
|
* Inherited instances must be [ICommandValueParameter] or [CommandReceiverParameter]
|
||||||
*/
|
*/
|
||||||
@ExperimentalCommandDescriptors
|
@ExperimentalCommandDescriptors
|
||||||
public interface ICommandParameter<T : Any?> {
|
public interface ICommandParameter<T : Any?> {
|
||||||
public val name: String
|
public val name: String?
|
||||||
|
|
||||||
/**
|
|
||||||
* If [isOptional] is `false`, [defaultValue] is always `null`.
|
|
||||||
* Otherwise [defaultValue] may be `null` iff [T] is nullable.
|
|
||||||
*/
|
|
||||||
public val defaultValue: T?
|
|
||||||
public val isOptional: Boolean
|
public val isOptional: Boolean
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reified type of [T]
|
* Reified type of [T]
|
||||||
*/
|
*/
|
||||||
public val type: KType
|
public val type: KType
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inherited instances must be [CommandValueParameter]
|
||||||
|
*/
|
||||||
|
@ExperimentalCommandDescriptors
|
||||||
|
public interface ICommandValueParameter<T : Any?> : ICommandParameter<T> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If [isOptional] is `false`, [defaultValue] is always `null`.
|
||||||
|
* Otherwise [defaultValue] may be `null` iff [T] is nullable.
|
||||||
|
*/
|
||||||
|
public val defaultValue: T?
|
||||||
|
|
||||||
public val isVararg: Boolean
|
public val isVararg: Boolean
|
||||||
|
|
||||||
@ -105,9 +138,21 @@ public sealed class ArgumentAcceptance(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ExperimentalCommandDescriptors
|
||||||
|
public class CommandReceiverParameter<T : CommandSender>(
|
||||||
|
override val isOptional: Boolean,
|
||||||
|
override val type: KType,
|
||||||
|
) : ICommandParameter<T> {
|
||||||
|
override val name: String get() = PARAMETER_NAME
|
||||||
|
|
||||||
|
public companion object {
|
||||||
|
public const val PARAMETER_NAME: String = "<receiver>"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@ExperimentalCommandDescriptors
|
@ExperimentalCommandDescriptors
|
||||||
public sealed class CommandValueParameter<T> : ICommandParameter<T> {
|
public sealed class CommandValueParameter<T> : ICommandValueParameter<T> {
|
||||||
internal fun validate() { // // TODO: 2020/10/18 net.mamoe.mirai.console.command.descriptor.CommandValueParameter.validate$mirai_console_mirai_console_main
|
internal fun validate() { // // TODO: 2020/10/18 net.mamoe.mirai.console.command.descriptor.CommandValueParameter.validate$mirai_console_mirai_console_main
|
||||||
require(type.classifier?.safeCast<KClass<*>>()?.isInstance(defaultValue) == true) {
|
require(type.classifier?.safeCast<KClass<*>>()?.isInstance(defaultValue) == true) {
|
||||||
"defaultValue is not instance of type"
|
"defaultValue is not instance of type"
|
||||||
@ -132,8 +177,10 @@ public sealed class CommandValueParameter<T> : ICommandParameter<T> {
|
|||||||
return ArgumentAcceptance.Impossible
|
return ArgumentAcceptance.Impossible
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ConsoleExperimentalApi
|
||||||
public class StringConstant(
|
public class StringConstant(
|
||||||
public override val name: String,
|
@ConsoleExperimentalApi
|
||||||
|
public override val name: String?,
|
||||||
public val expectingValue: String,
|
public val expectingValue: String,
|
||||||
) : CommandValueParameter<String>() {
|
) : CommandValueParameter<String>() {
|
||||||
public override val type: KType get() = STRING_TYPE
|
public override val type: KType get() = STRING_TYPE
|
||||||
@ -152,7 +199,7 @@ public sealed class CommandValueParameter<T> : ICommandParameter<T> {
|
|||||||
* @see createRequired
|
* @see createRequired
|
||||||
*/
|
*/
|
||||||
public class UserDefinedType<T>(
|
public class UserDefinedType<T>(
|
||||||
public override val name: String,
|
public override val name: String?,
|
||||||
public override val defaultValue: T?,
|
public override val defaultValue: T?,
|
||||||
public override val isOptional: Boolean,
|
public override val isOptional: Boolean,
|
||||||
public override val isVararg: Boolean,
|
public override val isVararg: Boolean,
|
||||||
|
@ -13,8 +13,8 @@ package net.mamoe.mirai.console.command.descriptor
|
|||||||
|
|
||||||
import net.mamoe.mirai.console.command.parse.CommandCall
|
import net.mamoe.mirai.console.command.parse.CommandCall
|
||||||
import net.mamoe.mirai.console.command.parse.CommandValueArgument
|
import net.mamoe.mirai.console.command.parse.CommandValueArgument
|
||||||
import net.mamoe.mirai.console.internal.command.qualifiedNameOrTip
|
|
||||||
import net.mamoe.mirai.console.internal.data.classifierAsKClassOrNull
|
import net.mamoe.mirai.console.internal.data.classifierAsKClassOrNull
|
||||||
|
import net.mamoe.mirai.console.internal.data.qualifiedNameOrTip
|
||||||
import kotlin.reflect.KType
|
import kotlin.reflect.KType
|
||||||
|
|
||||||
|
|
||||||
|
@ -14,7 +14,7 @@ package net.mamoe.mirai.console.data
|
|||||||
import kotlinx.atomicfu.atomic
|
import kotlinx.atomicfu.atomic
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
import net.mamoe.mirai.console.MiraiConsole
|
import net.mamoe.mirai.console.MiraiConsole
|
||||||
import net.mamoe.mirai.console.internal.command.qualifiedNameOrTip
|
import net.mamoe.mirai.console.internal.data.qualifiedNameOrTip
|
||||||
import net.mamoe.mirai.console.internal.plugin.updateWhen
|
import net.mamoe.mirai.console.internal.plugin.updateWhen
|
||||||
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
|
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
|
||||||
import net.mamoe.mirai.utils.*
|
import net.mamoe.mirai.utils.*
|
||||||
|
@ -16,6 +16,7 @@ import net.mamoe.mirai.console.MiraiConsole
|
|||||||
import net.mamoe.mirai.console.command.*
|
import net.mamoe.mirai.console.command.*
|
||||||
import net.mamoe.mirai.console.command.Command.Companion.allNames
|
import net.mamoe.mirai.console.command.Command.Companion.allNames
|
||||||
import net.mamoe.mirai.console.command.CommandSender.Companion.toCommandSender
|
import net.mamoe.mirai.console.command.CommandSender.Companion.toCommandSender
|
||||||
|
import net.mamoe.mirai.console.command.descriptor.ExperimentalCommandDescriptors
|
||||||
import net.mamoe.mirai.console.util.CoroutineScopeUtils.childScope
|
import net.mamoe.mirai.console.util.CoroutineScopeUtils.childScope
|
||||||
import net.mamoe.mirai.event.Listener
|
import net.mamoe.mirai.event.Listener
|
||||||
import net.mamoe.mirai.event.subscribeAlways
|
import net.mamoe.mirai.event.subscribeAlways
|
||||||
@ -24,6 +25,7 @@ import net.mamoe.mirai.message.data.content
|
|||||||
import net.mamoe.mirai.utils.MiraiLogger
|
import net.mamoe.mirai.utils.MiraiLogger
|
||||||
import java.util.concurrent.locks.ReentrantLock
|
import java.util.concurrent.locks.ReentrantLock
|
||||||
|
|
||||||
|
@OptIn(ExperimentalCommandDescriptors::class)
|
||||||
internal object CommandManagerImpl : CommandManager, CoroutineScope by MiraiConsole.childScope("CommandManagerImpl") {
|
internal object CommandManagerImpl : CommandManager, CoroutineScope by MiraiConsole.childScope("CommandManagerImpl") {
|
||||||
private val logger: MiraiLogger by lazy {
|
private val logger: MiraiLogger by lazy {
|
||||||
MiraiConsole.createLogger("command")
|
MiraiConsole.createLogger("command")
|
||||||
@ -102,7 +104,9 @@ internal object CommandManagerImpl : CommandManager, CoroutineScope by MiraiCons
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun Command.register(override: Boolean): Boolean {
|
override fun Command.register(override: Boolean): Boolean {
|
||||||
if (this is CompositeCommand) this.subCommands // init lazy
|
if (this is CompositeCommand) {
|
||||||
|
this.overloads // init lazy
|
||||||
|
}
|
||||||
kotlin.runCatching {
|
kotlin.runCatching {
|
||||||
this.permission // init lazy
|
this.permission // init lazy
|
||||||
this.secondaryNames // init lazy
|
this.secondaryNames // init lazy
|
||||||
|
@ -0,0 +1,215 @@
|
|||||||
|
package net.mamoe.mirai.console.internal.command
|
||||||
|
|
||||||
|
import net.mamoe.mirai.console.command.*
|
||||||
|
import net.mamoe.mirai.console.command.descriptor.*
|
||||||
|
import net.mamoe.mirai.console.internal.data.classifierAsKClass
|
||||||
|
import net.mamoe.mirai.console.internal.data.classifierAsKClassOrNull
|
||||||
|
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
|
||||||
|
import net.mamoe.mirai.message.data.MessageChain
|
||||||
|
import net.mamoe.mirai.message.data.PlainText
|
||||||
|
import net.mamoe.mirai.message.data.SingleMessage
|
||||||
|
import net.mamoe.mirai.message.data.buildMessageChain
|
||||||
|
import kotlin.reflect.KFunction
|
||||||
|
import kotlin.reflect.KParameter
|
||||||
|
import kotlin.reflect.KVisibility
|
||||||
|
import kotlin.reflect.full.*
|
||||||
|
|
||||||
|
|
||||||
|
internal val ILLEGAL_SUB_NAME_CHARS = "\\/!@#$%^&*()_+-={}[];':\",.<>?`~".toCharArray()
|
||||||
|
|
||||||
|
internal fun Any.flattenCommandComponents(): MessageChain = buildMessageChain {
|
||||||
|
when (this@flattenCommandComponents) {
|
||||||
|
is PlainText -> this@flattenCommandComponents.content.splitToSequence(' ').filterNot { it.isBlank() }
|
||||||
|
.forEach { +PlainText(it) }
|
||||||
|
is CharSequence -> this@flattenCommandComponents.splitToSequence(' ').filterNot { it.isBlank() }
|
||||||
|
.forEach { +PlainText(it) }
|
||||||
|
is SingleMessage -> add(this@flattenCommandComponents)
|
||||||
|
is Array<*> -> this@flattenCommandComponents.forEach { if (it != null) addAll(it.flattenCommandComponents()) }
|
||||||
|
is Iterable<*> -> this@flattenCommandComponents.forEach { if (it != null) addAll(it.flattenCommandComponents()) }
|
||||||
|
else -> add(this@flattenCommandComponents.toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER")
|
||||||
|
internal object CompositeCommandSubCommandAnnotationResolver :
|
||||||
|
SubCommandAnnotationResolver {
|
||||||
|
override fun hasAnnotation(ownerCommand: Command, function: KFunction<*>) =
|
||||||
|
function.hasAnnotation<CompositeCommand.SubCommand>()
|
||||||
|
|
||||||
|
override fun getSubCommandNames(ownerCommand: Command, function: KFunction<*>): Array<out String> =
|
||||||
|
function.findAnnotation<CompositeCommand.SubCommand>()!!.value
|
||||||
|
|
||||||
|
override fun getAnnotatedName(ownerCommand: Command, parameter: KParameter): String? =
|
||||||
|
parameter.findAnnotation<CompositeCommand.Name>()?.value
|
||||||
|
|
||||||
|
override fun getDescription(ownerCommand: Command, function: KFunction<*>): String? =
|
||||||
|
function.findAnnotation<CompositeCommand.Description>()?.value
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER")
|
||||||
|
internal object SimpleCommandSubCommandAnnotationResolver :
|
||||||
|
SubCommandAnnotationResolver {
|
||||||
|
override fun hasAnnotation(ownerCommand: Command, function: KFunction<*>) =
|
||||||
|
function.hasAnnotation<SimpleCommand.Handler>()
|
||||||
|
|
||||||
|
override fun getSubCommandNames(ownerCommand: Command, function: KFunction<*>): Array<out String> =
|
||||||
|
ownerCommand.secondaryNames
|
||||||
|
|
||||||
|
override fun getAnnotatedName(ownerCommand: Command, parameter: KParameter): String? =
|
||||||
|
parameter.findAnnotation<SimpleCommand.Name>()?.value
|
||||||
|
|
||||||
|
override fun getDescription(ownerCommand: Command, function: KFunction<*>): String? =
|
||||||
|
ownerCommand.description
|
||||||
|
}
|
||||||
|
|
||||||
|
internal interface SubCommandAnnotationResolver {
|
||||||
|
fun hasAnnotation(ownerCommand: Command, function: KFunction<*>): Boolean
|
||||||
|
fun getSubCommandNames(ownerCommand: Command, function: KFunction<*>): Array<out String>
|
||||||
|
fun getAnnotatedName(ownerCommand: Command, parameter: KParameter): String?
|
||||||
|
fun getDescription(ownerCommand: Command, function: KFunction<*>): String?
|
||||||
|
}
|
||||||
|
|
||||||
|
@ConsoleExperimentalApi
|
||||||
|
public class IllegalCommandDeclarationException : Exception {
|
||||||
|
public override val message: String?
|
||||||
|
|
||||||
|
public constructor(
|
||||||
|
ownerCommand: Command,
|
||||||
|
correspondingFunction: KFunction<*>,
|
||||||
|
message: String?,
|
||||||
|
) : super("Illegal command declaration: ${correspondingFunction.name} declared in ${ownerCommand::class.qualifiedName}") {
|
||||||
|
this.message = message
|
||||||
|
}
|
||||||
|
|
||||||
|
public constructor(
|
||||||
|
ownerCommand: Command,
|
||||||
|
message: String?,
|
||||||
|
) : super("Illegal command declaration: ${ownerCommand::class.qualifiedName}") {
|
||||||
|
this.message = message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalCommandDescriptors::class)
|
||||||
|
internal class CommandReflector(
|
||||||
|
val command: Command,
|
||||||
|
val annotationResolver: SubCommandAnnotationResolver,
|
||||||
|
) {
|
||||||
|
|
||||||
|
@Suppress("NOTHING_TO_INLINE")
|
||||||
|
private inline fun KFunction<*>.illegalDeclaration(
|
||||||
|
message: String,
|
||||||
|
): Nothing {
|
||||||
|
throw IllegalCommandDeclarationException(command, this, message)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun KFunction<*>.isSubCommandFunction(): Boolean = annotationResolver.hasAnnotation(command, this)
|
||||||
|
private fun KFunction<*>.checkExtensionReceiver() {
|
||||||
|
this.extensionReceiverParameter?.let { receiver ->
|
||||||
|
if (receiver.type.classifierAsKClassOrNull()?.isSubclassOf(CommandSender::class) != true) {
|
||||||
|
illegalDeclaration("Extension receiver parameter type is not subclass of CommandSender.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun KFunction<*>.checkNames() {
|
||||||
|
val names = annotationResolver.getSubCommandNames(command, this)
|
||||||
|
for (name in names) {
|
||||||
|
ILLEGAL_SUB_NAME_CHARS.find { it in name }?.let {
|
||||||
|
illegalDeclaration("'$it' is forbidden in command name.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun KFunction<*>.checkModifiers() {
|
||||||
|
if (isInline) illegalDeclaration("Command function cannot be inline")
|
||||||
|
if (visibility == KVisibility.PRIVATE) illegalDeclaration("Command function must be accessible from Mirai Console, that is, effectively public.")
|
||||||
|
if (this.hasAnnotation<JvmStatic>()) illegalDeclaration("Command function must not be static.")
|
||||||
|
|
||||||
|
// should we allow abstract?
|
||||||
|
|
||||||
|
// if (isAbstract) illegalDeclaration("Command function cannot be abstract")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun generateUsage(overloads: Iterable<CommandSignatureVariantFromKFunction>): String {
|
||||||
|
return overloads.joinToString("\n") { subcommand ->
|
||||||
|
buildString {
|
||||||
|
if (command.prefixOptional) {
|
||||||
|
append("(")
|
||||||
|
append(CommandManager.commandPrefix)
|
||||||
|
append(")")
|
||||||
|
} else {
|
||||||
|
append(CommandManager.commandPrefix)
|
||||||
|
}
|
||||||
|
if (command is CompositeCommand) {
|
||||||
|
append(command.primaryName)
|
||||||
|
append(" ")
|
||||||
|
}
|
||||||
|
append(subcommand.valueParameters.joinToString(" ") { it.render() })
|
||||||
|
annotationResolver.getDescription(command, subcommand.originFunction).let { description ->
|
||||||
|
append(" ")
|
||||||
|
append(description)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
private fun <T> CommandValueParameter<T>.render(): String {
|
||||||
|
return when (this) {
|
||||||
|
is CommandValueParameter.Extended,
|
||||||
|
is CommandValueParameter.UserDefinedType<*>,
|
||||||
|
-> {
|
||||||
|
"<${this.name ?: this.type.classifierAsKClass().simpleName}>"
|
||||||
|
}
|
||||||
|
is CommandValueParameter.StringConstant -> {
|
||||||
|
this.expectingValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Throws(IllegalCommandDeclarationException::class)
|
||||||
|
fun findSubCommands(): List<CommandSignatureVariantFromKFunctionImpl> {
|
||||||
|
return command::class.functions // exclude static later
|
||||||
|
.asSequence()
|
||||||
|
.filter { it.isSubCommandFunction() }
|
||||||
|
.onEach { it.checkExtensionReceiver() }
|
||||||
|
.onEach { it.checkModifiers() }
|
||||||
|
.onEach { it.checkNames() }
|
||||||
|
.map { function ->
|
||||||
|
|
||||||
|
val functionNameAsValueParameter =
|
||||||
|
annotationResolver.getSubCommandNames(command, function).map { createStringConstantParameter(it) }
|
||||||
|
|
||||||
|
val functionValueParameters =
|
||||||
|
function.valueParameters.map { it.toUserDefinedCommandParameter() }
|
||||||
|
|
||||||
|
CommandSignatureVariantFromKFunctionImpl(
|
||||||
|
receiverParameter = function.extensionReceiverParameter?.toCommandReceiverParameter(),
|
||||||
|
valueParameters = functionNameAsValueParameter + functionValueParameters,
|
||||||
|
originFunction = function
|
||||||
|
) { call ->
|
||||||
|
function.callSuspend(command, *call.resolvedValueArguments.toTypedArray())
|
||||||
|
}
|
||||||
|
}.toList()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun KParameter.toCommandReceiverParameter(): CommandReceiverParameter<out CommandSender>? {
|
||||||
|
check(!this.isVararg) { "Receiver cannot be vararg." }
|
||||||
|
check(this.type.classifierAsKClass().isSubclassOf(CommandSender::class)) { "Receiver must be subclass of CommandSender" }
|
||||||
|
|
||||||
|
return CommandReceiverParameter(this.type.isMarkedNullable, this.type)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createStringConstantParameter(expectingValue: String): CommandValueParameter.StringConstant {
|
||||||
|
return CommandValueParameter.StringConstant(null, expectingValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun KParameter.toUserDefinedCommandParameter(): CommandValueParameter.UserDefinedType<KParameter> {
|
||||||
|
return CommandValueParameter.UserDefinedType(nameForCommandParameter(), this, this.isOptional, this.isVararg, this.type)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun KParameter.nameForCommandParameter(): String? = annotationResolver.getAnnotatedName(command, this) ?: this.name
|
||||||
|
}
|
@ -1,334 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2019-2020 Mamoe Technologies and contributors.
|
|
||||||
*
|
|
||||||
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
|
|
||||||
* Use of this source code is governed by the GNU AFFERO GENERAL PUBLIC LICENSE version 3 license that can be found via 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.internal.command
|
|
||||||
|
|
||||||
import net.mamoe.mirai.console.command.*
|
|
||||||
import net.mamoe.mirai.console.command.descriptor.*
|
|
||||||
import net.mamoe.mirai.console.internal.command.hasAnnotation
|
|
||||||
import net.mamoe.mirai.console.permission.Permission
|
|
||||||
import net.mamoe.mirai.message.data.MessageChain
|
|
||||||
import net.mamoe.mirai.message.data.PlainText
|
|
||||||
import net.mamoe.mirai.message.data.SingleMessage
|
|
||||||
import net.mamoe.mirai.message.data.buildMessageChain
|
|
||||||
import kotlin.reflect.KAnnotatedElement
|
|
||||||
import kotlin.reflect.KClass
|
|
||||||
import kotlin.reflect.KFunction
|
|
||||||
import kotlin.reflect.KParameter
|
|
||||||
import kotlin.reflect.full.*
|
|
||||||
|
|
||||||
internal object CompositeCommandSubCommandAnnotationResolver :
|
|
||||||
AbstractReflectionCommand.SubCommandAnnotationResolver {
|
|
||||||
override fun hasAnnotation(baseCommand: AbstractReflectionCommand, function: KFunction<*>) =
|
|
||||||
function.hasAnnotation<CompositeCommand.SubCommand>()
|
|
||||||
|
|
||||||
override fun getSubCommandNames(baseCommand: AbstractReflectionCommand, function: KFunction<*>): Array<out String> =
|
|
||||||
function.findAnnotation<CompositeCommand.SubCommand>()!!.value
|
|
||||||
}
|
|
||||||
|
|
||||||
internal object SimpleCommandSubCommandAnnotationResolver :
|
|
||||||
AbstractReflectionCommand.SubCommandAnnotationResolver {
|
|
||||||
override fun hasAnnotation(baseCommand: AbstractReflectionCommand, function: KFunction<*>) =
|
|
||||||
function.hasAnnotation<SimpleCommand.Handler>()
|
|
||||||
|
|
||||||
override fun getSubCommandNames(baseCommand: AbstractReflectionCommand, function: KFunction<*>): Array<out String> =
|
|
||||||
baseCommand.secondaryNames
|
|
||||||
}
|
|
||||||
|
|
||||||
internal abstract class AbstractReflectionCommand
|
|
||||||
@JvmOverloads constructor(
|
|
||||||
owner: CommandOwner,
|
|
||||||
primaryName: String,
|
|
||||||
secondaryNames: Array<out String>,
|
|
||||||
description: String = "<no description available>",
|
|
||||||
parentPermission: Permission = owner.parentPermission,
|
|
||||||
prefixOptional: Boolean = false,
|
|
||||||
) : Command, AbstractCommand(
|
|
||||||
owner,
|
|
||||||
primaryName = primaryName,
|
|
||||||
secondaryNames = secondaryNames,
|
|
||||||
description = description,
|
|
||||||
parentPermission = parentPermission,
|
|
||||||
prefixOptional = prefixOptional
|
|
||||||
), CommandArgumentContextAware {
|
|
||||||
internal abstract val subCommandAnnotationResolver: SubCommandAnnotationResolver
|
|
||||||
|
|
||||||
@JvmField
|
|
||||||
@Suppress("PropertyName")
|
|
||||||
internal var _usage: String = "<not yet initialized>"
|
|
||||||
|
|
||||||
override val usage: String // initialized by subCommand reflection
|
|
||||||
get() {
|
|
||||||
subCommands // ensure init
|
|
||||||
return _usage
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract suspend fun CommandSender.onDefault(rawArgs: MessageChain)
|
|
||||||
|
|
||||||
internal val defaultSubCommand: DefaultSubCommandDescriptor by lazy {
|
|
||||||
DefaultSubCommandDescriptor(
|
|
||||||
"",
|
|
||||||
createOrFindCommandPermission(parentPermission),
|
|
||||||
onCommand = { sender: CommandSender, args: MessageChain ->
|
|
||||||
sender.onDefault(args)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
internal open fun checkSubCommand(subCommands: Array<SubCommandDescriptor>) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@OptIn(ExperimentalCommandDescriptors::class)
|
|
||||||
private fun <T : Any> CommandParameter<T>.toCommandValueParameter(): CommandValueParameter<T> {
|
|
||||||
return CommandValueParameter.UserDefinedType<T>(name, null, false, false, type)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@OptIn(ExperimentalCommandDescriptors::class)
|
|
||||||
override val overloads: List<CommandSignatureVariant> by lazy {
|
|
||||||
subCommands.map { desc ->
|
|
||||||
CommandSignatureVariantImpl(desc.params.map { it.toCommandValueParameter() }) { call ->
|
|
||||||
desc.onCommand(call.caller, call.resolvedValueArguments)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
interface SubCommandAnnotationResolver {
|
|
||||||
fun hasAnnotation(baseCommand: AbstractReflectionCommand, function: KFunction<*>): Boolean
|
|
||||||
fun getSubCommandNames(baseCommand: AbstractReflectionCommand, function: KFunction<*>): Array<out String>
|
|
||||||
}
|
|
||||||
|
|
||||||
internal val subCommands: Array<SubCommandDescriptor> by lazy {
|
|
||||||
this::class.declaredFunctions.filter { subCommandAnnotationResolver.hasAnnotation(this, 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.createUsage(this)
|
|
||||||
}.also { checkSubCommand(it) }
|
|
||||||
}
|
|
||||||
|
|
||||||
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 { o1, o2 -> o1!!.contentHashCode() - o2!!.contentHashCode() }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal class DefaultSubCommandDescriptor(
|
|
||||||
val description: String,
|
|
||||||
val permission: Permission,
|
|
||||||
val onCommand: suspend (sender: CommandSender, rawArgs: MessageChain) -> Unit,
|
|
||||||
)
|
|
||||||
|
|
||||||
internal inner class SubCommandDescriptor(
|
|
||||||
val names: Array<out String>,
|
|
||||||
val params: Array<CommandParameter<*>>,
|
|
||||||
val description: String,
|
|
||||||
val permission: Permission,
|
|
||||||
val onCommand: suspend (sender: CommandSender, parsedArgs: List<Any?>) -> Boolean,
|
|
||||||
val context: CommandArgumentContext,
|
|
||||||
val argumentBuilder: (sender: CommandSender) -> MutableMap<KParameter, Any?>,
|
|
||||||
) {
|
|
||||||
val usage: String = createUsage(this@AbstractReflectionCommand)
|
|
||||||
|
|
||||||
private fun KParameter.isOptional(): Boolean {
|
|
||||||
return isOptional || this.type.isMarkedNullable
|
|
||||||
}
|
|
||||||
|
|
||||||
val minimalArgumentsSize = params.count {
|
|
||||||
!it.parameter.isOptional()
|
|
||||||
}
|
|
||||||
|
|
||||||
@JvmField
|
|
||||||
internal val bakedSubNames: Array<Array<String>> = names.map { it.bakeSubName() }.toTypedArray()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal fun <T> Array<T>.contentEqualsOffset(other: MessageChain, length: Int): Boolean {
|
|
||||||
repeat(length) { index ->
|
|
||||||
if (!other[index].toString().equals(this[index].toString(), ignoreCase = true)) {
|
|
||||||
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(): MessageChain = buildMessageChain {
|
|
||||||
when (this@flattenCommandComponents) {
|
|
||||||
is PlainText -> this@flattenCommandComponents.content.splitToSequence(' ').filterNot { it.isBlank() }
|
|
||||||
.forEach { +PlainText(it) }
|
|
||||||
is CharSequence -> this@flattenCommandComponents.splitToSequence(' ').filterNot { it.isBlank() }
|
|
||||||
.forEach { +PlainText(it) }
|
|
||||||
is SingleMessage -> add(this@flattenCommandComponents)
|
|
||||||
is Array<*> -> this@flattenCommandComponents.forEach { if (it != null) addAll(it.flattenCommandComponents()) }
|
|
||||||
is Iterable<*> -> this@flattenCommandComponents.forEach { if (it != null) addAll(it.flattenCommandComponents()) }
|
|
||||||
else -> add(this@flattenCommandComponents.toString())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal inline fun <reified T : Annotation> KAnnotatedElement.hasAnnotation(): Boolean =
|
|
||||||
findAnnotation<T>() != null
|
|
||||||
|
|
||||||
internal val KClass<*>.qualifiedNameOrTip: String get() = this.qualifiedName ?: "<anonymous class>"
|
|
||||||
|
|
||||||
internal fun Array<AbstractReflectionCommand.SubCommandDescriptor>.createUsage(baseCommand: AbstractReflectionCommand): String =
|
|
||||||
buildString {
|
|
||||||
appendLine(baseCommand.description)
|
|
||||||
appendLine()
|
|
||||||
|
|
||||||
for (subCommandDescriptor in this@createUsage) {
|
|
||||||
appendLine(subCommandDescriptor.usage)
|
|
||||||
}
|
|
||||||
}.trimEnd()
|
|
||||||
|
|
||||||
internal fun AbstractReflectionCommand.SubCommandDescriptor.createUsage(baseCommand: AbstractReflectionCommand): String =
|
|
||||||
buildString {
|
|
||||||
if (baseCommand.prefixOptional) {
|
|
||||||
append("(")
|
|
||||||
append(CommandManager.commandPrefix)
|
|
||||||
append(")")
|
|
||||||
} else {
|
|
||||||
append(CommandManager.commandPrefix)
|
|
||||||
}
|
|
||||||
if (baseCommand is CompositeCommand) {
|
|
||||||
append(baseCommand.primaryName)
|
|
||||||
append(" ")
|
|
||||||
}
|
|
||||||
append(names.first())
|
|
||||||
append(" ")
|
|
||||||
append(params.joinToString(" ") { "<${it.name}>" })
|
|
||||||
append(" ")
|
|
||||||
append(description)
|
|
||||||
appendLine()
|
|
||||||
}.trimEnd()
|
|
||||||
|
|
||||||
internal fun <T1, R1, R2> ((T1) -> R1).then(then: (T1, R1) -> R2): ((T1) -> R2) {
|
|
||||||
return { a -> then.invoke(a, (this@then(a))) }
|
|
||||||
}
|
|
||||||
|
|
||||||
internal fun AbstractReflectionCommand.createSubCommand(
|
|
||||||
function: KFunction<*>,
|
|
||||||
context: CommandArgumentContext,
|
|
||||||
): AbstractReflectionCommand.SubCommandDescriptor {
|
|
||||||
val notStatic = !function.hasAnnotation<JvmStatic>()
|
|
||||||
//val overridePermission = null//function.findAnnotation<CompositeCommand.PermissionId>()//optional
|
|
||||||
val subDescription =
|
|
||||||
function.findAnnotation<CompositeCommand.Description>()?.value ?: ""
|
|
||||||
|
|
||||||
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})")
|
|
||||||
}
|
|
||||||
var argumentBuilder: (sender: CommandSender) -> MutableMap<KParameter, Any?> = { HashMap() }
|
|
||||||
val parameters = function.parameters.toMutableList()
|
|
||||||
|
|
||||||
if (notStatic) {
|
|
||||||
val type = parameters.removeAt(0) // instance
|
|
||||||
argumentBuilder = argumentBuilder.then { _, map ->
|
|
||||||
map[type] = this@createSubCommand
|
|
||||||
map
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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) {
|
|
||||||
val senderType = parameters.removeAt(0)
|
|
||||||
argumentBuilder = argumentBuilder.then { sender, map ->
|
|
||||||
map[senderType] = sender
|
|
||||||
map
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val commandName =
|
|
||||||
subCommandAnnotationResolver.getSubCommandNames(this, function)
|
|
||||||
.let { namesFromAnnotation ->
|
|
||||||
if (namesFromAnnotation.isNotEmpty()) {
|
|
||||||
namesFromAnnotation.map(String::toLowerCase).toTypedArray()
|
|
||||||
} else arrayOf(function.name.toLowerCase())
|
|
||||||
}.also { names ->
|
|
||||||
names.forEach {
|
|
||||||
check(it.isValidSubName()) {
|
|
||||||
"Name of sub command ${function.name} is invalid"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//map parameter
|
|
||||||
val params = parameters.map { param ->
|
|
||||||
|
|
||||||
// if (param.isOptional) error("optional parameters are not yet supported. (at ${this::class.qualifiedNameOrTip}.${function.name}.$param)")
|
|
||||||
|
|
||||||
val paramName = param.findAnnotation<CompositeCommand.Name>()?.value ?: param.name ?: "unknown"
|
|
||||||
CommandParameter<Any>(
|
|
||||||
paramName,
|
|
||||||
param.type,
|
|
||||||
param
|
|
||||||
)
|
|
||||||
}.toTypedArray()
|
|
||||||
|
|
||||||
// TODO: 2020/09/19 检查 optional/nullable 是否都在最后
|
|
||||||
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
|
||||||
return SubCommandDescriptor(
|
|
||||||
commandName,
|
|
||||||
params as Array<CommandParameter<*>>,
|
|
||||||
subDescription, // overridePermission?.value
|
|
||||||
permission,//overridePermission?.value?.let { PermissionService.INSTANCE[PermissionId.parseFromString(it)] } ?: permission,
|
|
||||||
onCommand = { _: CommandSender, args ->
|
|
||||||
val p = parameters.zip(args).toMap(LinkedHashMap())
|
|
||||||
if (notStatic) p[function.instanceParameter!!] = this@createSubCommand
|
|
||||||
val result = function.callSuspendBy(p)
|
|
||||||
|
|
||||||
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,
|
|
||||||
argumentBuilder = argumentBuilder
|
|
||||||
)
|
|
||||||
}
|
|
@ -12,7 +12,6 @@ package net.mamoe.mirai.console.internal.data
|
|||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import net.mamoe.mirai.console.MiraiConsole
|
import net.mamoe.mirai.console.MiraiConsole
|
||||||
import net.mamoe.mirai.console.data.*
|
import net.mamoe.mirai.console.data.*
|
||||||
import net.mamoe.mirai.console.internal.command.qualifiedNameOrTip
|
|
||||||
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
|
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
|
||||||
import net.mamoe.mirai.utils.MiraiLogger
|
import net.mamoe.mirai.utils.MiraiLogger
|
||||||
import net.mamoe.mirai.utils.SilentLogger
|
import net.mamoe.mirai.utils.SilentLogger
|
||||||
|
@ -11,14 +11,15 @@ package net.mamoe.mirai.console.internal.data
|
|||||||
|
|
||||||
import net.mamoe.mirai.console.data.PluginData
|
import net.mamoe.mirai.console.data.PluginData
|
||||||
import net.mamoe.mirai.console.data.ValueName
|
import net.mamoe.mirai.console.data.ValueName
|
||||||
import net.mamoe.mirai.console.internal.command.qualifiedNameOrTip
|
import kotlin.reflect.*
|
||||||
import kotlin.reflect.KClass
|
|
||||||
import kotlin.reflect.KParameter
|
|
||||||
import kotlin.reflect.KProperty
|
|
||||||
import kotlin.reflect.KType
|
|
||||||
import kotlin.reflect.full.findAnnotation
|
import kotlin.reflect.full.findAnnotation
|
||||||
import kotlin.reflect.full.isSubclassOf
|
import kotlin.reflect.full.isSubclassOf
|
||||||
|
|
||||||
|
internal val KClass<*>.qualifiedNameOrTip: String get() = this.qualifiedName ?: "<anonymous class>"
|
||||||
|
|
||||||
|
internal inline fun <reified T : Annotation> KAnnotatedElement.hasAnnotation(): Boolean =
|
||||||
|
findAnnotation<T>() != null
|
||||||
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
internal inline fun <reified T : Any> KType.toKClass(): KClass<out T> {
|
internal inline fun <reified T : Any> KType.toKClass(): KClass<out T> {
|
||||||
val clazz = requireNotNull(classifier as? KClass<T>) { "Unsupported classifier: $classifier" }
|
val clazz = requireNotNull(classifier as? KClass<T>) { "Unsupported classifier: $classifier" }
|
||||||
|
@ -16,7 +16,6 @@ import net.mamoe.mirai.console.data.PluginData
|
|||||||
import net.mamoe.mirai.console.data.SerializableValue.Companion.serializableValueWith
|
import net.mamoe.mirai.console.data.SerializableValue.Companion.serializableValueWith
|
||||||
import net.mamoe.mirai.console.data.SerializerAwareValue
|
import net.mamoe.mirai.console.data.SerializerAwareValue
|
||||||
import net.mamoe.mirai.console.data.valueFromKType
|
import net.mamoe.mirai.console.data.valueFromKType
|
||||||
import net.mamoe.mirai.console.internal.command.qualifiedNameOrTip
|
|
||||||
import kotlin.contracts.contract
|
import kotlin.contracts.contract
|
||||||
import kotlin.reflect.KClass
|
import kotlin.reflect.KClass
|
||||||
import kotlin.reflect.KType
|
import kotlin.reflect.KType
|
||||||
|
@ -12,7 +12,7 @@
|
|||||||
package net.mamoe.mirai.console.util
|
package net.mamoe.mirai.console.util
|
||||||
|
|
||||||
import net.mamoe.mirai.Bot
|
import net.mamoe.mirai.Bot
|
||||||
import net.mamoe.mirai.console.internal.command.qualifiedNameOrTip
|
import net.mamoe.mirai.console.internal.data.qualifiedNameOrTip
|
||||||
import net.mamoe.mirai.contact.*
|
import net.mamoe.mirai.contact.*
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -261,7 +261,7 @@ internal class TestCommand {
|
|||||||
"testOptional"
|
"testOptional"
|
||||||
) {
|
) {
|
||||||
@SubCommand
|
@SubCommand
|
||||||
fun optional(arg1: String, arg2: String = "Here is optional", arg3: String?) {
|
fun optional(arg1: String, arg2: String = "Here is optional", arg3: String? = null) {
|
||||||
println(arg1)
|
println(arg1)
|
||||||
println(arg2)
|
println(arg2)
|
||||||
println(arg3)
|
println(arg3)
|
||||||
|
Loading…
Reference in New Issue
Block a user