1
0
mirror of https://github.com/mamoe/mirai.git synced 2025-04-24 20:43:33 +08:00

rebase as PR at 2022

This commit is contained in:
hundun 2024-08-20 11:53:04 +08:00
parent f7649d225d
commit 3f812d6613
10 changed files with 436 additions and 150 deletions

View File

@ -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;
@ -379,14 +387,6 @@ public abstract class net/mamoe/mirai/console/command/CompositeCommand : net/mam
public fun getUsage ()Ljava/lang/String;
}
protected abstract interface annotation class net/mamoe/mirai/console/command/CompositeCommand$Description : java/lang/annotation/Annotation {
public abstract fun value ()Ljava/lang/String;
}
protected abstract interface annotation class net/mamoe/mirai/console/command/CompositeCommand$SubCommand : java/lang/annotation/Annotation {
public abstract fun value ()[Ljava/lang/String;
}
public final class net/mamoe/mirai/console/command/ConsoleCommandOwner : net/mamoe/mirai/console/command/CommandOwner {
public static final field INSTANCE Lnet/mamoe/mirai/console/command/ConsoleCommandOwner;
public fun getParentPermission ()Lnet/mamoe/mirai/console/permission/Permission;
@ -605,6 +605,21 @@ 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$Description : java/lang/annotation/Annotation {
public abstract fun value ()Ljava/lang/String;
}
public abstract interface annotation class net/mamoe/mirai/console/command/SubCommandGroup$FlattenSubCommands : java/lang/annotation/Annotation {
}
public abstract interface annotation class net/mamoe/mirai/console/command/SubCommandGroup$SubCommand : java/lang/annotation/Annotation {
public abstract fun value ()[Ljava/lang/String;
}
public abstract interface class net/mamoe/mirai/console/command/SystemCommandSender : net/mamoe/mirai/console/command/CommandSender {
public abstract fun isAnsiSupported ()Z
}

View File

@ -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
}

View File

@ -23,6 +23,9 @@ import net.mamoe.mirai.console.MiraiConsoleImplementation
import net.mamoe.mirai.console.MiraiConsoleImplementation.ConsoleDataScope.Companion.get
import net.mamoe.mirai.console.command.CommandManager.INSTANCE.allRegisteredCommands
import net.mamoe.mirai.console.command.CommandManager.INSTANCE.register
import net.mamoe.mirai.console.command.SubCommandGroup.Description
import net.mamoe.mirai.console.command.SubCommandGroup.Name
import net.mamoe.mirai.console.command.SubCommandGroup.SubCommand
import net.mamoe.mirai.console.command.descriptor.CommandArgumentParserException
import net.mamoe.mirai.console.command.descriptor.CommandValueArgumentParser.Companion.map
import net.mamoe.mirai.console.command.descriptor.PermissionIdValueArgumentParser

View File

@ -18,10 +18,10 @@ import net.mamoe.mirai.console.internal.command.CommandReflector
import net.mamoe.mirai.console.internal.command.CompositeCommandSubCommandAnnotationResolver
import net.mamoe.mirai.console.permission.Permission
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
import kotlin.DeprecationLevel.*
import kotlin.annotation.AnnotationRetention.RUNTIME
import kotlin.annotation.AnnotationTarget.FUNCTION
/**
* 复合指令. 指令注册时候会通过反射构造指令解析器.
*
@ -92,7 +92,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) }
@ -116,26 +116,30 @@ public abstract class CompositeCommand(
*/ // open since 2.12
public override val context: CommandArgumentContext = CommandArgumentContext.Builtins + overrideContext
/**
/* *//**
* 标记一个函数为子指令, [value] 为空时使用函数名.
* @param value 子指令名
*/
*//*
@Retention(RUNTIME)
@Target(FUNCTION)
@Deprecated(level = HIDDEN, message = "use SubCommandGroup.SubCommand")
protected annotation class SubCommand(
@ResolveContext(COMMAND_NAME) vararg val value: String = [],
)
/** 指令描述 */
*//** 指令描述 *//*
@Retention(RUNTIME)
@Target(FUNCTION)
@Deprecated(level = HIDDEN, message = "use SubCommandGroup.Description")
protected annotation class Description(val value: String)
/** 参数名, 将参与构成 [usage] */
*//** 参数名, 将参与构成 [usage] *//*
@ConsoleExperimentalApi("Classname might change")
@Retention(RUNTIME)
@Target(AnnotationTarget.VALUE_PARAMETER)
protected annotation class Name(val value: String)
@Deprecated(level = HIDDEN, message = "use SubCommandGroup.Name")
protected annotation class Name(val value: String)*/
}

View File

@ -0,0 +1,46 @@
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(
)
/**
* 1. 标记一个函数为子指令, [value] 为空时使用函数名.
* 2. 标记一个属性为子指令集合且使用sub策略
* @param value 子指令名
*/
@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY)
public annotation class SubCommand(
@ResolveContext(ResolveContext.Kind.COMMAND_NAME) vararg val value: String = [],
)
/** 指令描述 */
@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.FUNCTION)
public annotation class Description(val value: String)
/** 参数名, 由具体Command决定用途 */
@ConsoleExperimentalApi("Classname might change")
@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.VALUE_PARAMETER)
public annotation class Name(val value: String)
}

