mirror of
https://github.com/mamoe/mirai.git
synced 2025-01-10 18:40:15 +08:00
Command resolving
This commit is contained in:
parent
ccc9128023
commit
df461290c0
@ -11,15 +11,16 @@
|
||||
|
||||
package net.mamoe.mirai.console.command
|
||||
|
||||
import net.mamoe.kjbb.JvmBlockingBridge
|
||||
import net.mamoe.mirai.console.command.CommandManager.INSTANCE.executeCommand
|
||||
import net.mamoe.mirai.console.command.CommandManager.INSTANCE.register
|
||||
import net.mamoe.mirai.console.command.descriptor.CommandArgumentContextAware
|
||||
import net.mamoe.mirai.console.command.descriptor.CommandSignatureVariant
|
||||
import net.mamoe.mirai.console.command.descriptor.ExperimentalCommandDescriptors
|
||||
import net.mamoe.mirai.console.command.java.JCommand
|
||||
import net.mamoe.mirai.console.compiler.common.ResolveContext
|
||||
import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.COMMAND_NAME
|
||||
import net.mamoe.mirai.console.permission.Permission
|
||||
import net.mamoe.mirai.console.permission.PermissionId
|
||||
import net.mamoe.mirai.message.data.MessageChain
|
||||
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
|
||||
|
||||
/**
|
||||
* 指令
|
||||
@ -30,6 +31,8 @@ import net.mamoe.mirai.message.data.MessageChain
|
||||
* @see CompositeCommand 复合指令
|
||||
* @see SimpleCommand 简单的, 支持参数自动解析的指令
|
||||
*
|
||||
* @see CommandArgumentContextAware
|
||||
*
|
||||
* @see JCommand 为 Java 用户添加协程帮助的 [Command]
|
||||
*/
|
||||
public interface Command {
|
||||
@ -48,6 +51,13 @@ public interface Command {
|
||||
@ResolveContext(COMMAND_NAME)
|
||||
public val secondaryNames: Array<out String>
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
@ConsoleExperimentalApi("Property name is experimental")
|
||||
@ExperimentalCommandDescriptors
|
||||
public val overloads: List<CommandSignatureVariant>
|
||||
|
||||
/**
|
||||
* 用法说明, 用于发送给用户. [usage] 一般包含 [description].
|
||||
*/
|
||||
@ -80,16 +90,6 @@ public interface Command {
|
||||
*/
|
||||
public val owner: CommandOwner
|
||||
|
||||
/**
|
||||
* 在指令被执行时调用.
|
||||
*
|
||||
* @param args 精确的指令参数. [MessageChain] 每个元素代表一个精确的参数.
|
||||
*
|
||||
* @see CommandManager.executeCommand 查看更多信息
|
||||
*/
|
||||
@JvmBlockingBridge
|
||||
public suspend fun CommandSender.onCommand(args: MessageChain)
|
||||
|
||||
public companion object {
|
||||
|
||||
/**
|
||||
@ -116,12 +116,3 @@ public interface Command {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 调用 [Command.onCommand]
|
||||
* @see Command.onCommand
|
||||
*/
|
||||
@JvmSynthetic
|
||||
public suspend inline fun Command.onCommand(sender: CommandSender, args: MessageChain): Unit =
|
||||
sender.onCommand(args)
|
||||
|
||||
|
@ -12,6 +12,8 @@
|
||||
package net.mamoe.mirai.console.command
|
||||
|
||||
import net.mamoe.mirai.console.command.CommandExecuteResult.CommandExecuteStatus
|
||||
import net.mamoe.mirai.console.command.descriptor.ExperimentalCommandDescriptors
|
||||
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
|
||||
import net.mamoe.mirai.message.data.Message
|
||||
import net.mamoe.mirai.message.data.MessageChain
|
||||
import kotlin.contracts.contract
|
||||
@ -21,6 +23,8 @@ import kotlin.contracts.contract
|
||||
*
|
||||
* @see CommandExecuteStatus
|
||||
*/
|
||||
@ConsoleExperimentalApi("Not yet implemented")
|
||||
@ExperimentalCommandDescriptors
|
||||
public sealed class CommandExecuteResult {
|
||||
/** 指令最终执行状态 */
|
||||
public abstract val status: CommandExecuteStatus
|
||||
|
@ -8,16 +8,26 @@
|
||||
*/
|
||||
|
||||
@file:Suppress(
|
||||
"NOTHING_TO_INLINE", "unused", "INVISIBLE_MEMBER", "INVISIBLE_REFERENCE", "RESULT_CLASS_IN_RETURN_TYPE",
|
||||
"NOTHING_TO_INLINE", "unused",
|
||||
"MemberVisibilityCanBePrivate", "INAPPLICABLE_JVM_NAME"
|
||||
)
|
||||
@file:JvmName("CommandManagerKt")
|
||||
|
||||
package net.mamoe.mirai.console.command
|
||||
|
||||
import net.mamoe.kjbb.JvmBlockingBridge
|
||||
import net.mamoe.mirai.console.command.CommandManager.INSTANCE.executeCommand
|
||||
import net.mamoe.mirai.console.command.descriptor.ExperimentalCommandDescriptors
|
||||
import net.mamoe.mirai.console.command.parse.CommandCall
|
||||
import net.mamoe.mirai.console.command.parse.CommandCallParser
|
||||
import net.mamoe.mirai.console.command.parse.CommandCallParser.Companion.parseCommandCall
|
||||
import net.mamoe.mirai.console.command.resolve.CommandCallResolver
|
||||
import net.mamoe.mirai.console.command.resolve.ResolvedCommandCall
|
||||
import net.mamoe.mirai.console.command.resolve.ResolvedCommandCall.Companion.call
|
||||
import net.mamoe.mirai.console.extensions.CommandCallResolverProvider
|
||||
import net.mamoe.mirai.console.internal.command.CommandManagerImpl
|
||||
import net.mamoe.mirai.console.internal.command.CommandManagerImpl.executeCommand
|
||||
import net.mamoe.mirai.console.internal.extension.GlobalComponentStorage
|
||||
import net.mamoe.mirai.console.permission.PermissionService.Companion.testPermission
|
||||
import net.mamoe.mirai.message.data.*
|
||||
|
||||
/**
|
||||
@ -101,19 +111,45 @@ public interface CommandManager {
|
||||
* 注意: 字符串与消息元素之间不需要空格, 会被强制分割. 如 "bar[mirai:image:]" 会被分割为 "bar" 和 [Image] 类型的消息元素.
|
||||
* 3. 参数解析. 各类型指令实现不同. 详见 [RawCommand], [CompositeCommand], [SimpleCommand]
|
||||
*
|
||||
* ### 未来的扩展
|
||||
* 在将来, 参数语法分析过程可能会被扩展, 允许插件自定义处理方式, 因此可能不会简单地使用空格分隔.
|
||||
* ### 扩展
|
||||
* 参数语法分析过程可能会被扩展, 插件可以自定义处理方式, 因此可能不会简单地使用空格分隔.
|
||||
*
|
||||
* @param message 一条完整的指令. 如 "/managers add 123456.123456"
|
||||
* @param checkPermission 为 `true` 时检查权限
|
||||
*
|
||||
* @see CommandCallParser
|
||||
* @see CommandCallResolver
|
||||
*
|
||||
* @return 执行结果
|
||||
*/
|
||||
@JvmBlockingBridge
|
||||
public suspend fun CommandSender.executeCommand(
|
||||
// @JvmBlockingBridge
|
||||
@OptIn(ExperimentalCommandDescriptors::class)
|
||||
public suspend fun executeCommand(
|
||||
caller: CommandSender,
|
||||
message: Message,
|
||||
checkPermission: Boolean = true,
|
||||
): CommandExecuteResult
|
||||
): CommandExecuteResult {
|
||||
val call = message.asMessageChain().parseCommandCall(caller) ?: return CommandExecuteResult.CommandNotFound("")
|
||||
val resolved = call.resolve() ?: return CommandExecuteResult.CommandNotFound(call.calleeName)
|
||||
|
||||
val command = resolved.callee
|
||||
|
||||
if (checkPermission && !command.permission.testPermission(caller)) {
|
||||
return CommandExecuteResult.PermissionDenied(command, call.calleeName)
|
||||
}
|
||||
|
||||
return kotlin.runCatching {
|
||||
resolved.call()
|
||||
}.fold(
|
||||
onSuccess = {
|
||||
CommandExecuteResult.Success(resolved.callee, call.calleeName, EmptyMessageChain)
|
||||
},
|
||||
onFailure = {
|
||||
CommandExecuteResult.ExecutionFailed(it, resolved.callee, call.calleeName, EmptyMessageChain)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 解析并执行一个指令
|
||||
@ -124,35 +160,33 @@ public interface CommandManager {
|
||||
* @return 执行结果
|
||||
* @see executeCommand
|
||||
*/
|
||||
@JvmBlockingBridge
|
||||
// @JvmBlockingBridge
|
||||
public suspend fun CommandSender.executeCommand(
|
||||
message: String,
|
||||
checkPermission: Boolean = true,
|
||||
): CommandExecuteResult = executeCommand(PlainText(message).asMessageChain(), checkPermission)
|
||||
): CommandExecuteResult = executeCommand(this, PlainText(message).asMessageChain(), checkPermission)
|
||||
|
||||
@JvmName("resolveCall")
|
||||
@ExperimentalCommandDescriptors
|
||||
public fun CommandCall.resolve(): ResolvedCommandCall? {
|
||||
GlobalComponentStorage.run {
|
||||
CommandCallResolverProvider.useExtensions { provider ->
|
||||
provider.instance.resolve(this@resolve)?.let { return it }
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行一个确切的指令
|
||||
* @see executeCommand 获取更多信息
|
||||
* 从 [指令名称][commandName] 匹配对应的 [Command].
|
||||
*
|
||||
* #### 实现细节
|
||||
* - [commandName] 带有 [commandPrefix] 时可以匹配到所有指令
|
||||
* - [commandName] 不带有 [commandPrefix] 时只能匹配到 [Command.prefixOptional] 的指令
|
||||
*
|
||||
* @param commandName 可能带有或不带有 [commandPrefix].
|
||||
*/
|
||||
@JvmBlockingBridge
|
||||
@JvmName("executeCommand")
|
||||
public suspend fun Command.execute(
|
||||
sender: CommandSender,
|
||||
arguments: Message = EmptyMessageChain,
|
||||
checkPermission: Boolean = true,
|
||||
): CommandExecuteResult
|
||||
|
||||
/**
|
||||
* 执行一个确切的指令
|
||||
* @see executeCommand 获取更多信息
|
||||
*/
|
||||
@JvmBlockingBridge
|
||||
@JvmName("executeCommand")
|
||||
public suspend fun Command.execute(
|
||||
sender: CommandSender,
|
||||
arguments: String = "",
|
||||
checkPermission: Boolean = true,
|
||||
): CommandExecuteResult = execute(sender, PlainText(arguments).asMessageChain(), checkPermission)
|
||||
public fun matchCommand(commandName: String): Command?
|
||||
|
||||
public companion object INSTANCE : CommandManager by CommandManagerImpl {
|
||||
// TODO: 2020/8/20 https://youtrack.jetbrains.com/issue/KT-41191
|
||||
@ -167,52 +201,37 @@ public interface CommandManager {
|
||||
override val allRegisteredCommands: List<Command>
|
||||
get() = CommandManagerImpl.allRegisteredCommands
|
||||
|
||||
|
||||
override suspend fun Command.execute(
|
||||
sender: CommandSender,
|
||||
arguments: Message,
|
||||
checkPermission: Boolean,
|
||||
): CommandExecuteResult =
|
||||
CommandManagerImpl.run { execute(sender, arguments = arguments, checkPermission = checkPermission) }
|
||||
|
||||
override suspend fun CommandSender.executeCommand(
|
||||
message: String,
|
||||
checkPermission: Boolean,
|
||||
): CommandExecuteResult = CommandManagerImpl.run { executeCommand(message, checkPermission) }
|
||||
|
||||
override suspend fun Command.execute(
|
||||
sender: CommandSender,
|
||||
arguments: String,
|
||||
checkPermission: Boolean,
|
||||
): CommandExecuteResult = CommandManagerImpl.run { execute(sender, arguments, checkPermission) }
|
||||
|
||||
override suspend fun CommandSender.executeCommand(
|
||||
message: Message,
|
||||
checkPermission: Boolean,
|
||||
): CommandExecuteResult = CommandManagerImpl.run { executeCommand(message, checkPermission) }
|
||||
|
||||
/**
|
||||
* 执行一个确切的指令
|
||||
* @see execute 获取更多信息
|
||||
*/
|
||||
public suspend fun CommandSender.execute(
|
||||
command: Command,
|
||||
arguments: Message,
|
||||
checkPermission: Boolean = true,
|
||||
): CommandExecuteResult {
|
||||
return command.execute(this, arguments, checkPermission)
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行一个确切的指令
|
||||
* @see execute 获取更多信息
|
||||
*/
|
||||
public suspend fun CommandSender.execute(
|
||||
command: Command,
|
||||
arguments: String,
|
||||
checkPermission: Boolean = true,
|
||||
): CommandExecuteResult {
|
||||
return command.execute(this, arguments, checkPermission)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行一个确切的指令
|
||||
* @see executeCommand 获取更多信息
|
||||
*/
|
||||
// @JvmBlockingBridge
|
||||
// @JvmName("executeCommand")
|
||||
public suspend fun Command.execute(
|
||||
sender: CommandSender,
|
||||
arguments: String = "",
|
||||
checkPermission: Boolean = true,
|
||||
): CommandExecuteResult = execute(sender, PlainText(arguments).asMessageChain(), checkPermission)
|
||||
|
||||
/**
|
||||
* 执行一个确切的指令
|
||||
* @see executeCommand 获取更多信息
|
||||
*/
|
||||
// @JvmBlockingBridge
|
||||
// @JvmName("executeCommand")
|
||||
public suspend fun Command.execute(
|
||||
sender: CommandSender,
|
||||
arguments: Message = EmptyMessageChain,
|
||||
checkPermission: Boolean = true,
|
||||
): CommandExecuteResult {
|
||||
// TODO: 2020/10/18 net.mamoe.mirai.console.command.CommandManager.execute
|
||||
val chain = buildMessageChain {
|
||||
append(this@execute.primaryName)
|
||||
append(' ')
|
||||
append(arguments)
|
||||
}
|
||||
return CommandManager.executeCommand(sender, chain, checkPermission)
|
||||
}
|
@ -20,7 +20,6 @@ import kotlinx.coroutines.launch
|
||||
import net.mamoe.kjbb.JvmBlockingBridge
|
||||
import net.mamoe.mirai.Bot
|
||||
import net.mamoe.mirai.console.MiraiConsole
|
||||
import net.mamoe.mirai.console.command.CommandManager.INSTANCE.execute
|
||||
import net.mamoe.mirai.console.command.CommandSender.Companion.asCommandSender
|
||||
import net.mamoe.mirai.console.command.CommandSender.Companion.asMemberCommandSender
|
||||
import net.mamoe.mirai.console.command.CommandSender.Companion.asTempCommandSender
|
||||
|
@ -124,13 +124,26 @@ public abstract class CompositeCommand(
|
||||
@Target(AnnotationTarget.VALUE_PARAMETER)
|
||||
protected annotation class Name(val value: String)
|
||||
|
||||
public final override suspend fun CommandSender.onCommand(args: MessageChain) {
|
||||
matchSubCommand(args)?.parseAndExecute(this, args, true) ?: kotlin.run {
|
||||
defaultSubCommand.onCommand(this, args)
|
||||
@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)
|
||||
}
|
||||
|
@ -11,14 +11,18 @@
|
||||
|
||||
package net.mamoe.mirai.console.command
|
||||
|
||||
import net.mamoe.mirai.console.command.CommandManager.INSTANCE.execute
|
||||
import net.mamoe.mirai.console.command.CommandManager.INSTANCE.executeCommand
|
||||
import net.mamoe.mirai.console.command.descriptor.CommandSignatureVariant
|
||||
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.compiler.common.ResolveContext
|
||||
import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.COMMAND_NAME
|
||||
import net.mamoe.mirai.console.internal.command.createOrFindCommandPermission
|
||||
import net.mamoe.mirai.console.permission.Permission
|
||||
import net.mamoe.mirai.message.data.MessageChain
|
||||
import net.mamoe.mirai.message.data.MessageChainBuilder
|
||||
|
||||
/**
|
||||
* 无参数解析, 接收原生参数的指令.
|
||||
@ -52,6 +56,15 @@ public abstract class RawCommand(
|
||||
) : Command {
|
||||
public override val permission: Permission by lazy { createOrFindCommandPermission(parentPermission) }
|
||||
|
||||
@ExperimentalCommandDescriptors
|
||||
override val overloads: List<CommandSignatureVariant> = listOf(
|
||||
CommandSignatureVariantImpl(listOf(CommandValueParameter.UserDefinedType.createRequired<MessageChain>("args", true))) { call ->
|
||||
val sender = call.caller
|
||||
val arguments = call.rawValueArguments
|
||||
sender.onCommand(arguments.mapTo(MessageChainBuilder()) { it.value }.build())
|
||||
}
|
||||
)
|
||||
|
||||
/**
|
||||
* 在指令被执行时调用.
|
||||
*
|
||||
@ -59,7 +72,7 @@ public abstract class RawCommand(
|
||||
*
|
||||
* @see CommandManager.execute 查看更多信息
|
||||
*/
|
||||
public abstract override suspend fun CommandSender.onCommand(args: MessageChain)
|
||||
public abstract suspend fun CommandSender.onCommand(args: MessageChain)
|
||||
}
|
||||
|
||||
|
||||
|
@ -61,6 +61,14 @@ public abstract class SimpleCommand(
|
||||
) : Command, AbstractReflectionCommand(owner, primaryName, secondaryNames = secondaryNames, description, parentPermission, prefixOptional),
|
||||
CommandArgumentContextAware {
|
||||
|
||||
@ExperimentalCommandDescriptors
|
||||
override val overloads: List<CommandSignatureVariant> = listOf(
|
||||
CommandSignatureVariantImpl(listOf(CommandValueParameter.UserDefinedType.createRequired<MessageChain>("args", true))) { call ->
|
||||
val sender = call.caller
|
||||
subCommands.single().onCommand(sender, call.resolvedValueArguments)
|
||||
}
|
||||
)
|
||||
|
||||
/**
|
||||
* 自动根据带有 [Handler] 注解的函数签名生成 [usage]. 也可以被覆盖.
|
||||
*/
|
||||
@ -76,10 +84,6 @@ public abstract class SimpleCommand(
|
||||
*/
|
||||
public override val context: CommandArgumentContext = CommandArgumentContext.Builtins + overrideContext
|
||||
|
||||
public final override suspend fun CommandSender.onCommand(args: MessageChain) {
|
||||
subCommands.single().parseAndExecute(this, args, false)
|
||||
}
|
||||
|
||||
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." }
|
||||
|
@ -9,19 +9,39 @@
|
||||
|
||||
package net.mamoe.mirai.console.command.descriptor
|
||||
|
||||
import net.mamoe.kjbb.JvmBlockingBridge
|
||||
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.createRequired
|
||||
import net.mamoe.mirai.console.command.parse.CommandValueArgument
|
||||
import net.mamoe.mirai.console.command.resolve.ResolvedCommandCall
|
||||
import net.mamoe.mirai.console.internal.data.classifierAsKClassOrNull
|
||||
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
|
||||
import net.mamoe.mirai.console.util.safeCast
|
||||
import kotlin.reflect.KClass
|
||||
import kotlin.reflect.KType
|
||||
import kotlin.reflect.full.isSubtypeOf
|
||||
import kotlin.reflect.typeOf
|
||||
|
||||
@ExperimentalCommandDescriptors
|
||||
public interface CommandDescriptor {
|
||||
public val overloads: List<CommandSignatureVariant>
|
||||
}
|
||||
|
||||
/**
|
||||
* @see CommandSignatureVariantImpl
|
||||
*/
|
||||
@ExperimentalCommandDescriptors
|
||||
public interface CommandSignatureVariant {
|
||||
public val valueParameters: List<CommandValueParameter<*>>
|
||||
|
||||
@JvmBlockingBridge
|
||||
public suspend fun call(resolvedCommandCall: ResolvedCommandCall)
|
||||
}
|
||||
|
||||
@ExperimentalCommandDescriptors
|
||||
public class CommandSignatureVariantImpl(
|
||||
override val valueParameters: List<CommandValueParameter<*>>,
|
||||
private val onCall: suspend CommandSignatureVariantImpl.(resolvedCommandCall: ResolvedCommandCall) -> Unit,
|
||||
) : CommandSignatureVariant {
|
||||
override suspend fun call(resolvedCommandCall: ResolvedCommandCall) {
|
||||
return onCall(resolvedCommandCall)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -43,23 +63,85 @@ public interface ICommandParameter<T : Any?> {
|
||||
* Reified type of [T]
|
||||
*/
|
||||
public val type: KType
|
||||
|
||||
public val isVararg: Boolean
|
||||
|
||||
public fun accepts(argument: CommandValueArgument, commandArgumentContext: CommandArgumentContext?): Boolean =
|
||||
accepting(argument, commandArgumentContext).isAcceptable
|
||||
|
||||
public fun accepting(argument: CommandValueArgument, commandArgumentContext: CommandArgumentContext?): ArgumentAcceptance
|
||||
}
|
||||
|
||||
@ExperimentalCommandDescriptors
|
||||
public sealed class ArgumentAcceptance(
|
||||
/**
|
||||
* Higher means more acceptable
|
||||
*/
|
||||
@ConsoleExperimentalApi
|
||||
public val acceptanceLevel: Int,
|
||||
) {
|
||||
public object Direct : ArgumentAcceptance(Int.MAX_VALUE)
|
||||
|
||||
public class WithTypeConversion(
|
||||
public val typeVariant: TypeVariant<*>,
|
||||
) : ArgumentAcceptance(20)
|
||||
|
||||
public class WithContextualConversion(
|
||||
public val parser: CommandValueArgumentParser<*>,
|
||||
) : ArgumentAcceptance(10)
|
||||
|
||||
public class ResolutionAmbiguity(
|
||||
public val candidates: List<TypeVariant<*>>,
|
||||
) : ArgumentAcceptance(0)
|
||||
|
||||
public object Impossible : ArgumentAcceptance(-1)
|
||||
|
||||
public companion object {
|
||||
@JvmStatic
|
||||
public val ArgumentAcceptance.isAcceptable: Boolean
|
||||
get() = acceptanceLevel > 0
|
||||
|
||||
@JvmStatic
|
||||
public val ArgumentAcceptance.isNotAcceptable: Boolean
|
||||
get() = acceptanceLevel <= 0
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ExperimentalCommandDescriptors
|
||||
public sealed class CommandValueParameter<T> : ICommandParameter<T> {
|
||||
init {
|
||||
@Suppress("LeakingThis")
|
||||
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) {
|
||||
"defaultValue is not instance of type"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public override fun accepting(argument: CommandValueArgument, commandArgumentContext: CommandArgumentContext?): ArgumentAcceptance {
|
||||
val expectingType = this.type
|
||||
|
||||
if (argument.type.isSubtypeOf(expectingType)) return ArgumentAcceptance.Direct
|
||||
|
||||
argument.typeVariants.associateWith { typeVariant ->
|
||||
if (typeVariant.outType.isSubtypeOf(expectingType)) {
|
||||
// TODO: 2020/10/11 resolution ambiguity
|
||||
return ArgumentAcceptance.WithTypeConversion(typeVariant)
|
||||
}
|
||||
}
|
||||
expectingType.classifierAsKClassOrNull()?.let { commandArgumentContext?.get(it) }?.let { parser ->
|
||||
return ArgumentAcceptance.WithContextualConversion(parser)
|
||||
}
|
||||
return ArgumentAcceptance.Impossible
|
||||
}
|
||||
|
||||
public class StringConstant(
|
||||
public override val name: String,
|
||||
public val expectingValue: String,
|
||||
) : CommandValueParameter<String>() {
|
||||
public override val type: KType get() = STRING_TYPE
|
||||
public override val defaultValue: Nothing? get() = null
|
||||
public override val isOptional: Boolean get() = false
|
||||
public override val isVararg: Boolean get() = false
|
||||
|
||||
private companion object {
|
||||
@OptIn(ExperimentalStdlibApi::class)
|
||||
@ -67,23 +149,28 @@ public sealed class CommandValueParameter<T> : ICommandParameter<T> {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @see createOptional
|
||||
* @see createRequired
|
||||
*/
|
||||
public class UserDefinedType<T>(
|
||||
public override val name: String,
|
||||
public override val defaultValue: T?,
|
||||
public override val isOptional: Boolean,
|
||||
public override val isVararg: Boolean,
|
||||
public override val type: KType,
|
||||
) : CommandValueParameter<T>() {
|
||||
public companion object {
|
||||
@JvmStatic
|
||||
public inline fun <reified T : Any> createOptional(name: String, defaultValue: T): UserDefinedType<T> {
|
||||
public inline fun <reified T : Any> createOptional(name: String, isVararg: Boolean, defaultValue: T): UserDefinedType<T> {
|
||||
@OptIn(ExperimentalStdlibApi::class)
|
||||
return UserDefinedType(name, defaultValue, true, typeOf<T>())
|
||||
return UserDefinedType(name, defaultValue, true, isVararg, typeOf<T>())
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
public inline fun <reified T : Any> createRequired(name: String): UserDefinedType<T> {
|
||||
public inline fun <reified T : Any> createRequired(name: String, isVararg: Boolean): UserDefinedType<T> {
|
||||
@OptIn(ExperimentalStdlibApi::class)
|
||||
return UserDefinedType(name, null, false, typeOf<T>())
|
||||
return UserDefinedType(name, null, false, isVararg, typeOf<T>())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -9,13 +9,8 @@
|
||||
|
||||
package net.mamoe.mirai.console.command.java
|
||||
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import net.mamoe.mirai.console.command.Command
|
||||
import net.mamoe.mirai.console.command.CommandManager
|
||||
import net.mamoe.mirai.console.command.CommandManager.INSTANCE.executeCommand
|
||||
import net.mamoe.mirai.console.command.CommandSender
|
||||
import net.mamoe.mirai.message.data.MessageChain
|
||||
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
|
||||
|
||||
/**
|
||||
* 为 Java 用户添加协程帮助的 [Command].
|
||||
@ -24,17 +19,7 @@ import net.mamoe.mirai.message.data.MessageChain
|
||||
*
|
||||
* @see Command
|
||||
*/
|
||||
@ConsoleExperimentalApi("Not yet supported")
|
||||
public interface JCommand : Command {
|
||||
public override suspend fun CommandSender.onCommand(args: MessageChain) {
|
||||
withContext(Dispatchers.IO) { onCommand(this@onCommand, args) }
|
||||
}
|
||||
|
||||
/**
|
||||
* 在指令被执行时调用.
|
||||
*
|
||||
* @param args 精确的指令参数. [MessageChain] 每个元素代表一个精确的参数.
|
||||
*
|
||||
* @see CommandManager.executeCommand 查看更多信息
|
||||
*/
|
||||
public fun onCommand(sender: CommandSender, args: MessageChain) // overrides blocking bridge
|
||||
// TODO: 2020/10/18 JCommand
|
||||
}
|
@ -17,6 +17,7 @@ import net.mamoe.mirai.console.command.descriptor.buildCommandArgumentContext
|
||||
import net.mamoe.mirai.console.compiler.common.ResolveContext
|
||||
import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.COMMAND_NAME
|
||||
import net.mamoe.mirai.console.permission.Permission
|
||||
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
|
||||
|
||||
/**
|
||||
* 复合指令. 指令注册时候会通过反射构造指令解析器.
|
||||
@ -68,6 +69,7 @@ import net.mamoe.mirai.console.permission.Permission
|
||||
*
|
||||
* @see buildCommandArgumentContext
|
||||
*/
|
||||
@ConsoleExperimentalApi("Not yet supported")
|
||||
public abstract class JCompositeCommand
|
||||
@JvmOverloads constructor(
|
||||
owner: CommandOwner,
|
||||
|
@ -9,16 +9,15 @@
|
||||
|
||||
package net.mamoe.mirai.console.command.java
|
||||
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import net.mamoe.mirai.console.command.*
|
||||
import net.mamoe.mirai.console.command.CommandManager.INSTANCE.execute
|
||||
import net.mamoe.mirai.console.command.BuiltInCommands
|
||||
import net.mamoe.mirai.console.command.Command
|
||||
import net.mamoe.mirai.console.command.CommandManager
|
||||
import net.mamoe.mirai.console.command.CommandOwner
|
||||
import net.mamoe.mirai.console.compiler.common.ResolveContext
|
||||
import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.COMMAND_NAME
|
||||
import net.mamoe.mirai.console.internal.command.createOrFindCommandPermission
|
||||
import net.mamoe.mirai.console.permission.Permission
|
||||
import net.mamoe.mirai.message.data.MessageChain
|
||||
import net.mamoe.mirai.message.data.SingleMessage
|
||||
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
|
||||
|
||||
/**
|
||||
* 供 Java 用户继承
|
||||
@ -46,6 +45,7 @@ import net.mamoe.mirai.message.data.SingleMessage
|
||||
*
|
||||
* @see JRawCommand
|
||||
*/
|
||||
@ConsoleExperimentalApi("Not yet supported")
|
||||
public abstract class JRawCommand
|
||||
@JvmOverloads constructor(
|
||||
/**
|
||||
@ -72,19 +72,4 @@ public abstract class JRawCommand
|
||||
/** 为 `true` 时表示 [指令前缀][CommandManager.commandPrefix] 可选 */
|
||||
public final override var prefixOptional: Boolean = false
|
||||
protected set
|
||||
|
||||
/**
|
||||
* 在指令被执行时调用.
|
||||
*
|
||||
* @param args 指令参数. 数组元素类型可能是 [SingleMessage] 或 [String]. 且已经以 ' ' 分割.
|
||||
*
|
||||
* @see CommandManager.execute 查看更多信息
|
||||
*/
|
||||
@Suppress("INAPPLICABLE_JVM_NAME")
|
||||
@JvmName("onCommand")
|
||||
public abstract fun onCommand(sender: CommandSender, args: MessageChain)
|
||||
|
||||
public final override suspend fun CommandSender.onCommand(args: MessageChain) {
|
||||
withContext(Dispatchers.IO) { onCommand(this@onCommand, args) }
|
||||
}
|
||||
}
|
@ -17,6 +17,7 @@ import net.mamoe.mirai.console.command.descriptor.CommandArgumentContext
|
||||
import net.mamoe.mirai.console.compiler.common.ResolveContext
|
||||
import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.COMMAND_NAME
|
||||
import net.mamoe.mirai.console.permission.Permission
|
||||
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
|
||||
|
||||
/**
|
||||
* Java 实现:
|
||||
@ -42,6 +43,7 @@ import net.mamoe.mirai.console.permission.Permission
|
||||
* @see SimpleCommand
|
||||
* @see [CommandManager.executeCommand]
|
||||
*/
|
||||
@ConsoleExperimentalApi("Not yet supported")
|
||||
public abstract class JSimpleCommand(
|
||||
owner: CommandOwner,
|
||||
@ResolveContext(COMMAND_NAME) primaryName: String,
|
||||
|
@ -14,6 +14,8 @@ import net.mamoe.mirai.message.data.MessageChain
|
||||
*
|
||||
* @see CommandCallResolver The call resolver for [CommandCall] to become [ResolvedCommandCall]
|
||||
* @see CommandCallParserProvider The extension point
|
||||
*
|
||||
* @see SpaceSeparatedCommandCallParser
|
||||
*/
|
||||
@ConsoleExperimentalApi
|
||||
@ExperimentalCommandDescriptors
|
||||
@ -24,7 +26,7 @@ public interface CommandCallParser {
|
||||
*
|
||||
* @return `null` if unable to parse (i.e. due to syntax errors).
|
||||
*/
|
||||
public fun parse(sender: CommandSender, message: MessageChain): CommandCall?
|
||||
public fun parse(caller: CommandSender, message: MessageChain): CommandCall?
|
||||
|
||||
public companion object {
|
||||
/**
|
||||
|
@ -14,6 +14,7 @@ import net.mamoe.mirai.console.command.descriptor.MessageContentTypeVariant
|
||||
import net.mamoe.mirai.console.command.descriptor.NoValueArgumentMappingException
|
||||
import net.mamoe.mirai.console.command.descriptor.TypeVariant
|
||||
import net.mamoe.mirai.message.data.MessageContent
|
||||
import kotlin.reflect.KType
|
||||
import kotlin.reflect.full.isSubtypeOf
|
||||
import kotlin.reflect.typeOf
|
||||
|
||||
@ -34,6 +35,7 @@ public interface CommandArgument
|
||||
*/
|
||||
@ExperimentalCommandDescriptors
|
||||
public interface CommandValueArgument : CommandArgument {
|
||||
public val type: KType
|
||||
public val value: RawCommandArgument
|
||||
public val typeVariants: List<TypeVariant<*>>
|
||||
}
|
||||
@ -45,6 +47,8 @@ public interface CommandValueArgument : CommandArgument {
|
||||
public data class InvariantCommandValueArgument(
|
||||
public override val value: RawCommandArgument,
|
||||
) : CommandValueArgument {
|
||||
@OptIn(ExperimentalStdlibApi::class)
|
||||
override val type: KType = typeOf<MessageContent>()
|
||||
override val typeVariants: List<TypeVariant<*>> = listOf(MessageContentTypeVariant)
|
||||
}
|
||||
|
||||
@ -57,10 +61,14 @@ public fun <T> CommandValueArgument.mapValue(typeVariant: TypeVariant<T>): T = t
|
||||
public inline fun <reified T> CommandValueArgument.mapToType(): T =
|
||||
mapToTypeOrNull() ?: throw NoValueArgumentMappingException(this, typeOf<T>())
|
||||
|
||||
@OptIn(ExperimentalStdlibApi::class)
|
||||
@ExperimentalCommandDescriptors
|
||||
public inline fun <reified T> CommandValueArgument.mapToTypeOrNull(): T? {
|
||||
public fun <T> CommandValueArgument.mapToType(type: KType): T =
|
||||
mapToTypeOrNull(type) ?: throw NoValueArgumentMappingException(this, type)
|
||||
|
||||
@ExperimentalCommandDescriptors
|
||||
public fun <T> CommandValueArgument.mapToTypeOrNull(expectingType: KType): T? {
|
||||
@OptIn(ExperimentalStdlibApi::class)
|
||||
val expectingType = typeOf<T>()
|
||||
val result = typeVariants
|
||||
.filter { it.outType.isSubtypeOf(expectingType) }
|
||||
.also {
|
||||
@ -71,5 +79,12 @@ public inline fun <reified T> CommandValueArgument.mapToTypeOrNull(): T? {
|
||||
acc
|
||||
else typeVariant
|
||||
}
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
return result.mapValue(value) as T
|
||||
}
|
||||
|
||||
@ExperimentalCommandDescriptors
|
||||
public inline fun <reified T> CommandValueArgument.mapToTypeOrNull(): T? {
|
||||
@OptIn(ExperimentalStdlibApi::class)
|
||||
return mapToTypeOrNull(typeOf<T>())
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
package net.mamoe.mirai.console.command.parse
|
||||
|
||||
import net.mamoe.mirai.console.command.CommandSender
|
||||
import net.mamoe.mirai.console.command.descriptor.ExperimentalCommandDescriptors
|
||||
import net.mamoe.mirai.console.internal.command.flattenCommandComponents
|
||||
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
|
||||
import net.mamoe.mirai.message.data.MessageChain
|
||||
import net.mamoe.mirai.message.data.MessageContent
|
||||
import net.mamoe.mirai.message.data.content
|
||||
|
||||
@ConsoleExperimentalApi
|
||||
@ExperimentalCommandDescriptors
|
||||
public object SpaceSeparatedCommandCallParser : CommandCallParser {
|
||||
override fun parse(caller: CommandSender, message: MessageChain): CommandCall? {
|
||||
val flatten = message.flattenCommandComponents().filterIsInstance<MessageContent>()
|
||||
if (flatten.isEmpty()) return null
|
||||
return CommandCallImpl(
|
||||
caller = caller,
|
||||
calleeName = flatten.first().content,
|
||||
valueArguments = flatten.drop(1).map(::InvariantCommandValueArgument)
|
||||
)
|
||||
}
|
||||
}
|
@ -0,0 +1,121 @@
|
||||
package net.mamoe.mirai.console.command.resolve
|
||||
|
||||
import net.mamoe.mirai.console.command.Command
|
||||
import net.mamoe.mirai.console.command.CommandManager
|
||||
import net.mamoe.mirai.console.command.descriptor.*
|
||||
import net.mamoe.mirai.console.command.descriptor.ArgumentAcceptance.Companion.isNotAcceptable
|
||||
import net.mamoe.mirai.console.command.parse.CommandCall
|
||||
import net.mamoe.mirai.console.command.parse.CommandValueArgument
|
||||
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
|
||||
import net.mamoe.mirai.console.util.cast
|
||||
import net.mamoe.mirai.console.util.safeCast
|
||||
import java.util.*
|
||||
|
||||
@ConsoleExperimentalApi
|
||||
@ExperimentalCommandDescriptors
|
||||
public object BuiltInCommandCallResolver : CommandCallResolver {
|
||||
override fun resolve(call: CommandCall): ResolvedCommandCall? {
|
||||
val callee = CommandManager.matchCommand(call.calleeName) ?: return null
|
||||
|
||||
val valueArguments = call.valueArguments
|
||||
val context = callee.safeCast<CommandArgumentContextAware>()?.context
|
||||
|
||||
val signature = resolveImpl(callee, valueArguments, context) ?: return null
|
||||
|
||||
return ResolvedCommandCallImpl(call.caller, callee, signature, call.valueArguments)
|
||||
}
|
||||
|
||||
private data class ResolveData(
|
||||
val variant: CommandSignatureVariant,
|
||||
val argumentAcceptances: List<ArgumentAcceptanceWithIndex>,
|
||||
)
|
||||
|
||||
private data class ArgumentAcceptanceWithIndex(
|
||||
val index: Int,
|
||||
val acceptance: ArgumentAcceptance,
|
||||
)
|
||||
|
||||
private fun resolveImpl(
|
||||
callee: Command,
|
||||
valueArguments: List<CommandValueArgument>,
|
||||
context: CommandArgumentContext?,
|
||||
): CommandSignatureVariant? {
|
||||
|
||||
callee.overloads
|
||||
.mapNotNull l@{ signature ->
|
||||
val zipped = signature.valueParameters.zip(valueArguments)
|
||||
|
||||
if (signature.valueParameters.drop(zipped.size).any { !it.isOptional }) return@l null // not enough args
|
||||
|
||||
ResolveData(signature, zipped.mapIndexed { index, (parameter, argument) ->
|
||||
val accepting = parameter.accepting(argument, context)
|
||||
if (accepting.isNotAcceptable) {
|
||||
return@l null // argument type not assignable
|
||||
}
|
||||
ArgumentAcceptanceWithIndex(index, accepting)
|
||||
})
|
||||
}
|
||||
.also { result -> result.singleOrNull()?.let { return it.variant } }
|
||||
.takeLongestMatches()
|
||||
.ifEmpty { return null }
|
||||
.also { result -> result.singleOrNull()?.let { return it.variant } }
|
||||
// take single ArgumentAcceptance.Direct
|
||||
.also { list ->
|
||||
|
||||
val candidates = list
|
||||
.flatMap { phase ->
|
||||
phase.argumentAcceptances.filter { it.acceptance is ArgumentAcceptance.Direct }.map { phase to it }
|
||||
}
|
||||
candidates.singleOrNull()?.let { return it.first.variant } // single Direct
|
||||
if (candidates.distinctBy { it.second.index }.size != candidates.size) {
|
||||
// Resolution ambiguity
|
||||
/*
|
||||
open class A
|
||||
open class AA: A()
|
||||
|
||||
open class C
|
||||
open class CC: C()
|
||||
|
||||
fun foo(a: A, c: CC) = 1
|
||||
fun foo(a: AA, c: C) = 1
|
||||
*/
|
||||
// The call is foo(AA(), C()) or foo(A(), CC())
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
|
||||
open class A
|
||||
open class B : A()
|
||||
open class C : A()
|
||||
open class D : C()
|
||||
open class BB : B()
|
||||
|
||||
fun foo(a: A, c: C) = 1
|
||||
//fun foo(a: A, c: A) = 1
|
||||
//fun foo(a: A, c: C, def: Int = 0) = 1
|
||||
fun foo(a: B, c: C, d: D) = ""
|
||||
|
||||
fun foo(b: BB, a: A, d: C) = 1.0
|
||||
|
||||
|
||||
fun main() {
|
||||
val a = foo(D(), D()) // int
|
||||
val b = foo(A(), C()) // int
|
||||
val d = foo(BB(), c = C(), D()) // string
|
||||
}
|
||||
*/
|
||||
|
||||
private fun List<ResolveData>.takeLongestMatches(): List<ResolveData> {
|
||||
if (isEmpty()) return emptyList()
|
||||
return associateByTo(TreeMap(Comparator.reverseOrder())) { it.variant.valueParameters.size }.let { m ->
|
||||
val firstKey = m.keys.first().cast<Int>()
|
||||
m.filter { it.key == firstKey }.map { it.value.cast() }
|
||||
}
|
||||
}
|
||||
}
|
@ -11,8 +11,6 @@ package net.mamoe.mirai.console.command.resolve
|
||||
|
||||
import net.mamoe.mirai.console.command.descriptor.ExperimentalCommandDescriptors
|
||||
import net.mamoe.mirai.console.command.parse.CommandCall
|
||||
import net.mamoe.mirai.console.extensions.CommandCallResolverProvider
|
||||
import net.mamoe.mirai.console.internal.extension.GlobalComponentStorage
|
||||
|
||||
/**
|
||||
* The resolver converting a [CommandCall] into [ResolvedCommandCall] based on registered []
|
||||
@ -20,17 +18,4 @@ import net.mamoe.mirai.console.internal.extension.GlobalComponentStorage
|
||||
@ExperimentalCommandDescriptors
|
||||
public interface CommandCallResolver {
|
||||
public fun resolve(call: CommandCall): ResolvedCommandCall?
|
||||
|
||||
public companion object {
|
||||
@JvmStatic
|
||||
@JvmName("resolveCall")
|
||||
public fun CommandCall.resolve(): ResolvedCommandCall? {
|
||||
GlobalComponentStorage.run {
|
||||
CommandCallResolverProvider.useExtensions { provider ->
|
||||
provider.instance.resolve(this@resolve)?.let { return it }
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
}
|
@ -7,19 +7,23 @@
|
||||
* https://github.com/mamoe/mirai/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
package net.mamoe.mirai.console.command.resolve;
|
||||
package net.mamoe.mirai.console.command.resolve
|
||||
|
||||
import net.mamoe.mirai.console.command.Command
|
||||
import net.mamoe.mirai.console.command.CommandSender
|
||||
import net.mamoe.mirai.console.command.CompositeCommand
|
||||
import net.mamoe.mirai.console.command.descriptor.CommandDescriptor
|
||||
import net.mamoe.mirai.console.command.descriptor.CommandSignatureVariant
|
||||
import net.mamoe.mirai.console.command.descriptor.ExperimentalCommandDescriptors
|
||||
import net.mamoe.mirai.console.command.parse.CommandCall
|
||||
import net.mamoe.mirai.console.command.parse.CommandValueArgument
|
||||
import net.mamoe.mirai.console.command.parse.mapToType
|
||||
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
|
||||
import kotlin.LazyThreadSafetyMode.PUBLICATION
|
||||
|
||||
/**
|
||||
* The resolved [CommandCall].
|
||||
*
|
||||
* @see ResolvedCommandCallImpl
|
||||
*/
|
||||
@ExperimentalCommandDescriptors
|
||||
public interface ResolvedCommandCall {
|
||||
@ -31,17 +35,41 @@ public interface ResolvedCommandCall {
|
||||
public val callee: Command
|
||||
|
||||
/**
|
||||
* The callee [CommandDescriptor], specifically a sub command from [CompositeCommand]
|
||||
*/
|
||||
public val calleeDescriptor: CommandDescriptor
|
||||
|
||||
/**
|
||||
* The callee [CommandSignatureVariant]
|
||||
* The callee [CommandSignatureVariant], specifically a sub command from [CompositeCommand]
|
||||
*/
|
||||
public val calleeSignature: CommandSignatureVariant
|
||||
|
||||
/**
|
||||
* Original arguments
|
||||
*/
|
||||
public val rawValueArguments: List<CommandValueArgument>
|
||||
|
||||
/**
|
||||
* Resolved value arguments arranged mapping the [CommandSignatureVariant.valueParameters] by index.
|
||||
*/
|
||||
public val valueArguments: List<CommandValueArgument>
|
||||
@ConsoleExperimentalApi
|
||||
public val resolvedValueArguments: List<Any?>
|
||||
|
||||
public companion object {
|
||||
@JvmStatic
|
||||
@ExperimentalCommandDescriptors
|
||||
public suspend fun ResolvedCommandCall.call() {
|
||||
return this.calleeSignature.call(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ExperimentalCommandDescriptors
|
||||
public class ResolvedCommandCallImpl(
|
||||
override val caller: CommandSender,
|
||||
override val callee: Command,
|
||||
override val calleeSignature: CommandSignatureVariant,
|
||||
override val rawValueArguments: List<CommandValueArgument>,
|
||||
) : ResolvedCommandCall {
|
||||
override val resolvedValueArguments: List<Any?> by lazy(PUBLICATION) {
|
||||
calleeSignature.valueParameters.zip(rawValueArguments).map { (parameter, argument) ->
|
||||
argument.mapToType(parameter.type)
|
||||
// TODO: 2020/10/17 consider vararg and optional
|
||||
}
|
||||
}
|
||||
}
|
@ -16,17 +16,15 @@ import net.mamoe.mirai.console.MiraiConsole
|
||||
import net.mamoe.mirai.console.command.*
|
||||
import net.mamoe.mirai.console.command.Command.Companion.allNames
|
||||
import net.mamoe.mirai.console.command.CommandSender.Companion.toCommandSender
|
||||
import net.mamoe.mirai.console.util.CoroutineScopeUtils.childScope
|
||||
import net.mamoe.mirai.event.Listener
|
||||
import net.mamoe.mirai.event.subscribeAlways
|
||||
import net.mamoe.mirai.message.MessageEvent
|
||||
import net.mamoe.mirai.message.data.Message
|
||||
import net.mamoe.mirai.message.data.MessageContent
|
||||
import net.mamoe.mirai.message.data.asMessageChain
|
||||
import net.mamoe.mirai.message.data.content
|
||||
import net.mamoe.mirai.utils.MiraiLogger
|
||||
import java.util.concurrent.locks.ReentrantLock
|
||||
|
||||
internal object CommandManagerImpl : CommandManager, CoroutineScope by CoroutineScope(MiraiConsole.job) {
|
||||
internal object CommandManagerImpl : CommandManager, CoroutineScope by MiraiConsole.childScope("CommandManagerImpl") {
|
||||
private val logger: MiraiLogger by lazy {
|
||||
MiraiConsole.createLogger("command")
|
||||
}
|
||||
@ -48,11 +46,11 @@ internal object CommandManagerImpl : CommandManager, CoroutineScope by Coroutine
|
||||
/**
|
||||
* 从原始的 command 中解析出 Command 对象
|
||||
*/
|
||||
internal fun matchCommand(rawCommand: String): Command? {
|
||||
if (rawCommand.startsWith(commandPrefix)) {
|
||||
return requiredPrefixCommandMap[rawCommand.substringAfter(commandPrefix).toLowerCase()]
|
||||
override fun matchCommand(commandName: String): Command? {
|
||||
if (commandName.startsWith(commandPrefix)) {
|
||||
return requiredPrefixCommandMap[commandName.substringAfter(commandPrefix).toLowerCase()]
|
||||
}
|
||||
return optionalPrefixCommandMap[rawCommand.toLowerCase()]
|
||||
return optionalPrefixCommandMap[commandName.toLowerCase()]
|
||||
}
|
||||
|
||||
internal val commandListener: Listener<MessageEvent> by lazy {
|
||||
@ -65,7 +63,7 @@ internal object CommandManagerImpl : CommandManager, CoroutineScope by Coroutine
|
||||
) {
|
||||
val sender = this.toCommandSender()
|
||||
|
||||
when (val result = sender.executeCommand(message)) {
|
||||
when (val result = executeCommand(sender, message)) {
|
||||
is CommandExecuteResult.PermissionDenied -> {
|
||||
if (!result.command.prefixOptional || message.content.startsWith(CommandManager.commandPrefix)) {
|
||||
sender.sendMessage("权限不足")
|
||||
@ -152,44 +150,4 @@ internal object CommandManagerImpl : CommandManager, CoroutineScope by Coroutine
|
||||
}
|
||||
|
||||
override fun Command.isRegistered(): Boolean = this in _registeredCommands
|
||||
|
||||
override suspend fun Command.execute(
|
||||
sender: CommandSender,
|
||||
arguments: Message,
|
||||
checkPermission: Boolean
|
||||
): CommandExecuteResult {
|
||||
return sender.executeCommandInternal(
|
||||
this,
|
||||
arguments.flattenCommandComponents(),
|
||||
primaryName,
|
||||
checkPermission
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun Command.execute(
|
||||
sender: CommandSender,
|
||||
arguments: String,
|
||||
checkPermission: Boolean
|
||||
): CommandExecuteResult {
|
||||
return sender.executeCommandInternal(
|
||||
this,
|
||||
arguments.flattenCommandComponents(),
|
||||
primaryName,
|
||||
checkPermission
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun CommandSender.executeCommand(
|
||||
message: Message,
|
||||
checkPermission: Boolean
|
||||
): CommandExecuteResult {
|
||||
val msg = message.asMessageChain().filterIsInstance<MessageContent>()
|
||||
if (msg.isEmpty()) return CommandExecuteResult.CommandNotFound("")
|
||||
return executeCommandInternal(msg, msg[0].content.substringBefore(' '), checkPermission)
|
||||
}
|
||||
|
||||
override suspend fun CommandSender.executeCommand(message: String, checkPermission: Boolean): CommandExecuteResult {
|
||||
if (message.isBlank()) return CommandExecuteResult.CommandNotFound("")
|
||||
return executeCommandInternal(message, message.substringBefore(' '), checkPermission)
|
||||
}
|
||||
}
|
@ -13,8 +13,8 @@ package net.mamoe.mirai.console.internal.command
|
||||
|
||||
import net.mamoe.mirai.console.command.CompositeCommand
|
||||
import net.mamoe.mirai.console.command.descriptor.CommandValueArgumentParser
|
||||
import kotlin.reflect.KClass
|
||||
import kotlin.reflect.KParameter
|
||||
import kotlin.reflect.KType
|
||||
|
||||
/*
|
||||
internal fun Parameter.toCommandParam(): CommandParameter<*> {
|
||||
@ -39,10 +39,10 @@ internal data class CommandParameter<T : Any>(
|
||||
/**
|
||||
* 参数类型. 将从 [CompositeCommand.context] 中寻找 [CommandValueArgumentParser] 解析.
|
||||
*/
|
||||
val type: KClass<T>, // exact type
|
||||
val type: KType, // exact type
|
||||
val parameter: KParameter, // source parameter
|
||||
) {
|
||||
constructor(name: String, type: KClass<T>, parameter: KParameter, parser: CommandValueArgumentParser<T>) : this(
|
||||
constructor(name: String, type: KType, parameter: KParameter, parser: CommandValueArgumentParser<T>) : this(
|
||||
name, type, parameter
|
||||
) {
|
||||
this._overrideParser = parser
|
||||
|
@ -12,12 +12,12 @@
|
||||
package net.mamoe.mirai.console.internal.command
|
||||
|
||||
import net.mamoe.mirai.console.command.*
|
||||
import net.mamoe.mirai.console.command.descriptor.CommandArgumentContext
|
||||
import net.mamoe.mirai.console.command.descriptor.CommandArgumentContextAware
|
||||
import net.mamoe.mirai.console.internal.data.kClassQualifiedNameOrTip
|
||||
import net.mamoe.mirai.console.command.descriptor.*
|
||||
import net.mamoe.mirai.console.permission.Permission
|
||||
import net.mamoe.mirai.console.permission.PermissionService.Companion.testPermission
|
||||
import net.mamoe.mirai.message.data.*
|
||||
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
|
||||
@ -89,6 +89,21 @@ internal abstract class AbstractReflectionCommand
|
||||
|
||||
}
|
||||
|
||||
@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>
|
||||
@ -132,27 +147,12 @@ internal abstract class AbstractReflectionCommand
|
||||
val params: Array<CommandParameter<*>>,
|
||||
val description: String,
|
||||
val permission: Permission,
|
||||
val onCommand: suspend (sender: CommandSender, parsedArgs: Map<KParameter, Any?>) -> Boolean,
|
||||
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)
|
||||
|
||||
internal suspend fun parseAndExecute(
|
||||
sender: CommandSender,
|
||||
argsWithSubCommandNameNotRemoved: MessageChain,
|
||||
removeSubName: Boolean,
|
||||
) {
|
||||
val args = parseArgs(sender, argsWithSubCommandNameNotRemoved, if (removeSubName) 1 else 0)
|
||||
if (!this.permission.testPermission(sender)) {
|
||||
sender.sendMessage(usage) // TODO: 2020/8/26 #127
|
||||
return
|
||||
}
|
||||
if (args == null || !onCommand(sender, args)) {
|
||||
sender.sendMessage(usage)
|
||||
}
|
||||
}
|
||||
|
||||
private fun KParameter.isOptional(): Boolean {
|
||||
return isOptional || this.type.isMarkedNullable
|
||||
}
|
||||
@ -163,49 +163,6 @@ internal abstract class AbstractReflectionCommand
|
||||
|
||||
@JvmField
|
||||
internal val bakedSubNames: Array<Array<String>> = names.map { it.bakeSubName() }.toTypedArray()
|
||||
private fun parseArgs(sender: CommandSender, rawArgs: MessageChain, offset: Int): MutableMap<KParameter, Any?>? {
|
||||
if (rawArgs.size < offset + minimalArgumentsSize)
|
||||
return null
|
||||
//require(rawArgs.size >= offset + this.params.size) { "No enough args. Required ${params.size}, but given ${rawArgs.size - offset}" }
|
||||
return argumentBuilder(sender).also { result ->
|
||||
params.forEachIndexed { index, parameter ->
|
||||
val rawArg = rawArgs.getOrNull(offset + index)
|
||||
result[parameter.parameter] = when (rawArg) {
|
||||
null -> {
|
||||
val p = parameter.parameter
|
||||
when {
|
||||
p.isOptional -> return@forEachIndexed
|
||||
p.type.isMarkedNullable -> {
|
||||
result[parameter.parameter] = null
|
||||
return@forEachIndexed
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
is PlainText -> context[parameter.type]?.parse(rawArg.content, sender)
|
||||
is MessageContent -> context[parameter.type]?.parse(rawArg, sender)
|
||||
else -> throw IllegalArgumentException("Illegal Message kind: ${rawArg.kClassQualifiedNameOrTip}")
|
||||
} ?: error("Cannot find a parser for $rawArg")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param rawArgs 元素类型必须为 [SingleMessage] 或 [String], 且已经经过扁平化处理. 否则抛出异常 [IllegalArgumentException]
|
||||
*/
|
||||
internal fun matchSubCommand(rawArgs: MessageChain): SubCommandDescriptor? {
|
||||
val maxCount = rawArgs.size
|
||||
var cur = 0
|
||||
bakedCommandNameToSubDescriptorArray.forEach { (name, descriptor) ->
|
||||
if (name.size != cur) {
|
||||
if (cur++ == maxCount) return null
|
||||
}
|
||||
if (name.contentEqualsOffset(rawArgs, length = cur)) {
|
||||
return descriptor
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
@ -349,23 +306,23 @@ internal fun AbstractReflectionCommand.createSubCommand(
|
||||
// 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(
|
||||
CommandParameter<Any>(
|
||||
paramName,
|
||||
(param.type.classifier as? KClass<*>)
|
||||
?: throw IllegalArgumentException("unsolved type reference from param " + param.name + ". (at ${this::class.qualifiedNameOrTip}.${function.name}.$param)"),
|
||||
param.type,
|
||||
param
|
||||
)
|
||||
}.toTypedArray()
|
||||
|
||||
// TODO: 2020/09/19 检查 optional/nullable 是否都在最后
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
return SubCommandDescriptor(
|
||||
commandName,
|
||||
params,
|
||||
params as Array<CommandParameter<*>>,
|
||||
subDescription, // overridePermission?.value
|
||||
permission,//overridePermission?.value?.let { PermissionService.INSTANCE[PermissionId.parseFromString(it)] } ?: permission,
|
||||
onCommand = { sender: CommandSender, args: Map<KParameter, Any?> ->
|
||||
val result = function.callSuspendBy(args)
|
||||
onCommand = { _: CommandSender, args ->
|
||||
val result = function.callSuspendBy(parameters.zip(args).toMap())
|
||||
|
||||
checkNotNull(result) { "sub command return value is null (at ${this::class.qualifiedName}.${function.name})" }
|
||||
|
||||
|
@ -1,67 +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
|
||||
*/
|
||||
|
||||
package net.mamoe.mirai.console.internal.command
|
||||
|
||||
import net.mamoe.mirai.console.command.*
|
||||
import net.mamoe.mirai.console.permission.PermissionService.Companion.testPermission
|
||||
import net.mamoe.mirai.message.data.MessageChain
|
||||
import net.mamoe.mirai.message.data.asMessageChain
|
||||
|
||||
@JvmSynthetic
|
||||
@Throws(CommandExecutionException::class)
|
||||
internal suspend fun CommandSender.executeCommandInternal(
|
||||
command: Command,
|
||||
args: MessageChain,
|
||||
commandName: String,
|
||||
checkPermission: Boolean,
|
||||
): CommandExecuteResult {
|
||||
if (checkPermission && !command.permission.testPermission(this)) {
|
||||
return CommandExecuteResult.PermissionDenied(command, commandName)
|
||||
}
|
||||
|
||||
kotlin.runCatching {
|
||||
command.onCommand(this, args)
|
||||
}.fold(
|
||||
onSuccess = {
|
||||
return CommandExecuteResult.Success(
|
||||
commandName = commandName,
|
||||
command = command,
|
||||
args = args
|
||||
)
|
||||
},
|
||||
onFailure = { exception ->
|
||||
return if (exception is IllegalArgumentException) CommandExecuteResult.IllegalArgument(
|
||||
commandName = commandName,
|
||||
command = command,
|
||||
exception = exception,
|
||||
args = args
|
||||
) else CommandExecuteResult.ExecutionFailed(
|
||||
commandName = commandName,
|
||||
command = command,
|
||||
exception = exception,
|
||||
args = args
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@JvmSynthetic
|
||||
internal suspend fun CommandSender.executeCommandInternal(
|
||||
messages: Any,
|
||||
commandName: String,
|
||||
checkPermission: Boolean,
|
||||
): CommandExecuteResult {
|
||||
val command =
|
||||
CommandManagerImpl.matchCommand(commandName) ?: return CommandExecuteResult.CommandNotFound(commandName)
|
||||
val args = messages.flattenCommandComponents()
|
||||
|
||||
return executeCommandInternal(command, args.drop(1).asMessageChain(), commandName, checkPermission)
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright 2020 Mamoe Technologies and contributors.
|
||||
*
|
||||
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
|
||||
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
|
||||
*
|
||||
* https://github.com/mamoe/mirai/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
package net.mamoe.mirai.console.util
|
||||
|
||||
import net.mamoe.mirai.message.data.MessageChain
|
||||
import net.mamoe.mirai.message.data.MessageContent
|
||||
|
||||
@ConsoleExperimentalApi
|
||||
public object MessageUtils {
|
||||
@JvmStatic
|
||||
public fun MessageChain.messageContentsSequence(): Sequence<MessageContent> = asSequence().filterIsInstance<MessageContent>()
|
||||
|
||||
@JvmStatic
|
||||
public fun MessageChain.firstContent(): MessageContent = messageContentsSequence().first()
|
||||
|
||||
@JvmStatic
|
||||
public fun MessageChain.firstContentOrNull(): MessageContent? = messageContentsSequence().firstOrNull()
|
||||
}
|
@ -16,7 +16,6 @@ import kotlinx.coroutines.runBlocking
|
||||
import net.mamoe.mirai.console.MiraiConsole
|
||||
import net.mamoe.mirai.console.Testing
|
||||
import net.mamoe.mirai.console.Testing.withTesting
|
||||
import net.mamoe.mirai.console.command.CommandManager.INSTANCE.execute
|
||||
import net.mamoe.mirai.console.command.CommandManager.INSTANCE.executeCommand
|
||||
import net.mamoe.mirai.console.command.CommandManager.INSTANCE.register
|
||||
import net.mamoe.mirai.console.command.CommandManager.INSTANCE.registeredCommands
|
||||
|
Loading…
Reference in New Issue
Block a user