mirror of
https://github.com/mamoe/mirai.git
synced 2025-04-24 20:43:33 +08:00
Merge 1d3e03d958
into 283f8840d4
This commit is contained in:
commit
5210304d13
mirai-console/backend/mirai-console
compatibility-validation/jvm/api
src
command
internal/command
test/command
@ -94,6 +94,14 @@ public abstract class net/mamoe/mirai/console/command/AbstractPluginCustomComman
|
||||
protected abstract fun sendMessageImpl (Ljava/lang/String;)V
|
||||
}
|
||||
|
||||
public abstract class net/mamoe/mirai/console/command/AbstractSubCommandGroup : net/mamoe/mirai/console/command/SubCommandGroup, net/mamoe/mirai/console/command/descriptor/CommandArgumentContextAware {
|
||||
public fun <init> ()V
|
||||
public fun <init> (Lnet/mamoe/mirai/console/command/descriptor/CommandArgumentContext;)V
|
||||
public synthetic fun <init> (Lnet/mamoe/mirai/console/command/descriptor/CommandArgumentContext;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
|
||||
public fun getContext ()Lnet/mamoe/mirai/console/command/descriptor/CommandArgumentContext;
|
||||
public final fun getOverloads ()Ljava/util/List;
|
||||
}
|
||||
|
||||
public abstract class net/mamoe/mirai/console/command/AbstractUserCommandSender : net/mamoe/mirai/console/command/AbstractCommandSender, net/mamoe/mirai/console/command/UserCommandSender {
|
||||
public fun getBot ()Lnet/mamoe/mirai/Bot;
|
||||
public final fun getName ()Ljava/lang/String;
|
||||
@ -371,7 +379,7 @@ public abstract interface class net/mamoe/mirai/console/command/CommandSenderOnM
|
||||
public abstract fun getFromEvent ()Lnet/mamoe/mirai/event/events/MessageEvent;
|
||||
}
|
||||
|
||||
public abstract class net/mamoe/mirai/console/command/CompositeCommand : net/mamoe/mirai/console/command/AbstractCommand, net/mamoe/mirai/console/command/Command, net/mamoe/mirai/console/command/descriptor/CommandArgumentContextAware {
|
||||
public abstract class net/mamoe/mirai/console/command/CompositeCommand : net/mamoe/mirai/console/command/AbstractCommand, net/mamoe/mirai/console/command/Command, net/mamoe/mirai/console/command/SubCommandGroup, net/mamoe/mirai/console/command/descriptor/CommandArgumentContextAware {
|
||||
public fun <init> (Lnet/mamoe/mirai/console/command/CommandOwner;Ljava/lang/String;[Ljava/lang/String;Ljava/lang/String;Lnet/mamoe/mirai/console/permission/Permission;Lnet/mamoe/mirai/console/command/descriptor/CommandArgumentContext;)V
|
||||
public synthetic fun <init> (Lnet/mamoe/mirai/console/command/CommandOwner;Ljava/lang/String;[Ljava/lang/String;Ljava/lang/String;Lnet/mamoe/mirai/console/permission/Permission;Lnet/mamoe/mirai/console/command/descriptor/CommandArgumentContext;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
|
||||
public fun getContext ()Lnet/mamoe/mirai/console/command/descriptor/CommandArgumentContext;
|
||||
@ -605,6 +613,13 @@ public final class net/mamoe/mirai/console/command/StrangerCommandSenderOnMessag
|
||||
public fun getFromEvent ()Lnet/mamoe/mirai/event/events/StrangerMessageEvent;
|
||||
}
|
||||
|
||||
public abstract interface class net/mamoe/mirai/console/command/SubCommandGroup {
|
||||
public abstract fun getOverloads ()Ljava/util/List;
|
||||
}
|
||||
|
||||
public abstract interface annotation class net/mamoe/mirai/console/command/SubCommandGroup$FlattenSubCommands : java/lang/annotation/Annotation {
|
||||
}
|
||||
|
||||
public abstract interface class net/mamoe/mirai/console/command/SystemCommandSender : net/mamoe/mirai/console/command/CommandSender {
|
||||
public abstract fun isAnsiSupported ()Z
|
||||
}
|
||||
|
@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright 2019-2022 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/dev/LICENSE
|
||||
*/
|
||||
|
||||
package net.mamoe.mirai.console.command
|
||||
|
||||
import net.mamoe.mirai.console.command.descriptor.*
|
||||
import net.mamoe.mirai.console.internal.command.GroupedCommandSubCommandAnnotationResolver
|
||||
import net.mamoe.mirai.console.internal.command.SubCommandReflector
|
||||
|
||||
public abstract class AbstractSubCommandGroup(
|
||||
overrideContext: CommandArgumentContext = EmptyCommandArgumentContext,
|
||||
) : CommandArgumentContextAware, SubCommandGroup {
|
||||
|
||||
private val reflector by lazy { SubCommandReflector(this, GroupedCommandSubCommandAnnotationResolver) }
|
||||
|
||||
@ExperimentalCommandDescriptors
|
||||
public final override val overloads: List<CommandSignatureFromKFunction> by lazy {
|
||||
reflector.findSubCommands().also {
|
||||
reflector.validate(it)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 智能参数解析环境
|
||||
*/ // open since 2.12
|
||||
public override val context: CommandArgumentContext = CommandArgumentContext.Builtins + overrideContext
|
||||
|
||||
}
|
||||
|
||||
|
@ -21,7 +21,6 @@ import net.mamoe.mirai.console.util.ConsoleExperimentalApi
|
||||
import kotlin.annotation.AnnotationRetention.RUNTIME
|
||||
import kotlin.annotation.AnnotationTarget.FUNCTION
|
||||
|
||||
|
||||
/**
|
||||
* 复合指令. 指令注册时候会通过反射构造指令解析器.
|
||||
*
|
||||
@ -92,7 +91,7 @@ public abstract class CompositeCommand(
|
||||
parentPermission: Permission = owner.parentPermission,
|
||||
overrideContext: CommandArgumentContext = EmptyCommandArgumentContext,
|
||||
) : Command, AbstractCommand(owner, primaryName, secondaryNames = secondaryNames, description, parentPermission),
|
||||
CommandArgumentContextAware {
|
||||
CommandArgumentContextAware, SubCommandGroup {
|
||||
|
||||
private val reflector by lazy { CommandReflector(this, CompositeCommandSubCommandAnnotationResolver) }
|
||||
|
||||
|
@ -0,0 +1,24 @@
|
||||
package net.mamoe.mirai.console.command
|
||||
|
||||
import net.mamoe.mirai.console.command.descriptor.CommandSignatureFromKFunction
|
||||
import net.mamoe.mirai.console.command.descriptor.ExperimentalCommandDescriptors
|
||||
import net.mamoe.mirai.console.compiler.common.ResolveContext
|
||||
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
|
||||
|
||||
public interface SubCommandGroup {
|
||||
|
||||
/**
|
||||
* 被聚合时提供的子指令
|
||||
*/
|
||||
@ExperimentalCommandDescriptors
|
||||
public val overloads: List<@JvmWildcard CommandSignatureFromKFunction>
|
||||
|
||||
/**
|
||||
* 标记一个属性为子指令集合,且使用flat策略
|
||||
*/
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
@Target(AnnotationTarget.PROPERTY)
|
||||
public annotation class FlattenSubCommands(
|
||||
)
|
||||
|
||||
}
|
@ -42,6 +42,13 @@ public open class CommandDeclarationClashException(
|
||||
public val signatures: List<CommandSignature>,
|
||||
) : CommandDeclarationException("Declaration clash for command '${command.primaryName}': \n${signatures.joinToString("\n")}")
|
||||
|
||||
@ExperimentalCommandDescriptors
|
||||
public open class SubcommandDeclarationClashException(
|
||||
public val owner: Any,
|
||||
public val signatures: List<CommandSignature>,
|
||||
) : CommandDeclarationException("Declaration clash for owner '${owner::class.qualifiedName}': \n${signatures.joinToString("\n")}")
|
||||
|
||||
|
||||
public open class CommandDeclarationException : RuntimeException {
|
||||
public constructor() : super()
|
||||
public constructor(message: String?) : super(message)
|
||||
|
@ -66,11 +66,11 @@ internal fun Any.flattenCommandComponents(): MessageChain = buildMessageChain {
|
||||
|
||||
@Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER")
|
||||
internal object CompositeCommandSubCommandAnnotationResolver :
|
||||
SubCommandAnnotationResolver {
|
||||
override fun hasAnnotation(ownerCommand: Command, function: KFunction<*>) =
|
||||
SubCommandAnnotationResolver<Command> {
|
||||
override fun isDeclaredSubCommand(ownerCommand: Command, function: KFunction<*>) =
|
||||
function.hasAnnotation<CompositeCommand.SubCommand>()
|
||||
|
||||
override fun getSubCommandNames(ownerCommand: Command, function: KFunction<*>): Array<out String> {
|
||||
override fun getDeclaredSubCommandNames(ownerCommand: Command, function: KFunction<*>): Array<out String> {
|
||||
val annotated = function.findAnnotation<CompositeCommand.SubCommand>()!!.value
|
||||
return if (annotated.isEmpty()) arrayOf(function.name)
|
||||
else annotated
|
||||
@ -81,15 +81,61 @@ internal object CompositeCommandSubCommandAnnotationResolver :
|
||||
|
||||
override fun getDescription(ownerCommand: Command, function: KFunction<*>): String? =
|
||||
function.findAnnotation<CompositeCommand.Description>()?.value
|
||||
|
||||
override fun isCombinedSubCommands(command: Command, kProperty: KProperty<*>): Boolean =
|
||||
kProperty.hasAnnotation<SubCommandGroup.FlattenSubCommands>() || kProperty.hasAnnotation<CompositeCommand.SubCommand>()
|
||||
|
||||
override fun getCombinedAdditionNames(command: Command, kProperty: KProperty<*>): Array<out String> {
|
||||
return if (kProperty.hasAnnotation<SubCommandGroup.FlattenSubCommands>()) {
|
||||
emptyArray()
|
||||
} else {
|
||||
val annotated = kProperty.findAnnotation<CompositeCommand.SubCommand>()!!.value
|
||||
if (annotated.isEmpty()) arrayOf(kProperty.name)
|
||||
else annotated
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER")
|
||||
internal object GroupedCommandSubCommandAnnotationResolver :
|
||||
SubCommandAnnotationResolver<SubCommandGroup> {
|
||||
override fun isDeclaredSubCommand(ownerCommand: SubCommandGroup, function: KFunction<*>) =
|
||||
function.hasAnnotation<CompositeCommand.SubCommand>()
|
||||
|
||||
override fun getDeclaredSubCommandNames(ownerCommand: SubCommandGroup, function: KFunction<*>): Array<out String> {
|
||||
val annotated = function.findAnnotation<CompositeCommand.SubCommand>()!!.value
|
||||
return if (annotated.isEmpty()) arrayOf(function.name)
|
||||
else annotated
|
||||
}
|
||||
|
||||
override fun getAnnotatedName(ownerCommand: SubCommandGroup, parameter: KParameter): String? =
|
||||
parameter.findAnnotation<CompositeCommand.Name>()?.value
|
||||
|
||||
override fun getDescription(ownerCommand: SubCommandGroup, function: KFunction<*>): String? =
|
||||
function.findAnnotation<CompositeCommand.Description>()?.value
|
||||
|
||||
override fun isCombinedSubCommands(command: SubCommandGroup, kProperty: KProperty<*>): Boolean =
|
||||
kProperty.hasAnnotation<SubCommandGroup.FlattenSubCommands>() || kProperty.hasAnnotation<CompositeCommand.SubCommand>()
|
||||
|
||||
override fun getCombinedAdditionNames(command: SubCommandGroup, kProperty: KProperty<*>): Array<out String> {
|
||||
return if (kProperty.hasAnnotation<SubCommandGroup.FlattenSubCommands>()) {
|
||||
emptyArray()
|
||||
} else {
|
||||
val annotated = kProperty.findAnnotation<CompositeCommand.SubCommand>()!!.value
|
||||
if (annotated.isEmpty()) arrayOf(kProperty.name)
|
||||
else annotated
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER")
|
||||
internal object SimpleCommandSubCommandAnnotationResolver :
|
||||
SubCommandAnnotationResolver {
|
||||
override fun hasAnnotation(ownerCommand: Command, function: KFunction<*>) =
|
||||
SubCommandAnnotationResolver<Command> {
|
||||
override fun isDeclaredSubCommand(ownerCommand: Command, function: KFunction<*>) =
|
||||
function.hasAnnotation<SimpleCommand.Handler>()
|
||||
|
||||
override fun getSubCommandNames(ownerCommand: Command, function: KFunction<*>): Array<out String> =
|
||||
override fun getDeclaredSubCommandNames(ownerCommand: Command, function: KFunction<*>): Array<out String> =
|
||||
emptyArray()
|
||||
|
||||
override fun getAnnotatedName(ownerCommand: Command, parameter: KParameter): String? =
|
||||
@ -97,13 +143,39 @@ internal object SimpleCommandSubCommandAnnotationResolver :
|
||||
|
||||
override fun getDescription(ownerCommand: Command, function: KFunction<*>): String =
|
||||
ownerCommand.description
|
||||
|
||||
override fun isCombinedSubCommands(command: Command, kProperty: KProperty<*>): Boolean = false
|
||||
|
||||
override fun getCombinedAdditionNames(command: Command, kProperty: KProperty<*>): Array<out String> =
|
||||
emptyArray()
|
||||
|
||||
}
|
||||
|
||||
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?
|
||||
internal interface SubCommandAnnotationResolver<T> {
|
||||
/**
|
||||
* 判断ownerCommand中的一个function是否能成为SubCommand
|
||||
*/
|
||||
fun isDeclaredSubCommand(ownerCommand: T, function: KFunction<*>): Boolean
|
||||
/**
|
||||
* 得出ownerCommand中的一个function成为SubCommand时的名字列表
|
||||
*/
|
||||
fun getDeclaredSubCommandNames(ownerCommand: T, function: KFunction<*>): Array<out String>
|
||||
/**
|
||||
* 得出ownerCommand中的一个function成为SubCommand时其参数表中的参数的描述
|
||||
*/
|
||||
fun getAnnotatedName(ownerCommand: T, parameter: KParameter): String?
|
||||
/**
|
||||
* 得出ownerCommand中的一个function成为SubCommand时的描述
|
||||
*/
|
||||
fun getDescription(ownerCommand: T, function: KFunction<*>): String?
|
||||
/**
|
||||
* 判断ownerCommand中的一个kProperty是否能成为SubCommand
|
||||
*/
|
||||
fun isCombinedSubCommands(command: T, kProperty: KProperty<*>): Boolean
|
||||
/**
|
||||
* 得出ownerCommand中的一个kProperty成为SubCommand时的指令路径的增加部分
|
||||
*/
|
||||
fun getCombinedAdditionNames(command: T, kProperty: KProperty<*>): Array<out String>
|
||||
}
|
||||
|
||||
@ConsoleExperimentalApi
|
||||
@ -111,10 +183,10 @@ public class IllegalCommandDeclarationException : Exception {
|
||||
public override val message: String?
|
||||
|
||||
public constructor(
|
||||
ownerCommand: Command,
|
||||
owner: Any,
|
||||
correspondingFunction: KFunction<*>,
|
||||
message: String?,
|
||||
) : super("Illegal command declaration: ${correspondingFunction.name} declared in ${ownerCommand::class.qualifiedName}") {
|
||||
) : super("Illegal command declaration: ${correspondingFunction.name} declared in ${owner::class.qualifiedName}") {
|
||||
this.message = message
|
||||
}
|
||||
|
||||
@ -129,46 +201,8 @@ public class IllegalCommandDeclarationException : Exception {
|
||||
@OptIn(ExperimentalCommandDescriptors::class)
|
||||
internal class CommandReflector(
|
||||
val command: Command,
|
||||
private 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 ->
|
||||
val classifier = receiver.type.classifierAsKClassOrNull()
|
||||
if (classifier != null) {
|
||||
if (!classifier.isSubclassOf(CommandSender::class) && !classifier.isSubclassOf(CommandContext::class)) {
|
||||
illegalDeclaration("Extension receiver parameter type is not subclass of CommandSender nor CommandContext.")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
private val annotationResolver: SubCommandAnnotationResolver<Command>,
|
||||
) : SubCommandReflectible by SubCommandReflector(command, annotationResolver) {
|
||||
|
||||
fun generateUsage(overloads: Iterable<CommandSignatureFromKFunction>): String {
|
||||
return generateUsage(command, annotationResolver, overloads)
|
||||
@ -177,7 +211,7 @@ internal class CommandReflector(
|
||||
companion object {
|
||||
fun generateUsage(
|
||||
command: Command,
|
||||
annotationResolver: SubCommandAnnotationResolver?,
|
||||
annotationResolver: SubCommandAnnotationResolver<Command>?,
|
||||
overloads: Iterable<CommandSignature>
|
||||
): String {
|
||||
return overloads.joinToString("\n") { subcommand ->
|
||||
@ -218,8 +252,62 @@ internal class CommandReflector(
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun validate(signatures: List<CommandSignatureFromKFunctionImpl>) {
|
||||
@OptIn(ExperimentalCommandDescriptors::class)
|
||||
internal interface SubCommandReflectible {
|
||||
@Throws(IllegalCommandDeclarationException::class)
|
||||
fun findSubCommands(): List<CommandSignatureFromKFunction>
|
||||
fun validate(signatures: List<CommandSignatureFromKFunction>)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalCommandDescriptors::class)
|
||||
internal class SubCommandReflector<T: Any>(
|
||||
val owner: T,
|
||||
private val annotationResolver: SubCommandAnnotationResolver<T>,
|
||||
) : SubCommandReflectible {
|
||||
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
private inline fun KFunction<*>.illegalDeclaration(
|
||||
message: String,
|
||||
): Nothing {
|
||||
throw IllegalCommandDeclarationException(owner, this, message)
|
||||
}
|
||||
|
||||
private fun KProperty<*>.isSubCommandProviderProperty(): Boolean = annotationResolver.isCombinedSubCommands(owner, this)
|
||||
private fun KFunction<*>.isSubCommandFunction(): Boolean = annotationResolver.isDeclaredSubCommand(owner, this)
|
||||
private fun KProperty<*>.getCombinedAdditionNames(): Array<out String> = annotationResolver.getCombinedAdditionNames(owner, this)
|
||||
private fun KFunction<*>.checkExtensionReceiver() {
|
||||
this.extensionReceiverParameter?.let { receiver ->
|
||||
val classifier = receiver.type.classifierAsKClassOrNull()
|
||||
if (classifier != null) {
|
||||
if (!classifier.isSubclassOf(CommandSender::class) && !classifier.isSubclassOf(CommandContext::class)) {
|
||||
illegalDeclaration("Extension receiver parameter type is not subclass of CommandSender nor CommandContext.")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun KFunction<*>.checkNames() {
|
||||
val names = annotationResolver.getDeclaredSubCommandNames(owner, 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")
|
||||
}
|
||||
|
||||
override fun validate(signatures: List<CommandSignatureFromKFunction>) {
|
||||
|
||||
data class ErasedParameterInfo(
|
||||
val index: Int,
|
||||
@ -255,93 +343,136 @@ internal class CommandReflector(
|
||||
value.size > 1
|
||||
} ?: return
|
||||
|
||||
throw CommandDeclarationClashException(command, clashes.value.map { it.first })
|
||||
|
||||
throw SubcommandDeclarationClashException(owner, clashes.value.map { it.first })
|
||||
}
|
||||
|
||||
private fun generateCommandSignatureFromKFunctionImplWithAdditionName(name: String?, origin: CommandSignatureFromKFunction): CommandSignatureFromKFunctionImpl {
|
||||
val functionNameAsValueParameter =
|
||||
name?.split(' ')?.mapIndexed { index, s -> createStringConstantParameterForName(index, s) }
|
||||
.orEmpty()
|
||||
|
||||
return CommandSignatureFromKFunctionImpl(
|
||||
receiverParameter = origin.receiverParameter,
|
||||
valueParameters = functionNameAsValueParameter + origin.valueParameters,
|
||||
originFunction = origin.originFunction
|
||||
) { call ->
|
||||
origin.call(call)
|
||||
}
|
||||
}
|
||||
|
||||
private fun generateCommandSignatureFromKFunctionImplWithAdditionName(name: String?, function: KFunction<*>): CommandSignatureFromKFunctionImpl {
|
||||
val functionNameAsValueParameter =
|
||||
name?.split(' ')?.mapIndexed { index, s -> createStringConstantParameterForName(index, s) }
|
||||
.orEmpty()
|
||||
|
||||
val valueParameters = function.valueParameters.toMutableList()
|
||||
var receiverParameter = function.extensionReceiverParameter
|
||||
if (receiverParameter == null && valueParameters.isNotEmpty()) {
|
||||
val valueFirstParameter = valueParameters[0]
|
||||
val classifier = valueFirstParameter.type.classifierAsKClassOrNull()
|
||||
if (classifier != null && isAcceptableReceiverType(classifier)
|
||||
) {
|
||||
receiverParameter = valueFirstParameter
|
||||
valueParameters.removeAt(0)
|
||||
}
|
||||
}
|
||||
|
||||
val functionValueParameters =
|
||||
valueParameters.associateBy { it.toUserDefinedCommandParameter() }
|
||||
|
||||
return CommandSignatureFromKFunctionImpl(
|
||||
receiverParameter = receiverParameter?.toCommandReceiverParameter(),
|
||||
valueParameters = functionNameAsValueParameter + functionValueParameters.keys,
|
||||
originFunction = function
|
||||
) { call ->
|
||||
val args = LinkedHashMap<KParameter, Any?>()
|
||||
|
||||
for ((commandParameter, value) in call.resolvedValueArguments) {
|
||||
if (commandParameter is AbstractCommandValueParameter.StringConstant) {
|
||||
continue
|
||||
}
|
||||
val functionParameter =
|
||||
functionValueParameters[commandParameter]
|
||||
?: error("Could not find a corresponding function parameter '${commandParameter.name}'")
|
||||
args[functionParameter] = value
|
||||
}
|
||||
|
||||
val instanceParameter = function.instanceParameter
|
||||
if (instanceParameter != null) {
|
||||
check(instanceParameter.type.classifierAsKClass().isInstance(owner)) {
|
||||
"Bad command call resolved. " +
|
||||
"Function expects instance parameter ${instanceParameter.type} whereas actual instance is ${owner::class}."
|
||||
}
|
||||
args[instanceParameter] = owner
|
||||
}
|
||||
|
||||
if (receiverParameter != null) {
|
||||
|
||||
val receiverType = receiverParameter.type.classifierAsKClass()
|
||||
|
||||
if (receiverType.isSubclassOf(CommandContext::class)) {
|
||||
args[receiverParameter] = CommandContextImpl(call.caller, call.originalMessage)
|
||||
} else {
|
||||
check(receiverType.isInstance(call.caller)) {
|
||||
"Bad command call resolved. " +
|
||||
"Function expects receiver parameter ${receiverParameter.type} whereas actual is ${call.caller::class}."
|
||||
}
|
||||
args[receiverParameter] = call.caller
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// mirai-console#341
|
||||
if (function.isSuspend) {
|
||||
function.callSuspendBy(args)
|
||||
} else {
|
||||
runBIO { function.callBy(args) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Throws(IllegalCommandDeclarationException::class)
|
||||
fun findSubCommands(): List<CommandSignatureFromKFunctionImpl> {
|
||||
return command::class.functions // exclude static later
|
||||
override fun findSubCommands(): List<CommandSignatureFromKFunction> {
|
||||
val fromMemberFunctions = owner::class.functions // exclude static later
|
||||
.asSequence()
|
||||
.filter { it.isSubCommandFunction() }
|
||||
.onEach { it.checkExtensionReceiver() }
|
||||
.onEach { it.checkModifiers() }
|
||||
.onEach { it.checkNames() }
|
||||
.flatMap { function ->
|
||||
val names = annotationResolver.getSubCommandNames(command, function)
|
||||
val names = annotationResolver.getDeclaredSubCommandNames(owner, function)
|
||||
if (names.isEmpty()) sequenceOf(createMapEntry(null, function))
|
||||
else names.associateWith { function }.asSequence()
|
||||
}
|
||||
.map { (name, function) ->
|
||||
|
||||
val functionNameAsValueParameter =
|
||||
name?.split(' ')?.mapIndexed { index, s -> createStringConstantParameterForName(index, s) }
|
||||
.orEmpty()
|
||||
|
||||
val valueParameters = function.valueParameters.toMutableList()
|
||||
var receiverParameter = function.extensionReceiverParameter
|
||||
if (receiverParameter == null && valueParameters.isNotEmpty()) {
|
||||
val valueFirstParameter = valueParameters[0]
|
||||
val classifier = valueFirstParameter.type.classifierAsKClassOrNull()
|
||||
if (classifier != null && isAcceptableReceiverType(classifier)
|
||||
) {
|
||||
receiverParameter = valueFirstParameter
|
||||
valueParameters.removeAt(0)
|
||||
}
|
||||
}
|
||||
|
||||
val functionValueParameters =
|
||||
valueParameters.associateBy { it.toUserDefinedCommandParameter() }
|
||||
|
||||
CommandSignatureFromKFunctionImpl(
|
||||
receiverParameter = receiverParameter?.toCommandReceiverParameter(),
|
||||
valueParameters = functionNameAsValueParameter + functionValueParameters.keys,
|
||||
originFunction = function
|
||||
) { call ->
|
||||
val args = LinkedHashMap<KParameter, Any?>()
|
||||
|
||||
for ((commandParameter, value) in call.resolvedValueArguments) {
|
||||
if (commandParameter is AbstractCommandValueParameter.StringConstant) {
|
||||
continue
|
||||
}
|
||||
val functionParameter =
|
||||
functionValueParameters[commandParameter]
|
||||
?: error("Could not find a corresponding function parameter '${commandParameter.name}'")
|
||||
args[functionParameter] = value
|
||||
}
|
||||
|
||||
val instanceParameter = function.instanceParameter
|
||||
if (instanceParameter != null) {
|
||||
check(instanceParameter.type.classifierAsKClass().isInstance(command)) {
|
||||
"Bad command call resolved. " +
|
||||
"Function expects instance parameter ${instanceParameter.type} whereas actual instance is ${command::class}."
|
||||
}
|
||||
args[instanceParameter] = command
|
||||
}
|
||||
|
||||
if (receiverParameter != null) {
|
||||
|
||||
val receiverType = receiverParameter.type.classifierAsKClass()
|
||||
|
||||
if (receiverType.isSubclassOf(CommandContext::class)) {
|
||||
args[receiverParameter] = CommandContextImpl(call.caller, call.originalMessage)
|
||||
} else {
|
||||
check(receiverType.isInstance(call.caller)) {
|
||||
"Bad command call resolved. " +
|
||||
"Function expects receiver parameter ${receiverParameter.type} whereas actual is ${call.caller::class}."
|
||||
}
|
||||
args[receiverParameter] = call.caller
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// mirai-console#341
|
||||
if (function.isSuspend) {
|
||||
function.callSuspendBy(args)
|
||||
} else {
|
||||
runBIO { function.callBy(args) }
|
||||
}
|
||||
}
|
||||
generateCommandSignatureFromKFunctionImplWithAdditionName(name, function)
|
||||
}.toList()
|
||||
|
||||
val fromMemberProperties = owner::class.declaredMemberProperties
|
||||
.asSequence()
|
||||
.filter { it.isSubCommandProviderProperty() }
|
||||
.filter { it.getter.call(owner) is SubCommandGroup }
|
||||
.flatMap {
|
||||
val names = it.getCombinedAdditionNames()
|
||||
val originOverloads = (it.getter.call(owner) as SubCommandGroup).overloads
|
||||
if (names.isEmpty()) sequenceOf(createMapEntry(null, originOverloads))
|
||||
else names.associateWith { originOverloads }.asSequence()
|
||||
}
|
||||
.map { (name, originOverloads) ->
|
||||
originOverloads
|
||||
.map { originOverload ->
|
||||
generateCommandSignatureFromKFunctionImplWithAdditionName(name, originOverload)
|
||||
}
|
||||
}
|
||||
.flatten()
|
||||
.toList()
|
||||
|
||||
val list: MutableList<CommandSignatureFromKFunction> = ArrayList()
|
||||
list.addAll(fromMemberFunctions)
|
||||
list.addAll(fromMemberProperties)
|
||||
return list
|
||||
}
|
||||
|
||||
private fun isAcceptableReceiverType(classifier: KClass<Any>) =
|
||||
@ -386,5 +517,5 @@ internal class CommandReflector(
|
||||
}
|
||||
|
||||
private fun KParameter.nameForCommandParameter(): String? =
|
||||
annotationResolver.getAnnotatedName(command, this) ?: this.name
|
||||
annotationResolver.getAnnotatedName(owner, this) ?: this.name
|
||||
}
|
@ -24,6 +24,7 @@ import net.mamoe.mirai.console.command.CommandManager.INSTANCE.unregisterCommand
|
||||
import net.mamoe.mirai.console.command.descriptor.CommandValueArgumentParser
|
||||
import net.mamoe.mirai.console.command.descriptor.ExperimentalCommandDescriptors
|
||||
import net.mamoe.mirai.console.command.descriptor.buildCommandArgumentContext
|
||||
|
||||
import net.mamoe.mirai.console.internal.command.CommandManagerImpl
|
||||
import net.mamoe.mirai.console.internal.command.flattenCommandComponents
|
||||
import net.mamoe.mirai.console.permission.PermissionService.Companion.permit
|
||||
@ -34,6 +35,87 @@ import java.time.temporal.TemporalAccessor
|
||||
import kotlin.reflect.KClass
|
||||
import kotlin.test.*
|
||||
|
||||
import net.mamoe.mirai.console.command.SubCommandGroup.FlattenSubCommands
|
||||
|
||||
/**
|
||||
* 测试:CompositeCommand下接3种子节点;AbstractSubCommandGroup下接3种子节点;
|
||||
*
|
||||
*
|
||||
* [MyUnifiedCommand : CompositeCommand]
|
||||
* |
|
||||
* -------------------------------------------------------
|
||||
* | \ \
|
||||
* [ModuleB : AbstractSubCommandGroup] [functionA0:KFunction] [ModuleE : CompositeCommand]
|
||||
* |
|
||||
* -----------------------------------------------------------------
|
||||
* | \ \
|
||||
* [ModuleC : AbstractSubCommandGroup] [ModuleD : CompositeCommand] [functionB0/functionB1:KFunction]
|
||||
*
|
||||
*/
|
||||
class MyUnifiedCommand : CompositeCommand(
|
||||
owner, "testMyUnifiedCommand", "tsMUC"
|
||||
) {
|
||||
class ModuleB : AbstractSubCommandGroup() {
|
||||
@FlattenSubCommands
|
||||
val moduleC = ModuleC()
|
||||
@FlattenSubCommands
|
||||
val moduleDInB = ModuleD()
|
||||
|
||||
@SubCommand
|
||||
fun functionB0(arg0: Int) {
|
||||
Testing.ok(arg0)
|
||||
}
|
||||
@SubCommand("customNameB1")
|
||||
fun functionB1(arg0: Int) {
|
||||
Testing.ok(arg0)
|
||||
}
|
||||
}
|
||||
|
||||
class ModuleC : AbstractSubCommandGroup() {
|
||||
@SubCommand
|
||||
fun functionC0(arg0: Int) {
|
||||
Testing.ok(arg0)
|
||||
}
|
||||
@SubCommand("customNameC1")
|
||||
fun functionC1(arg0: Int) {
|
||||
Testing.ok(arg0)
|
||||
}
|
||||
}
|
||||
|
||||
class ModuleD : CompositeCommand(owner, "USELESS") {
|
||||
@SubCommand
|
||||
fun functionD0(arg0: Int) {
|
||||
Testing.ok(arg0)
|
||||
}
|
||||
@SubCommand("customNameD1")
|
||||
fun functionD1(arg0: Int) {
|
||||
Testing.ok(arg0)
|
||||
}
|
||||
}
|
||||
|
||||
class ModuleE : CompositeCommand(owner, "USELESS") {
|
||||
@SubCommand
|
||||
fun functionE0(arg0: Int) {
|
||||
Testing.ok(arg0)
|
||||
}
|
||||
@SubCommand("customNameE1")
|
||||
fun functionE1(arg0: Int) {
|
||||
Testing.ok(arg0)
|
||||
}
|
||||
}
|
||||
|
||||
@FlattenSubCommands
|
||||
val moduleB = ModuleB()
|
||||
|
||||
@FlattenSubCommands
|
||||
val moduleE = ModuleE()
|
||||
|
||||
@SubCommand
|
||||
fun functionA0(arg0: Int) {
|
||||
Testing.ok(arg0)
|
||||
}
|
||||
}
|
||||
|
||||
class TestCompositeCommand : CompositeCommand(
|
||||
owner,
|
||||
"testComposite", "tsC"
|
||||
@ -163,6 +245,7 @@ internal class InstanceTestCommand : AbstractConsoleInstanceTest() {
|
||||
private val simpleCommand by lazy { TestSimpleCommand() }
|
||||
private val rawCommand by lazy { TestRawCommand() }
|
||||
private val compositeCommand by lazy { TestCompositeCommand() }
|
||||
private val unifiedCompositeCommand by lazy { MyUnifiedCommand() }
|
||||
|
||||
@BeforeTest
|
||||
fun grantPermission() {
|
||||
@ -499,6 +582,42 @@ internal class InstanceTestCommand : AbstractConsoleInstanceTest() {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `unified composite command executing`() = runBlocking {
|
||||
unifiedCompositeCommand.withRegistration {
|
||||
assertEquals(0, withTesting {
|
||||
assertSuccess(unifiedCompositeCommand.execute(sender, "functionA0 0"))
|
||||
})
|
||||
assertEquals(0, withTesting {
|
||||
assertSuccess(unifiedCompositeCommand.execute(sender, "functionB0 0"))
|
||||
})
|
||||
assertEquals(0, withTesting {
|
||||
assertSuccess(unifiedCompositeCommand.execute(sender, "customNameB1 0"))
|
||||
})
|
||||
assertEquals(0, withTesting {
|
||||
assertSuccess(unifiedCompositeCommand.execute(sender, "functionC0 0"))
|
||||
})
|
||||
assertEquals(0, withTesting {
|
||||
assertSuccess(unifiedCompositeCommand.execute(sender, "customNameC1 0"))
|
||||
})
|
||||
assertEquals(0, withTesting {
|
||||
assertSuccess(unifiedCompositeCommand.execute(sender, "functionD0 0"))
|
||||
})
|
||||
assertEquals(0, withTesting {
|
||||
assertSuccess(unifiedCompositeCommand.execute(sender, "customNameD1 0"))
|
||||
})
|
||||
assertEquals(true, unifiedCompositeCommand.usage.contains("/${unifiedCompositeCommand.primaryName} functionA0 <arg0>"))
|
||||
assertEquals(true, unifiedCompositeCommand.usage.contains("/${unifiedCompositeCommand.primaryName} functionB0 <arg0>"))
|
||||
assertEquals(true, unifiedCompositeCommand.usage.contains("/${unifiedCompositeCommand.primaryName} customNameB1 <arg0>"))
|
||||
assertEquals(true, unifiedCompositeCommand.usage.contains("/${unifiedCompositeCommand.primaryName} functionC0 <arg0>"))
|
||||
assertEquals(true, unifiedCompositeCommand.usage.contains("/${unifiedCompositeCommand.primaryName} customNameC1 <arg0>"))
|
||||
assertEquals(true, unifiedCompositeCommand.usage.contains("/${unifiedCompositeCommand.primaryName} functionD0 <arg0>"))
|
||||
assertEquals(true, unifiedCompositeCommand.usage.contains("/${unifiedCompositeCommand.primaryName} customNameD1 <arg0>"))
|
||||
assertEquals(true, unifiedCompositeCommand.usage.contains("/${unifiedCompositeCommand.primaryName} functionE0 <arg0>"))
|
||||
assertEquals(true, unifiedCompositeCommand.usage.contains("/${unifiedCompositeCommand.primaryName} customNameE1 <arg0>"))
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test first param command sender`() = runTest {
|
||||
object : CompositeCommand(owner, "cmd") {
|
||||
|
Loading…
Reference in New Issue
Block a user