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
|
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.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.command.java.JCommand
|
||||||
import net.mamoe.mirai.console.compiler.common.ResolveContext
|
import net.mamoe.mirai.console.compiler.common.ResolveContext
|
||||||
import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.COMMAND_NAME
|
import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.COMMAND_NAME
|
||||||
import net.mamoe.mirai.console.permission.Permission
|
import net.mamoe.mirai.console.permission.Permission
|
||||||
import net.mamoe.mirai.console.permission.PermissionId
|
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 CompositeCommand 复合指令
|
||||||
* @see SimpleCommand 简单的, 支持参数自动解析的指令
|
* @see SimpleCommand 简单的, 支持参数自动解析的指令
|
||||||
*
|
*
|
||||||
|
* @see CommandArgumentContextAware
|
||||||
|
*
|
||||||
* @see JCommand 为 Java 用户添加协程帮助的 [Command]
|
* @see JCommand 为 Java 用户添加协程帮助的 [Command]
|
||||||
*/
|
*/
|
||||||
public interface Command {
|
public interface Command {
|
||||||
@ -48,6 +51,13 @@ public interface Command {
|
|||||||
@ResolveContext(COMMAND_NAME)
|
@ResolveContext(COMMAND_NAME)
|
||||||
public val secondaryNames: Array<out String>
|
public val secondaryNames: Array<out String>
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@ConsoleExperimentalApi("Property name is experimental")
|
||||||
|
@ExperimentalCommandDescriptors
|
||||||
|
public val overloads: List<CommandSignatureVariant>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 用法说明, 用于发送给用户. [usage] 一般包含 [description].
|
* 用法说明, 用于发送给用户. [usage] 一般包含 [description].
|
||||||
*/
|
*/
|
||||||
@ -80,16 +90,6 @@ public interface Command {
|
|||||||
*/
|
*/
|
||||||
public val owner: CommandOwner
|
public val owner: CommandOwner
|
||||||
|
|
||||||
/**
|
|
||||||
* 在指令被执行时调用.
|
|
||||||
*
|
|
||||||
* @param args 精确的指令参数. [MessageChain] 每个元素代表一个精确的参数.
|
|
||||||
*
|
|
||||||
* @see CommandManager.executeCommand 查看更多信息
|
|
||||||
*/
|
|
||||||
@JvmBlockingBridge
|
|
||||||
public suspend fun CommandSender.onCommand(args: MessageChain)
|
|
||||||
|
|
||||||
public companion object {
|
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
|
package net.mamoe.mirai.console.command
|
||||||
|
|
||||||
import net.mamoe.mirai.console.command.CommandExecuteResult.CommandExecuteStatus
|
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.Message
|
||||||
import net.mamoe.mirai.message.data.MessageChain
|
import net.mamoe.mirai.message.data.MessageChain
|
||||||
import kotlin.contracts.contract
|
import kotlin.contracts.contract
|
||||||
@ -21,6 +23,8 @@ import kotlin.contracts.contract
|
|||||||
*
|
*
|
||||||
* @see CommandExecuteStatus
|
* @see CommandExecuteStatus
|
||||||
*/
|
*/
|
||||||
|
@ConsoleExperimentalApi("Not yet implemented")
|
||||||
|
@ExperimentalCommandDescriptors
|
||||||
public sealed class CommandExecuteResult {
|
public sealed class CommandExecuteResult {
|
||||||
/** 指令最终执行状态 */
|
/** 指令最终执行状态 */
|
||||||
public abstract val status: CommandExecuteStatus
|
public abstract val status: CommandExecuteStatus
|
||||||
|
@ -8,16 +8,26 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
@file:Suppress(
|
@file:Suppress(
|
||||||
"NOTHING_TO_INLINE", "unused", "INVISIBLE_MEMBER", "INVISIBLE_REFERENCE", "RESULT_CLASS_IN_RETURN_TYPE",
|
"NOTHING_TO_INLINE", "unused",
|
||||||
"MemberVisibilityCanBePrivate", "INAPPLICABLE_JVM_NAME"
|
"MemberVisibilityCanBePrivate", "INAPPLICABLE_JVM_NAME"
|
||||||
)
|
)
|
||||||
@file:JvmName("CommandManagerKt")
|
@file:JvmName("CommandManagerKt")
|
||||||
|
|
||||||
package net.mamoe.mirai.console.command
|
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
|
||||||
import net.mamoe.mirai.console.internal.command.CommandManagerImpl.executeCommand
|
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.*
|
import net.mamoe.mirai.message.data.*
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -101,19 +111,45 @@ public interface CommandManager {
|
|||||||
* 注意: 字符串与消息元素之间不需要空格, 会被强制分割. 如 "bar[mirai:image:]" 会被分割为 "bar" 和 [Image] 类型的消息元素.
|
* 注意: 字符串与消息元素之间不需要空格, 会被强制分割. 如 "bar[mirai:image:]" 会被分割为 "bar" 和 [Image] 类型的消息元素.
|
||||||
* 3. 参数解析. 各类型指令实现不同. 详见 [RawCommand], [CompositeCommand], [SimpleCommand]
|
* 3. 参数解析. 各类型指令实现不同. 详见 [RawCommand], [CompositeCommand], [SimpleCommand]
|
||||||
*
|
*
|
||||||
* ### 未来的扩展
|
* ### 扩展
|
||||||
* 在将来, 参数语法分析过程可能会被扩展, 允许插件自定义处理方式, 因此可能不会简单地使用空格分隔.
|
* 参数语法分析过程可能会被扩展, 插件可以自定义处理方式, 因此可能不会简单地使用空格分隔.
|
||||||
*
|
*
|
||||||
* @param message 一条完整的指令. 如 "/managers add 123456.123456"
|
* @param message 一条完整的指令. 如 "/managers add 123456.123456"
|
||||||
* @param checkPermission 为 `true` 时检查权限
|
* @param checkPermission 为 `true` 时检查权限
|
||||||
*
|
*
|
||||||
|
* @see CommandCallParser
|
||||||
|
* @see CommandCallResolver
|
||||||
|
*
|
||||||
* @return 执行结果
|
* @return 执行结果
|
||||||
*/
|
*/
|
||||||
@JvmBlockingBridge
|
// @JvmBlockingBridge
|
||||||
public suspend fun CommandSender.executeCommand(
|
@OptIn(ExperimentalCommandDescriptors::class)
|
||||||
|
public suspend fun executeCommand(
|
||||||
|
caller: CommandSender,
|
||||||
message: Message,
|
message: Message,
|
||||||
checkPermission: Boolean = true,
|
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 执行结果
|
* @return 执行结果
|
||||||
* @see executeCommand
|
* @see executeCommand
|
||||||
*/
|
*/
|
||||||
@JvmBlockingBridge
|
// @JvmBlockingBridge
|
||||||
public suspend fun CommandSender.executeCommand(
|
public suspend fun CommandSender.executeCommand(
|
||||||
message: String,
|
message: String,
|
||||||
checkPermission: Boolean = true,
|
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
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 执行一个确切的指令
|
* 从 [指令名称][commandName] 匹配对应的 [Command].
|
||||||
* @see executeCommand 获取更多信息
|
*
|
||||||
|
* #### 实现细节
|
||||||
|
* - [commandName] 带有 [commandPrefix] 时可以匹配到所有指令
|
||||||
|
* - [commandName] 不带有 [commandPrefix] 时只能匹配到 [Command.prefixOptional] 的指令
|
||||||
|
*
|
||||||
|
* @param commandName 可能带有或不带有 [commandPrefix].
|
||||||
*/
|
*/
|
||||||
@JvmBlockingBridge
|
public fun matchCommand(commandName: String): Command?
|
||||||
@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 companion object INSTANCE : CommandManager by CommandManagerImpl {
|
public companion object INSTANCE : CommandManager by CommandManagerImpl {
|
||||||
// TODO: 2020/8/20 https://youtrack.jetbrains.com/issue/KT-41191
|
// TODO: 2020/8/20 https://youtrack.jetbrains.com/issue/KT-41191
|
||||||
@ -167,52 +201,37 @@ public interface CommandManager {
|
|||||||
override val allRegisteredCommands: List<Command>
|
override val allRegisteredCommands: List<Command>
|
||||||
get() = CommandManagerImpl.allRegisteredCommands
|
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.kjbb.JvmBlockingBridge
|
||||||
import net.mamoe.mirai.Bot
|
import net.mamoe.mirai.Bot
|
||||||
import net.mamoe.mirai.console.MiraiConsole
|
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.asCommandSender
|
||||||
import net.mamoe.mirai.console.command.CommandSender.Companion.asMemberCommandSender
|
import net.mamoe.mirai.console.command.CommandSender.Companion.asMemberCommandSender
|
||||||
import net.mamoe.mirai.console.command.CommandSender.Companion.asTempCommandSender
|
import net.mamoe.mirai.console.command.CommandSender.Companion.asTempCommandSender
|
||||||
|
@ -124,13 +124,26 @@ public abstract class CompositeCommand(
|
|||||||
@Target(AnnotationTarget.VALUE_PARAMETER)
|
@Target(AnnotationTarget.VALUE_PARAMETER)
|
||||||
protected annotation class Name(val value: String)
|
protected annotation class Name(val value: String)
|
||||||
|
|
||||||
public final override suspend fun CommandSender.onCommand(args: MessageChain) {
|
@OptIn(ExperimentalCommandDescriptors::class)
|
||||||
matchSubCommand(args)?.parseAndExecute(this, args, true) ?: kotlin.run {
|
override val overloads: List<CommandSignatureVariant> by lazy {
|
||||||
defaultSubCommand.onCommand(this, args)
|
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) {
|
protected override suspend fun CommandSender.onDefault(rawArgs: MessageChain) {
|
||||||
sendMessage(usage)
|
sendMessage(usage)
|
||||||
}
|
}
|
||||||
|
@ -11,14 +11,18 @@
|
|||||||
|
|
||||||
package net.mamoe.mirai.console.command
|
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.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.command.java.JRawCommand
|
||||||
import net.mamoe.mirai.console.compiler.common.ResolveContext
|
import net.mamoe.mirai.console.compiler.common.ResolveContext
|
||||||
import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.COMMAND_NAME
|
import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.COMMAND_NAME
|
||||||
import net.mamoe.mirai.console.internal.command.createOrFindCommandPermission
|
import net.mamoe.mirai.console.internal.command.createOrFindCommandPermission
|
||||||
import net.mamoe.mirai.console.permission.Permission
|
import net.mamoe.mirai.console.permission.Permission
|
||||||
import net.mamoe.mirai.message.data.MessageChain
|
import net.mamoe.mirai.message.data.MessageChain
|
||||||
|
import net.mamoe.mirai.message.data.MessageChainBuilder
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 无参数解析, 接收原生参数的指令.
|
* 无参数解析, 接收原生参数的指令.
|
||||||
@ -52,6 +56,15 @@ public abstract class RawCommand(
|
|||||||
) : Command {
|
) : Command {
|
||||||
public override val permission: Permission by lazy { createOrFindCommandPermission(parentPermission) }
|
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 查看更多信息
|
* @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),
|
) : Command, AbstractReflectionCommand(owner, primaryName, secondaryNames = secondaryNames, description, parentPermission, prefixOptional),
|
||||||
CommandArgumentContextAware {
|
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]. 也可以被覆盖.
|
* 自动根据带有 [Handler] 注解的函数签名生成 [usage]. 也可以被覆盖.
|
||||||
*/
|
*/
|
||||||
@ -76,10 +84,6 @@ public abstract class SimpleCommand(
|
|||||||
*/
|
*/
|
||||||
public override val context: CommandArgumentContext = CommandArgumentContext.Builtins + overrideContext
|
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>) {
|
internal override fun checkSubCommand(subCommands: Array<SubCommandDescriptor>) {
|
||||||
super.checkSubCommand(subCommands)
|
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." }
|
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
|
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 net.mamoe.mirai.console.util.safeCast
|
||||||
import kotlin.reflect.KClass
|
import kotlin.reflect.KClass
|
||||||
import kotlin.reflect.KType
|
import kotlin.reflect.KType
|
||||||
|
import kotlin.reflect.full.isSubtypeOf
|
||||||
import kotlin.reflect.typeOf
|
import kotlin.reflect.typeOf
|
||||||
|
|
||||||
@ExperimentalCommandDescriptors
|
/**
|
||||||
public interface CommandDescriptor {
|
* @see CommandSignatureVariantImpl
|
||||||
public val overloads: List<CommandSignatureVariant>
|
*/
|
||||||
}
|
|
||||||
|
|
||||||
@ExperimentalCommandDescriptors
|
@ExperimentalCommandDescriptors
|
||||||
public interface CommandSignatureVariant {
|
public interface CommandSignatureVariant {
|
||||||
public val valueParameters: List<CommandValueParameter<*>>
|
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]
|
* Reified type of [T]
|
||||||
*/
|
*/
|
||||||
public val type: KType
|
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
|
@ExperimentalCommandDescriptors
|
||||||
public sealed class CommandValueParameter<T> : ICommandParameter<T> {
|
public sealed class CommandValueParameter<T> : ICommandParameter<T> {
|
||||||
init {
|
internal fun validate() { // // TODO: 2020/10/18 net.mamoe.mirai.console.command.descriptor.CommandValueParameter.validate$mirai_console_mirai_console_main
|
||||||
@Suppress("LeakingThis")
|
|
||||||
require(type.classifier?.safeCast<KClass<*>>()?.isInstance(defaultValue) == true) {
|
require(type.classifier?.safeCast<KClass<*>>()?.isInstance(defaultValue) == true) {
|
||||||
"defaultValue is not instance of type"
|
"defaultValue is not instance of type"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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 class StringConstant(
|
||||||
public override val name: String,
|
public override val name: String,
|
||||||
|
public val expectingValue: String,
|
||||||
) : CommandValueParameter<String>() {
|
) : CommandValueParameter<String>() {
|
||||||
public override val type: KType get() = STRING_TYPE
|
public override val type: KType get() = STRING_TYPE
|
||||||
public override val defaultValue: Nothing? get() = null
|
public override val defaultValue: Nothing? get() = null
|
||||||
public override val isOptional: Boolean get() = false
|
public override val isOptional: Boolean get() = false
|
||||||
|
public override val isVararg: Boolean get() = false
|
||||||
|
|
||||||
private companion object {
|
private companion object {
|
||||||
@OptIn(ExperimentalStdlibApi::class)
|
@OptIn(ExperimentalStdlibApi::class)
|
||||||
@ -67,23 +149,28 @@ public sealed class CommandValueParameter<T> : ICommandParameter<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see createOptional
|
||||||
|
* @see createRequired
|
||||||
|
*/
|
||||||
public class UserDefinedType<T>(
|
public class UserDefinedType<T>(
|
||||||
public override val name: String,
|
public override val name: String,
|
||||||
public override val defaultValue: T?,
|
public override val defaultValue: T?,
|
||||||
public override val isOptional: Boolean,
|
public override val isOptional: Boolean,
|
||||||
|
public override val isVararg: Boolean,
|
||||||
public override val type: KType,
|
public override val type: KType,
|
||||||
) : CommandValueParameter<T>() {
|
) : CommandValueParameter<T>() {
|
||||||
public companion object {
|
public companion object {
|
||||||
@JvmStatic
|
@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)
|
@OptIn(ExperimentalStdlibApi::class)
|
||||||
return UserDefinedType(name, defaultValue, true, typeOf<T>())
|
return UserDefinedType(name, defaultValue, true, isVararg, typeOf<T>())
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic
|
@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)
|
@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
|
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.Command
|
||||||
import net.mamoe.mirai.console.command.CommandManager
|
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
|
||||||
import net.mamoe.mirai.console.command.CommandManager.INSTANCE.executeCommand
|
|
||||||
import net.mamoe.mirai.console.command.CommandSender
|
|
||||||
import net.mamoe.mirai.message.data.MessageChain
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 为 Java 用户添加协程帮助的 [Command].
|
* 为 Java 用户添加协程帮助的 [Command].
|
||||||
@ -24,17 +19,7 @@ import net.mamoe.mirai.message.data.MessageChain
|
|||||||
*
|
*
|
||||||
* @see Command
|
* @see Command
|
||||||
*/
|
*/
|
||||||
|
@ConsoleExperimentalApi("Not yet supported")
|
||||||
public interface JCommand : Command {
|
public interface JCommand : Command {
|
||||||
public override suspend fun CommandSender.onCommand(args: MessageChain) {
|
// TODO: 2020/10/18 JCommand
|
||||||
withContext(Dispatchers.IO) { onCommand(this@onCommand, args) }
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 在指令被执行时调用.
|
|
||||||
*
|
|
||||||
* @param args 精确的指令参数. [MessageChain] 每个元素代表一个精确的参数.
|
|
||||||
*
|
|
||||||
* @see CommandManager.executeCommand 查看更多信息
|
|
||||||
*/
|
|
||||||
public fun onCommand(sender: CommandSender, args: MessageChain) // overrides blocking bridge
|
|
||||||
}
|
}
|
@ -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
|
||||||
import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.COMMAND_NAME
|
import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.COMMAND_NAME
|
||||||
import net.mamoe.mirai.console.permission.Permission
|
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
|
* @see buildCommandArgumentContext
|
||||||
*/
|
*/
|
||||||
|
@ConsoleExperimentalApi("Not yet supported")
|
||||||
public abstract class JCompositeCommand
|
public abstract class JCompositeCommand
|
||||||
@JvmOverloads constructor(
|
@JvmOverloads constructor(
|
||||||
owner: CommandOwner,
|
owner: CommandOwner,
|
||||||
|
@ -9,16 +9,15 @@
|
|||||||
|
|
||||||
package net.mamoe.mirai.console.command.java
|
package net.mamoe.mirai.console.command.java
|
||||||
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
import net.mamoe.mirai.console.command.BuiltInCommands
|
||||||
import kotlinx.coroutines.withContext
|
import net.mamoe.mirai.console.command.Command
|
||||||
import net.mamoe.mirai.console.command.*
|
import net.mamoe.mirai.console.command.CommandManager
|
||||||
import net.mamoe.mirai.console.command.CommandManager.INSTANCE.execute
|
import net.mamoe.mirai.console.command.CommandOwner
|
||||||
import net.mamoe.mirai.console.compiler.common.ResolveContext
|
import net.mamoe.mirai.console.compiler.common.ResolveContext
|
||||||
import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.COMMAND_NAME
|
import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.COMMAND_NAME
|
||||||
import net.mamoe.mirai.console.internal.command.createOrFindCommandPermission
|
import net.mamoe.mirai.console.internal.command.createOrFindCommandPermission
|
||||||
import net.mamoe.mirai.console.permission.Permission
|
import net.mamoe.mirai.console.permission.Permission
|
||||||
import net.mamoe.mirai.message.data.MessageChain
|
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
|
||||||
import net.mamoe.mirai.message.data.SingleMessage
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 供 Java 用户继承
|
* 供 Java 用户继承
|
||||||
@ -46,6 +45,7 @@ import net.mamoe.mirai.message.data.SingleMessage
|
|||||||
*
|
*
|
||||||
* @see JRawCommand
|
* @see JRawCommand
|
||||||
*/
|
*/
|
||||||
|
@ConsoleExperimentalApi("Not yet supported")
|
||||||
public abstract class JRawCommand
|
public abstract class JRawCommand
|
||||||
@JvmOverloads constructor(
|
@JvmOverloads constructor(
|
||||||
/**
|
/**
|
||||||
@ -72,19 +72,4 @@ public abstract class JRawCommand
|
|||||||
/** 为 `true` 时表示 [指令前缀][CommandManager.commandPrefix] 可选 */
|
/** 为 `true` 时表示 [指令前缀][CommandManager.commandPrefix] 可选 */
|
||||||
public final override var prefixOptional: Boolean = false
|
public final override var prefixOptional: Boolean = false
|
||||||
protected set
|
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
|
||||||
import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.COMMAND_NAME
|
import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.COMMAND_NAME
|
||||||
import net.mamoe.mirai.console.permission.Permission
|
import net.mamoe.mirai.console.permission.Permission
|
||||||
|
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Java 实现:
|
* Java 实现:
|
||||||
@ -42,6 +43,7 @@ import net.mamoe.mirai.console.permission.Permission
|
|||||||
* @see SimpleCommand
|
* @see SimpleCommand
|
||||||
* @see [CommandManager.executeCommand]
|
* @see [CommandManager.executeCommand]
|
||||||
*/
|
*/
|
||||||
|
@ConsoleExperimentalApi("Not yet supported")
|
||||||
public abstract class JSimpleCommand(
|
public abstract class JSimpleCommand(
|
||||||
owner: CommandOwner,
|
owner: CommandOwner,
|
||||||
@ResolveContext(COMMAND_NAME) primaryName: String,
|
@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 CommandCallResolver The call resolver for [CommandCall] to become [ResolvedCommandCall]
|
||||||
* @see CommandCallParserProvider The extension point
|
* @see CommandCallParserProvider The extension point
|
||||||
|
*
|
||||||
|
* @see SpaceSeparatedCommandCallParser
|
||||||
*/
|
*/
|
||||||
@ConsoleExperimentalApi
|
@ConsoleExperimentalApi
|
||||||
@ExperimentalCommandDescriptors
|
@ExperimentalCommandDescriptors
|
||||||
@ -24,7 +26,7 @@ public interface CommandCallParser {
|
|||||||
*
|
*
|
||||||
* @return `null` if unable to parse (i.e. due to syntax errors).
|
* @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 {
|
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.NoValueArgumentMappingException
|
||||||
import net.mamoe.mirai.console.command.descriptor.TypeVariant
|
import net.mamoe.mirai.console.command.descriptor.TypeVariant
|
||||||
import net.mamoe.mirai.message.data.MessageContent
|
import net.mamoe.mirai.message.data.MessageContent
|
||||||
|
import kotlin.reflect.KType
|
||||||
import kotlin.reflect.full.isSubtypeOf
|
import kotlin.reflect.full.isSubtypeOf
|
||||||
import kotlin.reflect.typeOf
|
import kotlin.reflect.typeOf
|
||||||
|
|
||||||
@ -34,6 +35,7 @@ public interface CommandArgument
|
|||||||
*/
|
*/
|
||||||
@ExperimentalCommandDescriptors
|
@ExperimentalCommandDescriptors
|
||||||
public interface CommandValueArgument : CommandArgument {
|
public interface CommandValueArgument : CommandArgument {
|
||||||
|
public val type: KType
|
||||||
public val value: RawCommandArgument
|
public val value: RawCommandArgument
|
||||||
public val typeVariants: List<TypeVariant<*>>
|
public val typeVariants: List<TypeVariant<*>>
|
||||||
}
|
}
|
||||||
@ -45,6 +47,8 @@ public interface CommandValueArgument : CommandArgument {
|
|||||||
public data class InvariantCommandValueArgument(
|
public data class InvariantCommandValueArgument(
|
||||||
public override val value: RawCommandArgument,
|
public override val value: RawCommandArgument,
|
||||||
) : CommandValueArgument {
|
) : CommandValueArgument {
|
||||||
|
@OptIn(ExperimentalStdlibApi::class)
|
||||||
|
override val type: KType = typeOf<MessageContent>()
|
||||||
override val typeVariants: List<TypeVariant<*>> = listOf(MessageContentTypeVariant)
|
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 =
|
public inline fun <reified T> CommandValueArgument.mapToType(): T =
|
||||||
mapToTypeOrNull() ?: throw NoValueArgumentMappingException(this, typeOf<T>())
|
mapToTypeOrNull() ?: throw NoValueArgumentMappingException(this, typeOf<T>())
|
||||||
|
|
||||||
|
@OptIn(ExperimentalStdlibApi::class)
|
||||||
@ExperimentalCommandDescriptors
|
@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)
|
@OptIn(ExperimentalStdlibApi::class)
|
||||||
val expectingType = typeOf<T>()
|
|
||||||
val result = typeVariants
|
val result = typeVariants
|
||||||
.filter { it.outType.isSubtypeOf(expectingType) }
|
.filter { it.outType.isSubtypeOf(expectingType) }
|
||||||
.also {
|
.also {
|
||||||
@ -71,5 +79,12 @@ public inline fun <reified T> CommandValueArgument.mapToTypeOrNull(): T? {
|
|||||||
acc
|
acc
|
||||||
else typeVariant
|
else typeVariant
|
||||||
}
|
}
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
return result.mapValue(value) as T
|
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.descriptor.ExperimentalCommandDescriptors
|
||||||
import net.mamoe.mirai.console.command.parse.CommandCall
|
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 []
|
* The resolver converting a [CommandCall] into [ResolvedCommandCall] based on registered []
|
||||||
@ -20,17 +18,4 @@ import net.mamoe.mirai.console.internal.extension.GlobalComponentStorage
|
|||||||
@ExperimentalCommandDescriptors
|
@ExperimentalCommandDescriptors
|
||||||
public interface CommandCallResolver {
|
public interface CommandCallResolver {
|
||||||
public fun resolve(call: CommandCall): ResolvedCommandCall?
|
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
|
* 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.Command
|
||||||
import net.mamoe.mirai.console.command.CommandSender
|
import net.mamoe.mirai.console.command.CommandSender
|
||||||
import net.mamoe.mirai.console.command.CompositeCommand
|
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.CommandSignatureVariant
|
||||||
import net.mamoe.mirai.console.command.descriptor.ExperimentalCommandDescriptors
|
import net.mamoe.mirai.console.command.descriptor.ExperimentalCommandDescriptors
|
||||||
import net.mamoe.mirai.console.command.parse.CommandCall
|
import net.mamoe.mirai.console.command.parse.CommandCall
|
||||||
import net.mamoe.mirai.console.command.parse.CommandValueArgument
|
import net.mamoe.mirai.console.command.parse.CommandValueArgument
|
||||||
|
import net.mamoe.mirai.console.command.parse.mapToType
|
||||||
|
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
|
||||||
|
import kotlin.LazyThreadSafetyMode.PUBLICATION
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The resolved [CommandCall].
|
* The resolved [CommandCall].
|
||||||
|
*
|
||||||
|
* @see ResolvedCommandCallImpl
|
||||||
*/
|
*/
|
||||||
@ExperimentalCommandDescriptors
|
@ExperimentalCommandDescriptors
|
||||||
public interface ResolvedCommandCall {
|
public interface ResolvedCommandCall {
|
||||||
@ -31,17 +35,41 @@ public interface ResolvedCommandCall {
|
|||||||
public val callee: Command
|
public val callee: Command
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The callee [CommandDescriptor], specifically a sub command from [CompositeCommand]
|
* The callee [CommandSignatureVariant], specifically a sub command from [CompositeCommand]
|
||||||
*/
|
|
||||||
public val calleeDescriptor: CommandDescriptor
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The callee [CommandSignatureVariant]
|
|
||||||
*/
|
*/
|
||||||
public val calleeSignature: CommandSignatureVariant
|
public val calleeSignature: CommandSignatureVariant
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Original arguments
|
||||||
|
*/
|
||||||
|
public val rawValueArguments: List<CommandValueArgument>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resolved value arguments arranged mapping the [CommandSignatureVariant.valueParameters] by index.
|
* 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.*
|
||||||
import net.mamoe.mirai.console.command.Command.Companion.allNames
|
import net.mamoe.mirai.console.command.Command.Companion.allNames
|
||||||
import net.mamoe.mirai.console.command.CommandSender.Companion.toCommandSender
|
import net.mamoe.mirai.console.command.CommandSender.Companion.toCommandSender
|
||||||
|
import net.mamoe.mirai.console.util.CoroutineScopeUtils.childScope
|
||||||
import net.mamoe.mirai.event.Listener
|
import net.mamoe.mirai.event.Listener
|
||||||
import net.mamoe.mirai.event.subscribeAlways
|
import net.mamoe.mirai.event.subscribeAlways
|
||||||
import net.mamoe.mirai.message.MessageEvent
|
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.message.data.content
|
||||||
import net.mamoe.mirai.utils.MiraiLogger
|
import net.mamoe.mirai.utils.MiraiLogger
|
||||||
import java.util.concurrent.locks.ReentrantLock
|
import java.util.concurrent.locks.ReentrantLock
|
||||||
|
|
||||||
internal object CommandManagerImpl : CommandManager, CoroutineScope by CoroutineScope(MiraiConsole.job) {
|
internal object CommandManagerImpl : CommandManager, CoroutineScope by MiraiConsole.childScope("CommandManagerImpl") {
|
||||||
private val logger: MiraiLogger by lazy {
|
private val logger: MiraiLogger by lazy {
|
||||||
MiraiConsole.createLogger("command")
|
MiraiConsole.createLogger("command")
|
||||||
}
|
}
|
||||||
@ -48,11 +46,11 @@ internal object CommandManagerImpl : CommandManager, CoroutineScope by Coroutine
|
|||||||
/**
|
/**
|
||||||
* 从原始的 command 中解析出 Command 对象
|
* 从原始的 command 中解析出 Command 对象
|
||||||
*/
|
*/
|
||||||
internal fun matchCommand(rawCommand: String): Command? {
|
override fun matchCommand(commandName: String): Command? {
|
||||||
if (rawCommand.startsWith(commandPrefix)) {
|
if (commandName.startsWith(commandPrefix)) {
|
||||||
return requiredPrefixCommandMap[rawCommand.substringAfter(commandPrefix).toLowerCase()]
|
return requiredPrefixCommandMap[commandName.substringAfter(commandPrefix).toLowerCase()]
|
||||||
}
|
}
|
||||||
return optionalPrefixCommandMap[rawCommand.toLowerCase()]
|
return optionalPrefixCommandMap[commandName.toLowerCase()]
|
||||||
}
|
}
|
||||||
|
|
||||||
internal val commandListener: Listener<MessageEvent> by lazy {
|
internal val commandListener: Listener<MessageEvent> by lazy {
|
||||||
@ -65,7 +63,7 @@ internal object CommandManagerImpl : CommandManager, CoroutineScope by Coroutine
|
|||||||
) {
|
) {
|
||||||
val sender = this.toCommandSender()
|
val sender = this.toCommandSender()
|
||||||
|
|
||||||
when (val result = sender.executeCommand(message)) {
|
when (val result = executeCommand(sender, message)) {
|
||||||
is CommandExecuteResult.PermissionDenied -> {
|
is CommandExecuteResult.PermissionDenied -> {
|
||||||
if (!result.command.prefixOptional || message.content.startsWith(CommandManager.commandPrefix)) {
|
if (!result.command.prefixOptional || message.content.startsWith(CommandManager.commandPrefix)) {
|
||||||
sender.sendMessage("权限不足")
|
sender.sendMessage("权限不足")
|
||||||
@ -152,44 +150,4 @@ internal object CommandManagerImpl : CommandManager, CoroutineScope by Coroutine
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun Command.isRegistered(): Boolean = this in _registeredCommands
|
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.CompositeCommand
|
||||||
import net.mamoe.mirai.console.command.descriptor.CommandValueArgumentParser
|
import net.mamoe.mirai.console.command.descriptor.CommandValueArgumentParser
|
||||||
import kotlin.reflect.KClass
|
|
||||||
import kotlin.reflect.KParameter
|
import kotlin.reflect.KParameter
|
||||||
|
import kotlin.reflect.KType
|
||||||
|
|
||||||
/*
|
/*
|
||||||
internal fun Parameter.toCommandParam(): CommandParameter<*> {
|
internal fun Parameter.toCommandParam(): CommandParameter<*> {
|
||||||
@ -39,10 +39,10 @@ internal data class CommandParameter<T : Any>(
|
|||||||
/**
|
/**
|
||||||
* 参数类型. 将从 [CompositeCommand.context] 中寻找 [CommandValueArgumentParser] 解析.
|
* 参数类型. 将从 [CompositeCommand.context] 中寻找 [CommandValueArgumentParser] 解析.
|
||||||
*/
|
*/
|
||||||
val type: KClass<T>, // exact type
|
val type: KType, // exact type
|
||||||
val parameter: KParameter, // source parameter
|
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
|
name, type, parameter
|
||||||
) {
|
) {
|
||||||
this._overrideParser = parser
|
this._overrideParser = parser
|
||||||
|
@ -12,12 +12,12 @@
|
|||||||
package net.mamoe.mirai.console.internal.command
|
package net.mamoe.mirai.console.internal.command
|
||||||
|
|
||||||
import net.mamoe.mirai.console.command.*
|
import net.mamoe.mirai.console.command.*
|
||||||
import net.mamoe.mirai.console.command.descriptor.CommandArgumentContext
|
import net.mamoe.mirai.console.command.descriptor.*
|
||||||
import net.mamoe.mirai.console.command.descriptor.CommandArgumentContextAware
|
|
||||||
import net.mamoe.mirai.console.internal.data.kClassQualifiedNameOrTip
|
|
||||||
import net.mamoe.mirai.console.permission.Permission
|
import net.mamoe.mirai.console.permission.Permission
|
||||||
import net.mamoe.mirai.console.permission.PermissionService.Companion.testPermission
|
import net.mamoe.mirai.message.data.MessageChain
|
||||||
import net.mamoe.mirai.message.data.*
|
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.KAnnotatedElement
|
||||||
import kotlin.reflect.KClass
|
import kotlin.reflect.KClass
|
||||||
import kotlin.reflect.KFunction
|
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 {
|
interface SubCommandAnnotationResolver {
|
||||||
fun hasAnnotation(baseCommand: AbstractReflectionCommand, function: KFunction<*>): Boolean
|
fun hasAnnotation(baseCommand: AbstractReflectionCommand, function: KFunction<*>): Boolean
|
||||||
fun getSubCommandNames(baseCommand: AbstractReflectionCommand, function: KFunction<*>): Array<out String>
|
fun getSubCommandNames(baseCommand: AbstractReflectionCommand, function: KFunction<*>): Array<out String>
|
||||||
@ -132,27 +147,12 @@ internal abstract class AbstractReflectionCommand
|
|||||||
val params: Array<CommandParameter<*>>,
|
val params: Array<CommandParameter<*>>,
|
||||||
val description: String,
|
val description: String,
|
||||||
val permission: Permission,
|
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 context: CommandArgumentContext,
|
||||||
val argumentBuilder: (sender: CommandSender) -> MutableMap<KParameter, Any?>,
|
val argumentBuilder: (sender: CommandSender) -> MutableMap<KParameter, Any?>,
|
||||||
) {
|
) {
|
||||||
val usage: String = createUsage(this@AbstractReflectionCommand)
|
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 {
|
private fun KParameter.isOptional(): Boolean {
|
||||||
return isOptional || this.type.isMarkedNullable
|
return isOptional || this.type.isMarkedNullable
|
||||||
}
|
}
|
||||||
@ -163,49 +163,6 @@ internal abstract class AbstractReflectionCommand
|
|||||||
|
|
||||||
@JvmField
|
@JvmField
|
||||||
internal val bakedSubNames: Array<Array<String>> = names.map { it.bakeSubName() }.toTypedArray()
|
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)")
|
// 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"
|
val paramName = param.findAnnotation<CompositeCommand.Name>()?.value ?: param.name ?: "unknown"
|
||||||
CommandParameter(
|
CommandParameter<Any>(
|
||||||
paramName,
|
paramName,
|
||||||
(param.type.classifier as? KClass<*>)
|
param.type,
|
||||||
?: throw IllegalArgumentException("unsolved type reference from param " + param.name + ". (at ${this::class.qualifiedNameOrTip}.${function.name}.$param)"),
|
|
||||||
param
|
param
|
||||||
)
|
)
|
||||||
}.toTypedArray()
|
}.toTypedArray()
|
||||||
|
|
||||||
// TODO: 2020/09/19 检查 optional/nullable 是否都在最后
|
// TODO: 2020/09/19 检查 optional/nullable 是否都在最后
|
||||||
|
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
return SubCommandDescriptor(
|
return SubCommandDescriptor(
|
||||||
commandName,
|
commandName,
|
||||||
params,
|
params as Array<CommandParameter<*>>,
|
||||||
subDescription, // overridePermission?.value
|
subDescription, // overridePermission?.value
|
||||||
permission,//overridePermission?.value?.let { PermissionService.INSTANCE[PermissionId.parseFromString(it)] } ?: permission,
|
permission,//overridePermission?.value?.let { PermissionService.INSTANCE[PermissionId.parseFromString(it)] } ?: permission,
|
||||||
onCommand = { sender: CommandSender, args: Map<KParameter, Any?> ->
|
onCommand = { _: CommandSender, args ->
|
||||||
val result = function.callSuspendBy(args)
|
val result = function.callSuspendBy(parameters.zip(args).toMap())
|
||||||
|
|
||||||
checkNotNull(result) { "sub command return value is null (at ${this::class.qualifiedName}.${function.name})" }
|
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.MiraiConsole
|
||||||
import net.mamoe.mirai.console.Testing
|
import net.mamoe.mirai.console.Testing
|
||||||
import net.mamoe.mirai.console.Testing.withTesting
|
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.executeCommand
|
||||||
import net.mamoe.mirai.console.command.CommandManager.INSTANCE.register
|
import net.mamoe.mirai.console.command.CommandManager.INSTANCE.register
|
||||||
import net.mamoe.mirai.console.command.CommandManager.INSTANCE.registeredCommands
|
import net.mamoe.mirai.console.command.CommandManager.INSTANCE.registeredCommands
|
||||||
|
Loading…
Reference in New Issue
Block a user