View File

@ -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)

View File

@ -66,30 +66,76 @@ internal fun Any.flattenCommandComponents(): MessageChain = buildMessageChain {
@Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER")
internal object CompositeCommandSubCommandAnnotationResolver :
SubCommandAnnotationResolver {
override fun hasAnnotation(ownerCommand: Command, function: KFunction<*>) =
function.hasAnnotation<CompositeCommand.SubCommand>()
SubCommandAnnotationResolver<Command> {
override fun isDeclaredSubCommand(ownerCommand: Command, function: KFunction<*>) =
function.hasAnnotation<SubCommandGroup.SubCommand>()
override fun getSubCommandNames(ownerCommand: Command, function: KFunction<*>): Array<out String> {
val annotated = function.findAnnotation<CompositeCommand.SubCommand>()!!.value
override fun getDeclaredSubCommandNames(ownerCommand: Command, function: KFunction<*>): Array<out String> {
val annotated = function.findAnnotation<SubCommandGroup.SubCommand>()!!.value
return if (annotated.isEmpty()) arrayOf(function.name)
else annotated
}
override fun getAnnotatedName(ownerCommand: Command, parameter: KParameter): String? =
parameter.findAnnotation<CompositeCommand.Name>()?.value
parameter.findAnnotation<SubCommandGroup.Name>()?.value
override fun getDescription(ownerCommand: Command, function: KFunction<*>): String? =
function.findAnnotation<CompositeCommand.Description>()?.value
function.findAnnotation<SubCommandGroup.Description>()?.value
override fun isCombinedSubCommands(command: Command, kProperty: KProperty<*>): Boolean =
kProperty.hasAnnotation<SubCommandGroup.FlattenSubCommands>() || kProperty.hasAnnotation<SubCommandGroup.SubCommand>()
override fun getCombinedAdditionNames(command: Command, kProperty: KProperty<*>): Array<out String> {
return if (kProperty.hasAnnotation<SubCommandGroup.FlattenSubCommands>()) {
emptyArray()
} else {
val annotated = kProperty.findAnnotation<SubCommandGroup.SubCommand>()!!.value
if (annotated.isEmpty()) arrayOf(kProperty.name)
else annotated
}
}
}
@Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER")
internal object GroupedCommandSubCommandAnnotationResolver :
SubCommandAnnotationResolver<Any> {
override fun isDeclaredSubCommand(ownerCommand: Any, function: KFunction<*>) =
function.hasAnnotation<SubCommandGroup.SubCommand>()
override fun getDeclaredSubCommandNames(ownerCommand: Any, function: KFunction<*>): Array<out String> {
val annotated = function.findAnnotation<SubCommandGroup.SubCommand>()!!.value
return if (annotated.isEmpty()) arrayOf(function.name)
else annotated
}
override fun getAnnotatedName(ownerCommand: Any, parameter: KParameter): String? =
parameter.findAnnotation<SubCommandGroup.Name>()?.value
override fun getDescription(ownerCommand: Any, function: KFunction<*>): String? =
function.findAnnotation<SubCommandGroup.Description>()?.value
override fun isCombinedSubCommands(command: Any, kProperty: KProperty<*>): Boolean =
kProperty.hasAnnotation<SubCommandGroup.FlattenSubCommands>() || kProperty.hasAnnotation<SubCommandGroup.SubCommand>()
override fun getCombinedAdditionNames(command: Any, kProperty: KProperty<*>): Array<out String> {
return if (kProperty.hasAnnotation<SubCommandGroup.FlattenSubCommands>()) {
emptyArray()
} else {
val annotated = kProperty.findAnnotation<SubCommandGroup.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,21 @@ 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> {
fun isDeclaredSubCommand(ownerCommand: T, function: KFunction<*>): Boolean
fun getDeclaredSubCommandNames(ownerCommand: T, function: KFunction<*>): Array<out String>
fun getAnnotatedName(ownerCommand: T, parameter: KParameter): String?
fun getDescription(ownerCommand: T, function: KFunction<*>): String?
fun isCombinedSubCommands(command: T, kProperty: KProperty<*>): Boolean
fun getCombinedAdditionNames(command: T, kProperty: KProperty<*>): Array<out String>
}
@ConsoleExperimentalApi
@ -111,10 +165,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 +183,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 +193,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 +234,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 +325,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 +499,5 @@ internal class CommandReflector(
}
private fun KParameter.nameForCommandParameter(): String? =
annotationResolver.getAnnotatedName(command, this) ?: this.name
}
annotationResolver.getAnnotatedName(owner, this) ?: this.name
}

View File

@ -18,6 +18,7 @@ import net.mamoe.mirai.console.command.descriptor.ExperimentalCommandDescriptors
import net.mamoe.mirai.console.command.java.JCompositeCommand
import net.mamoe.mirai.console.command.java.JRawCommand
import net.mamoe.mirai.console.command.java.JSimpleCommand
import net.mamoe.mirai.console.command.SubCommandGroup.SubCommand
import net.mamoe.mirai.console.internal.data.classifierAsKClass
import net.mamoe.mirai.message.data.*
import net.mamoe.mirai.utils.safeCast

View File

@ -19,6 +19,7 @@ import net.mamoe.mirai.console.command.descriptor.ExperimentalCommandDescriptors
import net.mamoe.mirai.console.command.descriptor.buildCommandArgumentContext
import net.mamoe.mirai.console.command.java.JCompositeCommand
import net.mamoe.mirai.console.command.java.JSimpleCommand
import net.mamoe.mirai.console.command.SubCommandGroup.SubCommand
import net.mamoe.mirai.console.internal.data.classifierAsKClass
import net.mamoe.mirai.message.data.Image
import net.mamoe.mirai.message.data.MessageContent

View File

@ -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,46 @@ import java.time.temporal.TemporalAccessor
import kotlin.reflect.KClass
import kotlin.test.*
import net.mamoe.mirai.console.command.SubCommandGroup.SubCommand
import net.mamoe.mirai.console.command.SubCommandGroup.FlattenSubCommands
class MyUnifiedCommand : CompositeCommand(
owner, "testMyUnifiedCommand", "tsMUC"
) {
// 插件一个模块的部分功能
class ModuleAPart1 : AbstractSubCommandGroup() {
@SubCommand
fun function1(arg0: Int) {
Testing.ok(arg0)
}
}
// 插件一个模块的另一部分功能
class ModuleAPart2 : AbstractSubCommandGroup() {
@SubCommand
fun function2(arg0: Int) {
Testing.ok(arg0)
}
}
class ModuleACommandGroup: AbstractSubCommandGroup() {
@SubCommand // 与在函数上标注这个注解类似, 它会带 `part1` 这个名称前缀来注册指令. 需要执行 /base part1 function1
val part1 = ModuleAPart1()
@SubCommand("part1NewName") // 也可以使用 SubCommand 的参数来覆盖名称 /base part1NewName function1
val part1b = ModuleAPart1()
@FlattenSubCommands // 新增, 不带前缀注册指令, 执行 /base function2
val part2 = ModuleAPart2()
}
@FlattenSubCommands
val moduleA = ModuleACommandGroup()
@SubCommand
fun about(arg0: Int) {
Testing.ok(arg0)
}
}
class TestCompositeCommand : CompositeCommand(
owner,
"testComposite", "tsC"
@ -163,6 +204,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 +541,24 @@ internal class InstanceTestCommand : AbstractConsoleInstanceTest() {
}
}
@Test
fun `container composite command executing`() = runBlocking {
unifiedCompositeCommand.withRegistration {
assertEquals(0, withTesting {
assertSuccess(unifiedCompositeCommand.execute(sender, "part1 function1 0"))
})
assertEquals(0, withTesting {
assertSuccess(unifiedCompositeCommand.execute(sender, "part1NewName function1 0"))
})
assertEquals(0, withTesting {
assertSuccess(unifiedCompositeCommand.execute(sender, "function2 0"))
})
assertEquals(0, withTesting {
assertSuccess(unifiedCompositeCommand.execute(sender, "about 0"))
})
}
}
@Test
fun `test first param command sender`() = runTest {
object : CompositeCommand(owner, "cmd") {