mirror of
https://github.com/mamoe/mirai.git
synced 2025-01-11 02:50:15 +08:00
commit
15d0cdaf90
@ -15,7 +15,7 @@ import kotlinx.coroutines.sync.withLock
|
||||
import net.mamoe.mirai.alsoLogin
|
||||
import net.mamoe.mirai.console.MiraiConsole
|
||||
import net.mamoe.mirai.console.command.CommandManager.INSTANCE.register
|
||||
import net.mamoe.mirai.console.command.description.*
|
||||
import net.mamoe.mirai.console.command.descriptor.*
|
||||
import net.mamoe.mirai.console.internal.command.CommandManagerImpl
|
||||
import net.mamoe.mirai.console.internal.command.CommandManagerImpl.allRegisteredCommands
|
||||
import net.mamoe.mirai.console.internal.util.runIgnoreException
|
||||
@ -150,8 +150,8 @@ public object BuiltInCommands {
|
||||
ConsoleCommandOwner, "permission", "权限", "perm",
|
||||
description = "管理权限",
|
||||
overrideContext = buildCommandArgumentContext {
|
||||
PermitteeId::class with PermitteeIdArgumentParser
|
||||
Permission::class with PermissionIdArgumentParser.map { id ->
|
||||
PermitteeId::class with PermitteeIdValueArgumentParser
|
||||
Permission::class with PermissionIdValueArgumentParser.map { id ->
|
||||
kotlin.runCatching {
|
||||
id.findCorrespondingPermissionOrFail()
|
||||
}.getOrElse { illegalArgument("指令不存在: $id", it) }
|
||||
|
@ -11,25 +11,27 @@
|
||||
|
||||
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
|
||||
|
||||
/**
|
||||
* 指令
|
||||
*
|
||||
* @see CommandManager.register 注册这个指令
|
||||
* @see CommandManager.registerCommand 注册这个指令
|
||||
*
|
||||
* @see RawCommand 无参数解析, 接收原生参数的指令
|
||||
* @see CompositeCommand 复合指令
|
||||
* @see SimpleCommand 简单的, 支持参数自动解析的指令
|
||||
*
|
||||
* @see CommandArgumentContextAware
|
||||
*
|
||||
* @see JCommand 为 Java 用户添加协程帮助的 [Command]
|
||||
*/
|
||||
public interface Command {
|
||||
@ -48,18 +50,25 @@ 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].
|
||||
*/
|
||||
public val usage: String
|
||||
|
||||
/**
|
||||
* 指令描述, 用于显示在 [BuiltInCommands.HelpCommand]
|
||||
* 描述, 用于显示在 [BuiltInCommands.HelpCommand]
|
||||
*/
|
||||
public val description: String
|
||||
|
||||
/**
|
||||
* 此指令所分配的权限.
|
||||
* 为此指令分配的权限.
|
||||
*
|
||||
* ### 实现约束
|
||||
* - [Permission.id] 应由 [CommandOwner.permissionId] 创建. 因此保证相同的 [PermissionId.namespace]
|
||||
@ -72,6 +81,8 @@ public interface Command {
|
||||
*
|
||||
* 会影响聊天语境中的解析.
|
||||
*/
|
||||
@ExperimentalCommandDescriptors
|
||||
@ConsoleExperimentalApi
|
||||
public val prefixOptional: Boolean
|
||||
|
||||
/**
|
||||
@ -80,16 +91,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 {
|
||||
|
||||
/**
|
||||
@ -109,19 +110,10 @@ public interface Command {
|
||||
public fun checkCommandName(@ResolveContext(COMMAND_NAME) name: String) {
|
||||
when {
|
||||
name.isBlank() -> throw IllegalArgumentException("Command name should not be blank.")
|
||||
name.any { it.isWhitespace() } -> throw IllegalArgumentException("Spaces is not yet allowed in command name.")
|
||||
name.any { it.isWhitespace() } -> throw IllegalArgumentException("Spaces are not yet allowed in command name.")
|
||||
name.contains(':') -> throw IllegalArgumentException("':' is forbidden in command name.")
|
||||
name.contains('.') -> throw IllegalArgumentException("'.' is forbidden in command name.")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 调用 [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
|
||||
@ -55,6 +59,21 @@ public sealed class CommandExecuteResult {
|
||||
public override val status: CommandExecuteStatus get() = CommandExecuteStatus.SUCCESSFUL
|
||||
}
|
||||
|
||||
/** 执行执行时发生了一个非法参数错误 */
|
||||
public class IllegalArgument(
|
||||
/** 指令执行时发生的错误 */
|
||||
public override val exception: IllegalCommandArgumentException,
|
||||
/** 尝试执行的指令 */
|
||||
public override val command: Command,
|
||||
/** 尝试执行的指令名 */
|
||||
public override val commandName: String,
|
||||
/** 基础分割后的实际参数列表, 元素类型可能为 [Message] 或 [String] */
|
||||
public override val args: MessageChain
|
||||
) : CommandExecuteResult() {
|
||||
/** 指令最终执行状态, 总是 [CommandExecuteStatus.EXECUTION_EXCEPTION] */
|
||||
public override val status: CommandExecuteStatus get() = CommandExecuteStatus.ILLEGAL_ARGUMENT
|
||||
}
|
||||
|
||||
/** 指令执行过程出现了错误 */
|
||||
public class ExecutionFailed(
|
||||
/** 指令执行时发生的错误 */
|
||||
@ -71,9 +90,9 @@ public sealed class CommandExecuteResult {
|
||||
}
|
||||
|
||||
/** 没有匹配的指令 */
|
||||
public class CommandNotFound(
|
||||
public class UnresolvedCall(
|
||||
/** 尝试执行的指令名 */
|
||||
public override val commandName: String
|
||||
public override val commandName: String,
|
||||
) : CommandExecuteResult() {
|
||||
/** 指令执行时发生的错误, 总是 `null` */
|
||||
public override val exception: Nothing? get() = null
|
||||
@ -119,7 +138,9 @@ public sealed class CommandExecuteResult {
|
||||
COMMAND_NOT_FOUND,
|
||||
|
||||
/** 权限不足 */
|
||||
PERMISSION_DENIED
|
||||
PERMISSION_DENIED,
|
||||
/** 非法参数 */
|
||||
ILLEGAL_ARGUMENT,
|
||||
}
|
||||
}
|
||||
|
||||
@ -138,6 +159,18 @@ public fun CommandExecuteResult.isSuccess(): Boolean {
|
||||
return this is CommandExecuteResult.Success
|
||||
}
|
||||
|
||||
/**
|
||||
* 当 [this] 为 [CommandExecuteResult.IllegalArgument] 时返回 `true`
|
||||
*/
|
||||
@JvmSynthetic
|
||||
public fun CommandExecuteResult.isIllegalArgument(): Boolean {
|
||||
contract {
|
||||
returns(true) implies (this@isIllegalArgument is CommandExecuteResult.IllegalArgument)
|
||||
returns(false) implies (this@isIllegalArgument !is CommandExecuteResult.IllegalArgument)
|
||||
}
|
||||
return this is CommandExecuteResult.IllegalArgument
|
||||
}
|
||||
|
||||
/**
|
||||
* 当 [this] 为 [CommandExecuteResult.ExecutionFailed] 时返回 `true`
|
||||
*/
|
||||
@ -151,7 +184,7 @@ public fun CommandExecuteResult.isExecutionException(): Boolean {
|
||||
}
|
||||
|
||||
/**
|
||||
* 当 [this] 为 [CommandExecuteResult.ExecutionFailed] 时返回 `true`
|
||||
* 当 [this] 为 [CommandExecuteResult.PermissionDenied] 时返回 `true`
|
||||
*/
|
||||
@JvmSynthetic
|
||||
public fun CommandExecuteResult.isPermissionDenied(): Boolean {
|
||||
@ -163,19 +196,19 @@ public fun CommandExecuteResult.isPermissionDenied(): Boolean {
|
||||
}
|
||||
|
||||
/**
|
||||
* 当 [this] 为 [CommandExecuteResult.ExecutionFailed] 时返回 `true`
|
||||
* 当 [this] 为 [CommandExecuteResult.UnresolvedCall] 时返回 `true`
|
||||
*/
|
||||
@JvmSynthetic
|
||||
public fun CommandExecuteResult.isCommandNotFound(): Boolean {
|
||||
contract {
|
||||
returns(true) implies (this@isCommandNotFound is CommandExecuteResult.CommandNotFound)
|
||||
returns(false) implies (this@isCommandNotFound !is CommandExecuteResult.CommandNotFound)
|
||||
returns(true) implies (this@isCommandNotFound is CommandExecuteResult.UnresolvedCall)
|
||||
returns(false) implies (this@isCommandNotFound !is CommandExecuteResult.UnresolvedCall)
|
||||
}
|
||||
return this is CommandExecuteResult.CommandNotFound
|
||||
return this is CommandExecuteResult.UnresolvedCall
|
||||
}
|
||||
|
||||
/**
|
||||
* 当 [this] 为 [CommandExecuteResult.ExecutionFailed] 或 [CommandExecuteResult.CommandNotFound] 时返回 `true`
|
||||
* 当 [this] 为 [CommandExecuteResult.ExecutionFailed], [CommandExecuteResult.IllegalArgument] 或 [CommandExecuteResult.UnresolvedCall] 时返回 `true`
|
||||
*/
|
||||
@JvmSynthetic
|
||||
public fun CommandExecuteResult.isFailure(): Boolean {
|
||||
|
@ -8,7 +8,7 @@
|
||||
*/
|
||||
|
||||
@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")
|
||||
@ -16,21 +16,21 @@
|
||||
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.resolve.CommandCallResolver
|
||||
import net.mamoe.mirai.console.internal.command.CommandManagerImpl
|
||||
import net.mamoe.mirai.console.internal.command.CommandManagerImpl.executeCommand
|
||||
import net.mamoe.mirai.console.internal.command.executeCommandImpl
|
||||
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
|
||||
import net.mamoe.mirai.message.data.*
|
||||
|
||||
/**
|
||||
* 指令管理器
|
||||
*/
|
||||
public interface CommandManager {
|
||||
/**
|
||||
* 获取已经注册了的属于这个 [CommandOwner] 的指令列表.
|
||||
*
|
||||
* @return 这一时刻的浅拷贝.
|
||||
*/
|
||||
public val CommandOwner.registeredCommands: List<Command>
|
||||
|
||||
/**
|
||||
* 获取所有已经注册了指令列表.
|
||||
*
|
||||
@ -44,9 +44,17 @@ public interface CommandManager {
|
||||
public val commandPrefix: String
|
||||
|
||||
/**
|
||||
* 取消注册所有属于 [this] 的指令
|
||||
* 获取已经注册了的属于这个 [CommandOwner] 的指令列表.
|
||||
*
|
||||
* @return 这一时刻的浅拷贝.
|
||||
*/
|
||||
public fun CommandOwner.unregisterAllCommands()
|
||||
public fun getRegisteredCommands(owner: CommandOwner): List<Command>
|
||||
|
||||
|
||||
/**
|
||||
* 取消注册所有属于 [owner] 的指令
|
||||
*/
|
||||
public fun unregisterAllCommands(owner: CommandOwner)
|
||||
|
||||
/**
|
||||
* 注册一个指令.
|
||||
@ -65,35 +73,31 @@ public interface CommandManager {
|
||||
*
|
||||
* 注意: [内建指令][BuiltInCommands] 也可以被覆盖.
|
||||
*/
|
||||
@JvmName("registerCommand")
|
||||
public fun Command.register(override: Boolean = false): Boolean
|
||||
public fun registerCommand(command: Command, override: Boolean = false): Boolean
|
||||
|
||||
/**
|
||||
* 查找并返回重名的指令. 返回重名指令.
|
||||
*/
|
||||
@JvmName("findCommandDuplicate")
|
||||
public fun Command.findDuplicate(): Command?
|
||||
public fun findDuplicateCommand(command: Command): Command?
|
||||
|
||||
/**
|
||||
* 取消注册这个指令.
|
||||
*
|
||||
* 若指令未注册, 返回 `false`.
|
||||
*/
|
||||
@JvmName("unregisterCommand")
|
||||
public fun Command.unregister(): Boolean
|
||||
public fun unregisterCommand(command: Command): Boolean
|
||||
|
||||
/**
|
||||
* 当 [this] 已经 [注册][register] 时返回 `true`
|
||||
* 当 [command] 已经 [注册][registerCommand] 时返回 `true`
|
||||
*/
|
||||
@JvmName("isCommandRegistered")
|
||||
public fun Command.isRegistered(): Boolean
|
||||
public fun isCommandRegistered(command: Command): Boolean
|
||||
|
||||
/**
|
||||
* 解析并执行一个指令.
|
||||
*
|
||||
* 如要避免参数解析, 请使用 [Command.onCommand]
|
||||
*
|
||||
* ### 指令解析流程
|
||||
* 1. [CommandCallParser] 将 [MessageChain] 解析为 [CommandCall]
|
||||
* 2. [CommandCallResolver] 将 [CommandCall] 解析为 []
|
||||
* 1. [message] 的第一个消息元素的 [内容][Message.contentToString] 被作为指令名, 在已注册指令列表中搜索. (包含 [Command.prefixOptional] 相关的处理)
|
||||
* 2. 参数语法分析.
|
||||
* 在当前的实现下, [message] 被以空格和 [SingleMessage] 分割.
|
||||
@ -101,21 +105,115 @@ public interface CommandManager {
|
||||
* 注意: 字符串与消息元素之间不需要空格, 会被强制分割. 如 "bar[mirai:image:]" 会被分割为 "bar" 和 [Image] 类型的消息元素.
|
||||
* 3. 参数解析. 各类型指令实现不同. 详见 [RawCommand], [CompositeCommand], [SimpleCommand]
|
||||
*
|
||||
* ### 未来的扩展
|
||||
* 在将来, 参数语法分析过程可能会被扩展, 允许插件自定义处理方式, 因此可能不会简单地使用空格分隔.
|
||||
* ### 扩展
|
||||
* 参数语法分析过程可能会被扩展, 插件可以自定义处理方式 ([CommandCallParser]), 因此可能不会简单地使用空格分隔.
|
||||
*
|
||||
* @param message 一条完整的指令. 如 "/managers add 123456.123456"
|
||||
* @param checkPermission 为 `true` 时检查权限
|
||||
*
|
||||
* @see CommandCallParser
|
||||
* @see CommandCallResolver
|
||||
*
|
||||
* @see CommandSender.executeCommand
|
||||
* @see Command.execute
|
||||
*
|
||||
* @return 执行结果
|
||||
*/
|
||||
@ExperimentalCommandDescriptors
|
||||
@JvmBlockingBridge
|
||||
public suspend fun CommandSender.executeCommand(
|
||||
public suspend fun executeCommand(
|
||||
caller: CommandSender,
|
||||
message: Message,
|
||||
checkPermission: Boolean = true,
|
||||
): CommandExecuteResult
|
||||
): CommandExecuteResult {
|
||||
return executeCommandImpl(message, caller, checkPermission)
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行一个确切的指令
|
||||
*
|
||||
* @param command 目标指令
|
||||
* @param arguments 参数列表
|
||||
*
|
||||
* @see executeCommand 获取更多信息
|
||||
* @see Command.execute
|
||||
*/
|
||||
@ConsoleExperimentalApi
|
||||
@JvmName("executeCommand")
|
||||
@ExperimentalCommandDescriptors
|
||||
@JvmSynthetic
|
||||
public suspend fun executeCommand(
|
||||
sender: CommandSender,
|
||||
command: Command,
|
||||
arguments: Message = EmptyMessageChain,
|
||||
checkPermission: Boolean = true,
|
||||
): CommandExecuteResult {
|
||||
// TODO: 2020/10/18 net.mamoe.mirai.console.command.CommandManager.execute
|
||||
val chain = buildMessageChain {
|
||||
append(CommandManager.commandPrefix)
|
||||
append(command.primaryName)
|
||||
append(' ')
|
||||
append(arguments)
|
||||
}
|
||||
return CommandManager.executeCommand(sender, chain, checkPermission)
|
||||
}
|
||||
|
||||
/**
|
||||
* 从 [指令名称][commandName] 匹配对应的 [Command].
|
||||
*
|
||||
* #### 实现细节
|
||||
* - [commandName] 带有 [commandPrefix] 时可以匹配到所有指令
|
||||
* - [commandName] 不带有 [commandPrefix] 时只能匹配到 [Command.prefixOptional] 的指令
|
||||
*
|
||||
* @param commandName 可能带有或不带有 [commandPrefix].
|
||||
*/
|
||||
public fun matchCommand(commandName: String): Command?
|
||||
|
||||
public companion object INSTANCE : CommandManager by CommandManagerImpl {
|
||||
|
||||
/**
|
||||
* @see CommandManager.getRegisteredCommands
|
||||
*/
|
||||
@get:JvmName("registeredCommands0")
|
||||
@get:JvmSynthetic
|
||||
public inline val CommandOwner.registeredCommands: List<Command>
|
||||
get() = getRegisteredCommands(this)
|
||||
|
||||
/**
|
||||
* @see CommandManager.registerCommand
|
||||
*/
|
||||
@JvmSynthetic
|
||||
public inline fun Command.register(override: Boolean = false): Boolean = registerCommand(this, override)
|
||||
|
||||
/**
|
||||
* @see CommandManager.unregisterCommand
|
||||
*/
|
||||
@JvmSynthetic
|
||||
public inline fun Command.unregister(): Boolean = unregisterCommand(this)
|
||||
|
||||
/**
|
||||
* @see CommandManager.isCommandRegistered
|
||||
*/
|
||||
@get:JvmSynthetic
|
||||
public inline val Command.isRegistered: Boolean
|
||||
get() = isCommandRegistered(this)
|
||||
|
||||
/**
|
||||
* @see CommandManager.unregisterAll
|
||||
*/
|
||||
@JvmSynthetic
|
||||
public inline fun CommandOwner.unregisterAll(): Unit = unregisterAllCommands(this)
|
||||
|
||||
/**
|
||||
* @see CommandManager.findDuplicate
|
||||
*/
|
||||
@JvmSynthetic
|
||||
public inline fun Command.findDuplicate(): Command? = findDuplicateCommand(this)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析并执行一个指令
|
||||
*
|
||||
* @param message 一条完整的指令. 如 "/managers add 123456.123456"
|
||||
@ -124,95 +222,37 @@ public interface CommandManager {
|
||||
* @return 执行结果
|
||||
* @see executeCommand
|
||||
*/
|
||||
@JvmBlockingBridge
|
||||
public suspend fun CommandSender.executeCommand(
|
||||
@JvmName("execute0")
|
||||
@ExperimentalCommandDescriptors
|
||||
@JvmSynthetic
|
||||
public suspend inline fun CommandSender.executeCommand(
|
||||
message: String,
|
||||
checkPermission: Boolean = true,
|
||||
): CommandExecuteResult = executeCommand(PlainText(message).asMessageChain(), checkPermission)
|
||||
): CommandExecuteResult = CommandManager.executeCommand(this, PlainText(message).asMessageChain(), checkPermission)
|
||||
|
||||
/**
|
||||
|
||||
/**
|
||||
* 执行一个确切的指令
|
||||
* @see executeCommand 获取更多信息
|
||||
*/
|
||||
@JvmBlockingBridge
|
||||
@JvmName("executeCommand")
|
||||
public suspend fun Command.execute(
|
||||
@JvmName("execute0")
|
||||
@ExperimentalCommandDescriptors
|
||||
@JvmSynthetic
|
||||
public suspend inline fun Command.execute(
|
||||
sender: CommandSender,
|
||||
arguments: Message = EmptyMessageChain,
|
||||
checkPermission: Boolean = true,
|
||||
): CommandExecuteResult
|
||||
): CommandExecuteResult = CommandManager.executeCommand(sender, this, arguments, checkPermission)
|
||||
|
||||
/**
|
||||
/**
|
||||
* 执行一个确切的指令
|
||||
* @see executeCommand 获取更多信息
|
||||
*/
|
||||
@JvmBlockingBridge
|
||||
@JvmName("executeCommand")
|
||||
public suspend fun Command.execute(
|
||||
@JvmName("execute0")
|
||||
@ExperimentalCommandDescriptors
|
||||
@JvmSynthetic
|
||||
public suspend inline fun Command.execute(
|
||||
sender: CommandSender,
|
||||
arguments: String = "",
|
||||
checkPermission: Boolean = true,
|
||||
): CommandExecuteResult = execute(sender, PlainText(arguments).asMessageChain(), checkPermission)
|
||||
|
||||
public companion object INSTANCE : CommandManager by CommandManagerImpl {
|
||||
// TODO: 2020/8/20 https://youtrack.jetbrains.com/issue/KT-41191
|
||||
|
||||
override val CommandOwner.registeredCommands: List<Command> get() = CommandManagerImpl.run { this@registeredCommands.registeredCommands }
|
||||
override fun CommandOwner.unregisterAllCommands(): Unit = CommandManagerImpl.run { unregisterAllCommands() }
|
||||
override fun Command.register(override: Boolean): Boolean = CommandManagerImpl.run { register(override) }
|
||||
override fun Command.findDuplicate(): Command? = CommandManagerImpl.run { findDuplicate() }
|
||||
override fun Command.unregister(): Boolean = CommandManagerImpl.run { unregister() }
|
||||
override fun Command.isRegistered(): Boolean = CommandManagerImpl.run { isRegistered() }
|
||||
override val commandPrefix: String get() = CommandManagerImpl.commandPrefix
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
): CommandExecuteResult = execute(sender, PlainText(arguments), checkPermission)
|
||||
|
@ -20,15 +20,14 @@ 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
|
||||
import net.mamoe.mirai.console.command.CommandSender.Companion.toCommandSender
|
||||
import net.mamoe.mirai.console.command.description.CommandArgumentParserException
|
||||
import net.mamoe.mirai.console.command.descriptor.CommandArgumentParserException
|
||||
import net.mamoe.mirai.console.internal.MiraiConsoleImplementationBridge
|
||||
import net.mamoe.mirai.console.internal.command.qualifiedNameOrTip
|
||||
import net.mamoe.mirai.console.internal.data.castOrNull
|
||||
import net.mamoe.mirai.console.internal.data.qualifiedNameOrTip
|
||||
import net.mamoe.mirai.console.internal.plugin.rootCauseOrSelf
|
||||
import net.mamoe.mirai.console.permission.AbstractPermitteeId
|
||||
import net.mamoe.mirai.console.permission.Permittee
|
||||
@ -281,6 +280,13 @@ public sealed class AbstractCommandSender : CommandSender, CoroutineScope {
|
||||
if (this is CommandSenderOnMessage<*>) {
|
||||
val cause = e.rootCauseOrSelf
|
||||
|
||||
// TODO: 2020/10/17
|
||||
// CommandArgumentParserException 作为 IllegalCommandArgumentException 不会再进入此函数
|
||||
// 已在
|
||||
// - [console] CommandManagerImpl.commandListener
|
||||
// - [terminal] ConsoleThread.kt
|
||||
// 处理
|
||||
|
||||
val message = cause
|
||||
.takeIf { it is CommandArgumentParserException }?.message
|
||||
?: "${cause::class.simpleName.orEmpty()}: ${cause.message}"
|
||||
|
@ -17,14 +17,13 @@
|
||||
|
||||
package net.mamoe.mirai.console.command
|
||||
|
||||
import net.mamoe.mirai.console.command.description.*
|
||||
import net.mamoe.mirai.console.command.descriptor.*
|
||||
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.AbstractReflectionCommand
|
||||
import net.mamoe.mirai.console.internal.command.CommandReflector
|
||||
import net.mamoe.mirai.console.internal.command.CompositeCommandSubCommandAnnotationResolver
|
||||
import net.mamoe.mirai.console.permission.Permission
|
||||
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
|
||||
import net.mamoe.mirai.message.data.MessageChain
|
||||
import kotlin.annotation.AnnotationRetention.RUNTIME
|
||||
import kotlin.annotation.AnnotationTarget.FUNCTION
|
||||
|
||||
@ -90,16 +89,26 @@ public abstract class CompositeCommand(
|
||||
parentPermission: Permission = owner.parentPermission,
|
||||
prefixOptional: Boolean = false,
|
||||
overrideContext: CommandArgumentContext = EmptyCommandArgumentContext,
|
||||
) : Command, AbstractReflectionCommand(owner, primaryName, secondaryNames = secondaryNames, description, parentPermission, prefixOptional),
|
||||
) : Command, AbstractCommand(owner, primaryName, secondaryNames = secondaryNames, description, parentPermission, prefixOptional),
|
||||
CommandArgumentContextAware {
|
||||
|
||||
private val reflector by lazy { CommandReflector(this, CompositeCommandSubCommandAnnotationResolver) }
|
||||
|
||||
@ExperimentalCommandDescriptors
|
||||
public final override val overloads: List<CommandSignatureVariantFromKFunction> by lazy {
|
||||
reflector.findSubCommands()
|
||||
}
|
||||
|
||||
/**
|
||||
* 自动根据带有 [SubCommand] 注解的函数签名生成 [usage]. 也可以被覆盖.
|
||||
*/
|
||||
public override val usage: String get() = super.usage
|
||||
public override val usage: String by lazy {
|
||||
@OptIn(ExperimentalCommandDescriptors::class)
|
||||
reflector.generateUsage(overloads)
|
||||
}
|
||||
|
||||
/**
|
||||
* [CommandArgumentParser] 的环境
|
||||
* [CommandValueArgumentParser] 的环境
|
||||
*/
|
||||
public final override val context: CommandArgumentContext = CommandArgumentContext.Builtins + overrideContext
|
||||
|
||||
@ -123,20 +132,6 @@ public abstract class CompositeCommand(
|
||||
@Retention(RUNTIME)
|
||||
@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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected override suspend fun CommandSender.onDefault(rawArgs: MessageChain) {
|
||||
sendMessage(usage)
|
||||
}
|
||||
|
||||
internal final override val subCommandAnnotationResolver: SubCommandAnnotationResolver
|
||||
get() = CompositeCommandSubCommandAnnotationResolver
|
||||
}
|
||||
|
||||
|
||||
|
@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright 2019-2020 Mamoe Technologies and contributors.
|
||||
*
|
||||
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
|
||||
* Use of this source code is governed by the GNU AFFERO GENERAL PUBLIC LICENSE version 3 license that can be found via the following link.
|
||||
*
|
||||
* https://github.com/mamoe/mirai/blob/master/LICENSE
|
||||
*
|
||||
*/
|
||||
|
||||
@file:Suppress("unused")
|
||||
|
||||
package net.mamoe.mirai.console.command
|
||||
|
||||
import net.mamoe.mirai.console.command.descriptor.CommandArgumentParserException
|
||||
|
||||
/**
|
||||
* 在处理参数时遇到的 _正常_ 错误. 如参数不符合规范, 参数值越界等.
|
||||
*
|
||||
* [message] 将会发送给指令调用方.
|
||||
*
|
||||
* @see CommandArgumentParserException
|
||||
*/
|
||||
public open class IllegalCommandArgumentException : IllegalArgumentException {
|
||||
public constructor() : super()
|
||||
public constructor(message: String?) : super(message)
|
||||
public constructor(message: String?, cause: Throwable?) : super(message, cause)
|
||||
public constructor(cause: Throwable?) : super(cause)
|
||||
}
|
@ -11,14 +11,17 @@
|
||||
|
||||
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.*
|
||||
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.internal.data.typeOf0
|
||||
import net.mamoe.mirai.console.permission.Permission
|
||||
import net.mamoe.mirai.message.data.Message
|
||||
import net.mamoe.mirai.message.data.MessageChain
|
||||
import net.mamoe.mirai.message.data.buildMessageChain
|
||||
|
||||
/**
|
||||
* 无参数解析, 接收原生参数的指令.
|
||||
@ -52,6 +55,18 @@ public abstract class RawCommand(
|
||||
) : Command {
|
||||
public override val permission: Permission by lazy { createOrFindCommandPermission(parentPermission) }
|
||||
|
||||
@ExperimentalCommandDescriptors
|
||||
override val overloads: List<CommandSignatureVariant> = listOf(
|
||||
CommandSignatureVariantImpl(
|
||||
receiverParameter = CommandReceiverParameter(false, typeOf0<CommandSender>()),
|
||||
valueParameters = listOf(AbstractCommandValueParameter.UserDefinedType.createRequired<Array<out Message>>("args", true))
|
||||
) { call ->
|
||||
val sender = call.caller
|
||||
val arguments = call.rawValueArguments
|
||||
sender.onCommand(buildMessageChain { arguments.forEach { +it.value } })
|
||||
}
|
||||
)
|
||||
|
||||
/**
|
||||
* 在指令被执行时调用.
|
||||
*
|
||||
@ -59,7 +74,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)
|
||||
}
|
||||
|
||||
|
||||
|
@ -18,20 +18,23 @@
|
||||
package net.mamoe.mirai.console.command
|
||||
|
||||
import net.mamoe.mirai.console.command.CommandManager.INSTANCE.executeCommand
|
||||
import net.mamoe.mirai.console.command.description.*
|
||||
import net.mamoe.mirai.console.command.descriptor.*
|
||||
import net.mamoe.mirai.console.command.java.JSimpleCommand
|
||||
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.AbstractReflectionCommand
|
||||
import net.mamoe.mirai.console.internal.command.CommandReflector
|
||||
import net.mamoe.mirai.console.internal.command.IllegalCommandDeclarationException
|
||||
import net.mamoe.mirai.console.internal.command.SimpleCommandSubCommandAnnotationResolver
|
||||
import net.mamoe.mirai.console.permission.Permission
|
||||
import net.mamoe.mirai.message.data.MessageChain
|
||||
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
|
||||
import kotlin.annotation.AnnotationTarget.FUNCTION
|
||||
import kotlin.annotation.AnnotationTarget.VALUE_PARAMETER
|
||||
|
||||
/**
|
||||
* 简单的, 支持参数自动解析的指令.
|
||||
*
|
||||
* 要查看指令解析流程, 参考 [CommandManager.executeCommand]
|
||||
* 要查看参数解析方式, 参考 [CommandArgumentParser]
|
||||
* 要查看参数解析方式, 参考 [CommandValueArgumentParser]
|
||||
*
|
||||
* Kotlin 实现:
|
||||
* ```
|
||||
@ -58,39 +61,41 @@ public abstract class SimpleCommand(
|
||||
parentPermission: Permission = owner.parentPermission,
|
||||
prefixOptional: Boolean = false,
|
||||
overrideContext: CommandArgumentContext = EmptyCommandArgumentContext,
|
||||
) : Command, AbstractReflectionCommand(owner, primaryName, secondaryNames = secondaryNames, description, parentPermission, prefixOptional),
|
||||
) : Command, AbstractCommand(owner, primaryName, secondaryNames = secondaryNames, description, parentPermission, prefixOptional),
|
||||
CommandArgumentContextAware {
|
||||
|
||||
private val reflector by lazy { CommandReflector(this, SimpleCommandSubCommandAnnotationResolver) }
|
||||
|
||||
@ExperimentalCommandDescriptors
|
||||
public final override val overloads: List<CommandSignatureVariantFromKFunction> by lazy {
|
||||
reflector.findSubCommands().also {
|
||||
if (it.isEmpty())
|
||||
throw IllegalCommandDeclarationException(this, "SimpleCommand must have at least one subcommand, whereas zero present.")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 自动根据带有 [Handler] 注解的函数签名生成 [usage]. 也可以被覆盖.
|
||||
*/
|
||||
public override val usage: String get() = super.usage
|
||||
public override val usage: String by lazy {
|
||||
@OptIn(ExperimentalCommandDescriptors::class)
|
||||
reflector.generateUsage(overloads)
|
||||
}
|
||||
|
||||
/**
|
||||
* 标注指令处理器
|
||||
*/
|
||||
@Target(FUNCTION)
|
||||
protected annotation class Handler
|
||||
|
||||
/** 参数名, 将参与构成 [usage] */
|
||||
@ConsoleExperimentalApi("Classname might change")
|
||||
@Target(VALUE_PARAMETER)
|
||||
protected annotation class Name(val value: String)
|
||||
|
||||
/**
|
||||
* 指令参数环境. 默认为 [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>) {
|
||||
super.checkSubCommand(subCommands)
|
||||
check(subCommands.size == 1) { "There can only be exactly one function annotated with Handler at this moment as overloading is not yet supported." }
|
||||
}
|
||||
|
||||
@Deprecated("prohibited", level = DeprecationLevel.HIDDEN)
|
||||
internal override suspend fun CommandSender.onDefault(rawArgs: MessageChain) {
|
||||
sendMessage(usage)
|
||||
}
|
||||
|
||||
internal final override val subCommandAnnotationResolver: SubCommandAnnotationResolver
|
||||
get() = SimpleCommandSubCommandAnnotationResolver
|
||||
}
|
||||
|
||||
|
@ -9,18 +9,19 @@
|
||||
|
||||
@file:Suppress("NOTHING_TO_INLINE", "INVISIBLE_MEMBER", "INVISIBLE_REFERENCE", "unused", "MemberVisibilityCanBePrivate")
|
||||
|
||||
package net.mamoe.mirai.console.command.description
|
||||
package net.mamoe.mirai.console.command.descriptor
|
||||
|
||||
import net.mamoe.mirai.Bot
|
||||
import net.mamoe.mirai.console.command.CommandSender
|
||||
import net.mamoe.mirai.console.command.CompositeCommand
|
||||
import net.mamoe.mirai.console.command.SimpleCommand
|
||||
import net.mamoe.mirai.console.command.description.CommandArgumentContext.ParserPair
|
||||
import net.mamoe.mirai.console.command.descriptor.CommandArgumentContext.ParserPair
|
||||
import net.mamoe.mirai.console.permission.PermissionId
|
||||
import net.mamoe.mirai.console.permission.PermitteeId
|
||||
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
|
||||
import net.mamoe.mirai.contact.*
|
||||
import net.mamoe.mirai.message.data.Image
|
||||
import net.mamoe.mirai.message.data.MessageContent
|
||||
import net.mamoe.mirai.message.data.PlainText
|
||||
import kotlin.internal.LowPriorityInOverloadResolution
|
||||
import kotlin.reflect.KClass
|
||||
@ -28,7 +29,7 @@ import kotlin.reflect.full.isSubclassOf
|
||||
|
||||
|
||||
/**
|
||||
* 指令参数环境, 即 [CommandArgumentParser] 的集合, 用于 [CompositeCommand] 和 [SimpleCommand].
|
||||
* 指令参数环境, 即 [CommandValueArgumentParser] 的集合, 用于 [CompositeCommand] 和 [SimpleCommand].
|
||||
*
|
||||
* 在指令解析时, 总是从 [CommandArgumentContextAware.context] 搜索相关解析器
|
||||
*
|
||||
@ -37,20 +38,20 @@ import kotlin.reflect.full.isSubclassOf
|
||||
* @see SimpleCommandArgumentContext 简单实现
|
||||
* @see EmptyCommandArgumentContext 空实现, 类似 [emptyList]
|
||||
*
|
||||
* @see CommandArgumentContext.Builtins 内建 [CommandArgumentParser]
|
||||
* @see CommandArgumentContext.Builtins 内建 [CommandValueArgumentParser]
|
||||
*
|
||||
* @see buildCommandArgumentContext DSL 构造
|
||||
*/
|
||||
public interface CommandArgumentContext {
|
||||
/**
|
||||
* [KClass] 到 [CommandArgumentParser] 的匹配
|
||||
* [KClass] 到 [CommandValueArgumentParser] 的匹配
|
||||
*/
|
||||
public data class ParserPair<T : Any>(
|
||||
val klass: KClass<T>,
|
||||
val parser: CommandArgumentParser<T>,
|
||||
val parser: CommandValueArgumentParser<T>,
|
||||
)
|
||||
|
||||
public operator fun <T : Any> get(klass: KClass<out T>): CommandArgumentParser<T>?
|
||||
public operator fun <T : Any> get(klass: KClass<out T>): CommandValueArgumentParser<T>?
|
||||
|
||||
public fun toList(): List<ParserPair<*>>
|
||||
|
||||
@ -65,30 +66,32 @@ public interface CommandArgumentContext {
|
||||
}
|
||||
|
||||
/**
|
||||
* 内建的默认 [CommandArgumentParser]
|
||||
* 内建的默认 [CommandValueArgumentParser]
|
||||
*/
|
||||
public object Builtins : CommandArgumentContext by (buildCommandArgumentContext {
|
||||
Int::class with IntArgumentParser
|
||||
Byte::class with ByteArgumentParser
|
||||
Short::class with ShortArgumentParser
|
||||
Boolean::class with BooleanArgumentParser
|
||||
String::class with StringArgumentParser
|
||||
Long::class with LongArgumentParser
|
||||
Double::class with DoubleArgumentParser
|
||||
Float::class with FloatArgumentParser
|
||||
Int::class with IntValueArgumentParser
|
||||
Byte::class with ByteValueArgumentParser
|
||||
Short::class with ShortValueArgumentParser
|
||||
Boolean::class with BooleanValueArgumentParser
|
||||
String::class with StringValueArgumentParser
|
||||
Long::class with LongValueArgumentParser
|
||||
Double::class with DoubleValueArgumentParser
|
||||
Float::class with FloatValueArgumentParser
|
||||
|
||||
Image::class with ImageArgumentParser
|
||||
PlainText::class with PlainTextArgumentParser
|
||||
Image::class with ImageValueArgumentParser
|
||||
PlainText::class with PlainTextValueArgumentParser
|
||||
|
||||
Contact::class with ExistingContactArgumentParser
|
||||
User::class with ExistingUserArgumentParser
|
||||
Member::class with ExistingMemberArgumentParser
|
||||
Group::class with ExistingGroupArgumentParser
|
||||
Friend::class with ExistingFriendArgumentParser
|
||||
Bot::class with ExistingBotArgumentParser
|
||||
Contact::class with ExistingContactValueArgumentParser
|
||||
User::class with ExistingUserValueArgumentParser
|
||||
Member::class with ExistingMemberValueArgumentParser
|
||||
Group::class with ExistingGroupValueArgumentParser
|
||||
Friend::class with ExistingFriendValueArgumentParser
|
||||
Bot::class with ExistingBotValueArgumentParser
|
||||
|
||||
PermissionId::class with PermissionIdArgumentParser
|
||||
PermitteeId::class with PermitteeIdArgumentParser
|
||||
PermissionId::class with PermissionIdValueArgumentParser
|
||||
PermitteeId::class with PermitteeIdValueArgumentParser
|
||||
|
||||
MessageContent::class with RawContentValueArgumentParser
|
||||
})
|
||||
}
|
||||
|
||||
@ -100,7 +103,7 @@ public interface CommandArgumentContext {
|
||||
*/
|
||||
public interface CommandArgumentContextAware {
|
||||
/**
|
||||
* [CommandArgumentParser] 的集合
|
||||
* [CommandValueArgumentParser] 的集合
|
||||
*/
|
||||
public val context: CommandArgumentContext
|
||||
}
|
||||
@ -114,7 +117,7 @@ public operator fun CommandArgumentContext.plus(replacer: CommandArgumentContext
|
||||
if (replacer == EmptyCommandArgumentContext) return this
|
||||
if (this == EmptyCommandArgumentContext) return replacer
|
||||
return object : CommandArgumentContext {
|
||||
override fun <T : Any> get(klass: KClass<out T>): CommandArgumentParser<T>? =
|
||||
override fun <T : Any> get(klass: KClass<out T>): CommandValueArgumentParser<T>? =
|
||||
replacer[klass] ?: this@plus[klass]
|
||||
|
||||
override fun toList(): List<ParserPair<*>> = replacer.toList() + this@plus.toList()
|
||||
@ -129,8 +132,8 @@ public operator fun CommandArgumentContext.plus(replacer: List<ParserPair<*>>):
|
||||
if (this == EmptyCommandArgumentContext) return SimpleCommandArgumentContext(replacer)
|
||||
return object : CommandArgumentContext {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
override fun <T : Any> get(klass: KClass<out T>): CommandArgumentParser<T>? =
|
||||
replacer.firstOrNull { klass.isSubclassOf(it.klass) }?.parser as CommandArgumentParser<T>?
|
||||
override fun <T : Any> get(klass: KClass<out T>): CommandValueArgumentParser<T>? =
|
||||
replacer.firstOrNull { klass.isSubclassOf(it.klass) }?.parser as CommandValueArgumentParser<T>?
|
||||
?: this@plus[klass]
|
||||
|
||||
override fun toList(): List<ParserPair<*>> = replacer.toList() + this@plus.toList()
|
||||
@ -146,9 +149,9 @@ public operator fun CommandArgumentContext.plus(replacer: List<ParserPair<*>>):
|
||||
public class SimpleCommandArgumentContext(
|
||||
public val list: List<ParserPair<*>>,
|
||||
) : CommandArgumentContext {
|
||||
override fun <T : Any> get(klass: KClass<out T>): CommandArgumentParser<T>? =
|
||||
override fun <T : Any> get(klass: KClass<out T>): CommandValueArgumentParser<T>? =
|
||||
(this.list.firstOrNull { klass == it.klass }?.parser
|
||||
?: this.list.firstOrNull { klass.isSubclassOf(it.klass) }?.parser) as CommandArgumentParser<T>?
|
||||
?: this.list.firstOrNull { klass.isSubclassOf(it.klass) }?.parser) as CommandValueArgumentParser<T>?
|
||||
|
||||
override fun toList(): List<ParserPair<*>> = list
|
||||
}
|
||||
@ -160,7 +163,7 @@ public class SimpleCommandArgumentContext(
|
||||
* ```
|
||||
* val context = buildCommandArgumentContext {
|
||||
* Int::class with IntArgParser
|
||||
* Member::class with ExistMemberArgParser
|
||||
* Member::class with ExistingMemberArgParser
|
||||
* Group::class with { s: String, sender: CommandSender ->
|
||||
* Bot.getInstance(s.toLong()).getGroup(s.toLong())
|
||||
* }
|
||||
@ -200,14 +203,14 @@ public class CommandArgumentContextBuilder : MutableList<ParserPair<*>> by mutab
|
||||
* 添加一个指令解析器.
|
||||
*/
|
||||
@JvmName("add")
|
||||
public infix fun <T : Any> Class<T>.with(parser: CommandArgumentParser<T>): CommandArgumentContextBuilder =
|
||||
public infix fun <T : Any> Class<T>.with(parser: CommandValueArgumentParser<T>): CommandArgumentContextBuilder =
|
||||
this.kotlin with parser
|
||||
|
||||
/**
|
||||
* 添加一个指令解析器
|
||||
*/
|
||||
@JvmName("add")
|
||||
public inline infix fun <T : Any> KClass<T>.with(parser: CommandArgumentParser<T>): CommandArgumentContextBuilder {
|
||||
public inline infix fun <T : Any> KClass<T>.with(parser: CommandValueArgumentParser<T>): CommandArgumentContextBuilder {
|
||||
add(ParserPair(this, parser))
|
||||
return this@CommandArgumentContextBuilder
|
||||
}
|
||||
@ -218,9 +221,9 @@ public class CommandArgumentContextBuilder : MutableList<ParserPair<*>> by mutab
|
||||
@JvmSynthetic
|
||||
@LowPriorityInOverloadResolution
|
||||
public inline infix fun <T : Any> KClass<T>.with(
|
||||
crossinline parser: CommandArgumentParser<T>.(s: String, sender: CommandSender) -> T,
|
||||
crossinline parser: CommandValueArgumentParser<T>.(s: String, sender: CommandSender) -> T,
|
||||
): CommandArgumentContextBuilder {
|
||||
add(ParserPair(this, object : CommandArgumentParser<T> {
|
||||
add(ParserPair(this, object : CommandValueArgumentParser<T> {
|
||||
override fun parse(raw: String, sender: CommandSender): T = parser(raw, sender)
|
||||
}))
|
||||
return this@CommandArgumentContextBuilder
|
||||
@ -231,16 +234,16 @@ public class CommandArgumentContextBuilder : MutableList<ParserPair<*>> by mutab
|
||||
*/
|
||||
@JvmSynthetic
|
||||
public inline infix fun <T : Any> KClass<T>.with(
|
||||
crossinline parser: CommandArgumentParser<T>.(s: String) -> T,
|
||||
crossinline parser: CommandValueArgumentParser<T>.(s: String) -> T,
|
||||
): CommandArgumentContextBuilder {
|
||||
add(ParserPair(this, object : CommandArgumentParser<T> {
|
||||
add(ParserPair(this, object : CommandValueArgumentParser<T> {
|
||||
override fun parse(raw: String, sender: CommandSender): T = parser(raw)
|
||||
}))
|
||||
return this@CommandArgumentContextBuilder
|
||||
}
|
||||
|
||||
@JvmSynthetic
|
||||
public inline fun <reified T : Any> add(parser: CommandArgumentParser<T>): CommandArgumentContextBuilder {
|
||||
public inline fun <reified T : Any> add(parser: CommandValueArgumentParser<T>): CommandArgumentContextBuilder {
|
||||
add(ParserPair(T::class, parser))
|
||||
return this@CommandArgumentContextBuilder
|
||||
}
|
||||
@ -251,8 +254,8 @@ public class CommandArgumentContextBuilder : MutableList<ParserPair<*>> by mutab
|
||||
@ConsoleExperimentalApi
|
||||
@JvmSynthetic
|
||||
public inline infix fun <reified T : Any> add(
|
||||
crossinline parser: CommandArgumentParser<*>.(s: String) -> T,
|
||||
): CommandArgumentContextBuilder = T::class with object : CommandArgumentParser<T> {
|
||||
crossinline parser: CommandValueArgumentParser<*>.(s: String) -> T,
|
||||
): CommandArgumentContextBuilder = T::class with object : CommandValueArgumentParser<T> {
|
||||
override fun parse(raw: String, sender: CommandSender): T = parser(raw)
|
||||
}
|
||||
|
||||
@ -263,8 +266,8 @@ public class CommandArgumentContextBuilder : MutableList<ParserPair<*>> by mutab
|
||||
@JvmSynthetic
|
||||
@LowPriorityInOverloadResolution
|
||||
public inline infix fun <reified T : Any> add(
|
||||
crossinline parser: CommandArgumentParser<*>.(s: String, sender: CommandSender) -> T,
|
||||
): CommandArgumentContextBuilder = T::class with object : CommandArgumentParser<T> {
|
||||
crossinline parser: CommandValueArgumentParser<*>.(s: String, sender: CommandSender) -> T,
|
||||
): CommandArgumentContextBuilder = T::class with object : CommandValueArgumentParser<T> {
|
||||
override fun parse(raw: String, sender: CommandSender): T = parser(raw, sender)
|
||||
}
|
||||
|
@ -7,7 +7,7 @@
|
||||
* https://github.com/mamoe/mirai/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
package net.mamoe.mirai.console.command.description
|
||||
package net.mamoe.mirai.console.command.descriptor
|
||||
|
||||
import net.mamoe.mirai.Bot
|
||||
import net.mamoe.mirai.console.command.*
|
||||
@ -25,47 +25,47 @@ import net.mamoe.mirai.message.data.*
|
||||
/**
|
||||
* 使用 [String.toInt] 解析
|
||||
*/
|
||||
public object IntArgumentParser : InternalCommandArgumentParserExtensions<Int> {
|
||||
public object IntValueArgumentParser : InternalCommandValueArgumentParserExtensions<Int> {
|
||||
public override fun parse(raw: String, sender: CommandSender): Int =
|
||||
raw.toIntOrNull() ?: illegalArgument("无法解析 $raw 为整数")
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用 [String.toInt] 解析
|
||||
* 使用 [String.toLong] 解析
|
||||
*/
|
||||
public object LongArgumentParser : InternalCommandArgumentParserExtensions<Long> {
|
||||
public object LongValueArgumentParser : InternalCommandValueArgumentParserExtensions<Long> {
|
||||
public override fun parse(raw: String, sender: CommandSender): Long =
|
||||
raw.toLongOrNull() ?: illegalArgument("无法解析 $raw 为长整数")
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用 [String.toInt] 解析
|
||||
* 使用 [String.toShort] 解析
|
||||
*/
|
||||
public object ShortArgumentParser : InternalCommandArgumentParserExtensions<Short> {
|
||||
public object ShortValueArgumentParser : InternalCommandValueArgumentParserExtensions<Short> {
|
||||
public override fun parse(raw: String, sender: CommandSender): Short =
|
||||
raw.toShortOrNull() ?: illegalArgument("无法解析 $raw 为短整数")
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用 [String.toInt] 解析
|
||||
* 使用 [String.toByte] 解析
|
||||
*/
|
||||
public object ByteArgumentParser : InternalCommandArgumentParserExtensions<Byte> {
|
||||
public object ByteValueArgumentParser : InternalCommandValueArgumentParserExtensions<Byte> {
|
||||
public override fun parse(raw: String, sender: CommandSender): Byte =
|
||||
raw.toByteOrNull() ?: illegalArgument("无法解析 $raw 为字节")
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用 [String.toInt] 解析
|
||||
* 使用 [String.toDouble] 解析
|
||||
*/
|
||||
public object DoubleArgumentParser : InternalCommandArgumentParserExtensions<Double> {
|
||||
public object DoubleValueArgumentParser : InternalCommandValueArgumentParserExtensions<Double> {
|
||||
public override fun parse(raw: String, sender: CommandSender): Double =
|
||||
raw.toDoubleOrNull() ?: illegalArgument("无法解析 $raw 为小数")
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用 [String.toInt] 解析
|
||||
* 使用 [String.toFloat] 解析
|
||||
*/
|
||||
public object FloatArgumentParser : InternalCommandArgumentParserExtensions<Float> {
|
||||
public object FloatValueArgumentParser : InternalCommandValueArgumentParserExtensions<Float> {
|
||||
public override fun parse(raw: String, sender: CommandSender): Float =
|
||||
raw.toFloatOrNull() ?: illegalArgument("无法解析 $raw 为小数")
|
||||
}
|
||||
@ -73,14 +73,14 @@ public object FloatArgumentParser : InternalCommandArgumentParserExtensions<Floa
|
||||
/**
|
||||
* 直接返回 [String], 或取用 [SingleMessage.contentToString]
|
||||
*/
|
||||
public object StringArgumentParser : InternalCommandArgumentParserExtensions<String> {
|
||||
public object StringValueArgumentParser : InternalCommandValueArgumentParserExtensions<String> {
|
||||
public override fun parse(raw: String, sender: CommandSender): String = raw
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析 [String] 通过 [Image].
|
||||
*/
|
||||
public object ImageArgumentParser : InternalCommandArgumentParserExtensions<Image> {
|
||||
public object ImageValueArgumentParser : InternalCommandValueArgumentParserExtensions<Image> {
|
||||
public override fun parse(raw: String, sender: CommandSender): Image {
|
||||
return kotlin.runCatching {
|
||||
Image(raw)
|
||||
@ -95,7 +95,7 @@ public object ImageArgumentParser : InternalCommandArgumentParserExtensions<Imag
|
||||
}
|
||||
}
|
||||
|
||||
public object PlainTextArgumentParser : InternalCommandArgumentParserExtensions<PlainText> {
|
||||
public object PlainTextValueArgumentParser : InternalCommandValueArgumentParserExtensions<PlainText> {
|
||||
public override fun parse(raw: String, sender: CommandSender): PlainText {
|
||||
return PlainText(raw)
|
||||
}
|
||||
@ -109,7 +109,7 @@ public object PlainTextArgumentParser : InternalCommandArgumentParserExtensions<
|
||||
/**
|
||||
* 当字符串内容为(不区分大小写) "true", "yes", "enabled"
|
||||
*/
|
||||
public object BooleanArgumentParser : InternalCommandArgumentParserExtensions<Boolean> {
|
||||
public object BooleanValueArgumentParser : InternalCommandValueArgumentParserExtensions<Boolean> {
|
||||
public override fun parse(raw: String, sender: CommandSender): Boolean = raw.trim().let { str ->
|
||||
str.equals("true", ignoreCase = true)
|
||||
|| str.equals("yes", ignoreCase = true)
|
||||
@ -121,7 +121,7 @@ public object BooleanArgumentParser : InternalCommandArgumentParserExtensions<Bo
|
||||
/**
|
||||
* 根据 [Bot.id] 解析一个登录后的 [Bot]
|
||||
*/
|
||||
public object ExistingBotArgumentParser : InternalCommandArgumentParserExtensions<Bot> {
|
||||
public object ExistingBotValueArgumentParser : InternalCommandValueArgumentParserExtensions<Bot> {
|
||||
public override fun parse(raw: String, sender: CommandSender): Bot =
|
||||
if (raw == "~") sender.inferBotOrFail()
|
||||
else raw.findBotOrFail()
|
||||
@ -136,7 +136,7 @@ public object ExistingBotArgumentParser : InternalCommandArgumentParserExtension
|
||||
/**
|
||||
* 解析任意一个存在的好友.
|
||||
*/
|
||||
public object ExistingFriendArgumentParser : InternalCommandArgumentParserExtensions<Friend> {
|
||||
public object ExistingFriendValueArgumentParser : InternalCommandValueArgumentParserExtensions<Friend> {
|
||||
private val syntax = """
|
||||
- `botId.friendId`
|
||||
- `botId.friendNick` (模糊搜索, 寻找最优匹配)
|
||||
@ -175,7 +175,7 @@ public object ExistingFriendArgumentParser : InternalCommandArgumentParserExtens
|
||||
/**
|
||||
* 解析任意一个存在的群.
|
||||
*/
|
||||
public object ExistingGroupArgumentParser : InternalCommandArgumentParserExtensions<Group> {
|
||||
public object ExistingGroupValueArgumentParser : InternalCommandValueArgumentParserExtensions<Group> {
|
||||
private val syntax = """
|
||||
- `botId.groupId`
|
||||
- `~` (指代指令调用人自己所在群. 仅群聊天环境下)
|
||||
@ -202,7 +202,7 @@ public object ExistingGroupArgumentParser : InternalCommandArgumentParserExtensi
|
||||
}
|
||||
}
|
||||
|
||||
public object ExistingUserArgumentParser : InternalCommandArgumentParserExtensions<User> {
|
||||
public object ExistingUserValueArgumentParser : InternalCommandValueArgumentParserExtensions<User> {
|
||||
private val syntax: String = """
|
||||
- `botId.groupId.memberId`
|
||||
- `botId.groupId.memberCard` (模糊搜索, 寻找最优匹配)
|
||||
@ -215,11 +215,11 @@ public object ExistingUserArgumentParser : InternalCommandArgumentParserExtensio
|
||||
""".trimIndent()
|
||||
|
||||
override fun parse(raw: String, sender: CommandSender): User {
|
||||
return parseImpl(sender, raw, ExistingMemberArgumentParser::parse, ExistingFriendArgumentParser::parse)
|
||||
return parseImpl(sender, raw, ExistingMemberValueArgumentParser::parse, ExistingFriendValueArgumentParser::parse)
|
||||
}
|
||||
|
||||
override fun parse(raw: MessageContent, sender: CommandSender): User {
|
||||
return parseImpl(sender, raw, ExistingMemberArgumentParser::parse, ExistingFriendArgumentParser::parse)
|
||||
return parseImpl(sender, raw, ExistingMemberValueArgumentParser::parse, ExistingFriendValueArgumentParser::parse)
|
||||
}
|
||||
|
||||
private fun <T> parseImpl(
|
||||
@ -246,7 +246,7 @@ public object ExistingUserArgumentParser : InternalCommandArgumentParserExtensio
|
||||
}
|
||||
|
||||
|
||||
public object ExistingContactArgumentParser : InternalCommandArgumentParserExtensions<Contact> {
|
||||
public object ExistingContactValueArgumentParser : InternalCommandValueArgumentParserExtensions<Contact> {
|
||||
private val syntax: String = """
|
||||
- `botId.groupId.memberId`
|
||||
- `botId.groupId.memberCard` (模糊搜索, 寻找最优匹配)
|
||||
@ -259,11 +259,11 @@ public object ExistingContactArgumentParser : InternalCommandArgumentParserExten
|
||||
""".trimIndent()
|
||||
|
||||
override fun parse(raw: String, sender: CommandSender): Contact {
|
||||
return parseImpl(sender, raw, ExistingUserArgumentParser::parse, ExistingGroupArgumentParser::parse)
|
||||
return parseImpl(sender, raw, ExistingUserValueArgumentParser::parse, ExistingGroupValueArgumentParser::parse)
|
||||
}
|
||||
|
||||
override fun parse(raw: MessageContent, sender: CommandSender): Contact {
|
||||
return parseImpl(sender, raw, ExistingUserArgumentParser::parse, ExistingGroupArgumentParser::parse)
|
||||
return parseImpl(sender, raw, ExistingUserValueArgumentParser::parse, ExistingGroupValueArgumentParser::parse)
|
||||
}
|
||||
|
||||
private fun <T> parseImpl(
|
||||
@ -286,7 +286,7 @@ public object ExistingContactArgumentParser : InternalCommandArgumentParserExten
|
||||
/**
|
||||
* 解析任意一个群成员.
|
||||
*/
|
||||
public object ExistingMemberArgumentParser : InternalCommandArgumentParserExtensions<Member> {
|
||||
public object ExistingMemberValueArgumentParser : InternalCommandValueArgumentParserExtensions<Member> {
|
||||
private val syntax: String = """
|
||||
- `botId.groupId.memberId`
|
||||
- `botId.groupId.memberCard` (模糊搜索, 寻找最优匹配)
|
||||
@ -333,7 +333,7 @@ public object ExistingMemberArgumentParser : InternalCommandArgumentParserExtens
|
||||
}
|
||||
}
|
||||
|
||||
public object PermissionIdArgumentParser : CommandArgumentParser<PermissionId> {
|
||||
public object PermissionIdValueArgumentParser : CommandValueArgumentParser<PermissionId> {
|
||||
override fun parse(raw: String, sender: CommandSender): PermissionId {
|
||||
return kotlin.runCatching { PermissionId.parseFromString(raw) }.getOrElse {
|
||||
illegalArgument("无法解析 $raw 为 PermissionId")
|
||||
@ -341,7 +341,7 @@ public object PermissionIdArgumentParser : CommandArgumentParser<PermissionId> {
|
||||
}
|
||||
}
|
||||
|
||||
public object PermitteeIdArgumentParser : CommandArgumentParser<PermitteeId> {
|
||||
public object PermitteeIdValueArgumentParser : CommandValueArgumentParser<PermitteeId> {
|
||||
override fun parse(raw: String, sender: CommandSender): PermitteeId {
|
||||
return if (raw == "~") sender.permitteeId
|
||||
else kotlin.runCatching { AbstractPermitteeId.parseFromString(raw) }.getOrElse {
|
||||
@ -351,13 +351,19 @@ public object PermitteeIdArgumentParser : CommandArgumentParser<PermitteeId> {
|
||||
|
||||
override fun parse(raw: MessageContent, sender: CommandSender): PermitteeId {
|
||||
if (raw is At) {
|
||||
return ExistingUserArgumentParser.parse(raw, sender).asCommandSender(false).permitteeId
|
||||
return ExistingUserValueArgumentParser.parse(raw, sender).asCommandSender(false).permitteeId
|
||||
}
|
||||
return super.parse(raw, sender)
|
||||
}
|
||||
}
|
||||
|
||||
internal interface InternalCommandArgumentParserExtensions<T : Any> : CommandArgumentParser<T> {
|
||||
/** 直接返回原始参数 [MessageContent] */
|
||||
public object RawContentValueArgumentParser : CommandValueArgumentParser<MessageContent> {
|
||||
override fun parse(raw: String, sender: CommandSender): MessageContent = PlainText(raw)
|
||||
override fun parse(raw: MessageContent, sender: CommandSender): MessageContent = raw
|
||||
}
|
||||
|
||||
internal interface InternalCommandValueArgumentParserExtensions<T : Any> : CommandValueArgumentParser<T> {
|
||||
fun String.parseToLongOrFail(): Long = toLongOrNull() ?: illegalArgument("无法解析 $this 为整数")
|
||||
|
||||
fun Long.findBotOrFail(): Bot = Bot.getInstanceOrNull(this) ?: illegalArgument("无法找到 Bot: $this")
|
@ -9,17 +9,20 @@
|
||||
|
||||
@file:Suppress("unused")
|
||||
|
||||
package net.mamoe.mirai.console.command.description
|
||||
package net.mamoe.mirai.console.command.descriptor
|
||||
|
||||
import net.mamoe.mirai.console.command.IllegalCommandArgumentException
|
||||
|
||||
/**
|
||||
* 在解析参数时遇到的 _正常_ 错误. 如参数不符合规范等.
|
||||
*
|
||||
* [message] 将会发送给指令调用方.
|
||||
*
|
||||
* @see CommandArgumentParser
|
||||
* @see CommandArgumentParser.illegalArgument
|
||||
* @see IllegalCommandArgumentException
|
||||
* @see CommandValueArgumentParser
|
||||
* @see CommandValueArgumentParser.illegalArgument
|
||||
*/
|
||||
public class CommandArgumentParserException : RuntimeException {
|
||||
public class CommandArgumentParserException : IllegalCommandArgumentException {
|
||||
public constructor() : super()
|
||||
public constructor(message: String?) : super(message)
|
||||
public constructor(message: String?, cause: Throwable?) : super(message, cause)
|
@ -0,0 +1,287 @@
|
||||
/*
|
||||
* 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.command.descriptor
|
||||
|
||||
import net.mamoe.mirai.console.command.CommandSender
|
||||
import net.mamoe.mirai.console.command.descriptor.AbstractCommandValueParameter.UserDefinedType.Companion.createOptional
|
||||
import net.mamoe.mirai.console.command.descriptor.AbstractCommandValueParameter.UserDefinedType.Companion.createRequired
|
||||
import net.mamoe.mirai.console.command.descriptor.ArgumentAcceptance.Companion.isAcceptable
|
||||
import net.mamoe.mirai.console.command.parse.CommandValueArgument
|
||||
import net.mamoe.mirai.console.command.resolve.ResolvedCommandCall
|
||||
import net.mamoe.mirai.console.internal.data.classifierAsKClass
|
||||
import net.mamoe.mirai.console.internal.data.classifierAsKClassOrNull
|
||||
import net.mamoe.mirai.console.internal.data.typeOf0
|
||||
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
|
||||
import kotlin.reflect.KClass
|
||||
import kotlin.reflect.KFunction
|
||||
import kotlin.reflect.KType
|
||||
import kotlin.reflect.full.isSubtypeOf
|
||||
import kotlin.reflect.typeOf
|
||||
|
||||
/**
|
||||
* @see CommandSignatureVariantImpl
|
||||
*/
|
||||
@ExperimentalCommandDescriptors
|
||||
public interface CommandSignatureVariant {
|
||||
@ConsoleExperimentalApi
|
||||
public val receiverParameter: CommandReceiverParameter<out CommandSender>?
|
||||
|
||||
public val valueParameters: List<AbstractCommandValueParameter<*>>
|
||||
|
||||
public suspend fun call(resolvedCommandCall: ResolvedCommandCall)
|
||||
}
|
||||
|
||||
@ConsoleExperimentalApi
|
||||
@ExperimentalCommandDescriptors
|
||||
public interface CommandSignatureVariantFromKFunction : CommandSignatureVariant {
|
||||
public val originFunction: KFunction<*>
|
||||
}
|
||||
|
||||
@ExperimentalCommandDescriptors
|
||||
public abstract class AbstractCommandSignatureVariant : CommandSignatureVariant {
|
||||
override fun toString(): String {
|
||||
val receiverParameter = receiverParameter
|
||||
return if (receiverParameter == null) {
|
||||
"CommandSignatureVariant(${valueParameters.joinToString()})"
|
||||
} else {
|
||||
"CommandSignatureVariant($receiverParameter, ${valueParameters.joinToString()})"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ExperimentalCommandDescriptors
|
||||
public open class CommandSignatureVariantImpl(
|
||||
override val receiverParameter: CommandReceiverParameter<out CommandSender>?,
|
||||
override val valueParameters: List<AbstractCommandValueParameter<*>>,
|
||||
private val onCall: suspend CommandSignatureVariantImpl.(resolvedCommandCall: ResolvedCommandCall) -> Unit,
|
||||
) : CommandSignatureVariant, AbstractCommandSignatureVariant() {
|
||||
override suspend fun call(resolvedCommandCall: ResolvedCommandCall) {
|
||||
return onCall(resolvedCommandCall)
|
||||
}
|
||||
}
|
||||
|
||||
@ConsoleExperimentalApi
|
||||
@ExperimentalCommandDescriptors
|
||||
public open class CommandSignatureVariantFromKFunctionImpl(
|
||||
override val receiverParameter: CommandReceiverParameter<out CommandSender>?,
|
||||
override val valueParameters: List<AbstractCommandValueParameter<*>>,
|
||||
override val originFunction: KFunction<*>,
|
||||
private val onCall: suspend CommandSignatureVariantFromKFunctionImpl.(resolvedCommandCall: ResolvedCommandCall) -> Unit,
|
||||
) : CommandSignatureVariantFromKFunction, AbstractCommandSignatureVariant() {
|
||||
override suspend fun call(resolvedCommandCall: ResolvedCommandCall) {
|
||||
return onCall(resolvedCommandCall)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Inherited instances must be [CommandValueParameter] or [CommandReceiverParameter]
|
||||
*/
|
||||
@ExperimentalCommandDescriptors
|
||||
public interface CommandParameter<T : Any?> {
|
||||
public val name: String?
|
||||
|
||||
public val isOptional: Boolean
|
||||
|
||||
/**
|
||||
* Reified type of [T]
|
||||
*/
|
||||
public val type: KType
|
||||
}
|
||||
|
||||
@ExperimentalCommandDescriptors
|
||||
public abstract class AbstractCommandParameter<T> : CommandParameter<T> {
|
||||
override fun toString(): String = buildString {
|
||||
append(name)
|
||||
append(": ")
|
||||
append(type.classifierAsKClass().simpleName)
|
||||
append(if (type.isMarkedNullable) "?" else "")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Inherited instances must be [AbstractCommandValueParameter]
|
||||
*/
|
||||
@ExperimentalCommandDescriptors
|
||||
public interface CommandValueParameter<T : Any?> : CommandParameter<T> {
|
||||
|
||||
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 class CommandReceiverParameter<T : CommandSender>(
|
||||
override val isOptional: Boolean,
|
||||
override val type: KType,
|
||||
) : CommandParameter<T>, AbstractCommandParameter<T>() {
|
||||
override val name: String get() = PARAMETER_NAME
|
||||
|
||||
init {
|
||||
check(type.classifier is KClass<*>) {
|
||||
"CommandReceiverParameter.type.classifier must be KClass."
|
||||
}
|
||||
}
|
||||
|
||||
public companion object {
|
||||
public const val PARAMETER_NAME: String = "<receiver>"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
internal val ANY_TYPE = typeOf0<Any>()
|
||||
internal val ARRAY_OUT_ANY_TYPE = typeOf0<Array<out Any?>>()
|
||||
|
||||
@ExperimentalCommandDescriptors
|
||||
public sealed class AbstractCommandValueParameter<T> : CommandValueParameter<T>, AbstractCommandParameter<T>() {
|
||||
override fun toString(): String = buildString {
|
||||
if (isVararg) append("vararg ")
|
||||
append(super.toString())
|
||||
if (isOptional) {
|
||||
append(" = ...")
|
||||
}
|
||||
}
|
||||
|
||||
public override fun accepting(argument: CommandValueArgument, commandArgumentContext: CommandArgumentContext?): ArgumentAcceptance {
|
||||
if (isVararg) {
|
||||
val arrayElementType = this.type.arguments.single() // Array<T>
|
||||
return acceptingImpl(arrayElementType.type ?: ANY_TYPE, argument, commandArgumentContext)
|
||||
}
|
||||
|
||||
return acceptingImpl(this.type, argument, commandArgumentContext)
|
||||
}
|
||||
|
||||
private fun acceptingImpl(
|
||||
expectingType: KType,
|
||||
argument: CommandValueArgument,
|
||||
commandArgumentContext: CommandArgumentContext?,
|
||||
): ArgumentAcceptance {
|
||||
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
|
||||
}
|
||||
|
||||
@ConsoleExperimentalApi
|
||||
public class StringConstant(
|
||||
@ConsoleExperimentalApi
|
||||
public override val name: String?,
|
||||
public val expectingValue: String,
|
||||
) : AbstractCommandValueParameter<String>() {
|
||||
public override val type: KType get() = STRING_TYPE
|
||||
public override val isOptional: Boolean get() = false
|
||||
public override val isVararg: Boolean get() = false
|
||||
|
||||
init {
|
||||
require(expectingValue.isNotBlank()) {
|
||||
"expectingValue must not be blank"
|
||||
}
|
||||
require(expectingValue.none(Char::isWhitespace)) {
|
||||
"expectingValue must not contain whitespace"
|
||||
}
|
||||
}
|
||||
|
||||
override fun toString(): String = "<$expectingValue>"
|
||||
|
||||
private companion object {
|
||||
@OptIn(ExperimentalStdlibApi::class)
|
||||
val STRING_TYPE = typeOf<String>()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @see createOptional
|
||||
* @see createRequired
|
||||
*/
|
||||
public class UserDefinedType<T>(
|
||||
public override val name: String?,
|
||||
public override val isOptional: Boolean,
|
||||
public override val isVararg: Boolean,
|
||||
public override val type: KType,
|
||||
) : AbstractCommandValueParameter<T>() {
|
||||
init {
|
||||
requireNotNull(type.classifierAsKClassOrNull()) {
|
||||
"type.classifier must be KClass."
|
||||
}
|
||||
if (isVararg)
|
||||
check(type.isSubtypeOf(ARRAY_OUT_ANY_TYPE)) {
|
||||
"type must be subtype of Array if vararg. Given $type."
|
||||
}
|
||||
}
|
||||
|
||||
public companion object {
|
||||
@JvmStatic
|
||||
public inline fun <reified T : Any> createOptional(name: String, isVararg: Boolean): UserDefinedType<T> {
|
||||
@OptIn(ExperimentalStdlibApi::class)
|
||||
return UserDefinedType(name, true, isVararg, typeOf<T>())
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
public inline fun <reified T : Any> createRequired(name: String, isVararg: Boolean): UserDefinedType<T> {
|
||||
@OptIn(ExperimentalStdlibApi::class)
|
||||
return UserDefinedType(name, false, isVararg, typeOf<T>())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extended by [CommandValueArgumentParser]
|
||||
*/
|
||||
@ConsoleExperimentalApi
|
||||
public abstract class Extended<T> : AbstractCommandValueParameter<T>() {
|
||||
abstract override fun toString(): String
|
||||
}
|
||||
}
|
@ -9,7 +9,7 @@
|
||||
|
||||
@file:Suppress("NOTHING_TO_INLINE", "unused")
|
||||
|
||||
package net.mamoe.mirai.console.command.description
|
||||
package net.mamoe.mirai.console.command.descriptor
|
||||
|
||||
import net.mamoe.mirai.Bot
|
||||
import net.mamoe.mirai.console.command.CommandManager
|
||||
@ -32,27 +32,27 @@ import kotlin.contracts.contract
|
||||
* ```
|
||||
* suspend fun CommandSender.mute(target: Member, duration: Int)
|
||||
* ```
|
||||
* [CommandManager] 总是从 [SimpleCommand.context] 搜索一个 [T] 为 [Member] 的 [CommandArgumentParser], 并调用其 [CommandArgumentParser.parse]
|
||||
* [CommandManager] 总是从 [SimpleCommand.context] 搜索一个 [T] 为 [Member] 的 [CommandValueArgumentParser], 并调用其 [CommandValueArgumentParser.parse]
|
||||
*
|
||||
* ### 内建指令解析器
|
||||
* - 基础类型: [ByteArgumentParser], [ShortArgumentParser], [IntArgumentParser], [LongArgumentParser]
|
||||
* [FloatArgumentParser], [DoubleArgumentParser],
|
||||
* [BooleanArgumentParser], [StringArgumentParser]
|
||||
* - 基础类型: [ByteValueArgumentParser], [ShortValueArgumentParser], [IntValueArgumentParser], [LongValueArgumentParser]
|
||||
* [FloatValueArgumentParser], [DoubleValueArgumentParser],
|
||||
* [BooleanValueArgumentParser], [StringValueArgumentParser]
|
||||
*
|
||||
* - [Bot]: [ExistingBotArgumentParser]
|
||||
* - [Friend]: [ExistingFriendArgumentParser]
|
||||
* - [Group]: [ExistingGroupArgumentParser]
|
||||
* - [Member]: [ExistingMemberArgumentParser]
|
||||
* - [User]: [ExistingUserArgumentParser]
|
||||
* - [Contact]: [ExistingContactArgumentParser]
|
||||
* - [Bot]: [ExistingBotValueArgumentParser]
|
||||
* - [Friend]: [ExistingFriendValueArgumentParser]
|
||||
* - [Group]: [ExistingGroupValueArgumentParser]
|
||||
* - [Member]: [ExistingMemberValueArgumentParser]
|
||||
* - [User]: [ExistingUserValueArgumentParser]
|
||||
* - [Contact]: [ExistingContactValueArgumentParser]
|
||||
*
|
||||
*
|
||||
* @see SimpleCommand 简单指令
|
||||
* @see CompositeCommand 复合指令
|
||||
*
|
||||
* @see buildCommandArgumentContext 指令参数环境, 即 [CommandArgumentParser] 的集合
|
||||
* @see buildCommandArgumentContext 指令参数环境, 即 [CommandValueArgumentParser] 的集合
|
||||
*/
|
||||
public interface CommandArgumentParser<out T : Any> {
|
||||
public interface CommandValueArgumentParser<out T : Any> {
|
||||
/**
|
||||
* 解析一个字符串为 [T] 类型参数
|
||||
*
|
||||
@ -83,14 +83,14 @@ public interface CommandArgumentParser<out T : Any> {
|
||||
/**
|
||||
* 使用原 [this] 解析, 成功后使用 [mapper] 映射为另一个类型.
|
||||
*/
|
||||
public fun <T : Any, R : Any> CommandArgumentParser<T>.map(
|
||||
mapper: CommandArgumentParser<R>.(T) -> R
|
||||
): CommandArgumentParser<R> = MappingCommandArgumentParser(this, mapper)
|
||||
public fun <T : Any, R : Any> CommandValueArgumentParser<T>.map(
|
||||
mapper: CommandValueArgumentParser<R>.(T) -> R,
|
||||
): CommandValueArgumentParser<R> = MappingCommandValueArgumentParser(this, mapper)
|
||||
|
||||
private class MappingCommandArgumentParser<T : Any, R : Any>(
|
||||
private val original: CommandArgumentParser<T>,
|
||||
private val mapper: CommandArgumentParser<R>.(T) -> R
|
||||
) : CommandArgumentParser<R> {
|
||||
private class MappingCommandValueArgumentParser<T : Any, R : Any>(
|
||||
private val original: CommandValueArgumentParser<T>,
|
||||
private val mapper: CommandValueArgumentParser<R>.(T) -> R,
|
||||
) : CommandValueArgumentParser<R> {
|
||||
override fun parse(raw: String, sender: CommandSender): R = mapper(original.parse(raw, sender))
|
||||
override fun parse(raw: MessageContent, sender: CommandSender): R = mapper(original.parse(raw, sender))
|
||||
}
|
||||
@ -102,14 +102,14 @@ private class MappingCommandArgumentParser<T : Any, R : Any>(
|
||||
*/
|
||||
@JvmSynthetic
|
||||
@Throws(IllegalArgumentException::class)
|
||||
public fun <T : Any> CommandArgumentParser<T>.parse(raw: Any, sender: CommandSender): T {
|
||||
public fun <T : Any> CommandValueArgumentParser<T>.parse(raw: Any, sender: CommandSender): T {
|
||||
contract {
|
||||
returns() implies (raw is String || raw is SingleMessage)
|
||||
}
|
||||
|
||||
return when (raw) {
|
||||
is String -> parse(raw, sender)
|
||||
is SingleMessage -> parse(raw, sender)
|
||||
is MessageContent -> parse(raw, sender)
|
||||
else -> throw IllegalArgumentException("Illegal raw argument type: ${raw::class.qualifiedName}")
|
||||
}
|
||||
}
|
||||
@ -122,7 +122,7 @@ public fun <T : Any> CommandArgumentParser<T>.parse(raw: Any, sender: CommandSen
|
||||
@Suppress("unused")
|
||||
@JvmSynthetic
|
||||
@Throws(CommandArgumentParserException::class)
|
||||
public inline fun CommandArgumentParser<*>.illegalArgument(message: String, cause: Throwable? = null): Nothing {
|
||||
public inline fun CommandValueArgumentParser<*>.illegalArgument(message: String, cause: Throwable? = null): Nothing {
|
||||
throw CommandArgumentParserException(message, cause)
|
||||
}
|
||||
|
||||
@ -133,9 +133,9 @@ public inline fun CommandArgumentParser<*>.illegalArgument(message: String, caus
|
||||
*/
|
||||
@Throws(CommandArgumentParserException::class)
|
||||
@JvmSynthetic
|
||||
public inline fun CommandArgumentParser<*>.checkArgument(
|
||||
public inline fun CommandValueArgumentParser<*>.checkArgument(
|
||||
condition: Boolean,
|
||||
crossinline message: () -> String = { "Check failed." }
|
||||
crossinline message: () -> String = { "Check failed." },
|
||||
) {
|
||||
contract {
|
||||
returns() implies condition
|
@ -0,0 +1,40 @@
|
||||
/*
|
||||
* 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
|
||||
*/
|
||||
|
||||
@file:Suppress("MemberVisibilityCanBePrivate", "unused")
|
||||
|
||||
package net.mamoe.mirai.console.command.descriptor
|
||||
|
||||
import net.mamoe.mirai.console.command.parse.CommandCall
|
||||
import net.mamoe.mirai.console.command.parse.CommandValueArgument
|
||||
import net.mamoe.mirai.console.internal.data.classifierAsKClassOrNull
|
||||
import net.mamoe.mirai.console.internal.data.qualifiedNameOrTip
|
||||
import kotlin.reflect.KType
|
||||
|
||||
|
||||
internal val KType.qualifiedName: String
|
||||
get() = this.classifierAsKClassOrNull()?.qualifiedNameOrTip ?: classifier.toString()
|
||||
|
||||
@ExperimentalCommandDescriptors
|
||||
public open class NoValueArgumentMappingException(
|
||||
public val argument: CommandValueArgument,
|
||||
public val forType: KType,
|
||||
) : CommandResolutionException("Cannot find a CommandArgument mapping for ${forType.qualifiedName}")
|
||||
|
||||
@ExperimentalCommandDescriptors
|
||||
public open class UnresolvedCommandCallException(
|
||||
public val call: CommandCall,
|
||||
) : CommandResolutionException("Unresolved call: $call")
|
||||
|
||||
public open class CommandResolutionException : RuntimeException {
|
||||
public constructor() : super()
|
||||
public constructor(message: String?) : super(message)
|
||||
public constructor(message: String?, cause: Throwable?) : super(message, cause)
|
||||
public constructor(cause: Throwable?) : super(cause)
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
/*
|
||||
* 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.command.descriptor
|
||||
|
||||
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
|
||||
import kotlin.annotation.AnnotationTarget.*
|
||||
|
||||
/**
|
||||
* 标记一个实验性的指令解释器 API.
|
||||
*
|
||||
* 这些 API 不具有稳定性, 且可能会在任意时刻更改.
|
||||
* 不建议在发行版本中使用这些 API.
|
||||
*
|
||||
* @since 1.0-RC
|
||||
*/
|
||||
@Retention(AnnotationRetention.BINARY)
|
||||
@RequiresOptIn(level = RequiresOptIn.Level.WARNING)
|
||||
@Target(CLASS, TYPEALIAS, FUNCTION, PROPERTY, FIELD, CONSTRUCTOR)
|
||||
@MustBeDocumented
|
||||
@ConsoleExperimentalApi
|
||||
@ExperimentalCommandDescriptors
|
||||
public annotation class ExperimentalCommandDescriptors(
|
||||
val message: String = "Command descriptors are an experimental API.",
|
||||
)
|
@ -0,0 +1,70 @@
|
||||
/*
|
||||
* 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.command.descriptor
|
||||
|
||||
import net.mamoe.mirai.console.command.parse.CommandCall
|
||||
import net.mamoe.mirai.console.command.parse.CommandCallParser
|
||||
import net.mamoe.mirai.console.command.parse.CommandValueArgument
|
||||
import net.mamoe.mirai.console.internal.data.castOrNull
|
||||
import net.mamoe.mirai.console.internal.data.kClassQualifiedName
|
||||
import net.mamoe.mirai.message.data.*
|
||||
import kotlin.reflect.KType
|
||||
import kotlin.reflect.typeOf
|
||||
|
||||
/**
|
||||
* Implicit type variant specified by [CommandCallParser].
|
||||
*
|
||||
* [TypeVariant] is not necessary for all [CommandCall]s.
|
||||
*/
|
||||
@ExperimentalCommandDescriptors
|
||||
public interface TypeVariant<out OutType> {
|
||||
/**
|
||||
* The reified type of [OutType]
|
||||
*/
|
||||
public val outType: KType
|
||||
|
||||
/**
|
||||
* @see CommandValueArgument.value
|
||||
*/
|
||||
public fun mapValue(valueParameter: Message): OutType
|
||||
|
||||
public companion object {
|
||||
@OptIn(ExperimentalStdlibApi::class)
|
||||
@JvmSynthetic
|
||||
public inline operator fun <reified OutType> invoke(crossinline block: (valueParameter: Message) -> OutType): TypeVariant<OutType> {
|
||||
return object : TypeVariant<OutType> {
|
||||
override val outType: KType = typeOf<OutType>()
|
||||
override fun mapValue(valueParameter: Message): OutType = block(valueParameter)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ExperimentalCommandDescriptors
|
||||
public object MessageContentTypeVariant : TypeVariant<MessageContent> {
|
||||
@OptIn(ExperimentalStdlibApi::class)
|
||||
override val outType: KType = typeOf<MessageContent>()
|
||||
override fun mapValue(valueParameter: Message): MessageContent =
|
||||
valueParameter.castOrNull<MessageContent>() ?: error("Accepts MessageContent only but given ${valueParameter.kClassQualifiedName}")
|
||||
}
|
||||
|
||||
@ExperimentalCommandDescriptors
|
||||
public object MessageChainTypeVariant : TypeVariant<MessageChain> {
|
||||
@OptIn(ExperimentalStdlibApi::class)
|
||||
override val outType: KType = typeOf<MessageChain>()
|
||||
override fun mapValue(valueParameter: Message): MessageChain = valueParameter.asMessageChain()
|
||||
}
|
||||
|
||||
@ExperimentalCommandDescriptors
|
||||
public object ContentStringTypeVariant : TypeVariant<String> {
|
||||
@OptIn(ExperimentalStdlibApi::class)
|
||||
override val outType: KType = typeOf<String>()
|
||||
override fun mapValue(valueParameter: Message): String = valueParameter.content
|
||||
}
|
@ -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
|
||||
}
|
@ -13,10 +13,11 @@ import net.mamoe.mirai.console.command.BuiltInCommands
|
||||
import net.mamoe.mirai.console.command.CommandManager
|
||||
import net.mamoe.mirai.console.command.CommandOwner
|
||||
import net.mamoe.mirai.console.command.CompositeCommand
|
||||
import net.mamoe.mirai.console.command.description.buildCommandArgumentContext
|
||||
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) }
|
||||
}
|
||||
}
|
@ -13,10 +13,11 @@ import net.mamoe.mirai.console.command.CommandManager
|
||||
import net.mamoe.mirai.console.command.CommandManager.INSTANCE.executeCommand
|
||||
import net.mamoe.mirai.console.command.CommandOwner
|
||||
import net.mamoe.mirai.console.command.SimpleCommand
|
||||
import net.mamoe.mirai.console.command.description.CommandArgumentContext
|
||||
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,
|
||||
|
@ -0,0 +1,41 @@
|
||||
/*
|
||||
* 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
|
||||
*/
|
||||
|
||||
@file:OptIn(ExperimentalStdlibApi::class)
|
||||
|
||||
package net.mamoe.mirai.console.command.parse
|
||||
|
||||
import net.mamoe.mirai.console.command.Command
|
||||
import net.mamoe.mirai.console.command.CommandSender
|
||||
import net.mamoe.mirai.console.command.descriptor.ExperimentalCommandDescriptors
|
||||
|
||||
/**
|
||||
* Unresolved [CommandCall].
|
||||
*/
|
||||
@ExperimentalCommandDescriptors
|
||||
public interface CommandCall {
|
||||
public val caller: CommandSender
|
||||
|
||||
/**
|
||||
* One of callee [Command]'s [Command.allNames]
|
||||
*/
|
||||
public val calleeName: String
|
||||
|
||||
/**
|
||||
* Explicit value arguments
|
||||
*/
|
||||
public val valueArguments: List<CommandValueArgument>
|
||||
}
|
||||
|
||||
@ExperimentalCommandDescriptors
|
||||
public class CommandCallImpl(
|
||||
override val caller: CommandSender,
|
||||
override val calleeName: String,
|
||||
override val valueArguments: List<CommandValueArgument>,
|
||||
) : CommandCall
|
@ -0,0 +1,46 @@
|
||||
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.command.resolve.CommandCallResolver
|
||||
import net.mamoe.mirai.console.command.resolve.ResolvedCommandCall
|
||||
import net.mamoe.mirai.console.extensions.CommandCallParserProvider
|
||||
import net.mamoe.mirai.console.internal.extension.GlobalComponentStorage
|
||||
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
|
||||
import net.mamoe.mirai.message.data.MessageChain
|
||||
|
||||
/**
|
||||
* Lexical and syntactical parser for transforming a [MessageChain] into [CommandCall]
|
||||
*
|
||||
* @see CommandCallResolver The call resolver for [CommandCall] to become [ResolvedCommandCall]
|
||||
* @see CommandCallParserProvider The extension point
|
||||
*
|
||||
* @see SpaceSeparatedCommandCallParser
|
||||
*/
|
||||
@ConsoleExperimentalApi
|
||||
@ExperimentalCommandDescriptors
|
||||
public interface CommandCallParser {
|
||||
|
||||
/**
|
||||
* Lexically and syntactically parse a [message] into [CommandCall], but performs nothing about resolving a call.
|
||||
*
|
||||
* @return `null` if unable to parse (i.e. due to syntax errors).
|
||||
*/
|
||||
public fun parse(caller: CommandSender, message: MessageChain): CommandCall?
|
||||
|
||||
public companion object {
|
||||
/**
|
||||
* Calls [CommandCallParser]s provided by [CommandCallParserProvider] in [GlobalComponentStorage] sequentially,
|
||||
* returning the first non-null result, `null` otherwise.
|
||||
*/
|
||||
@JvmStatic
|
||||
public fun MessageChain.parseCommandCall(sender: CommandSender): CommandCall? {
|
||||
GlobalComponentStorage.run {
|
||||
CommandCallParserProvider.useExtensions { provider ->
|
||||
provider.instance.parse(sender, this@parseCommandCall)?.let { return it }
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,132 @@
|
||||
/*
|
||||
* 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
|
||||
*/
|
||||
|
||||
@file:Suppress("unused")
|
||||
|
||||
package net.mamoe.mirai.console.command.parse
|
||||
|
||||
import net.mamoe.mirai.console.command.descriptor.*
|
||||
import net.mamoe.mirai.console.internal.data.castOrInternalError
|
||||
import net.mamoe.mirai.console.internal.data.classifierAsKClass
|
||||
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
|
||||
import net.mamoe.mirai.message.data.Message
|
||||
import net.mamoe.mirai.message.data.MessageChain
|
||||
import net.mamoe.mirai.message.data.MessageContent
|
||||
import net.mamoe.mirai.message.data.SingleMessage
|
||||
import kotlin.reflect.KType
|
||||
import kotlin.reflect.full.isSubtypeOf
|
||||
import kotlin.reflect.typeOf
|
||||
|
||||
|
||||
/**
|
||||
* @see CommandValueArgument
|
||||
*/
|
||||
@ExperimentalCommandDescriptors
|
||||
public interface CommandArgument
|
||||
|
||||
/**
|
||||
* @see DefaultCommandValueArgument
|
||||
*/
|
||||
@ExperimentalCommandDescriptors
|
||||
public interface CommandValueArgument : CommandArgument {
|
||||
public val type: KType
|
||||
|
||||
/**
|
||||
* [MessageContent] if single argument
|
||||
* [MessageChain] is vararg
|
||||
*/
|
||||
public val value: Message
|
||||
public val typeVariants: List<TypeVariant<*>>
|
||||
}
|
||||
|
||||
/**
|
||||
* The [CommandValueArgument] that doesn't vary in type (remaining [MessageContent]).
|
||||
*/
|
||||
@ConsoleExperimentalApi
|
||||
@ExperimentalCommandDescriptors
|
||||
public data class DefaultCommandValueArgument(
|
||||
public override val value: Message,
|
||||
) : CommandValueArgument {
|
||||
@OptIn(ExperimentalStdlibApi::class)
|
||||
override val type: KType = typeOf<MessageContent>()
|
||||
override val typeVariants: List<TypeVariant<*>> = listOf(
|
||||
MessageContentTypeVariant,
|
||||
MessageChainTypeVariant,
|
||||
ContentStringTypeVariant,
|
||||
)
|
||||
}
|
||||
|
||||
@ExperimentalCommandDescriptors
|
||||
public fun <T> CommandValueArgument.mapValue(typeVariant: TypeVariant<T>): T = typeVariant.mapValue(this.value)
|
||||
|
||||
|
||||
@OptIn(ExperimentalStdlibApi::class)
|
||||
@ExperimentalCommandDescriptors
|
||||
public inline fun <reified T> CommandValueArgument.mapToType(): T =
|
||||
mapToTypeOrNull() ?: throw NoValueArgumentMappingException(this, typeOf<T>())
|
||||
|
||||
@OptIn(ExperimentalStdlibApi::class)
|
||||
@ExperimentalCommandDescriptors
|
||||
public fun <T> CommandValueArgument.mapToType(type: KType): T =
|
||||
mapToTypeOrNull(type) ?: throw NoValueArgumentMappingException(this, type)
|
||||
|
||||
@ExperimentalCommandDescriptors
|
||||
public fun <T> CommandValueArgument.mapToTypeOrNull(expectingType: KType): T? {
|
||||
if (expectingType.isSubtypeOf(ARRAY_OUT_ANY_TYPE)) {
|
||||
val arrayElementType = expectingType.arguments.single().type ?: ANY_TYPE
|
||||
|
||||
val result = ArrayList<Any?>()
|
||||
|
||||
when (val value = value) {
|
||||
is MessageChain -> {
|
||||
for (message in value) {
|
||||
result.add(mapToTypeOrNullImpl(arrayElementType, message))
|
||||
}
|
||||
}
|
||||
else -> { // single
|
||||
value.castOrInternalError<SingleMessage>()
|
||||
result.add(mapToTypeOrNullImpl(arrayElementType, value))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
return result.toArray(arrayElementType.createArray(result.size)) as T
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
return mapToTypeOrNullImpl(expectingType, value) as T
|
||||
}
|
||||
|
||||
private fun KType.createArray(size: Int): Array<Any?> {
|
||||
return java.lang.reflect.Array.newInstance(this.classifierAsKClass().javaObjectType, size).castOrInternalError()
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalCommandDescriptors::class)
|
||||
private fun CommandValueArgument.mapToTypeOrNullImpl(expectingType: KType, value: Message): Any? {
|
||||
@OptIn(ExperimentalStdlibApi::class)
|
||||
val result = typeVariants
|
||||
.filter { it.outType.isSubtypeOf(expectingType) }
|
||||
.ifEmpty {
|
||||
return null
|
||||
}
|
||||
.reduce { acc, typeVariant ->
|
||||
if (acc.outType.isSubtypeOf(typeVariant.outType))
|
||||
acc
|
||||
else typeVariant
|
||||
}
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
return result.mapValue(value)
|
||||
}
|
||||
|
||||
@ExperimentalCommandDescriptors
|
||||
public inline fun <reified T> CommandValueArgument.mapToTypeOrNull(): T? {
|
||||
@OptIn(ExperimentalStdlibApi::class)
|
||||
return mapToTypeOrNull(typeOf<T>())
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
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.extensions.CommandCallParserProvider
|
||||
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(::DefaultCommandValueArgument)
|
||||
)
|
||||
}
|
||||
|
||||
public object Provider : CommandCallParserProvider(SpaceSeparatedCommandCallParser)
|
||||
}
|
@ -0,0 +1,172 @@
|
||||
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.command.parse.DefaultCommandValueArgument
|
||||
import net.mamoe.mirai.console.extensions.CommandCallResolverProvider
|
||||
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
|
||||
import net.mamoe.mirai.console.util.safeCast
|
||||
import net.mamoe.mirai.message.data.EmptyMessageChain
|
||||
import net.mamoe.mirai.message.data.asMessageChain
|
||||
|
||||
/**
|
||||
* Builtin implementation of [CommandCallResolver]
|
||||
*/
|
||||
@ConsoleExperimentalApi
|
||||
@ExperimentalCommandDescriptors
|
||||
public object BuiltInCommandCallResolver : CommandCallResolver {
|
||||
public object Provider : CommandCallResolverProvider(BuiltInCommandCallResolver)
|
||||
|
||||
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.variant,
|
||||
signature.zippedArguments.map { it.second },
|
||||
context ?: EmptyCommandArgumentContext)
|
||||
}
|
||||
|
||||
private data class ResolveData(
|
||||
val variant: CommandSignatureVariant,
|
||||
val zippedArguments: List<Pair<AbstractCommandValueParameter<*>, CommandValueArgument>>,
|
||||
val argumentAcceptances: List<ArgumentAcceptanceWithIndex>,
|
||||
val remainingParameters: List<AbstractCommandValueParameter<*>>,
|
||||
) {
|
||||
val remainingOptionalCount: Int = remainingParameters.count { it.isOptional }
|
||||
}
|
||||
|
||||
private data class ArgumentAcceptanceWithIndex(
|
||||
val index: Int,
|
||||
val acceptance: ArgumentAcceptance,
|
||||
)
|
||||
|
||||
private fun resolveImpl(
|
||||
callee: Command,
|
||||
valueArguments: List<CommandValueArgument>,
|
||||
context: CommandArgumentContext?,
|
||||
): ResolveData? {
|
||||
|
||||
|
||||
callee.overloads
|
||||
.mapNotNull l@{ signature ->
|
||||
val valueParameters = signature.valueParameters
|
||||
|
||||
val zipped = valueParameters.zip(valueArguments).toMutableList()
|
||||
|
||||
val remainingParameters = valueParameters.drop(zipped.size).toMutableList()
|
||||
|
||||
if (remainingParameters.any { !it.isOptional && !it.isVararg }) return@l null // not enough args. // vararg can be empty.
|
||||
|
||||
if (zipped.isEmpty()) {
|
||||
ResolveData(
|
||||
variant = signature,
|
||||
zippedArguments = emptyList(),
|
||||
argumentAcceptances = emptyList(),
|
||||
remainingParameters = remainingParameters,
|
||||
)
|
||||
} else {
|
||||
if (valueArguments.size > valueParameters.size && zipped.last().first.isVararg) {
|
||||
// merge vararg arguments
|
||||
val (varargParameter, varargFirstArgument)
|
||||
= zipped.removeLast()
|
||||
|
||||
zipped.add(varargParameter to DefaultCommandValueArgument(valueArguments.drop(zipped.size).map { it.value }.asMessageChain()))
|
||||
} else {
|
||||
// add default empty vararg argument
|
||||
val remainingVararg = remainingParameters.find { it.isVararg }
|
||||
if (remainingVararg != null) {
|
||||
zipped.add(remainingVararg to DefaultCommandValueArgument(EmptyMessageChain))
|
||||
remainingParameters.remove(remainingVararg)
|
||||
}
|
||||
}
|
||||
|
||||
ResolveData(
|
||||
variant = signature,
|
||||
zippedArguments = zipped,
|
||||
argumentAcceptances = zipped.mapIndexed { index, (parameter, argument) ->
|
||||
val accepting = parameter.accepting(argument, context)
|
||||
if (accepting.isNotAcceptable) {
|
||||
return@l null // argument type not assignable
|
||||
}
|
||||
ArgumentAcceptanceWithIndex(index, accepting)
|
||||
},
|
||||
remainingParameters = remainingParameters
|
||||
)
|
||||
}
|
||||
}
|
||||
.also { result -> result.singleOrNull()?.let { return it } }
|
||||
.takeLongestMatches()
|
||||
.ifEmpty { return null }
|
||||
.also { result -> result.singleOrNull()?.let { return it } }
|
||||
// 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 } // 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(): Collection<ResolveData> {
|
||||
if (isEmpty()) return emptyList()
|
||||
return associateWith {
|
||||
it.variant.valueParameters.size - it.remainingOptionalCount * 1.001 // slightly lower priority with optional defaults.
|
||||
}.let { m ->
|
||||
val maxMatch = m.values.maxByOrNull { it }
|
||||
m.filter { it.value == maxMatch }.keys
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
/*
|
||||
* 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.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
|
||||
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
|
||||
|
||||
/**
|
||||
* The resolver converting a [CommandCall] into [ResolvedCommandCall] based on registered []
|
||||
*
|
||||
* @see CommandCallResolverProvider The provider to instances of this class
|
||||
* @see BuiltInCommandCallResolver The builtin implementation
|
||||
*/
|
||||
@ExperimentalCommandDescriptors
|
||||
public interface CommandCallResolver {
|
||||
public fun resolve(call: CommandCall): ResolvedCommandCall?
|
||||
|
||||
public companion object {
|
||||
@JvmName("resolveCall")
|
||||
@ConsoleExperimentalApi
|
||||
@ExperimentalCommandDescriptors
|
||||
public fun CommandCall.resolve(): ResolvedCommandCall? {
|
||||
GlobalComponentStorage.run {
|
||||
CommandCallResolverProvider.useExtensions { provider ->
|
||||
provider.instance.resolve(this@resolve)?.let { return it }
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,87 @@
|
||||
/*
|
||||
* 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.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.*
|
||||
import net.mamoe.mirai.console.command.parse.CommandCall
|
||||
import net.mamoe.mirai.console.command.parse.CommandValueArgument
|
||||
import net.mamoe.mirai.console.command.parse.mapToTypeOrNull
|
||||
import net.mamoe.mirai.console.internal.data.classifierAsKClass
|
||||
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
|
||||
import net.mamoe.mirai.console.util.cast
|
||||
import kotlin.LazyThreadSafetyMode.PUBLICATION
|
||||
|
||||
/**
|
||||
* The resolved [CommandCall].
|
||||
*
|
||||
* @see ResolvedCommandCallImpl
|
||||
*/
|
||||
@ExperimentalCommandDescriptors
|
||||
public interface ResolvedCommandCall {
|
||||
public val caller: CommandSender
|
||||
|
||||
/**
|
||||
* The callee [Command]
|
||||
*/
|
||||
public val callee: Command
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* **Implementation details**: Lazy calculation.
|
||||
*/
|
||||
@ConsoleExperimentalApi
|
||||
public val resolvedValueArguments: List<ResolvedCommandValueArgument<*>>
|
||||
|
||||
public companion object
|
||||
}
|
||||
|
||||
@ExperimentalCommandDescriptors
|
||||
public data class ResolvedCommandValueArgument<T>(
|
||||
val parameter: CommandValueParameter<T>,
|
||||
val value: T,
|
||||
)
|
||||
|
||||
// Don't move into companion, compilation error
|
||||
@ExperimentalCommandDescriptors
|
||||
public suspend inline fun ResolvedCommandCall.call() {
|
||||
return this@call.calleeSignature.call(this@call)
|
||||
}
|
||||
|
||||
@ExperimentalCommandDescriptors
|
||||
public class ResolvedCommandCallImpl(
|
||||
override val caller: CommandSender,
|
||||
override val callee: Command,
|
||||
override val calleeSignature: CommandSignatureVariant,
|
||||
override val rawValueArguments: List<CommandValueArgument>,
|
||||
private val context: CommandArgumentContext,
|
||||
) : ResolvedCommandCall {
|
||||
override val resolvedValueArguments: List<ResolvedCommandValueArgument<*>> by lazy(PUBLICATION) {
|
||||
calleeSignature.valueParameters.zip(rawValueArguments).map { (parameter, argument) ->
|
||||
val value = argument.mapToTypeOrNull(parameter.type) ?: context[parameter.type.classifierAsKClass()]?.parse(argument.value, caller)
|
||||
?: throw NoValueArgumentMappingException(argument, parameter.type)
|
||||
// TODO: 2020/10/17 consider vararg and optional
|
||||
ResolvedCommandValueArgument(parameter.cast(), value)
|
||||
}
|
||||
}
|
||||
}
|
@ -14,7 +14,7 @@ package net.mamoe.mirai.console.data
|
||||
import kotlinx.atomicfu.atomic
|
||||
import kotlinx.coroutines.*
|
||||
import net.mamoe.mirai.console.MiraiConsole
|
||||
import net.mamoe.mirai.console.internal.command.qualifiedNameOrTip
|
||||
import net.mamoe.mirai.console.internal.data.qualifiedNameOrTip
|
||||
import net.mamoe.mirai.console.internal.plugin.updateWhen
|
||||
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
|
||||
import net.mamoe.mirai.utils.*
|
||||
|
@ -24,6 +24,12 @@ public open class AbstractExtensionPoint<T : Extension>(
|
||||
public override val extensionType: KClass<T>,
|
||||
) : ExtensionPoint<T>
|
||||
|
||||
public open class InstanceExtensionPoint<E : InstanceExtension<T>, T>(
|
||||
extensionType: KClass<E>,
|
||||
public vararg val builtinImplementations: E,
|
||||
) : AbstractExtensionPoint<E>(extensionType)
|
||||
|
||||
|
||||
/**
|
||||
* 表示一个 [SingletonExtension] 的 [ExtensionPoint]
|
||||
*/
|
||||
|
@ -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.extensions
|
||||
|
||||
import net.mamoe.mirai.console.command.descriptor.ExperimentalCommandDescriptors
|
||||
import net.mamoe.mirai.console.command.parse.CommandCallParser
|
||||
import net.mamoe.mirai.console.command.parse.SpaceSeparatedCommandCallParser
|
||||
import net.mamoe.mirai.console.extension.InstanceExtension
|
||||
import net.mamoe.mirai.console.extension.InstanceExtensionPoint
|
||||
|
||||
/**
|
||||
* The provider of [CommandCallParser]
|
||||
*/
|
||||
@ExperimentalCommandDescriptors
|
||||
public open class CommandCallParserProvider(override val instance: CommandCallParser) : InstanceExtension<CommandCallParser> {
|
||||
public companion object ExtensionPoint :
|
||||
InstanceExtensionPoint<CommandCallParserProvider, CommandCallParser>(CommandCallParserProvider::class, SpaceSeparatedCommandCallParser.Provider)
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
/*
|
||||
* 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.extensions
|
||||
|
||||
import net.mamoe.mirai.console.command.descriptor.ExperimentalCommandDescriptors
|
||||
import net.mamoe.mirai.console.command.resolve.BuiltInCommandCallResolver
|
||||
import net.mamoe.mirai.console.command.resolve.CommandCallResolver
|
||||
import net.mamoe.mirai.console.extension.InstanceExtension
|
||||
import net.mamoe.mirai.console.extension.InstanceExtensionPoint
|
||||
|
||||
@ExperimentalCommandDescriptors
|
||||
public open class CommandCallResolverProvider(override val instance: CommandCallResolver) : InstanceExtension<CommandCallResolver> {
|
||||
public companion object ExtensionPoint :
|
||||
InstanceExtensionPoint<CommandCallResolverProvider, CommandCallResolver>(CommandCallResolverProvider::class, BuiltInCommandCallResolver.Provider)
|
||||
}
|
@ -29,7 +29,7 @@ import kotlin.reflect.KClass
|
||||
*/
|
||||
public interface SingletonExtensionSelector : FunctionExtension {
|
||||
public data class Registry<T : Extension>(
|
||||
val plugin: Plugin,
|
||||
val plugin: Plugin?,
|
||||
val extension: T,
|
||||
)
|
||||
|
||||
@ -55,11 +55,11 @@ public interface SingletonExtensionSelector : FunctionExtension {
|
||||
instances.isEmpty() -> BuiltInSingletonExtensionSelector
|
||||
instances.size == 1 -> {
|
||||
instances.single().also { (plugin, ext) ->
|
||||
MiraiConsole.mainLogger.info { "Loaded SingletonExtensionSelector: $ext from ${plugin.name}" }
|
||||
MiraiConsole.mainLogger.info { "Loaded SingletonExtensionSelector: $ext from ${plugin?.name ?: "<builtin>"}" }
|
||||
}.extension
|
||||
}
|
||||
else -> {
|
||||
error("Found too many SingletonExtensionSelectors: ${instances.joinToString { (p, i) -> "'$i' from '${p.name}'" }}. Check your plugins and ensure there is only one external SingletonExtensionSelectors")
|
||||
error("Found too many SingletonExtensionSelectors: ${instances.joinToString { (p, i) -> "'$i' from '${p?.name ?: "<builtin>"}'" }}. Check your plugins and ensure there is only one external SingletonExtensionSelectors")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -15,18 +15,25 @@ import kotlinx.coroutines.CoroutineScope
|
||||
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.CommandManager.INSTANCE.findDuplicate
|
||||
import net.mamoe.mirai.console.command.CommandSender.Companion.toCommandSender
|
||||
import net.mamoe.mirai.console.command.descriptor.ExperimentalCommandDescriptors
|
||||
import net.mamoe.mirai.console.command.parse.CommandCallParser.Companion.parseCommandCall
|
||||
import net.mamoe.mirai.console.command.resolve.CommandCallResolver.Companion.resolve
|
||||
import net.mamoe.mirai.console.permission.PermissionService.Companion.testPermission
|
||||
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.EmptyMessageChain
|
||||
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) {
|
||||
@OptIn(ExperimentalCommandDescriptors::class)
|
||||
internal object CommandManagerImpl : CommandManager, CoroutineScope by MiraiConsole.childScope("CommandManagerImpl") {
|
||||
private val logger: MiraiLogger by lazy {
|
||||
MiraiConsole.createLogger("command")
|
||||
}
|
||||
@ -48,11 +55,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,13 +72,17 @@ 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("权限不足")
|
||||
intercept()
|
||||
}
|
||||
}
|
||||
is CommandExecuteResult.IllegalArgument -> {
|
||||
result.exception.message?.let { sender.sendMessage(it) }
|
||||
intercept()
|
||||
}
|
||||
is CommandExecuteResult.Success -> {
|
||||
intercept()
|
||||
}
|
||||
@ -79,7 +90,7 @@ internal object CommandManagerImpl : CommandManager, CoroutineScope by Coroutine
|
||||
sender.catchExecutionException(result.exception)
|
||||
intercept()
|
||||
}
|
||||
is CommandExecuteResult.CommandNotFound -> {
|
||||
is CommandExecuteResult.UnresolvedCall -> {
|
||||
// noop
|
||||
}
|
||||
}
|
||||
@ -90,102 +101,90 @@ internal object CommandManagerImpl : CommandManager, CoroutineScope by Coroutine
|
||||
///// IMPL
|
||||
|
||||
|
||||
override val CommandOwner.registeredCommands: List<Command> get() = _registeredCommands.filter { it.owner == this }
|
||||
override fun getRegisteredCommands(owner: CommandOwner): List<Command> = _registeredCommands.filter { it.owner == owner }
|
||||
override val allRegisteredCommands: List<Command> get() = _registeredCommands.toList() // copy
|
||||
override val commandPrefix: String get() = "/"
|
||||
override fun CommandOwner.unregisterAllCommands() {
|
||||
for (registeredCommand in registeredCommands) {
|
||||
registeredCommand.unregister()
|
||||
override fun unregisterAllCommands(owner: CommandOwner) {
|
||||
for (registeredCommand in getRegisteredCommands(owner)) {
|
||||
unregisterCommand(registeredCommand)
|
||||
}
|
||||
}
|
||||
|
||||
override fun Command.register(override: Boolean): Boolean {
|
||||
if (this is CompositeCommand) this.subCommands // init lazy
|
||||
override fun registerCommand(command: Command, override: Boolean): Boolean {
|
||||
if (command is CompositeCommand) {
|
||||
command.overloads // init lazy
|
||||
}
|
||||
kotlin.runCatching {
|
||||
this.permission // init lazy
|
||||
this.secondaryNames // init lazy
|
||||
this.description // init lazy
|
||||
this.usage // init lazy
|
||||
command.permission // init lazy
|
||||
command.secondaryNames // init lazy
|
||||
command.description // init lazy
|
||||
command.usage // init lazy
|
||||
}.onFailure {
|
||||
throw IllegalStateException("Failed to init command ${this@register}.", it)
|
||||
throw IllegalStateException("Failed to init command ${command}.", it)
|
||||
}
|
||||
|
||||
modifyLock.withLock {
|
||||
this@CommandManagerImpl.modifyLock.withLock {
|
||||
if (!override) {
|
||||
if (findDuplicate() != null) return false
|
||||
if (command.findDuplicate() != null) return false
|
||||
}
|
||||
_registeredCommands.add(this@register)
|
||||
if (this.prefixOptional) {
|
||||
for (name in this.allNames) {
|
||||
this@CommandManagerImpl._registeredCommands.add(command)
|
||||
if (command.prefixOptional) {
|
||||
for (name in command.allNames) {
|
||||
val lowerCaseName = name.toLowerCase()
|
||||
optionalPrefixCommandMap[lowerCaseName] = this
|
||||
requiredPrefixCommandMap[lowerCaseName] = this
|
||||
this@CommandManagerImpl.optionalPrefixCommandMap[lowerCaseName] = command
|
||||
this@CommandManagerImpl.requiredPrefixCommandMap[lowerCaseName] = command
|
||||
}
|
||||
} else {
|
||||
for (name in this.allNames) {
|
||||
for (name in command.allNames) {
|
||||
val lowerCaseName = name.toLowerCase()
|
||||
optionalPrefixCommandMap.remove(lowerCaseName) // ensure resolution consistency
|
||||
requiredPrefixCommandMap[lowerCaseName] = this
|
||||
this@CommandManagerImpl.optionalPrefixCommandMap.remove(lowerCaseName) // ensure resolution consistency
|
||||
this@CommandManagerImpl.requiredPrefixCommandMap[lowerCaseName] = command
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
override fun Command.findDuplicate(): Command? =
|
||||
_registeredCommands.firstOrNull { it.allNames intersectsIgnoringCase this.allNames }
|
||||
override fun findDuplicateCommand(command: Command): Command? =
|
||||
_registeredCommands.firstOrNull { it.allNames intersectsIgnoringCase command.allNames }
|
||||
|
||||
override fun Command.unregister(): Boolean = modifyLock.withLock {
|
||||
if (this.prefixOptional) {
|
||||
this.allNames.forEach {
|
||||
optionalPrefixCommandMap.remove(it)
|
||||
override fun unregisterCommand(command: Command): Boolean = modifyLock.withLock {
|
||||
if (command.prefixOptional) {
|
||||
command.allNames.forEach {
|
||||
optionalPrefixCommandMap.remove(it.toLowerCase())
|
||||
}
|
||||
}
|
||||
this.allNames.forEach {
|
||||
requiredPrefixCommandMap.remove(it)
|
||||
command.allNames.forEach {
|
||||
requiredPrefixCommandMap.remove(it.toLowerCase())
|
||||
}
|
||||
_registeredCommands.remove(this)
|
||||
_registeredCommands.remove(command)
|
||||
}
|
||||
|
||||
override fun Command.isRegistered(): Boolean = this in _registeredCommands
|
||||
override fun isCommandRegistered(command: Command): Boolean = command 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(
|
||||
// Don't move into CommandManager, compilation error / VerifyError
|
||||
@OptIn(ExperimentalCommandDescriptors::class)
|
||||
internal suspend fun executeCommandImpl(
|
||||
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)
|
||||
caller: CommandSender,
|
||||
checkPermission: Boolean,
|
||||
): CommandExecuteResult {
|
||||
val call = message.asMessageChain().parseCommandCall(caller) ?: return CommandExecuteResult.UnresolvedCall("")
|
||||
val resolved = call.resolve() ?: return CommandExecuteResult.UnresolvedCall(call.calleeName)
|
||||
|
||||
val command = resolved.callee
|
||||
|
||||
if (checkPermission && !command.permission.testPermission(caller)) {
|
||||
return CommandExecuteResult.PermissionDenied(command, call.calleeName)
|
||||
}
|
||||
|
||||
override suspend fun CommandSender.executeCommand(message: String, checkPermission: Boolean): CommandExecuteResult {
|
||||
if (message.isBlank()) return CommandExecuteResult.CommandNotFound("")
|
||||
return executeCommandInternal(message, message.substringBefore(' '), checkPermission)
|
||||
return try {
|
||||
resolved.calleeSignature.call(resolved)
|
||||
CommandExecuteResult.Success(resolved.callee, call.calleeName, EmptyMessageChain)
|
||||
} catch (e: Throwable) {
|
||||
CommandExecuteResult.ExecutionFailed(e, resolved.callee, call.calleeName, EmptyMessageChain)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,242 @@
|
||||
package net.mamoe.mirai.console.internal.command
|
||||
|
||||
import net.mamoe.mirai.console.command.*
|
||||
import net.mamoe.mirai.console.command.descriptor.*
|
||||
import net.mamoe.mirai.console.internal.data.classifierAsKClass
|
||||
import net.mamoe.mirai.console.internal.data.classifierAsKClassOrNull
|
||||
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
|
||||
import net.mamoe.mirai.message.data.MessageChain
|
||||
import net.mamoe.mirai.message.data.PlainText
|
||||
import net.mamoe.mirai.message.data.SingleMessage
|
||||
import net.mamoe.mirai.message.data.buildMessageChain
|
||||
import kotlin.reflect.KFunction
|
||||
import kotlin.reflect.KParameter
|
||||
import kotlin.reflect.KVisibility
|
||||
import kotlin.reflect.full.*
|
||||
|
||||
|
||||
internal val ILLEGAL_SUB_NAME_CHARS = "\\/!@#$%^&*()_+-={}[];':\",.<>?`~".toCharArray()
|
||||
|
||||
internal fun Any.flattenCommandComponents(): MessageChain = buildMessageChain {
|
||||
when (this@flattenCommandComponents) {
|
||||
is PlainText -> this@flattenCommandComponents.content.splitToSequence(' ').filterNot { it.isBlank() }
|
||||
.forEach { +PlainText(it) }
|
||||
is CharSequence -> this@flattenCommandComponents.splitToSequence(' ').filterNot { it.isBlank() }
|
||||
.forEach { +PlainText(it) }
|
||||
is SingleMessage -> add(this@flattenCommandComponents)
|
||||
is Array<*> -> this@flattenCommandComponents.forEach { if (it != null) addAll(it.flattenCommandComponents()) }
|
||||
is Iterable<*> -> this@flattenCommandComponents.forEach { if (it != null) addAll(it.flattenCommandComponents()) }
|
||||
else -> add(this@flattenCommandComponents.toString())
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER")
|
||||
internal object CompositeCommandSubCommandAnnotationResolver :
|
||||
SubCommandAnnotationResolver {
|
||||
override fun hasAnnotation(ownerCommand: Command, function: KFunction<*>) =
|
||||
function.hasAnnotation<CompositeCommand.SubCommand>()
|
||||
|
||||
override fun getSubCommandNames(ownerCommand: Command, function: KFunction<*>): Array<out String> {
|
||||
val annotated = function.findAnnotation<CompositeCommand.SubCommand>()!!.value
|
||||
return if (annotated.isEmpty()) arrayOf(function.name)
|
||||
else annotated
|
||||
}
|
||||
|
||||
override fun getAnnotatedName(ownerCommand: Command, parameter: KParameter): String? =
|
||||
parameter.findAnnotation<CompositeCommand.Name>()?.value
|
||||
|
||||
override fun getDescription(ownerCommand: Command, function: KFunction<*>): String? =
|
||||
function.findAnnotation<CompositeCommand.Description>()?.value
|
||||
}
|
||||
|
||||
@Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER")
|
||||
internal object SimpleCommandSubCommandAnnotationResolver :
|
||||
SubCommandAnnotationResolver {
|
||||
override fun hasAnnotation(ownerCommand: Command, function: KFunction<*>) =
|
||||
function.hasAnnotation<SimpleCommand.Handler>()
|
||||
|
||||
override fun getSubCommandNames(ownerCommand: Command, function: KFunction<*>): Array<out String> =
|
||||
ownerCommand.secondaryNames
|
||||
|
||||
override fun getAnnotatedName(ownerCommand: Command, parameter: KParameter): String? =
|
||||
parameter.findAnnotation<SimpleCommand.Name>()?.value
|
||||
|
||||
override fun getDescription(ownerCommand: Command, function: KFunction<*>): String? =
|
||||
ownerCommand.description
|
||||
}
|
||||
|
||||
internal interface SubCommandAnnotationResolver {
|
||||
fun hasAnnotation(ownerCommand: Command, function: KFunction<*>): Boolean
|
||||
fun getSubCommandNames(ownerCommand: Command, function: KFunction<*>): Array<out String>
|
||||
fun getAnnotatedName(ownerCommand: Command, parameter: KParameter): String?
|
||||
fun getDescription(ownerCommand: Command, function: KFunction<*>): String?
|
||||
}
|
||||
|
||||
@ConsoleExperimentalApi
|
||||
public class IllegalCommandDeclarationException : Exception {
|
||||
public override val message: String?
|
||||
|
||||
public constructor(
|
||||
ownerCommand: Command,
|
||||
correspondingFunction: KFunction<*>,
|
||||
message: String?,
|
||||
) : super("Illegal command declaration: ${correspondingFunction.name} declared in ${ownerCommand::class.qualifiedName}") {
|
||||
this.message = message
|
||||
}
|
||||
|
||||
public constructor(
|
||||
ownerCommand: Command,
|
||||
message: String?,
|
||||
) : super("Illegal command declaration: ${ownerCommand::class.qualifiedName}") {
|
||||
this.message = message
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalCommandDescriptors::class)
|
||||
internal class CommandReflector(
|
||||
val command: Command,
|
||||
val annotationResolver: SubCommandAnnotationResolver,
|
||||
) {
|
||||
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
private inline fun KFunction<*>.illegalDeclaration(
|
||||
message: String,
|
||||
): Nothing {
|
||||
throw IllegalCommandDeclarationException(command, this, message)
|
||||
}
|
||||
|
||||
private fun KFunction<*>.isSubCommandFunction(): Boolean = annotationResolver.hasAnnotation(command, this)
|
||||
private fun KFunction<*>.checkExtensionReceiver() {
|
||||
this.extensionReceiverParameter?.let { receiver ->
|
||||
if (receiver.type.classifierAsKClassOrNull()?.isSubclassOf(CommandSender::class) != true) {
|
||||
illegalDeclaration("Extension receiver parameter type is not subclass of CommandSender.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun KFunction<*>.checkNames() {
|
||||
val names = annotationResolver.getSubCommandNames(command, this)
|
||||
for (name in names) {
|
||||
ILLEGAL_SUB_NAME_CHARS.find { it in name }?.let {
|
||||
illegalDeclaration("'$it' is forbidden in command name.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun KFunction<*>.checkModifiers() {
|
||||
if (isInline) illegalDeclaration("Command function cannot be inline")
|
||||
if (visibility == KVisibility.PRIVATE) illegalDeclaration("Command function must be accessible from Mirai Console, that is, effectively public.")
|
||||
if (this.hasAnnotation<JvmStatic>()) illegalDeclaration("Command function must not be static.")
|
||||
|
||||
// should we allow abstract?
|
||||
|
||||
// if (isAbstract) illegalDeclaration("Command function cannot be abstract")
|
||||
}
|
||||
|
||||
fun generateUsage(overloads: Iterable<CommandSignatureVariantFromKFunction>): String {
|
||||
return overloads.joinToString("\n") { subcommand ->
|
||||
buildString {
|
||||
if (command.prefixOptional) {
|
||||
append("(")
|
||||
append(CommandManager.commandPrefix)
|
||||
append(")")
|
||||
} else {
|
||||
append(CommandManager.commandPrefix)
|
||||
}
|
||||
if (command is CompositeCommand) {
|
||||
append(command.primaryName)
|
||||
append(" ")
|
||||
}
|
||||
append(subcommand.valueParameters.joinToString(" ") { it.render() })
|
||||
annotationResolver.getDescription(command, subcommand.originFunction).let { description ->
|
||||
append(" ")
|
||||
append(description)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
companion object {
|
||||
|
||||
private fun <T> AbstractCommandValueParameter<T>.render(): String {
|
||||
return when (this) {
|
||||
is AbstractCommandValueParameter.Extended,
|
||||
is AbstractCommandValueParameter.UserDefinedType<*>,
|
||||
-> {
|
||||
"<${this.name ?: this.type.classifierAsKClass().simpleName}>"
|
||||
}
|
||||
is AbstractCommandValueParameter.StringConstant -> {
|
||||
this.expectingValue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun validate(variants: List<CommandSignatureVariantFromKFunctionImpl>) {
|
||||
|
||||
data class ErasedParameters(
|
||||
val name: String,
|
||||
val x: String,
|
||||
)
|
||||
variants
|
||||
}
|
||||
|
||||
@Throws(IllegalCommandDeclarationException::class)
|
||||
fun findSubCommands(): List<CommandSignatureVariantFromKFunctionImpl> {
|
||||
return command::class.functions // exclude static later
|
||||
.asSequence()
|
||||
.filter { it.isSubCommandFunction() }
|
||||
.onEach { it.checkExtensionReceiver() }
|
||||
.onEach { it.checkModifiers() }
|
||||
.onEach { it.checkNames() }
|
||||
.map { function ->
|
||||
|
||||
val functionNameAsValueParameter =
|
||||
annotationResolver.getSubCommandNames(command, function).mapIndexed { index, s -> createStringConstantParameter(index, s) }
|
||||
|
||||
val functionValueParameters =
|
||||
function.valueParameters.associateBy { it.toUserDefinedCommandParameter() }
|
||||
|
||||
CommandSignatureVariantFromKFunctionImpl(
|
||||
receiverParameter = function.extensionReceiverParameter?.toCommandReceiverParameter(),
|
||||
valueParameters = functionNameAsValueParameter + functionValueParameters.keys,
|
||||
originFunction = function
|
||||
) { call ->
|
||||
val args = LinkedHashMap<KParameter, Any?>()
|
||||
|
||||
for ((commandParameter, value) in call.resolvedValueArguments) {
|
||||
if (commandParameter is AbstractCommandValueParameter.StringConstant) {
|
||||
continue
|
||||
}
|
||||
val functionParameter =
|
||||
functionValueParameters[commandParameter] ?: error("Could not find a corresponding function parameter '${commandParameter.name}'")
|
||||
args[functionParameter] = value
|
||||
}
|
||||
|
||||
val instanceParameter = function.instanceParameter
|
||||
if (instanceParameter != null) {
|
||||
args[instanceParameter] = command
|
||||
}
|
||||
function.callSuspendBy(args)
|
||||
}
|
||||
}.toList()
|
||||
}
|
||||
|
||||
private fun KParameter.toCommandReceiverParameter(): CommandReceiverParameter<out CommandSender>? {
|
||||
check(!this.isVararg) { "Receiver cannot be vararg." }
|
||||
check(this.type.classifierAsKClass().isSubclassOf(CommandSender::class)) { "Receiver must be subclass of CommandSender" }
|
||||
|
||||
return CommandReceiverParameter(this.type.isMarkedNullable, this.type)
|
||||
}
|
||||
|
||||
private fun createStringConstantParameter(index: Int, expectingValue: String): AbstractCommandValueParameter.StringConstant {
|
||||
return AbstractCommandValueParameter.StringConstant("#$index", expectingValue)
|
||||
}
|
||||
|
||||
private fun KParameter.toUserDefinedCommandParameter(): AbstractCommandValueParameter.UserDefinedType<*> {
|
||||
return AbstractCommandValueParameter.UserDefinedType<Any?>(nameForCommandParameter(), this.isOptional, this.isVararg, this.type) // Any? is erased
|
||||
}
|
||||
|
||||
private fun KParameter.nameForCommandParameter(): String? = annotationResolver.getAnnotatedName(command, this) ?: this.name
|
||||
}
|
@ -12,22 +12,24 @@
|
||||
package net.mamoe.mirai.console.internal.command
|
||||
|
||||
import net.mamoe.mirai.console.command.CompositeCommand
|
||||
import net.mamoe.mirai.console.command.description.CommandArgumentParser
|
||||
import java.lang.reflect.Parameter
|
||||
import kotlin.reflect.KClass
|
||||
import net.mamoe.mirai.console.command.descriptor.CommandValueArgumentParser
|
||||
import kotlin.reflect.KParameter
|
||||
import kotlin.reflect.KType
|
||||
|
||||
/*
|
||||
internal fun Parameter.toCommandParam(): CommandParameter<*> {
|
||||
val name = getAnnotation(CompositeCommand.Name::class.java)
|
||||
return CommandParameter(
|
||||
name?.value ?: this.name
|
||||
?: throw IllegalArgumentException("Cannot construct CommandParam from a unnamed param"),
|
||||
this.type.kotlin
|
||||
this.type.kotlin,
|
||||
null
|
||||
)
|
||||
}
|
||||
*/
|
||||
|
||||
/**
|
||||
* 指令形式参数.
|
||||
* @see toCommandParam
|
||||
*/
|
||||
internal data class CommandParameter<T : Any>(
|
||||
/**
|
||||
@ -35,24 +37,27 @@ internal data class CommandParameter<T : Any>(
|
||||
*/
|
||||
val name: String,
|
||||
/**
|
||||
* 参数类型. 将从 [CompositeCommand.context] 中寻找 [CommandArgumentParser] 解析.
|
||||
* 参数类型. 将从 [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>, parser: CommandArgumentParser<T>) : this(name, type) {
|
||||
constructor(name: String, type: KType, parameter: KParameter, parser: CommandValueArgumentParser<T>) : this(
|
||||
name, type, parameter
|
||||
) {
|
||||
this._overrideParser = parser
|
||||
}
|
||||
|
||||
@Suppress("PropertyName")
|
||||
@JvmField
|
||||
internal var _overrideParser: CommandArgumentParser<T>? = null
|
||||
internal var _overrideParser: CommandValueArgumentParser<T>? = null
|
||||
|
||||
|
||||
/**
|
||||
* 覆盖的 [CommandArgumentParser].
|
||||
* 覆盖的 [CommandValueArgumentParser].
|
||||
*
|
||||
* 如果非 `null`, 将不会从 [CommandArgumentContext] 寻找 [CommandArgumentParser]
|
||||
* 如果非 `null`, 将不会从 [CommandArgumentContext] 寻找 [CommandValueArgumentParser]
|
||||
*/
|
||||
val overrideParser: CommandArgumentParser<T>? get() = _overrideParser
|
||||
val overrideParser: CommandValueArgumentParser<T>? get() = _overrideParser
|
||||
}
|
||||
|
||||
|
@ -1,349 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019-2020 Mamoe Technologies and contributors.
|
||||
*
|
||||
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
|
||||
* Use of this source code is governed by the GNU AFFERO GENERAL PUBLIC LICENSE version 3 license that can be found via the following link.
|
||||
*
|
||||
* https://github.com/mamoe/mirai/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
@file:Suppress("NOTHING_TO_INLINE", "MemberVisibilityCanBePrivate", "INVISIBLE_REFERENCE", "INVISIBLE_MEMBER")
|
||||
|
||||
package net.mamoe.mirai.console.internal.command
|
||||
|
||||
import net.mamoe.mirai.console.command.*
|
||||
import net.mamoe.mirai.console.command.description.CommandArgumentContext
|
||||
import net.mamoe.mirai.console.command.description.CommandArgumentContextAware
|
||||
import net.mamoe.mirai.console.internal.data.kClassQualifiedNameOrTip
|
||||
import net.mamoe.mirai.console.permission.Permission
|
||||
import net.mamoe.mirai.console.permission.PermissionService.Companion.testPermission
|
||||
import net.mamoe.mirai.message.data.*
|
||||
import kotlin.reflect.KAnnotatedElement
|
||||
import kotlin.reflect.KClass
|
||||
import kotlin.reflect.KFunction
|
||||
import kotlin.reflect.full.callSuspend
|
||||
import kotlin.reflect.full.declaredFunctions
|
||||
import kotlin.reflect.full.findAnnotation
|
||||
import kotlin.reflect.full.isSubclassOf
|
||||
|
||||
internal object CompositeCommandSubCommandAnnotationResolver :
|
||||
AbstractReflectionCommand.SubCommandAnnotationResolver {
|
||||
override fun hasAnnotation(baseCommand: AbstractReflectionCommand, function: KFunction<*>) =
|
||||
function.hasAnnotation<CompositeCommand.SubCommand>()
|
||||
|
||||
override fun getSubCommandNames(baseCommand: AbstractReflectionCommand, function: KFunction<*>): Array<out String> =
|
||||
function.findAnnotation<CompositeCommand.SubCommand>()!!.value
|
||||
}
|
||||
|
||||
internal object SimpleCommandSubCommandAnnotationResolver :
|
||||
AbstractReflectionCommand.SubCommandAnnotationResolver {
|
||||
override fun hasAnnotation(baseCommand: AbstractReflectionCommand, function: KFunction<*>) =
|
||||
function.hasAnnotation<SimpleCommand.Handler>()
|
||||
|
||||
override fun getSubCommandNames(baseCommand: AbstractReflectionCommand, function: KFunction<*>): Array<out String> =
|
||||
baseCommand.secondaryNames
|
||||
}
|
||||
|
||||
internal abstract class AbstractReflectionCommand
|
||||
@JvmOverloads constructor(
|
||||
owner: CommandOwner,
|
||||
primaryName: String,
|
||||
secondaryNames: Array<out String>,
|
||||
description: String = "<no description available>",
|
||||
parentPermission: Permission = owner.parentPermission,
|
||||
prefixOptional: Boolean = false,
|
||||
) : Command, AbstractCommand(
|
||||
owner,
|
||||
primaryName = primaryName,
|
||||
secondaryNames = secondaryNames,
|
||||
description = description,
|
||||
parentPermission = parentPermission,
|
||||
prefixOptional = prefixOptional
|
||||
), CommandArgumentContextAware {
|
||||
internal abstract val subCommandAnnotationResolver: SubCommandAnnotationResolver
|
||||
|
||||
@JvmField
|
||||
@Suppress("PropertyName")
|
||||
internal var _usage: String = "<not yet initialized>"
|
||||
|
||||
override val usage: String // initialized by subCommand reflection
|
||||
get() {
|
||||
subCommands // ensure init
|
||||
return _usage
|
||||
}
|
||||
|
||||
abstract suspend fun CommandSender.onDefault(rawArgs: MessageChain)
|
||||
|
||||
internal val defaultSubCommand: DefaultSubCommandDescriptor by lazy {
|
||||
DefaultSubCommandDescriptor(
|
||||
"",
|
||||
createOrFindCommandPermission(parentPermission),
|
||||
onCommand = { sender: CommandSender, args: MessageChain ->
|
||||
sender.onDefault(args)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
internal open fun checkSubCommand(subCommands: Array<SubCommandDescriptor>) {
|
||||
|
||||
}
|
||||
|
||||
interface SubCommandAnnotationResolver {
|
||||
fun hasAnnotation(baseCommand: AbstractReflectionCommand, function: KFunction<*>): Boolean
|
||||
fun getSubCommandNames(baseCommand: AbstractReflectionCommand, function: KFunction<*>): Array<out String>
|
||||
}
|
||||
|
||||
internal val subCommands: Array<SubCommandDescriptor> by lazy {
|
||||
this::class.declaredFunctions.filter { subCommandAnnotationResolver.hasAnnotation(this, it) }
|
||||
.also { subCommandFunctions ->
|
||||
// overloading not yet supported
|
||||
val overloadFunction = subCommandFunctions.groupBy { it.name }.entries.firstOrNull { it.value.size > 1 }
|
||||
if (overloadFunction != null) {
|
||||
error("Sub command overloading is not yet supported. (at ${this::class.qualifiedNameOrTip}.${overloadFunction.key})")
|
||||
}
|
||||
}.map { function ->
|
||||
createSubCommand(function, context)
|
||||
}.toTypedArray().also {
|
||||
_usage = it.createUsage(this)
|
||||
}.also { checkSubCommand(it) }
|
||||
}
|
||||
|
||||
internal val bakedCommandNameToSubDescriptorArray: Map<Array<String>, SubCommandDescriptor> by lazy {
|
||||
kotlin.run {
|
||||
val map = LinkedHashMap<Array<String>, SubCommandDescriptor>(subCommands.size * 2)
|
||||
for (descriptor in subCommands) {
|
||||
for (name in descriptor.bakedSubNames) {
|
||||
map[name] = descriptor
|
||||
}
|
||||
}
|
||||
map.toSortedMap { o1, o2 -> o1!!.contentHashCode() - o2!!.contentHashCode() }
|
||||
}
|
||||
}
|
||||
|
||||
internal class DefaultSubCommandDescriptor(
|
||||
val description: String,
|
||||
val permission: Permission,
|
||||
val onCommand: suspend (sender: CommandSender, rawArgs: MessageChain) -> Unit,
|
||||
)
|
||||
|
||||
internal inner class SubCommandDescriptor(
|
||||
val names: Array<out String>,
|
||||
val params: Array<CommandParameter<*>>,
|
||||
val description: String,
|
||||
val permission: Permission,
|
||||
val onCommand: suspend (sender: CommandSender, parsedArgs: Array<out Any>) -> Boolean,
|
||||
val context: CommandArgumentContext,
|
||||
) {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
@JvmField
|
||||
internal val bakedSubNames: Array<Array<String>> = names.map { it.bakeSubName() }.toTypedArray()
|
||||
private fun parseArgs(sender: CommandSender, rawArgs: MessageChain, offset: Int): Array<out Any>? {
|
||||
if (rawArgs.size < offset + this.params.size)
|
||||
return null
|
||||
//require(rawArgs.size >= offset + this.params.size) { "No enough args. Required ${params.size}, but given ${rawArgs.size - offset}" }
|
||||
|
||||
return Array(this.params.size) { index ->
|
||||
val param = params[index]
|
||||
val rawArg = rawArgs[offset + index]
|
||||
when (rawArg) {
|
||||
is PlainText -> context[param.type]?.parse(rawArg.content, sender)
|
||||
is MessageContent -> context[param.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
|
||||
}
|
||||
}
|
||||
|
||||
internal fun <T> Array<T>.contentEqualsOffset(other: MessageChain, length: Int): Boolean {
|
||||
repeat(length) { index ->
|
||||
if (!other[index].toString().equals(this[index].toString(), ignoreCase = true)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
internal val ILLEGAL_SUB_NAME_CHARS = "\\/!@#$%^&*()_+-={}[];':\",.<>?`~".toCharArray()
|
||||
internal fun String.isValidSubName(): Boolean = ILLEGAL_SUB_NAME_CHARS.none { it in this }
|
||||
internal fun String.bakeSubName(): Array<String> = split(' ').filterNot { it.isBlank() }.toTypedArray()
|
||||
|
||||
internal fun Any.flattenCommandComponents(): MessageChain = buildMessageChain {
|
||||
when (this@flattenCommandComponents) {
|
||||
is PlainText -> this@flattenCommandComponents.content.splitToSequence(' ').filterNot { it.isBlank() }
|
||||
.forEach { +PlainText(it) }
|
||||
is CharSequence -> this@flattenCommandComponents.splitToSequence(' ').filterNot { it.isBlank() }
|
||||
.forEach { +PlainText(it) }
|
||||
is SingleMessage -> add(this@flattenCommandComponents)
|
||||
is Array<*> -> this@flattenCommandComponents.forEach { if (it != null) addAll(it.flattenCommandComponents()) }
|
||||
is Iterable<*> -> this@flattenCommandComponents.forEach { if (it != null) addAll(it.flattenCommandComponents()) }
|
||||
else -> add(this@flattenCommandComponents.toString())
|
||||
}
|
||||
}
|
||||
|
||||
internal inline fun <reified T : Annotation> KAnnotatedElement.hasAnnotation(): Boolean =
|
||||
findAnnotation<T>() != null
|
||||
|
||||
internal val KClass<*>.qualifiedNameOrTip: String get() = this.qualifiedName ?: "<anonymous class>"
|
||||
|
||||
internal fun Array<AbstractReflectionCommand.SubCommandDescriptor>.createUsage(baseCommand: AbstractReflectionCommand): String =
|
||||
buildString {
|
||||
appendLine(baseCommand.description)
|
||||
appendLine()
|
||||
|
||||
for (subCommandDescriptor in this@createUsage) {
|
||||
appendLine(subCommandDescriptor.usage)
|
||||
}
|
||||
}.trimEnd()
|
||||
|
||||
internal fun AbstractReflectionCommand.SubCommandDescriptor.createUsage(baseCommand: AbstractReflectionCommand): String =
|
||||
buildString {
|
||||
if (baseCommand.prefixOptional) {
|
||||
append("(")
|
||||
append(CommandManager.commandPrefix)
|
||||
append(")")
|
||||
} else {
|
||||
append(CommandManager.commandPrefix)
|
||||
}
|
||||
if (baseCommand is CompositeCommand) {
|
||||
append(baseCommand.primaryName)
|
||||
append(" ")
|
||||
}
|
||||
append(names.first())
|
||||
append(" ")
|
||||
append(params.joinToString(" ") { "<${it.name}>" })
|
||||
append(" ")
|
||||
append(description)
|
||||
appendLine()
|
||||
}.trimEnd()
|
||||
|
||||
internal fun AbstractReflectionCommand.createSubCommand(
|
||||
function: KFunction<*>,
|
||||
context: CommandArgumentContext,
|
||||
): AbstractReflectionCommand.SubCommandDescriptor {
|
||||
val notStatic = !function.hasAnnotation<JvmStatic>()
|
||||
//val overridePermission = null//function.findAnnotation<CompositeCommand.PermissionId>()//optional
|
||||
val subDescription =
|
||||
function.findAnnotation<CompositeCommand.Description>()?.value ?: ""
|
||||
|
||||
fun KClass<*>.isValidReturnType(): Boolean {
|
||||
return when (this) {
|
||||
Boolean::class, Void::class, Unit::class, Nothing::class -> true
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
check((function.returnType.classifier as? KClass<*>)?.isValidReturnType() == true) {
|
||||
error("Return type of sub command ${function.name} must be one of the following: kotlin.Boolean, java.lang.Boolean, kotlin.Unit (including implicit), kotlin.Nothing, boolean or void (at ${this::class.qualifiedNameOrTip}.${function.name})")
|
||||
}
|
||||
|
||||
check(!function.returnType.isMarkedNullable) {
|
||||
error("Return type of sub command ${function.name} must not be marked nullable in Kotlin, and must be marked with @NotNull or @NonNull explicitly in Java. (at ${this::class.qualifiedNameOrTip}.${function.name})")
|
||||
}
|
||||
|
||||
val parameters = function.parameters.toMutableList()
|
||||
|
||||
if (notStatic) parameters.removeAt(0) // instance
|
||||
|
||||
var hasSenderParam = false
|
||||
check(parameters.isNotEmpty()) {
|
||||
"Parameters of sub command ${function.name} must not be empty. (Must have CommandSender as its receiver or first parameter or absent, followed by naturally typed params) (at ${this::class.qualifiedNameOrTip}.${function.name})"
|
||||
}
|
||||
|
||||
parameters.forEach { param ->
|
||||
check(!param.isVararg) {
|
||||
"Parameter $param must not be vararg. (at ${this::class.qualifiedNameOrTip}.${function.name}.$param)"
|
||||
}
|
||||
}
|
||||
|
||||
(parameters.first()).let { receiver ->
|
||||
if ((receiver.type.classifier as? KClass<*>)?.isSubclassOf(CommandSender::class) == true) {
|
||||
hasSenderParam = true
|
||||
parameters.removeAt(0)
|
||||
}
|
||||
}
|
||||
|
||||
val commandName =
|
||||
subCommandAnnotationResolver.getSubCommandNames(this, function)
|
||||
.let { namesFromAnnotation ->
|
||||
if (namesFromAnnotation.isNotEmpty()) {
|
||||
namesFromAnnotation.map(String::toLowerCase).toTypedArray()
|
||||
} else arrayOf(function.name.toLowerCase())
|
||||
}.also { names ->
|
||||
names.forEach {
|
||||
check(it.isValidSubName()) {
|
||||
"Name of sub command ${function.name} is invalid"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//map parameter
|
||||
val params = parameters.map { param ->
|
||||
|
||||
if (param.isOptional) error("optional parameters are not yet supported. (at ${this::class.qualifiedNameOrTip}.${function.name}.$param)")
|
||||
|
||||
val paramName = param.findAnnotation<CompositeCommand.Name>()?.value ?: param.name ?: "unknown"
|
||||
CommandParameter(
|
||||
paramName,
|
||||
(param.type.classifier as? KClass<*>)
|
||||
?: throw IllegalArgumentException("unsolved type reference from param " + param.name + ". (at ${this::class.qualifiedNameOrTip}.${function.name}.$param)")
|
||||
)
|
||||
}.toTypedArray()
|
||||
|
||||
return SubCommandDescriptor(
|
||||
commandName,
|
||||
params,
|
||||
subDescription, // overridePermission?.value
|
||||
permission,//overridePermission?.value?.let { PermissionService.INSTANCE[PermissionId.parseFromString(it)] } ?: permission,
|
||||
onCommand = { sender: CommandSender, args: Array<out Any> ->
|
||||
val result = if (notStatic) {
|
||||
if (hasSenderParam) {
|
||||
function.isSuspend
|
||||
function.callSuspend(this, sender, *args)
|
||||
} else function.callSuspend(this, *args)
|
||||
} else {
|
||||
if (hasSenderParam) {
|
||||
function.callSuspend(sender, *args)
|
||||
} else function.callSuspend(*args)
|
||||
}
|
||||
|
||||
checkNotNull(result) { "sub command return value is null (at ${this::class.qualifiedName}.${function.name})" }
|
||||
|
||||
result as? Boolean ?: true // Unit, void is considered as true.
|
||||
},
|
||||
context = context
|
||||
)
|
||||
}
|
@ -1,62 +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 = {
|
||||
return CommandExecuteResult.ExecutionFailed(
|
||||
commandName = commandName,
|
||||
command = command,
|
||||
exception = it,
|
||||
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)
|
||||
}
|
@ -12,7 +12,6 @@ package net.mamoe.mirai.console.internal.data
|
||||
import kotlinx.serialization.json.Json
|
||||
import net.mamoe.mirai.console.MiraiConsole
|
||||
import net.mamoe.mirai.console.data.*
|
||||
import net.mamoe.mirai.console.internal.command.qualifiedNameOrTip
|
||||
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
|
||||
import net.mamoe.mirai.utils.MiraiLogger
|
||||
import net.mamoe.mirai.utils.SilentLogger
|
||||
|
@ -11,14 +11,15 @@ package net.mamoe.mirai.console.internal.data
|
||||
|
||||
import net.mamoe.mirai.console.data.PluginData
|
||||
import net.mamoe.mirai.console.data.ValueName
|
||||
import net.mamoe.mirai.console.internal.command.qualifiedNameOrTip
|
||||
import kotlin.reflect.KClass
|
||||
import kotlin.reflect.KParameter
|
||||
import kotlin.reflect.KProperty
|
||||
import kotlin.reflect.KType
|
||||
import kotlin.reflect.*
|
||||
import kotlin.reflect.full.findAnnotation
|
||||
import kotlin.reflect.full.isSubclassOf
|
||||
|
||||
internal val KClass<*>.qualifiedNameOrTip: String get() = this.qualifiedName ?: "<anonymous class>"
|
||||
|
||||
internal inline fun <reified T : Annotation> KAnnotatedElement.hasAnnotation(): Boolean =
|
||||
findAnnotation<T>() != null
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
internal inline fun <reified T : Any> KType.toKClass(): KClass<out T> {
|
||||
val clazz = requireNotNull(classifier as? KClass<T>) { "Unsupported classifier: $classifier" }
|
||||
@ -54,6 +55,12 @@ internal fun KType.classifierAsKClass() = when (val t = classifier) {
|
||||
else -> error("Only KClass supported as classifier, got $t")
|
||||
} as KClass<Any>
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
internal fun KType.classifierAsKClassOrNull() = when (val t = classifier) {
|
||||
is KClass<*> -> t
|
||||
else -> null
|
||||
} as KClass<Any>?
|
||||
|
||||
@JvmSynthetic
|
||||
internal fun <T : Any> KClass<T>.createInstanceOrNull(): T? {
|
||||
val noArgsConstructor = constructors.singleOrNull { it.parameters.all(KParameter::isOptional) }
|
||||
|
@ -16,7 +16,6 @@ import net.mamoe.mirai.console.data.PluginData
|
||||
import net.mamoe.mirai.console.data.SerializableValue.Companion.serializableValueWith
|
||||
import net.mamoe.mirai.console.data.SerializerAwareValue
|
||||
import net.mamoe.mirai.console.data.valueFromKType
|
||||
import net.mamoe.mirai.console.internal.command.qualifiedNameOrTip
|
||||
import kotlin.contracts.contract
|
||||
import kotlin.reflect.KClass
|
||||
import kotlin.reflect.KType
|
||||
|
@ -52,7 +52,7 @@ internal object BuiltInSingletonExtensionSelector : SingletonExtensionSelector {
|
||||
val candidatesList = candidates.toList()
|
||||
|
||||
for ((index, candidate) in candidatesList.withIndex()) {
|
||||
MiraiConsole.mainLogger.info { "${index + 1}. '${candidate.extension}' from '${candidate.plugin.name}'" }
|
||||
MiraiConsole.mainLogger.info { "${index + 1}. '${candidate.extension}' from '${candidate.plugin?.name ?: "<builtin>"}'" }
|
||||
}
|
||||
|
||||
MiraiConsole.mainLogger.info { "Please choose a number from 1 to ${candidatesList.count()}" }
|
||||
|
@ -20,12 +20,15 @@ import java.util.concurrent.CopyOnWriteArraySet
|
||||
import kotlin.contracts.contract
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
/**
|
||||
* The [ComponentStorage] containing all components provided by Mirai Console internals and installed plugins.
|
||||
*/
|
||||
internal object GlobalComponentStorage : AbstractConcurrentComponentStorage()
|
||||
internal interface ExtensionRegistry<out E : Extension> {
|
||||
val plugin: Plugin
|
||||
val plugin: Plugin?
|
||||
val extension: E
|
||||
|
||||
operator fun component1(): Plugin {
|
||||
operator fun component1(): Plugin? {
|
||||
return this.plugin
|
||||
}
|
||||
|
||||
@ -35,21 +38,27 @@ internal interface ExtensionRegistry<out E : Extension> {
|
||||
}
|
||||
|
||||
internal class LazyExtensionRegistry<out E : Extension>(
|
||||
override val plugin: Plugin,
|
||||
override val plugin: Plugin?,
|
||||
initializer: () -> E,
|
||||
) : ExtensionRegistry<E> {
|
||||
override val extension: E by lazy { initializer() }
|
||||
}
|
||||
|
||||
internal data class DataExtensionRegistry<out E : Extension>(
|
||||
override val plugin: Plugin,
|
||||
override val plugin: Plugin?,
|
||||
override val extension: E,
|
||||
) : ExtensionRegistry<E>
|
||||
|
||||
internal abstract class AbstractConcurrentComponentStorage : ComponentStorage {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
internal fun <T : Extension> ExtensionPoint<out T>.getExtensions(): Set<ExtensionRegistry<T>> {
|
||||
return instances.getOrPut(this, ::CopyOnWriteArraySet) as Set<ExtensionRegistry<T>>
|
||||
val userDefined = instances.getOrPut(this, ::CopyOnWriteArraySet) as Set<ExtensionRegistry<T>>
|
||||
|
||||
val builtins = if (this is InstanceExtensionPoint<*, *>) {
|
||||
this.builtinImplementations.mapTo(HashSet()) { DataExtensionRegistry(null, it) } as Set<ExtensionRegistry<T>>
|
||||
} else null
|
||||
|
||||
return builtins?.plus(userDefined) ?: userDefined
|
||||
}
|
||||
|
||||
internal fun mergeWith(another: AbstractConcurrentComponentStorage) {
|
||||
@ -68,7 +77,7 @@ internal abstract class AbstractConcurrentComponentStorage : ComponentStorage {
|
||||
|
||||
@Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
|
||||
@kotlin.internal.LowPriorityInOverloadResolution
|
||||
internal inline fun <T : Extension> ExtensionPoint<out T>.withExtensions(block: T.(plugin: Plugin) -> Unit) {
|
||||
internal inline fun <T : Extension> ExtensionPoint<out T>.withExtensions(block: T.(plugin: Plugin?) -> Unit) {
|
||||
contract {
|
||||
callsInPlace(block)
|
||||
}
|
||||
@ -128,11 +137,11 @@ internal abstract class AbstractConcurrentComponentStorage : ComponentStorage {
|
||||
|
||||
internal fun <T : Extension> ExtensionPoint<out T>.throwExtensionException(
|
||||
extension: T,
|
||||
plugin: Plugin,
|
||||
plugin: Plugin?,
|
||||
throwable: Throwable,
|
||||
) {
|
||||
throw ExtensionException(
|
||||
"Exception while executing extension '${extension.kClassQualifiedNameOrTip}' provided by plugin '${plugin.name}', registered for '${this.extensionType.qualifiedName}'",
|
||||
"Exception while executing extension '${extension.kClassQualifiedNameOrTip}' provided by plugin '${plugin?.name ?: "<builtin>"}', registered for '${this.extensionType.qualifiedName}'",
|
||||
throwable
|
||||
)
|
||||
}
|
||||
@ -142,7 +151,7 @@ internal abstract class AbstractConcurrentComponentStorage : ComponentStorage {
|
||||
|
||||
@Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
|
||||
@kotlin.internal.LowPriorityInOverloadResolution
|
||||
internal inline fun <T : Extension> ExtensionPoint<T>.useExtensions(block: (extension: T, plugin: Plugin) -> Unit): Unit =
|
||||
internal inline fun <T : Extension> ExtensionPoint<T>.useExtensions(block: (extension: T, plugin: Plugin?) -> Unit): Unit =
|
||||
withExtensions(block)
|
||||
|
||||
val instances: MutableMap<ExtensionPoint<*>, MutableSet<ExtensionRegistry<*>>> = ConcurrentHashMap()
|
||||
@ -154,6 +163,15 @@ internal abstract class AbstractConcurrentComponentStorage : ComponentStorage {
|
||||
instances.getOrPut(extensionPoint, ::CopyOnWriteArraySet).add(DataExtensionRegistry(plugin, extensionInstance))
|
||||
}
|
||||
|
||||
@JvmName("contribute1")
|
||||
fun <T : Extension> contribute(
|
||||
extensionPoint: ExtensionPoint<T>,
|
||||
plugin: Plugin?,
|
||||
extensionInstance: T,
|
||||
) {
|
||||
instances.getOrPut(extensionPoint, ::CopyOnWriteArraySet).add(DataExtensionRegistry(plugin, extensionInstance))
|
||||
}
|
||||
|
||||
override fun <T : Extension> contribute(
|
||||
extensionPoint: ExtensionPoint<T>,
|
||||
plugin: Plugin,
|
||||
@ -161,4 +179,13 @@ internal abstract class AbstractConcurrentComponentStorage : ComponentStorage {
|
||||
) {
|
||||
instances.getOrPut(extensionPoint, ::CopyOnWriteArraySet).add(LazyExtensionRegistry(plugin, lazyInstance))
|
||||
}
|
||||
|
||||
@JvmName("contribute1")
|
||||
fun <T : Extension> contribute(
|
||||
extensionPoint: ExtensionPoint<T>,
|
||||
plugin: Plugin?,
|
||||
lazyInstance: () -> T,
|
||||
) {
|
||||
instances.getOrPut(extensionPoint, ::CopyOnWriteArraySet).add(LazyExtensionRegistry(plugin, lazyInstance))
|
||||
}
|
||||
}
|
@ -48,7 +48,7 @@ internal abstract class JvmPluginInternal(
|
||||
|
||||
final override val parentPermission: Permission by lazy {
|
||||
PermissionService.INSTANCE.register(
|
||||
PermissionService.INSTANCE.allocatePermissionIdForPlugin(this, "*", PermissionService.PluginPermissionIdRequestType.ROOT_PERMISSION),
|
||||
PermissionService.INSTANCE.allocatePermissionIdForPlugin(this, "*", PermissionService.PluginPermissionIdRequestType.PLUGIN_ROOT_PERMISSION),
|
||||
"The base permission"
|
||||
)
|
||||
}
|
||||
|
@ -148,7 +148,7 @@ internal object PluginManagerImpl : PluginManager, CoroutineScope by MiraiConsol
|
||||
var count = 0
|
||||
GlobalComponentStorage.run {
|
||||
PluginLoaderProvider.useExtensions { ext, plugin ->
|
||||
logger.info { "Loaded PluginLoader ${ext.instance} from ${plugin.name}" }
|
||||
logger.info { "Loaded PluginLoader ${ext.instance} from ${plugin?.name ?: "<builtin>"}" }
|
||||
_pluginLoaders.add(ext.instance)
|
||||
count++
|
||||
}
|
||||
|
@ -7,6 +7,8 @@
|
||||
* https://github.com/mamoe/mirai/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
@file:Suppress("unused")
|
||||
|
||||
package net.mamoe.mirai.console.permission
|
||||
|
||||
import net.mamoe.mirai.console.command.BuiltInCommands
|
||||
@ -65,10 +67,10 @@ public interface Permission {
|
||||
* @see RootPermission 推荐 Kotlin 用户使用.
|
||||
*/
|
||||
@JvmStatic
|
||||
public fun getRootPermission(): Permission = PermissionService.INSTANCE.rootPermission
|
||||
public fun getRootPermission(): Permission = RootPermission
|
||||
|
||||
/**
|
||||
* 递归获取 [Permission.parent], `permission.parent.parent`, permission.parent.parent` ... 直到 [Permission.parent] 为它自己.
|
||||
* 递归获取 [Permission.parent], `permission.parent.parent`, permission.parent.parent.parent` ... 直到 [Permission.parent] 为它自己.
|
||||
*/
|
||||
@get:JvmStatic
|
||||
public val Permission.parentsWithSelf: Sequence<Permission>
|
||||
@ -82,5 +84,5 @@ public interface Permission {
|
||||
* 根权限. 是所有权限的父权限. 权限 ID 为 "*:*"
|
||||
*/
|
||||
@get:JvmSynthetic
|
||||
public val RootPermission: Permission
|
||||
public inline val RootPermission: Permission // It might be removed in the future, so make it inline to avoid ABI changes.
|
||||
get() = PermissionService.INSTANCE.rootPermission
|
@ -35,20 +35,20 @@ public data class PermissionId(
|
||||
"' ' is not allowed in namespace"
|
||||
}
|
||||
require(name.none { it.isWhitespace() }) {
|
||||
"' ' is not allowed in id"
|
||||
"' ' is not allowed in name"
|
||||
}
|
||||
|
||||
require(!namespace.contains(':')) {
|
||||
"':' is not allowed in namespace"
|
||||
}
|
||||
require(!name.contains(':')) {
|
||||
"':' is not allowed in id"
|
||||
"':' is not allowed in name"
|
||||
}
|
||||
}
|
||||
|
||||
public object PermissionIdAsStringSerializer : KSerializer<PermissionId> by String.serializer().map(
|
||||
serializer = { it.namespace + ":" + it.name },
|
||||
deserializer = { it.split(':').let { (namespace, id) -> PermissionId(namespace, id) } }
|
||||
deserializer = ::parseFromString
|
||||
)
|
||||
|
||||
/**
|
||||
@ -76,11 +76,11 @@ public data class PermissionId(
|
||||
*/
|
||||
@JvmStatic
|
||||
@Throws(IllegalArgumentException::class)
|
||||
public fun checkPermissionIdName(@ResolveContext(PERMISSION_NAME) value: String) {
|
||||
public fun checkPermissionIdName(@ResolveContext(PERMISSION_NAME) name: String) {
|
||||
when {
|
||||
value.isBlank() -> throw IllegalArgumentException("PermissionId.name should not be blank.")
|
||||
value.any { it.isWhitespace() } -> throw IllegalArgumentException("Spaces is not yet allowed in PermissionId.name.")
|
||||
value.contains(':') -> throw IllegalArgumentException("':' is forbidden in PermissionId.name.")
|
||||
name.isBlank() -> throw IllegalArgumentException("PermissionId.name should not be blank.")
|
||||
name.any { it.isWhitespace() } -> throw IllegalArgumentException("Spaces are not yet allowed in PermissionId.name.")
|
||||
name.contains(':') -> throw IllegalArgumentException("':' is forbidden in PermissionId.name.")
|
||||
}
|
||||
}
|
||||
|
||||
@ -89,11 +89,11 @@ public data class PermissionId(
|
||||
*/
|
||||
@JvmStatic
|
||||
@Throws(IllegalArgumentException::class)
|
||||
public fun checkPermissionIdNamespace(@ResolveContext(PERMISSION_NAME) value: String) {
|
||||
public fun checkPermissionIdNamespace(@ResolveContext(PERMISSION_NAME) namespace: String) {
|
||||
when {
|
||||
value.isBlank() -> throw IllegalArgumentException("PermissionId.namespace should not be blank.")
|
||||
value.any { it.isWhitespace() } -> throw IllegalArgumentException("Spaces is not yet allowed in PermissionId.namespace.")
|
||||
value.contains(':') -> throw IllegalArgumentException("':' is forbidden in PermissionId.namespace.")
|
||||
namespace.isBlank() -> throw IllegalArgumentException("PermissionId.namespace should not be blank.")
|
||||
namespace.any { it.isWhitespace() } -> throw IllegalArgumentException("Spaces are not yet allowed in PermissionId.namespace.")
|
||||
namespace.contains(':') -> throw IllegalArgumentException("':' is forbidden in PermissionId.namespace.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -129,10 +129,10 @@ public interface PermissionService<P : Permission> {
|
||||
/** [Plugin] 尝试分配的 [PermissionId] 来源 */
|
||||
public enum class PluginPermissionIdRequestType {
|
||||
/** For [Plugin.parentPermission] */
|
||||
ROOT_PERMISSION,
|
||||
PLUGIN_ROOT_PERMISSION,
|
||||
|
||||
/** For [Plugin.permissionId] */
|
||||
PERMISSION_ID
|
||||
NORMAL
|
||||
}
|
||||
|
||||
public companion object {
|
||||
|
@ -39,7 +39,7 @@ public abstract class AbstractJvmPlugin @JvmOverloads constructor(
|
||||
public final override val loader: JvmPluginLoader get() = super<JvmPluginInternal>.loader
|
||||
|
||||
public final override fun permissionId(name: String): PermissionId =
|
||||
PermissionService.INSTANCE.allocatePermissionIdForPlugin(this, name, PermissionService.PluginPermissionIdRequestType.PERMISSION_ID)
|
||||
PermissionService.INSTANCE.allocatePermissionIdForPlugin(this, name, PermissionService.PluginPermissionIdRequestType.NORMAL)
|
||||
|
||||
/**
|
||||
* 重载 [PluginData]
|
||||
|
@ -12,7 +12,7 @@
|
||||
package net.mamoe.mirai.console.util
|
||||
|
||||
import net.mamoe.mirai.Bot
|
||||
import net.mamoe.mirai.console.internal.command.qualifiedNameOrTip
|
||||
import net.mamoe.mirai.console.internal.data.qualifiedNameOrTip
|
||||
import net.mamoe.mirai.contact.*
|
||||
|
||||
/**
|
||||
|
@ -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()
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
/*
|
||||
* 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 kotlin.contracts.contract
|
||||
|
||||
/**
|
||||
* Perform `this as? T`.
|
||||
*/
|
||||
@JvmSynthetic
|
||||
public inline fun <reified T : Any> Any?.safeCast(): T? {
|
||||
contract {
|
||||
returnsNotNull() implies (this@safeCast is T)
|
||||
}
|
||||
return this as? T
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform `this as T`.
|
||||
*/
|
||||
@JvmSynthetic
|
||||
public inline fun <reified T : Any> Any?.cast(): T {
|
||||
contract {
|
||||
returns() implies (this@cast is T)
|
||||
}
|
||||
return this as T
|
||||
}
|
@ -89,7 +89,7 @@ internal object Testing {
|
||||
internal var cont: Continuation<Any?>? = null
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
suspend fun <R> withTesting(timeout: Long = 5000L, block: suspend () -> Unit): R {
|
||||
suspend fun <R> withTesting(timeout: Long = 50000L, block: suspend () -> Unit): R {
|
||||
@Suppress("RemoveExplicitTypeArguments") // bug
|
||||
return if (timeout != -1L) {
|
||||
withTimeout<R>(timeout) {
|
||||
|
@ -16,14 +16,14 @@ 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.getRegisteredCommands
|
||||
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.unregister
|
||||
import net.mamoe.mirai.console.command.CommandManager.INSTANCE.registerCommand
|
||||
import net.mamoe.mirai.console.command.CommandManager.INSTANCE.unregisterAllCommands
|
||||
import net.mamoe.mirai.console.command.description.CommandArgumentParser
|
||||
import net.mamoe.mirai.console.command.description.buildCommandArgumentContext
|
||||
import net.mamoe.mirai.console.command.CommandManager.INSTANCE.unregisterCommand
|
||||
import net.mamoe.mirai.console.command.descriptor.CommandValueArgumentParser
|
||||
import net.mamoe.mirai.console.command.descriptor.ExperimentalCommandDescriptors
|
||||
import net.mamoe.mirai.console.command.descriptor.buildCommandArgumentContext
|
||||
import net.mamoe.mirai.console.initTestEnvironment
|
||||
import net.mamoe.mirai.console.internal.command.CommandManagerImpl
|
||||
import net.mamoe.mirai.console.internal.command.flattenCommandComponents
|
||||
@ -38,7 +38,12 @@ object TestCompositeCommand : CompositeCommand(
|
||||
"testComposite", "tsC"
|
||||
) {
|
||||
@SubCommand
|
||||
fun mute(seconds: Int) {
|
||||
fun mute(seconds: Int = 60) {
|
||||
Testing.ok(seconds)
|
||||
}
|
||||
|
||||
@SubCommand
|
||||
fun mute(target: Long, seconds: Int) {
|
||||
Testing.ok(seconds)
|
||||
}
|
||||
}
|
||||
@ -54,6 +59,7 @@ internal val sender by lazy { ConsoleCommandSender }
|
||||
internal val owner by lazy { ConsoleCommandOwner }
|
||||
|
||||
|
||||
@OptIn(ExperimentalCommandDescriptors::class)
|
||||
internal class TestCommand {
|
||||
companion object {
|
||||
@JvmStatic
|
||||
@ -72,26 +78,31 @@ internal class TestCommand {
|
||||
@Test
|
||||
fun testRegister() {
|
||||
try {
|
||||
ConsoleCommandOwner.unregisterAllCommands() // builtins
|
||||
unregisterAllCommands(ConsoleCommandOwner) // builtins
|
||||
unregisterCommand(TestSimpleCommand)
|
||||
|
||||
assertTrue(TestCompositeCommand.register())
|
||||
assertFalse(TestCompositeCommand.register())
|
||||
|
||||
assertEquals(1, ConsoleCommandOwner.registeredCommands.size)
|
||||
assertEquals(1, getRegisteredCommands(ConsoleCommandOwner).size)
|
||||
|
||||
assertEquals(1, CommandManagerImpl._registeredCommands.size)
|
||||
assertEquals(2, CommandManagerImpl.requiredPrefixCommandMap.size)
|
||||
assertEquals(2,
|
||||
CommandManagerImpl.requiredPrefixCommandMap.size,
|
||||
CommandManagerImpl.requiredPrefixCommandMap.entries.joinToString { it.toString() })
|
||||
} finally {
|
||||
TestCompositeCommand.unregister()
|
||||
unregisterCommand(TestCompositeCommand)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSimpleExecute() = runBlocking {
|
||||
TestSimpleCommand.withRegistration {
|
||||
assertEquals("test", withTesting<MessageChain> {
|
||||
assertSuccess(TestSimpleCommand.execute(sender, "test"))
|
||||
}.contentToString())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test flattenCommandArgs`() {
|
||||
@ -105,15 +116,18 @@ internal class TestCommand {
|
||||
|
||||
@Test
|
||||
fun testSimpleArgsSplitting() = runBlocking {
|
||||
TestSimpleCommand.withRegistration {
|
||||
assertEquals(arrayOf("test", "ttt", "tt").joinToString(), withTesting<MessageChain> {
|
||||
assertSuccess(TestSimpleCommand.execute(sender, PlainText("test ttt tt")))
|
||||
}.joinToString())
|
||||
}
|
||||
}
|
||||
|
||||
val image = Image("/f8f1ab55-bf8e-4236-b55e-955848d7069f")
|
||||
|
||||
@Test
|
||||
fun `PlainText and Image args splitting`() = runBlocking {
|
||||
TestSimpleCommand.withRegistration {
|
||||
val result = withTesting<MessageChain> {
|
||||
assertSuccess(TestSimpleCommand.execute(sender, buildMessageChain {
|
||||
+"test"
|
||||
@ -124,6 +138,7 @@ internal class TestCommand {
|
||||
assertEquals<Any>(arrayOf("test", image, "tt").joinToString(), result.toTypedArray().joinToString())
|
||||
assertSame(image, result[1])
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test throw Exception`() {
|
||||
@ -134,20 +149,30 @@ internal class TestCommand {
|
||||
|
||||
@Test
|
||||
fun `executing command by string command`() = runBlocking {
|
||||
TestCompositeCommand.register()
|
||||
TestCompositeCommand.withRegistration {
|
||||
val result = withTesting<Int> {
|
||||
assertSuccess(sender.executeCommand("/testComposite mute 1"))
|
||||
}
|
||||
|
||||
assertEquals(1, result)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `composite command descriptors`() {
|
||||
val overloads = TestCompositeCommand.overloads
|
||||
assertEquals("CommandSignatureVariant(<mute>, seconds: Int = ...)", overloads[0].toString())
|
||||
assertEquals("CommandSignatureVariant(<mute>, target: Long, seconds: Int)", overloads[1].toString())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `composite command executing`() = runBlocking {
|
||||
TestCompositeCommand.withRegistration {
|
||||
assertEquals(1, withTesting {
|
||||
assertSuccess(TestCompositeCommand.execute(sender, "mute 1"))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `composite sub command resolution conflict`() {
|
||||
@ -164,19 +189,19 @@ internal class TestCommand {
|
||||
|
||||
@Suppress("UNUSED_PARAMETER")
|
||||
@SubCommand
|
||||
fun mute(seconds: Int, arg2: Int) {
|
||||
fun mute(seconds: Int, arg2: Int = 1) {
|
||||
Testing.ok(2)
|
||||
}
|
||||
}
|
||||
|
||||
assertFailsWith<IllegalStateException> {
|
||||
composite.register()
|
||||
}
|
||||
/*
|
||||
registerCommand(composite)
|
||||
|
||||
println(composite.overloads.joinToString())
|
||||
|
||||
composite.withRegistration {
|
||||
assertEquals(1, withTesting { execute(sender, "tr", "mute 123") }) // one args, resolves to mute(Int)
|
||||
assertEquals(2, withTesting { execute(sender, "tr", "mute 123 123") })
|
||||
}*/
|
||||
assertEquals(1, withTesting { assertSuccess(composite.execute(sender, "mute 123")) }) // one arg, resolves to mute(Int)
|
||||
assertEquals(2, withTesting { assertSuccess(composite.execute(sender, "mute 123 1")) }) // two arg, resolved to mute(Int, Int)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -184,19 +209,20 @@ internal class TestCommand {
|
||||
fun `composite sub command parsing`() {
|
||||
runBlocking {
|
||||
class MyClass(
|
||||
val value: Int
|
||||
val value: Int,
|
||||
)
|
||||
|
||||
val composite = object : CompositeCommand(
|
||||
ConsoleCommandOwner,
|
||||
"test22",
|
||||
overrideContext = buildCommandArgumentContext {
|
||||
add(object : CommandArgumentParser<MyClass> {
|
||||
add(object : CommandValueArgumentParser<MyClass> {
|
||||
override fun parse(raw: String, sender: CommandSender): MyClass {
|
||||
return MyClass(raw.toInt())
|
||||
}
|
||||
|
||||
override fun parse(raw: MessageContent, sender: CommandSender): MyClass {
|
||||
if (raw is PlainText) return parse(raw.content, sender)
|
||||
assertSame(image, raw)
|
||||
return MyClass(2)
|
||||
}
|
||||
@ -210,12 +236,14 @@ internal class TestCommand {
|
||||
}
|
||||
|
||||
composite.withRegistration {
|
||||
assertEquals(333, withTesting<MyClass> { execute(sender, "mute 333") }.value)
|
||||
assertEquals(333, withTesting<MyClass> { assertSuccess(execute(sender, "mute 333")) }.value)
|
||||
assertEquals(2, withTesting<MyClass> {
|
||||
assertSuccess(
|
||||
execute(sender, buildMessageChain {
|
||||
+"mute"
|
||||
+image
|
||||
})
|
||||
)
|
||||
}.value)
|
||||
}
|
||||
}
|
||||
@ -238,8 +266,76 @@ internal class TestCommand {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test optional argument command`() {
|
||||
runBlocking {
|
||||
val optionCommand = object : CompositeCommand(
|
||||
ConsoleCommandOwner,
|
||||
"testOptional"
|
||||
) {
|
||||
@SubCommand
|
||||
fun optional(arg1: String, arg2: String = "Here is optional", arg3: String? = null) {
|
||||
println(arg1)
|
||||
println(arg2)
|
||||
println(arg3)
|
||||
// println(arg3)
|
||||
Testing.ok(Unit)
|
||||
}
|
||||
}
|
||||
optionCommand.withRegistration {
|
||||
withTesting<Unit> {
|
||||
assertSuccess(sender.executeCommand("/testOptional optional 1"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test vararg`() {
|
||||
runBlocking {
|
||||
val optionCommand = object : CompositeCommand(
|
||||
ConsoleCommandOwner,
|
||||
"test"
|
||||
) {
|
||||
@SubCommand
|
||||
fun vararg(arg1: Int, vararg x: String) {
|
||||
assertEquals(1, arg1)
|
||||
Testing.ok(x)
|
||||
}
|
||||
}
|
||||
optionCommand.withRegistration {
|
||||
assertArrayEquals(
|
||||
emptyArray<String>(),
|
||||
withTesting {
|
||||
assertSuccess(sender.executeCommand("/test vararg 1"))
|
||||
}
|
||||
)
|
||||
|
||||
assertArrayEquals(
|
||||
arrayOf("s"),
|
||||
withTesting<Array<String>> {
|
||||
assertSuccess(sender.executeCommand("/test vararg 1 s"))
|
||||
}
|
||||
)
|
||||
assertArrayEquals(
|
||||
arrayOf("s", "s", "s"),
|
||||
withTesting {
|
||||
assertSuccess(sender.executeCommand("/test vararg 1 s s s"))
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal fun assertSuccess(result: CommandExecuteResult) {
|
||||
assertTrue(result.isSuccess(), result.toString())
|
||||
fun <T> assertArrayEquals(expected: Array<out T>, actual: Array<out T>, message: String? = null) {
|
||||
asserter.assertEquals(message, expected.contentToString(), actual.contentToString())
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalCommandDescriptors::class)
|
||||
internal fun assertSuccess(result: CommandExecuteResult) {
|
||||
if (result.isFailure()) {
|
||||
throw result.exception ?: AssertionError(result.toString())
|
||||
}
|
||||
}
|
@ -10,13 +10,13 @@
|
||||
package net.mamoe.mirai.console.command
|
||||
|
||||
import net.mamoe.mirai.console.command.CommandManager.INSTANCE.register
|
||||
import net.mamoe.mirai.console.command.CommandManager.INSTANCE.unregister
|
||||
import net.mamoe.mirai.console.command.CommandManager.INSTANCE.unregisterCommand
|
||||
|
||||
inline fun <T : Command, R> T.withRegistration(block: T.() -> R): R {
|
||||
this.register()
|
||||
try {
|
||||
return block()
|
||||
} finally {
|
||||
this.unregister()
|
||||
unregisterCommand(this)
|
||||
}
|
||||
}
|
@ -37,9 +37,9 @@
|
||||
[`RawCommand`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/RawCommand.kt
|
||||
[`CommandManager`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandManager.kt
|
||||
[`CommandSender`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandSender.kt
|
||||
[`CommandArgumentParser`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/description/CommandArgumentParser.kt
|
||||
[`CommandArgumentContext`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/description/CommandArgumentContext.kt
|
||||
[`CommandArgumentContext.BuiltIns`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/description/CommandArgumentContext.kt#L66
|
||||
[`CommandArgumentParser`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/descriptor/CommandArgumentParser.kt
|
||||
[`CommandArgumentContext`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/descriptor/CommandArgumentContext.kt
|
||||
[`CommandArgumentContext.BuiltIns`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/descriptor/CommandArgumentContext.kt#L66
|
||||
|
||||
[`MessageScope`]: ../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/util/MessageScope.kt
|
||||
|
||||
@ -113,7 +113,7 @@ interface CommandArgumentParser<out T : Any> {
|
||||
支持原生数据类型,`Contact` 及其子类,`Bot`。
|
||||
|
||||
#### 构建 [`CommandArgumentContext`]
|
||||
查看源码内注释:[CommandArgumentContext.kt: Line 146](../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/description/CommandArgumentContext.kt#L146-L183)
|
||||
查看源码内注释:[CommandArgumentContext.kt: Line 146](../backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/descriptor/CommandArgumentContext.kt#L146-L183)
|
||||
|
||||
### 支持参数解析的 [`Command`] 实现
|
||||
Mirai Console 内建 [`SimpleCommand`] 与 [`CompositeCommand`] 拥有 [`CommandArgumentContext`],在处理参数时会首先解析参数再传递给插件的实现。
|
||||
|
@ -15,11 +15,8 @@ import kotlinx.coroutines.CoroutineName
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import net.mamoe.mirai.console.MiraiConsole
|
||||
import net.mamoe.mirai.console.command.BuiltInCommands
|
||||
import net.mamoe.mirai.console.command.CommandExecuteStatus
|
||||
import net.mamoe.mirai.console.command.CommandManager
|
||||
import net.mamoe.mirai.console.command.CommandManager.INSTANCE.executeCommand
|
||||
import net.mamoe.mirai.console.command.ConsoleCommandSender
|
||||
import net.mamoe.mirai.console.command.*
|
||||
import net.mamoe.mirai.console.command.descriptor.ExperimentalCommandDescriptors
|
||||
import net.mamoe.mirai.console.terminal.noconsole.NoConsole
|
||||
import net.mamoe.mirai.console.util.ConsoleInternalApi
|
||||
import net.mamoe.mirai.console.util.requestInput
|
||||
@ -29,7 +26,7 @@ import org.jline.reader.UserInterruptException
|
||||
|
||||
val consoleLogger by lazy { DefaultLogger("console") }
|
||||
|
||||
@OptIn(ConsoleInternalApi::class, ConsoleTerminalExperimentalApi::class)
|
||||
@OptIn(ConsoleInternalApi::class, ConsoleTerminalExperimentalApi::class, ExperimentalCommandDescriptors::class)
|
||||
internal fun startupConsoleThread() {
|
||||
if (terminal is NoConsole) return
|
||||
|
||||
@ -65,6 +62,9 @@ internal fun startupConsoleThread() {
|
||||
when (result.status) {
|
||||
CommandExecuteStatus.SUCCESSFUL -> {
|
||||
}
|
||||
CommandExecuteStatus.ILLEGAL_ARGUMENT -> {
|
||||
result.exception?.message?.let { consoleLogger.warning(it) }
|
||||
}
|
||||
CommandExecuteStatus.EXECUTION_EXCEPTION -> {
|
||||
result.exception?.let(consoleLogger::error)
|
||||
}
|
||||
|
@ -1,2 +1,3 @@
|
||||
# style guide
|
||||
kotlin.code.style=official
|
||||
org.gradle.vfs.watch=true
|
||||
|
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@ -1,5 +1,5 @@
|
||||
#Wed Mar 04 22:27:09 CST 2020
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.6.1-all.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-all.zip
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStorePath=wrapper/dists
|
||||
|
Loading…
Reference in New Issue
Block a user