mirror of
https://github.com/mamoe/mirai.git
synced 2025-01-10 18:40:15 +08:00
Merge remote-tracking branch 'origin/master' into logger
This commit is contained in:
commit
b60ce7d856
@ -31,6 +31,7 @@ import java.util.*
|
||||
import java.util.concurrent.locks.ReentrantLock
|
||||
import kotlin.annotation.AnnotationTarget.*
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
import kotlin.system.exitProcess
|
||||
|
||||
|
||||
/**
|
||||
@ -176,7 +177,7 @@ public interface MiraiConsoleImplementation : CoroutineScope {
|
||||
/**
|
||||
* 可由前端调用, 获取当前的 [MiraiConsoleImplementation] 实例
|
||||
*
|
||||
* 必须在 [start] 之后才能使用.
|
||||
* 必须在 [start] 之后才能使用, 否则抛出 [UninitializedPropertyAccessException]
|
||||
*/
|
||||
@JvmStatic
|
||||
@ConsoleFrontEndImplementation
|
||||
@ -189,7 +190,22 @@ public interface MiraiConsoleImplementation : CoroutineScope {
|
||||
public fun MiraiConsoleImplementation.start(): Unit = initLock.withLock {
|
||||
if (::instance.isInitialized) error("Mirai Console is already initialized.")
|
||||
this@Companion.instance = this
|
||||
kotlin.runCatching {
|
||||
MiraiConsoleImplementationBridge.doStart()
|
||||
}.onFailure { e ->
|
||||
kotlin.runCatching {
|
||||
MiraiConsole.mainLogger.error("Failed to init MiraiConsole.", e)
|
||||
}.onFailure {
|
||||
e.printStackTrace()
|
||||
}
|
||||
|
||||
kotlin.runCatching {
|
||||
MiraiConsole.cancel()
|
||||
}.onFailure {
|
||||
it.printStackTrace()
|
||||
}
|
||||
exitProcess(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -15,16 +15,20 @@ 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.CommandArgumentParserException
|
||||
import net.mamoe.mirai.console.command.descriptor.CommandValueArgumentParser.Companion.map
|
||||
import net.mamoe.mirai.console.command.descriptor.PermissionIdValueArgumentParser
|
||||
import net.mamoe.mirai.console.command.descriptor.PermitteeIdValueArgumentParser
|
||||
import net.mamoe.mirai.console.command.descriptor.buildCommandArgumentContext
|
||||
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
|
||||
import net.mamoe.mirai.console.permission.Permission
|
||||
import net.mamoe.mirai.console.permission.PermissionService
|
||||
import net.mamoe.mirai.console.permission.PermissionService.Companion.denyPermission
|
||||
import net.mamoe.mirai.console.permission.PermissionService.Companion.cancel
|
||||
import net.mamoe.mirai.console.permission.PermissionService.Companion.findCorrespondingPermissionOrFail
|
||||
import net.mamoe.mirai.console.permission.PermissionService.Companion.getPermittedPermissions
|
||||
import net.mamoe.mirai.console.permission.PermissionService.Companion.grantPermission
|
||||
import net.mamoe.mirai.console.permission.PermissionService.Companion.permit
|
||||
import net.mamoe.mirai.console.permission.PermitteeId
|
||||
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
|
||||
import net.mamoe.mirai.console.util.ConsoleInternalApi
|
||||
@ -150,11 +154,11 @@ 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) }
|
||||
}.getOrElse { throw CommandArgumentParserException("指令不存在: $id", it) }
|
||||
}
|
||||
},
|
||||
), BuiltInCommandInternal {
|
||||
@ -166,7 +170,7 @@ public object BuiltInCommands {
|
||||
@Name("被许可人 ID") target: PermitteeId,
|
||||
@Name("权限 ID") permission: Permission,
|
||||
) {
|
||||
target.grantPermission(permission)
|
||||
target.permit(permission)
|
||||
sendMessage("OK")
|
||||
}
|
||||
|
||||
@ -176,7 +180,7 @@ public object BuiltInCommands {
|
||||
@Name("被许可人 ID") target: PermitteeId,
|
||||
@Name("权限 ID") permission: Permission,
|
||||
) {
|
||||
target.denyPermission(permission, false)
|
||||
target.cancel(permission, false)
|
||||
sendMessage("OK")
|
||||
}
|
||||
|
||||
@ -186,7 +190,7 @@ public object BuiltInCommands {
|
||||
@Name("被许可人 ID") target: PermitteeId,
|
||||
@Name("权限 ID") permission: Permission,
|
||||
) {
|
||||
target.denyPermission(permission, true)
|
||||
target.cancel(permission, true)
|
||||
sendMessage("OK")
|
||||
}
|
||||
|
||||
|
@ -11,26 +11,25 @@
|
||||
|
||||
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.java.JCommand
|
||||
import net.mamoe.mirai.console.command.descriptor.CommandArgumentContextAware
|
||||
import net.mamoe.mirai.console.command.descriptor.CommandSignature
|
||||
import net.mamoe.mirai.console.command.descriptor.ExperimentalCommandDescriptors
|
||||
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 JCommand 为 Java 用户添加协程帮助的 [Command]
|
||||
* @see CommandArgumentContextAware
|
||||
*/
|
||||
public interface Command {
|
||||
/**
|
||||
@ -48,18 +47,25 @@ public interface Command {
|
||||
@ResolveContext(COMMAND_NAME)
|
||||
public val secondaryNames: Array<out String>
|
||||
|
||||
/**
|
||||
* 指令可能的参数列表.
|
||||
*/
|
||||
@ConsoleExperimentalApi("Property name is experimental")
|
||||
@ExperimentalCommandDescriptors
|
||||
public val overloads: List<CommandSignature>
|
||||
|
||||
/**
|
||||
* 用法说明, 用于发送给用户. [usage] 一般包含 [description].
|
||||
*/
|
||||
public val usage: String
|
||||
|
||||
/**
|
||||
* 指令描述, 用于显示在 [BuiltInCommands.HelpCommand]
|
||||
* 描述, 用于显示在 [BuiltInCommands.HelpCommand]
|
||||
*/
|
||||
public val description: String
|
||||
|
||||
/**
|
||||
* 此指令所分配的权限.
|
||||
* 为此指令分配的权限.
|
||||
*
|
||||
* ### 实现约束
|
||||
* - [Permission.id] 应由 [CommandOwner.permissionId] 创建. 因此保证相同的 [PermissionId.namespace]
|
||||
@ -72,6 +78,8 @@ public interface Command {
|
||||
*
|
||||
* 会影响聊天语境中的解析.
|
||||
*/
|
||||
@ExperimentalCommandDescriptors
|
||||
@ConsoleExperimentalApi
|
||||
public val prefixOptional: Boolean
|
||||
|
||||
/**
|
||||
@ -80,16 +88,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 +107,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,28 @@ 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<CommandSignatureFromKFunction> by lazy {
|
||||
reflector.findSubCommands().also {
|
||||
reflector.validate(it)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 自动根据带有 [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 +134,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
|
||||
}
|
||||
|
||||
|
||||
|
@ -5,21 +5,23 @@
|
||||
* 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.description
|
||||
package net.mamoe.mirai.console.command
|
||||
|
||||
import net.mamoe.mirai.console.command.descriptor.CommandArgumentParserException
|
||||
|
||||
/**
|
||||
* 在解析参数时遇到的 _正常_ 错误. 如参数不符合规范等.
|
||||
* 在处理参数时遇到的 _正常_ 错误. 如参数不符合规范, 参数值越界等.
|
||||
*
|
||||
* [message] 将会发送给指令调用方.
|
||||
*
|
||||
* @see CommandArgumentParser
|
||||
* @see CommandArgumentParser.illegalArgument
|
||||
* @see CommandArgumentParserException
|
||||
*/
|
||||
public class CommandArgumentParserException : RuntimeException {
|
||||
public open class IllegalCommandArgumentException : IllegalArgumentException {
|
||||
public constructor() : super()
|
||||
public constructor(message: String?) : super(message)
|
||||
public constructor(message: String?, cause: Throwable?) : super(message, cause)
|
@ -11,14 +11,16 @@
|
||||
|
||||
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
|
||||
|
||||
/**
|
||||
* 无参数解析, 接收原生参数的指令.
|
||||
@ -48,10 +50,23 @@ public abstract class RawCommand(
|
||||
/** 指令父权限 */
|
||||
parentPermission: Permission = owner.parentPermission,
|
||||
/** 为 `true` 时表示 [指令前缀][CommandManager.commandPrefix] 可选 */
|
||||
@OptIn(ExperimentalCommandDescriptors::class)
|
||||
public override val prefixOptional: Boolean = false,
|
||||
) : Command {
|
||||
public override val permission: Permission by lazy { createOrFindCommandPermission(parentPermission) }
|
||||
|
||||
@ExperimentalCommandDescriptors
|
||||
override val overloads: List<CommandSignature> = listOf(
|
||||
CommandSignatureImpl(
|
||||
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)
|
||||
}
|
||||
|
||||
|
||||
|
@ -17,21 +17,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 +60,42 @@ 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<CommandSignatureFromKFunction> by lazy {
|
||||
reflector.findSubCommands().also {
|
||||
reflector.validate(it)
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -1,145 +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", "unused")
|
||||
|
||||
package net.mamoe.mirai.console.command.description
|
||||
|
||||
import net.mamoe.mirai.Bot
|
||||
import net.mamoe.mirai.console.command.CommandManager
|
||||
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.contact.*
|
||||
import net.mamoe.mirai.message.data.MessageContent
|
||||
import net.mamoe.mirai.message.data.SingleMessage
|
||||
import net.mamoe.mirai.message.data.content
|
||||
import kotlin.contracts.InvocationKind
|
||||
import kotlin.contracts.contract
|
||||
|
||||
/**
|
||||
* 指令参数解析器. 用于解析字符串或 [SingleMessage] 到特定参数类型.
|
||||
*
|
||||
* ### 参数解析
|
||||
*
|
||||
* 如 [SimpleCommand] 中的示例:
|
||||
* ```
|
||||
* suspend fun CommandSender.mute(target: Member, duration: Int)
|
||||
* ```
|
||||
* [CommandManager] 总是从 [SimpleCommand.context] 搜索一个 [T] 为 [Member] 的 [CommandArgumentParser], 并调用其 [CommandArgumentParser.parse]
|
||||
*
|
||||
* ### 内建指令解析器
|
||||
* - 基础类型: [ByteArgumentParser], [ShortArgumentParser], [IntArgumentParser], [LongArgumentParser]
|
||||
* [FloatArgumentParser], [DoubleArgumentParser],
|
||||
* [BooleanArgumentParser], [StringArgumentParser]
|
||||
*
|
||||
* - [Bot]: [ExistingBotArgumentParser]
|
||||
* - [Friend]: [ExistingFriendArgumentParser]
|
||||
* - [Group]: [ExistingGroupArgumentParser]
|
||||
* - [Member]: [ExistingMemberArgumentParser]
|
||||
* - [User]: [ExistingUserArgumentParser]
|
||||
* - [Contact]: [ExistingContactArgumentParser]
|
||||
*
|
||||
*
|
||||
* @see SimpleCommand 简单指令
|
||||
* @see CompositeCommand 复合指令
|
||||
*
|
||||
* @see buildCommandArgumentContext 指令参数环境, 即 [CommandArgumentParser] 的集合
|
||||
*/
|
||||
public interface CommandArgumentParser<out T : Any> {
|
||||
/**
|
||||
* 解析一个字符串为 [T] 类型参数
|
||||
*
|
||||
* **实现提示**: 在解析时遇到意料之中的问题, 如无法找到目标群员, 可抛出 [CommandArgumentParserException].
|
||||
* 此异常将会被特殊处理, 不会引发一个错误, 而是作为指令调用成功的情况, 将错误信息发送给用户.
|
||||
*
|
||||
* @throws CommandArgumentParserException 当解析时遇到*意料之中*的问题时抛出.
|
||||
*
|
||||
* @see CommandArgumentParserException
|
||||
*/
|
||||
@Throws(CommandArgumentParserException::class)
|
||||
public fun parse(raw: String, sender: CommandSender): T
|
||||
|
||||
/**
|
||||
* 解析一个消息内容元素为 [T] 类型参数
|
||||
*
|
||||
* **实现提示**: 在解析时遇到意料之中的问题, 如无法找到目标群员, 可抛出 [CommandArgumentParserException].
|
||||
* 此异常将会被特殊处理, 不会引发一个错误, 而是作为指令调用成功的情况, 将错误信息发送给用户.
|
||||
*
|
||||
* @throws CommandArgumentParserException 当解析时遇到*意料之中*的问题时抛出.
|
||||
*
|
||||
* @see CommandArgumentParserException
|
||||
*/
|
||||
@Throws(CommandArgumentParserException::class)
|
||||
public fun parse(raw: MessageContent, sender: CommandSender): T = parse(raw.content, sender)
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用原 [this] 解析, 成功后使用 [mapper] 映射为另一个类型.
|
||||
*/
|
||||
public fun <T : Any, R : Any> CommandArgumentParser<T>.map(
|
||||
mapper: CommandArgumentParser<R>.(T) -> R
|
||||
): CommandArgumentParser<R> = MappingCommandArgumentParser(this, mapper)
|
||||
|
||||
private class MappingCommandArgumentParser<T : Any, R : Any>(
|
||||
private val original: CommandArgumentParser<T>,
|
||||
private val mapper: CommandArgumentParser<R>.(T) -> R
|
||||
) : CommandArgumentParser<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))
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析一个字符串或 [SingleMessage] 为 [T] 类型参数
|
||||
*
|
||||
* @throws IllegalArgumentException 当 [raw] 既不是 [SingleMessage], 也不是 [String] 时抛出.
|
||||
*/
|
||||
@JvmSynthetic
|
||||
@Throws(IllegalArgumentException::class)
|
||||
public fun <T : Any> CommandArgumentParser<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)
|
||||
else -> throw IllegalArgumentException("Illegal raw argument type: ${raw::class.qualifiedName}")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 抛出一个 [CommandArgumentParserException] 的捷径
|
||||
*
|
||||
* @throws CommandArgumentParserException
|
||||
*/
|
||||
@Suppress("unused")
|
||||
@JvmSynthetic
|
||||
@Throws(CommandArgumentParserException::class)
|
||||
public inline fun CommandArgumentParser<*>.illegalArgument(message: String, cause: Throwable? = null): Nothing {
|
||||
throw CommandArgumentParserException(message, cause)
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查参数 [condition]. 当它为 `false` 时调用 [message] 并以其返回值作为消息, 抛出异常 [CommandArgumentParserException]
|
||||
*
|
||||
* @throws CommandArgumentParserException
|
||||
*/
|
||||
@Throws(CommandArgumentParserException::class)
|
||||
@JvmSynthetic
|
||||
public inline fun CommandArgumentParser<*>.checkArgument(
|
||||
condition: Boolean,
|
||||
crossinline message: () -> String = { "Check failed." }
|
||||
) {
|
||||
contract {
|
||||
returns() implies condition
|
||||
callsInPlace(message, InvocationKind.AT_MOST_ONCE)
|
||||
}
|
||||
if (!condition) illegalArgument(message())
|
||||
}
|
@ -9,26 +9,29 @@
|
||||
|
||||
@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.contracts.InvocationKind.EXACTLY_ONCE
|
||||
import kotlin.contracts.contract
|
||||
import kotlin.internal.LowPriorityInOverloadResolution
|
||||
import kotlin.reflect.KClass
|
||||
import kotlin.reflect.full.isSubclassOf
|
||||
|
||||
|
||||
/**
|
||||
* 指令参数环境, 即 [CommandArgumentParser] 的集合, 用于 [CompositeCommand] 和 [SimpleCommand].
|
||||
* 指令参数环境, 即 [CommandValueArgumentParser] 的集合, 用于 [CompositeCommand] 和 [SimpleCommand].
|
||||
*
|
||||
* 在指令解析时, 总是从 [CommandArgumentContextAware.context] 搜索相关解析器
|
||||
*
|
||||
@ -37,20 +40,28 @@ 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 companion object {
|
||||
@JvmStatic
|
||||
public fun <T : Any> ParserPair<T>.toPair(): Pair<KClass<T>, CommandValueArgumentParser<T>> = klass to parser
|
||||
}
|
||||
}
|
||||
|
||||
public operator fun <T : Any> get(klass: KClass<out T>): CommandArgumentParser<T>?
|
||||
/**
|
||||
* 获取一个 [kClass] 类型的解析器.
|
||||
*/
|
||||
public operator fun <T : Any> get(kClass: KClass<T>): CommandValueArgumentParser<T>?
|
||||
|
||||
public fun toList(): List<ParserPair<*>>
|
||||
|
||||
@ -58,37 +69,39 @@ public interface CommandArgumentContext {
|
||||
/**
|
||||
* For Java callers.
|
||||
*
|
||||
* @see [EmptyCommandArgumentContext]
|
||||
* @see EmptyCommandArgumentContext
|
||||
*/
|
||||
@JvmStatic
|
||||
public val EMPTY: CommandArgumentContext = EmptyCommandArgumentContext
|
||||
}
|
||||
|
||||
/**
|
||||
* 内建的默认 [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,11 +113,14 @@ public interface CommandArgumentContext {
|
||||
*/
|
||||
public interface CommandArgumentContextAware {
|
||||
/**
|
||||
* [CommandArgumentParser] 的集合
|
||||
* [CommandValueArgumentParser] 的集合
|
||||
*/
|
||||
public val context: CommandArgumentContext
|
||||
}
|
||||
|
||||
/**
|
||||
* @see CommandArgumentContext.EMPTY
|
||||
*/
|
||||
public object EmptyCommandArgumentContext : CommandArgumentContext by SimpleCommandArgumentContext(listOf())
|
||||
|
||||
/**
|
||||
@ -114,8 +130,8 @@ 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>? =
|
||||
replacer[klass] ?: this@plus[klass]
|
||||
override fun <T : Any> get(kClass: KClass<T>): CommandValueArgumentParser<T>? =
|
||||
replacer[kClass] ?: this@plus[kClass]
|
||||
|
||||
override fun toList(): List<ParserPair<*>> = replacer.toList() + this@plus.toList()
|
||||
}
|
||||
@ -129,9 +145,9 @@ 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>?
|
||||
?: this@plus[klass]
|
||||
override fun <T : Any> get(kClass: KClass<T>): CommandValueArgumentParser<T>? =
|
||||
replacer.firstOrNull { kClass.isSubclassOf(it.klass) }?.parser as CommandValueArgumentParser<T>?
|
||||
?: this@plus[kClass]
|
||||
|
||||
override fun toList(): List<ParserPair<*>> = replacer.toList() + this@plus.toList()
|
||||
}
|
||||
@ -146,9 +162,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>? =
|
||||
(this.list.firstOrNull { klass == it.klass }?.parser
|
||||
?: this.list.firstOrNull { klass.isSubclassOf(it.klass) }?.parser) as CommandArgumentParser<T>?
|
||||
override fun <T : Any> get(kClass: KClass<T>): CommandValueArgumentParser<T>? =
|
||||
(this.list.firstOrNull { kClass == it.klass }?.parser
|
||||
?: this.list.firstOrNull { kClass.isSubclassOf(it.klass) }?.parser) as CommandValueArgumentParser<T>?
|
||||
|
||||
override fun toList(): List<ParserPair<*>> = list
|
||||
}
|
||||
@ -160,7 +176,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())
|
||||
* }
|
||||
@ -189,6 +205,9 @@ public class SimpleCommandArgumentContext(
|
||||
*/
|
||||
@JvmSynthetic
|
||||
public fun buildCommandArgumentContext(block: CommandArgumentContextBuilder.() -> Unit): CommandArgumentContext {
|
||||
contract {
|
||||
callsInPlace(block, EXACTLY_ONCE)
|
||||
}
|
||||
return CommandArgumentContextBuilder().apply(block).build()
|
||||
}
|
||||
|
||||
@ -200,14 +219,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 +237,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 +250,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 +270,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 +282,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,9 @@
|
||||
* https://github.com/mamoe/mirai/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
package net.mamoe.mirai.console.command.description
|
||||
@file:Suppress("EXPOSED_SUPER_CLASS")
|
||||
|
||||
package net.mamoe.mirai.console.command.descriptor
|
||||
|
||||
import net.mamoe.mirai.Bot
|
||||
import net.mamoe.mirai.console.command.*
|
||||
@ -25,47 +27,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 +75,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 +97,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 +111,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 +123,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 +138,7 @@ public object ExistingBotArgumentParser : InternalCommandArgumentParserExtension
|
||||
/**
|
||||
* 解析任意一个存在的好友.
|
||||
*/
|
||||
public object ExistingFriendArgumentParser : InternalCommandArgumentParserExtensions<Friend> {
|
||||
public object ExistingFriendValueArgumentParser : InternalCommandValueArgumentParserExtensions<Friend>() {
|
||||
private val syntax = """
|
||||
- `botId.friendId`
|
||||
- `botId.friendNick` (模糊搜索, 寻找最优匹配)
|
||||
@ -175,7 +177,7 @@ public object ExistingFriendArgumentParser : InternalCommandArgumentParserExtens
|
||||
/**
|
||||
* 解析任意一个存在的群.
|
||||
*/
|
||||
public object ExistingGroupArgumentParser : InternalCommandArgumentParserExtensions<Group> {
|
||||
public object ExistingGroupValueArgumentParser : InternalCommandValueArgumentParserExtensions<Group>() {
|
||||
private val syntax = """
|
||||
- `botId.groupId`
|
||||
- `~` (指代指令调用人自己所在群. 仅群聊天环境下)
|
||||
@ -202,7 +204,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 +217,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 +248,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 +261,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 +288,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 +335,7 @@ public object ExistingMemberArgumentParser : InternalCommandArgumentParserExtens
|
||||
}
|
||||
}
|
||||
|
||||
public object PermissionIdArgumentParser : CommandArgumentParser<PermissionId> {
|
||||
public object PermissionIdValueArgumentParser : InternalCommandValueArgumentParserExtensions<PermissionId>() {
|
||||
override fun parse(raw: String, sender: CommandSender): PermissionId {
|
||||
return kotlin.runCatching { PermissionId.parseFromString(raw) }.getOrElse {
|
||||
illegalArgument("无法解析 $raw 为 PermissionId")
|
||||
@ -341,7 +343,7 @@ public object PermissionIdArgumentParser : CommandArgumentParser<PermissionId> {
|
||||
}
|
||||
}
|
||||
|
||||
public object PermitteeIdArgumentParser : CommandArgumentParser<PermitteeId> {
|
||||
public object PermitteeIdValueArgumentParser : InternalCommandValueArgumentParserExtensions<PermitteeId>() {
|
||||
override fun parse(raw: String, sender: CommandSender): PermitteeId {
|
||||
return if (raw == "~") sender.permitteeId
|
||||
else kotlin.runCatching { AbstractPermitteeId.parseFromString(raw) }.getOrElse {
|
||||
@ -351,32 +353,38 @@ 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> {
|
||||
fun String.parseToLongOrFail(): Long = toLongOrNull() ?: illegalArgument("无法解析 $this 为整数")
|
||||
/** 直接返回原始参数 [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
|
||||
}
|
||||
|
||||
fun Long.findBotOrFail(): Bot = Bot.getInstanceOrNull(this) ?: illegalArgument("无法找到 Bot: $this")
|
||||
internal abstract class InternalCommandValueArgumentParserExtensions<T : Any> : AbstractCommandValueArgumentParser<T>() {
|
||||
private fun String.parseToLongOrFail(): Long = toLongOrNull() ?: illegalArgument("无法解析 $this 为整数")
|
||||
|
||||
fun String.findBotOrFail(): Bot =
|
||||
protected fun Long.findBotOrFail(): Bot = Bot.getInstanceOrNull(this) ?: illegalArgument("无法找到 Bot: $this")
|
||||
|
||||
protected fun String.findBotOrFail(): Bot =
|
||||
Bot.getInstanceOrNull(this.parseToLongOrFail()) ?: illegalArgument("无法找到 Bot: $this")
|
||||
|
||||
fun Bot.findGroupOrFail(id: Long): Group = getGroupOrNull(id) ?: illegalArgument("无法找到群: $this")
|
||||
protected fun Bot.findGroupOrFail(id: Long): Group = getGroupOrNull(id) ?: illegalArgument("无法找到群: $this")
|
||||
|
||||
fun Bot.findGroupOrFail(id: String): Group =
|
||||
protected fun Bot.findGroupOrFail(id: String): Group =
|
||||
getGroupOrNull(id.parseToLongOrFail()) ?: illegalArgument("无法找到群: $this")
|
||||
|
||||
fun Bot.findFriendOrFail(id: String): Friend =
|
||||
protected fun Bot.findFriendOrFail(id: String): Friend =
|
||||
getFriendOrNull(id.parseToLongOrFail()) ?: illegalArgument("无法找到好友: $this")
|
||||
|
||||
fun Bot.findMemberOrFail(id: String): Friend =
|
||||
protected fun Bot.findMemberOrFail(id: String): Friend =
|
||||
getFriendOrNull(id.parseToLongOrFail()) ?: illegalArgument("无法找到群员: $this")
|
||||
|
||||
fun Group.findMemberOrFail(idOrCard: String): Member {
|
||||
protected fun Group.findMemberOrFail(idOrCard: String): Member {
|
||||
if (idOrCard == "\$") return members.randomOrNull() ?: illegalArgument("当前语境下无法推断随机群员")
|
||||
idOrCard.toLongOrNull()?.let { getOrNull(it) }?.let { return it }
|
||||
this.members.singleOrNull { it.nameCardOrNick.contains(idOrCard) }?.let { return it }
|
||||
@ -399,23 +407,21 @@ internal interface InternalCommandArgumentParserExtensions<T : Any> : CommandArg
|
||||
}
|
||||
}
|
||||
|
||||
fun CommandSender.inferBotOrFail(): Bot =
|
||||
protected fun CommandSender.inferBotOrFail(): Bot =
|
||||
(this as? UserCommandSender)?.bot
|
||||
?: Bot.botInstancesSequence.singleOrNull()
|
||||
?: illegalArgument("当前语境下无法推断目标 Bot, 因为目前有多个 Bot 在线.")
|
||||
|
||||
fun CommandSender.inferGroupOrFail(): Group =
|
||||
protected fun CommandSender.inferGroupOrFail(): Group =
|
||||
inferGroup() ?: illegalArgument("当前语境下无法推断目标群")
|
||||
|
||||
fun CommandSender.inferGroup(): Group? = (this as? GroupAwareCommandSender)?.group
|
||||
protected fun CommandSender.inferGroup(): Group? = (this as? GroupAwareCommandSender)?.group
|
||||
|
||||
fun CommandSender.inferFriendOrFail(): Friend =
|
||||
protected fun CommandSender.inferFriendOrFail(): Friend =
|
||||
(this as? FriendCommandSender)?.user ?: illegalArgument("当前语境下无法推断目标好友")
|
||||
}
|
||||
|
||||
internal fun Double.toDecimalPlace(n: Int): String {
|
||||
return "%.${n}f".format(this)
|
||||
}
|
||||
internal fun Double.toDecimalPlace(n: Int): String = "%.${n}f".format(this)
|
||||
|
||||
internal fun String.truncate(lengthLimit: Int, replacement: String = "..."): String = buildString {
|
||||
var lengthSum = 0
|
@ -0,0 +1,307 @@
|
||||
/*
|
||||
* 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 AbstractCommandSignature
|
||||
*/
|
||||
@ExperimentalCommandDescriptors
|
||||
public interface CommandSignature {
|
||||
/**
|
||||
* 接收者参数, 为 [CommandSender] 子类
|
||||
*/
|
||||
@ConsoleExperimentalApi
|
||||
public val receiverParameter: CommandReceiverParameter<out CommandSender>?
|
||||
|
||||
/**
|
||||
* 形式 值参数.
|
||||
*/
|
||||
public val valueParameters: List<AbstractCommandValueParameter<*>>
|
||||
|
||||
/**
|
||||
* 调用这个指令.
|
||||
*/
|
||||
public suspend fun call(resolvedCommandCall: ResolvedCommandCall)
|
||||
}
|
||||
|
||||
/**
|
||||
* 来自 [KFunction] 反射得到的 [CommandSignature]
|
||||
*
|
||||
* @see CommandSignatureFromKFunctionImpl
|
||||
*/
|
||||
@ConsoleExperimentalApi
|
||||
@ExperimentalCommandDescriptors
|
||||
public interface CommandSignatureFromKFunction : CommandSignature {
|
||||
public val originFunction: KFunction<*>
|
||||
}
|
||||
|
||||
/**
|
||||
* @see CommandSignatureImpl
|
||||
* @see CommandSignatureFromKFunctionImpl
|
||||
*/
|
||||
@ExperimentalCommandDescriptors
|
||||
public abstract class AbstractCommandSignature : CommandSignature {
|
||||
override fun toString(): String {
|
||||
val receiverParameter = receiverParameter
|
||||
return if (receiverParameter == null) {
|
||||
"CommandSignatureVariant(${valueParameters.joinToString()})"
|
||||
} else {
|
||||
"CommandSignatureVariant($receiverParameter, ${valueParameters.joinToString()})"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ExperimentalCommandDescriptors
|
||||
public open class CommandSignatureImpl(
|
||||
override val receiverParameter: CommandReceiverParameter<out CommandSender>?,
|
||||
override val valueParameters: List<AbstractCommandValueParameter<*>>,
|
||||
private val onCall: suspend CommandSignatureImpl.(resolvedCommandCall: ResolvedCommandCall) -> Unit,
|
||||
) : CommandSignature, AbstractCommandSignature() {
|
||||
override suspend fun call(resolvedCommandCall: ResolvedCommandCall) {
|
||||
return onCall(resolvedCommandCall)
|
||||
}
|
||||
}
|
||||
|
||||
@ConsoleExperimentalApi
|
||||
@ExperimentalCommandDescriptors
|
||||
public open class CommandSignatureFromKFunctionImpl(
|
||||
override val receiverParameter: CommandReceiverParameter<out CommandSender>?,
|
||||
override val valueParameters: List<AbstractCommandValueParameter<*>>,
|
||||
override val originFunction: KFunction<*>,
|
||||
private val onCall: suspend CommandSignatureFromKFunctionImpl.(resolvedCommandCall: ResolvedCommandCall) -> Unit,
|
||||
) : CommandSignatureFromKFunction, AbstractCommandSignature() {
|
||||
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
|
||||
}
|
||||
}
|
@ -0,0 +1,152 @@
|
||||
/*
|
||||
* 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", "unused")
|
||||
|
||||
package net.mamoe.mirai.console.command.descriptor
|
||||
|
||||
import net.mamoe.mirai.Bot
|
||||
import net.mamoe.mirai.console.command.CommandManager
|
||||
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.descriptor.CommandValueArgumentParser.Companion.parse
|
||||
import net.mamoe.mirai.contact.*
|
||||
import net.mamoe.mirai.message.data.*
|
||||
import kotlin.contracts.InvocationKind
|
||||
import kotlin.contracts.contract
|
||||
|
||||
/**
|
||||
* 指令参数解析器. 用于解析字符串或 [SingleMessage] 到特定参数类型.
|
||||
*
|
||||
* ### 参数解析
|
||||
*
|
||||
* 如 [SimpleCommand] 中的示例:
|
||||
* ```
|
||||
* suspend fun CommandSender.mute(target: Member, duration: Int)
|
||||
* ```
|
||||
* [CommandManager] 总是从 [SimpleCommand.context] 搜索一个 [T] 为 [Member] 的 [CommandValueArgumentParser], 并调用其 [CommandValueArgumentParser.parse]
|
||||
*
|
||||
* ### 内建指令解析器
|
||||
* - 基础类型: [ByteValueArgumentParser], [ShortValueArgumentParser], [IntValueArgumentParser], [LongValueArgumentParser]
|
||||
* [FloatValueArgumentParser], [DoubleValueArgumentParser],
|
||||
* [BooleanValueArgumentParser], [StringValueArgumentParser]
|
||||
*
|
||||
* - [Bot]: [ExistingBotValueArgumentParser]
|
||||
* - [Friend]: [ExistingFriendValueArgumentParser]
|
||||
* - [Group]: [ExistingGroupValueArgumentParser]
|
||||
* - [Member]: [ExistingMemberValueArgumentParser]
|
||||
* - [User]: [ExistingUserValueArgumentParser]
|
||||
* - [Contact]: [ExistingContactValueArgumentParser]
|
||||
*
|
||||
*
|
||||
* @see SimpleCommand 简单指令
|
||||
* @see CompositeCommand 复合指令
|
||||
*
|
||||
* @see buildCommandArgumentContext 指令参数环境, 即 [CommandValueArgumentParser] 的集合
|
||||
*/
|
||||
public interface CommandValueArgumentParser<out T : Any> {
|
||||
/**
|
||||
* 解析一个字符串为 [T] 类型参数
|
||||
*
|
||||
* **实现提示**: 在解析时遇到意料之中的问题, 如无法找到目标群员, 可抛出 [CommandArgumentParserException].
|
||||
* 此异常将会被特殊处理, 不会引发一个错误, 而是作为指令调用成功的情况, 将错误信息发送给用户.
|
||||
*
|
||||
* @throws CommandArgumentParserException 当解析时遇到*意料之中*的问题时抛出.
|
||||
*
|
||||
* @see CommandArgumentParserException
|
||||
*/
|
||||
@Throws(CommandArgumentParserException::class)
|
||||
public fun parse(raw: String, sender: CommandSender): T
|
||||
|
||||
/**
|
||||
* 解析一个消息内容元素为 [T] 类型参数
|
||||
*
|
||||
* **实现提示**: 在解析时遇到意料之中的问题, 如无法找到目标群员, 可抛出 [CommandArgumentParserException].
|
||||
* 此异常将会被特殊处理, 不会引发一个错误, 而是作为指令调用成功的情况, 将错误信息发送给用户.
|
||||
*
|
||||
* @throws CommandArgumentParserException 当解析时遇到*意料之中*的问题时抛出.
|
||||
*
|
||||
* @see CommandArgumentParserException
|
||||
*/
|
||||
@Throws(CommandArgumentParserException::class)
|
||||
public fun parse(raw: MessageContent, sender: CommandSender): T = parse(raw.content, sender)
|
||||
|
||||
public companion object {
|
||||
/**
|
||||
* 解析一个字符串或 [SingleMessage] 为 [T] 类型参数
|
||||
*
|
||||
* @throws IllegalArgumentException 当 [raw] 既不是 [SingleMessage], 也不是 [String] 时抛出.
|
||||
*
|
||||
* @see CommandValueArgumentParser.parse
|
||||
*/
|
||||
@JvmStatic
|
||||
@Throws(IllegalArgumentException::class)
|
||||
public fun <T : Any> CommandValueArgumentParser<T>.parse(raw: Message, sender: CommandSender): T {
|
||||
return when (raw) {
|
||||
is PlainText -> parse(raw.content, sender)
|
||||
is MessageContent -> parse(raw, sender)
|
||||
else -> throw IllegalArgumentException("Illegal raw argument type: ${raw::class.qualifiedName}")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用原 [this] 解析, 成功后使用 [mapper] 映射为另一个类型.
|
||||
*/
|
||||
@JvmStatic
|
||||
public fun <Original : Any, Result : Any> CommandValueArgumentParser<Original>.map(
|
||||
mapper: MappingCommandValueArgumentParser<Original, Result>.(Original) -> Result,
|
||||
): CommandValueArgumentParser<Result> = MappingCommandValueArgumentParser(this, mapper)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @see CommandValueArgumentParser 的基础实现.
|
||||
*/
|
||||
public abstract class AbstractCommandValueArgumentParser<T : Any> : CommandValueArgumentParser<T> {
|
||||
public companion object {
|
||||
/**
|
||||
* 抛出一个 [CommandArgumentParserException] 的捷径
|
||||
*
|
||||
* @throws CommandArgumentParserException
|
||||
*/
|
||||
@JvmStatic
|
||||
@JvmSynthetic
|
||||
@Throws(CommandArgumentParserException::class)
|
||||
protected inline fun CommandValueArgumentParser<*>.illegalArgument(message: String, cause: Throwable? = null): Nothing =
|
||||
throw CommandArgumentParserException(message, cause)
|
||||
|
||||
/**
|
||||
* 检查参数 [condition]. 当它为 `false` 时调用 [message] 并以其返回值作为消息, 抛出异常 [CommandArgumentParserException]
|
||||
*
|
||||
* @throws CommandArgumentParserException
|
||||
*/
|
||||
@JvmStatic
|
||||
@Throws(CommandArgumentParserException::class)
|
||||
@JvmSynthetic
|
||||
protected inline fun CommandValueArgumentParser<*>.checkArgument(
|
||||
condition: Boolean,
|
||||
crossinline message: () -> String = { "Check failed." },
|
||||
) {
|
||||
contract {
|
||||
returns() implies condition
|
||||
callsInPlace(message, InvocationKind.AT_MOST_ONCE)
|
||||
}
|
||||
if (!condition) illegalArgument(message())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class MappingCommandValueArgumentParser<T : Any, R : Any>(
|
||||
private val original: CommandValueArgumentParser<T>,
|
||||
private val mapper: MappingCommandValueArgumentParser<T, R>.(T) -> R,
|
||||
) : AbstractCommandValueArgumentParser<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))
|
||||
}
|
@ -0,0 +1,59 @@
|
||||
/*
|
||||
* 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.Command
|
||||
import net.mamoe.mirai.console.command.IllegalCommandArgumentException
|
||||
import net.mamoe.mirai.console.command.descriptor.AbstractCommandValueArgumentParser.Companion.illegalArgument
|
||||
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 CommandDeclarationClashException(
|
||||
public val command: Command,
|
||||
public val signatures: List<CommandSignature>,
|
||||
) : CommandResolutionException("Command declaration clash: \n${signatures.joinToString("\n")}")
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
/**
|
||||
* 在解析参数时遇到的 _正常_ 错误. 如参数不符合规范等.
|
||||
*
|
||||
* [message] 将会发送给指令调用方.
|
||||
*
|
||||
* @see IllegalCommandArgumentException
|
||||
* @see CommandValueArgumentParser
|
||||
* @see AbstractCommandValueArgumentParser.illegalArgument
|
||||
*/
|
||||
public class CommandArgumentParserException : IllegalCommandArgumentException {
|
||||
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
|
||||
}
|
@ -1,40 +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.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
|
||||
|
||||
/**
|
||||
* 为 Java 用户添加协程帮助的 [Command].
|
||||
*
|
||||
* 注意, [JSimpleCommand], [JCompositeCommand], [JRawCommand] 都不实现这个接口. [JCommand] 只设计为 Java 使用者自己实现 [Command] 相关内容.
|
||||
*
|
||||
* @see Command
|
||||
*/
|
||||
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
|
||||
}
|
@ -13,7 +13,8 @@ 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.ExperimentalCommandDescriptors
|
||||
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
|
||||
@ -83,6 +84,7 @@ public abstract class JCompositeCommand
|
||||
protected set
|
||||
|
||||
/** 为 `true` 时表示 [指令前缀][CommandManager.commandPrefix] 可选 */
|
||||
@ExperimentalCommandDescriptors
|
||||
public final override var prefixOptional: Boolean = false
|
||||
protected set
|
||||
|
||||
|
@ -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.command.descriptor.ExperimentalCommandDescriptors
|
||||
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
|
||||
|
||||
/**
|
||||
* 供 Java 用户继承
|
||||
@ -70,21 +69,7 @@ public abstract class JRawCommand
|
||||
protected set
|
||||
|
||||
/** 为 `true` 时表示 [指令前缀][CommandManager.commandPrefix] 可选 */
|
||||
@ExperimentalCommandDescriptors
|
||||
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) }
|
||||
}
|
||||
}
|
@ -10,10 +10,10 @@
|
||||
package net.mamoe.mirai.console.command.java
|
||||
|
||||
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.command.descriptor.ExperimentalCommandDescriptors
|
||||
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
|
||||
@ -50,9 +50,10 @@ public abstract class JSimpleCommand(
|
||||
) : SimpleCommand(owner, primaryName, secondaryNames = secondaryNames, parentPermission = basePermission) {
|
||||
public override var description: String = super.description
|
||||
protected set
|
||||
|
||||
public override var permission: Permission = super.permission
|
||||
protected set
|
||||
|
||||
@ExperimentalCommandDescriptors
|
||||
public override var prefixOptional: Boolean = super.prefixOptional
|
||||
protected set
|
||||
public override var context: CommandArgumentContext = super.context
|
||||
|
@ -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,27 @@
|
||||
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.extensions.CommandCallParserProviderImpl
|
||||
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 by CommandCallParserProviderImpl(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.signature,
|
||||
signature.zippedArguments.map { it.second },
|
||||
context ?: EmptyCommandArgumentContext)
|
||||
}
|
||||
|
||||
private data class ResolveData(
|
||||
val signature: CommandSignature,
|
||||
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(
|
||||
signature = signature,
|
||||
zippedArguments = emptyList(),
|
||||
argumentAcceptances = emptyList(),
|
||||
remainingParameters = remainingParameters,
|
||||
)
|
||||
} else {
|
||||
if (valueArguments.size > valueParameters.size && zipped.last().first.isVararg) {
|
||||
// merge vararg arguments
|
||||
val (varargParameter, _)
|
||||
= 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(
|
||||
signature = 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.signature.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,88 @@
|
||||
/*
|
||||
* 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.descriptor.CommandValueArgumentParser.Companion.parse
|
||||
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 [CommandSignature], specifically a sub command from [CompositeCommand]
|
||||
*/
|
||||
public val calleeSignature: CommandSignature
|
||||
|
||||
/**
|
||||
* Original arguments
|
||||
*/
|
||||
public val rawValueArguments: List<CommandValueArgument>
|
||||
|
||||
/**
|
||||
* Resolved value arguments arranged mapping the [CommandSignature.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: CommandSignature,
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
@ -11,22 +11,21 @@
|
||||
|
||||
package net.mamoe.mirai.console.compiler.common
|
||||
|
||||
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
|
||||
import net.mamoe.mirai.console.command.Command
|
||||
import net.mamoe.mirai.console.data.PluginData
|
||||
import net.mamoe.mirai.console.data.value
|
||||
import net.mamoe.mirai.console.permission.PermissionId
|
||||
import net.mamoe.mirai.console.plugin.description.PluginDescription
|
||||
import net.mamoe.mirai.console.util.SemVersion
|
||||
import kotlin.annotation.AnnotationTarget.*
|
||||
|
||||
/**
|
||||
* 标记一个参数的语境类型, 用于帮助编译器和 IntelliJ 插件进行语境推断.
|
||||
*/
|
||||
@ConsoleExperimentalApi
|
||||
@Target(
|
||||
VALUE_PARAMETER,
|
||||
PROPERTY, FIELD,
|
||||
FUNCTION,
|
||||
TYPE, TYPE_PARAMETER
|
||||
)
|
||||
@Target(VALUE_PARAMETER, PROPERTY, FIELD, FUNCTION, TYPE, TYPE_PARAMETER)
|
||||
@Retention(AnnotationRetention.BINARY)
|
||||
public annotation class ResolveContext(
|
||||
val kind: Kind,
|
||||
vararg val kinds: Kind,
|
||||
) {
|
||||
/**
|
||||
* 元素数量可能在任意时间被改动
|
||||
@ -36,18 +35,57 @@ public annotation class ResolveContext(
|
||||
// ConstantKind
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
||||
PLUGIN_ID, // ILLEGAL_PLUGIN_DESCRIPTION
|
||||
PLUGIN_NAME, // ILLEGAL_PLUGIN_DESCRIPTION
|
||||
PLUGIN_VERSION, // ILLEGAL_PLUGIN_DESCRIPTION
|
||||
/*
|
||||
* WARNING: IF YOU CHANGE NAMES HERE,
|
||||
* YOU SHOULD ALSO CHANGE THEIR COUNTERPARTS AT net.mamoe.mirai.console.compiler.common.resolve.ResolveContextKind
|
||||
*/
|
||||
|
||||
/**
|
||||
* @see PluginDescription.id
|
||||
*/
|
||||
PLUGIN_ID, // ILLEGAL_PLUGIN_DESCRIPTION
|
||||
|
||||
/**
|
||||
* @see PluginDescription.name
|
||||
*/
|
||||
PLUGIN_NAME, // ILLEGAL_PLUGIN_DESCRIPTION
|
||||
|
||||
/**
|
||||
* @see PluginDescription.version
|
||||
* @see SemVersion.Companion.invoke
|
||||
*/
|
||||
SEMANTIC_VERSION, // ILLEGAL_PLUGIN_DESCRIPTION
|
||||
|
||||
/**
|
||||
* @see SemVersion.Companion.parseRangeRequirement
|
||||
*/
|
||||
VERSION_REQUIREMENT, // ILLEGAL_VERSION_REQUIREMENT // TODO
|
||||
|
||||
/**
|
||||
* @see Command.allNames
|
||||
*/
|
||||
COMMAND_NAME, // ILLEGAL_COMMAND_NAME
|
||||
|
||||
PERMISSION_NAMESPACE, // ILLEGAL_COMMAND_NAMESPACE
|
||||
PERMISSION_NAME, // ILLEGAL_COMMAND_NAME
|
||||
PERMISSION_ID, // ILLEGAL_COMMAND_ID
|
||||
/**
|
||||
* @see PermissionId.name
|
||||
*/
|
||||
PERMISSION_NAMESPACE, // ILLEGAL_PERMISSION_NAMESPACE
|
||||
|
||||
/**
|
||||
* @see PermissionId.name
|
||||
*/
|
||||
PERMISSION_NAME, // ILLEGAL_PERMISSION_NAME
|
||||
|
||||
/**
|
||||
* @see PermissionId.parseFromString
|
||||
*/
|
||||
PERMISSION_ID, // ILLEGAL_PERMISSION_ID
|
||||
|
||||
/**
|
||||
* 标注一个泛型, 要求这个泛型必须拥有一个公开无参 (或所有参数都可选) 构造器.
|
||||
*
|
||||
* @see PluginData.value
|
||||
*/
|
||||
RESTRICTED_NO_ARG_CONSTRUCTOR, // NOT_CONSTRUCTABLE_TYPE
|
||||
}
|
||||
}
|
@ -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.*
|
||||
|
@ -9,6 +9,8 @@
|
||||
|
||||
package net.mamoe.mirai.console.data
|
||||
|
||||
import kotlinx.serialization.SerialInfo
|
||||
|
||||
/**
|
||||
* 序列化之后的注释.
|
||||
*
|
||||
@ -30,6 +32,7 @@ package net.mamoe.mirai.console.data
|
||||
* a: b
|
||||
* ```
|
||||
*/
|
||||
@SerialInfo
|
||||
@Target(AnnotationTarget.PROPERTY, AnnotationTarget.CLASS)
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
public annotation class ValueDescription(val value: String)
|
@ -7,24 +7,72 @@
|
||||
* https://github.com/mamoe/mirai/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
@file:Suppress("unused", "INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
|
||||
@file:Suppress("unused", "INVISIBLE_MEMBER", "INVISIBLE_REFERENCE", "MemberVisibilityCanBePrivate")
|
||||
|
||||
package net.mamoe.mirai.console.extension
|
||||
|
||||
import net.mamoe.mirai.console.extensions.SingletonExtensionSelector
|
||||
import net.mamoe.mirai.console.internal.extension.GlobalComponentStorage
|
||||
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
|
||||
/**
|
||||
* 由 [Extension] 的 `companion` 实现.
|
||||
* 由 [Extension] 的伴生对象实现.
|
||||
*
|
||||
* @see AbstractExtensionPoint
|
||||
*/
|
||||
public interface ExtensionPoint<T : Extension> {
|
||||
/**
|
||||
* 扩展实例 [T] 的类型
|
||||
*/
|
||||
public val extensionType: KClass<T>
|
||||
}
|
||||
|
||||
public open class AbstractExtensionPoint<T : Extension>(
|
||||
public abstract class AbstractExtensionPoint<T : Extension>(
|
||||
public override val extensionType: KClass<T>,
|
||||
) : ExtensionPoint<T>
|
||||
|
||||
|
||||
/**
|
||||
* 表示一个 [SingletonExtension] 的 [ExtensionPoint]
|
||||
*/
|
||||
public interface SingletonExtensionPoint<T : SingletonExtension<*>> : ExtensionPoint<T>
|
||||
|
||||
/**
|
||||
* 表示一个 [InstanceExtension] 的 [ExtensionPoint]
|
||||
*/
|
||||
public interface InstanceExtensionPoint<T : InstanceExtension<*>> : ExtensionPoint<T>
|
||||
|
||||
/**
|
||||
* 表示一个 [FunctionExtension] 的 [ExtensionPoint]
|
||||
*/
|
||||
public interface FunctionExtensionPoint<T : FunctionExtension> : ExtensionPoint<T>
|
||||
|
||||
|
||||
public abstract class AbstractInstanceExtensionPoint<E : InstanceExtension<T>, T>(
|
||||
extensionType: KClass<E>,
|
||||
/**
|
||||
* 内建的实现列表.
|
||||
*/
|
||||
@ConsoleExperimentalApi
|
||||
public vararg val builtinImplementations: E,
|
||||
) : AbstractExtensionPoint<E>(extensionType)
|
||||
|
||||
public abstract class AbstractSingletonExtensionPoint<E : SingletonExtension<T>, T>(
|
||||
extensionType: KClass<E>,
|
||||
/**
|
||||
* 内建的实现.
|
||||
*/
|
||||
@ConsoleExperimentalApi
|
||||
public val builtinImplementation: T,
|
||||
) : AbstractExtensionPoint<E>(extensionType), SingletonExtensionPoint<E> {
|
||||
|
||||
/**
|
||||
* 由 [SingletonExtensionSelector] 选择后的实例.
|
||||
*/
|
||||
@ConsoleExperimentalApi
|
||||
public val selectedInstance: T by lazy {
|
||||
GlobalComponentStorage.run { this@AbstractSingletonExtensionPoint.findSingletonInstance(extensionType, builtinImplementation) }
|
||||
}
|
||||
}
|
@ -9,6 +9,8 @@
|
||||
|
||||
package net.mamoe.mirai.console.extension
|
||||
|
||||
import net.mamoe.mirai.console.command.descriptor.ExperimentalCommandDescriptors
|
||||
import net.mamoe.mirai.console.command.parse.CommandCallParser
|
||||
import net.mamoe.mirai.console.extensions.*
|
||||
import net.mamoe.mirai.console.internal.extension.AbstractConcurrentComponentStorage
|
||||
import net.mamoe.mirai.console.permission.PermissionService
|
||||
@ -35,7 +37,7 @@ public class PluginComponentStorage(
|
||||
): Unit = contribute(extensionPoint, plugin, lazyInstance)
|
||||
|
||||
/**
|
||||
* 注册一个扩展
|
||||
* 注册一个扩展. [E] 必须拥有伴生对象为 [ExtensionPoint].
|
||||
*/
|
||||
public inline fun <reified E : Extension> contribute(
|
||||
noinline lazyInstance: () -> E,
|
||||
@ -56,6 +58,7 @@ public class PluginComponentStorage(
|
||||
public fun contributeSingletonExtensionSelector(lazyInstance: () -> SingletonExtensionSelector): Unit =
|
||||
contribute(SingletonExtensionSelector, plugin, lazyInstance)
|
||||
|
||||
@Suppress("SpellCheckingInspection") // alterer
|
||||
/** 注册一个 [BotConfigurationAlterer] */
|
||||
public fun contributeBotConfigurationAlterer(instance: BotConfigurationAlterer): Unit =
|
||||
contribute(BotConfigurationAlterer, plugin, lazyInstance = { instance })
|
||||
@ -73,16 +76,14 @@ public class PluginComponentStorage(
|
||||
|
||||
/** 注册一个 [PermissionServiceProvider] */
|
||||
@OverloadResolutionByLambdaReturnType
|
||||
public fun contributePermissionService(
|
||||
lazyInstance: () -> PermissionService<*>,
|
||||
): Unit = contribute(PermissionServiceProvider, plugin, LazyPermissionServiceProviderImpl(lazyInstance))
|
||||
public fun contributePermissionService(lazyInstance: () -> PermissionService<*>): Unit =
|
||||
contribute(PermissionServiceProvider, plugin, LazyPermissionServiceProviderImpl(lazyInstance))
|
||||
|
||||
/** 注册一个 [PermissionServiceProvider] */
|
||||
@JvmName("contributePermissionServiceProvider")
|
||||
@OverloadResolutionByLambdaReturnType
|
||||
public fun contributePermissionService(
|
||||
lazyProvider: () -> PermissionServiceProvider,
|
||||
): Unit = contribute(PermissionServiceProvider, plugin, lazyProvider)
|
||||
public fun contributePermissionService(lazyProvider: () -> PermissionServiceProvider): Unit =
|
||||
contribute(PermissionServiceProvider, plugin, lazyProvider)
|
||||
|
||||
/////////////////////////////////////
|
||||
|
||||
@ -95,5 +96,20 @@ public class PluginComponentStorage(
|
||||
@JvmName("contributePluginLoaderProvider")
|
||||
@OverloadResolutionByLambdaReturnType
|
||||
public fun contributePluginLoader(lazyProvider: () -> PluginLoaderProvider): Unit =
|
||||
contribute(PluginLoaderProvider, plugin, lazyProvider)
|
||||
contribute(PluginLoaderProvider, plugin, lazyProvider) // lazy for safety
|
||||
|
||||
/////////////////////////////////////
|
||||
|
||||
/** 注册一个 [CommandCallParserProvider] */
|
||||
@ExperimentalCommandDescriptors
|
||||
@OverloadResolutionByLambdaReturnType
|
||||
public fun contributeCommandCallParser(lazyInstance: () -> CommandCallParser): Unit =
|
||||
contribute(CommandCallParserProvider, plugin, LazyCommandCallParserProviderImpl(lazyInstance))
|
||||
|
||||
/** 注册一个 [CommandCallParserProvider] */
|
||||
@ExperimentalCommandDescriptors
|
||||
@JvmName("contributeCommandCallParserProvider")
|
||||
@OverloadResolutionByLambdaReturnType
|
||||
public fun contributeCommandCallParser(provider: CommandCallParserProvider): Unit =
|
||||
contribute(CommandCallParserProvider, plugin, provider)
|
||||
}
|
@ -21,6 +21,7 @@ import net.mamoe.mirai.utils.BotConfiguration
|
||||
*
|
||||
* @see MiraiConsole.addBot
|
||||
*/
|
||||
@Suppress("SpellCheckingInspection") // alterer
|
||||
public fun interface BotConfigurationAlterer : FunctionExtension {
|
||||
|
||||
/**
|
||||
|
@ -0,0 +1,35 @@
|
||||
/*
|
||||
* 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.AbstractInstanceExtensionPoint
|
||||
import net.mamoe.mirai.console.extension.InstanceExtension
|
||||
|
||||
/**
|
||||
* The provider of [CommandCallParser]
|
||||
*/
|
||||
@ExperimentalCommandDescriptors
|
||||
public interface CommandCallParserProvider : InstanceExtension<CommandCallParser> {
|
||||
public companion object ExtensionPoint :
|
||||
AbstractInstanceExtensionPoint<CommandCallParserProvider, CommandCallParser>(CommandCallParserProvider::class,
|
||||
SpaceSeparatedCommandCallParser.Provider)
|
||||
}
|
||||
|
||||
|
||||
@ExperimentalCommandDescriptors
|
||||
public class CommandCallParserProviderImpl(override val instance: CommandCallParser) : CommandCallParserProvider
|
||||
|
||||
@ExperimentalCommandDescriptors
|
||||
public class LazyCommandCallParserProviderImpl(instanceCalculator: () -> CommandCallParser) : CommandCallParserProvider {
|
||||
override val instance: CommandCallParser by lazy(instanceCalculator)
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
/*
|
||||
* 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.AbstractInstanceExtensionPoint
|
||||
import net.mamoe.mirai.console.extension.InstanceExtension
|
||||
|
||||
@ExperimentalCommandDescriptors
|
||||
public open class CommandCallResolverProvider(override val instance: CommandCallResolver) : InstanceExtension<CommandCallResolver> {
|
||||
public companion object ExtensionPoint :
|
||||
AbstractInstanceExtensionPoint<CommandCallResolverProvider, CommandCallResolver>(CommandCallResolverProvider::class,
|
||||
BuiltInCommandCallResolver.Provider)
|
||||
}
|
@ -9,9 +9,9 @@
|
||||
|
||||
package net.mamoe.mirai.console.extensions
|
||||
|
||||
import net.mamoe.mirai.console.extension.AbstractExtensionPoint
|
||||
import net.mamoe.mirai.console.extension.AbstractSingletonExtensionPoint
|
||||
import net.mamoe.mirai.console.extension.SingletonExtension
|
||||
import net.mamoe.mirai.console.extension.SingletonExtensionPoint
|
||||
import net.mamoe.mirai.console.internal.permission.BuiltInPermissionService
|
||||
import net.mamoe.mirai.console.permission.PermissionService
|
||||
|
||||
/**
|
||||
@ -21,8 +21,7 @@ import net.mamoe.mirai.console.permission.PermissionService
|
||||
*/
|
||||
public interface PermissionServiceProvider : SingletonExtension<PermissionService<*>> {
|
||||
public companion object ExtensionPoint :
|
||||
AbstractExtensionPoint<PermissionServiceProvider>(PermissionServiceProvider::class),
|
||||
SingletonExtensionPoint<PermissionServiceProvider>
|
||||
AbstractSingletonExtensionPoint<PermissionServiceProvider, PermissionService<*>>(PermissionServiceProvider::class, BuiltInPermissionService)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -12,12 +12,19 @@ package net.mamoe.mirai.console.extensions
|
||||
import net.mamoe.mirai.console.extension.AbstractExtensionPoint
|
||||
import net.mamoe.mirai.console.extension.Extension
|
||||
import net.mamoe.mirai.console.extension.InstanceExtension
|
||||
import net.mamoe.mirai.console.extension.PluginComponentStorage
|
||||
import net.mamoe.mirai.console.plugin.loader.PluginLoader
|
||||
|
||||
/**
|
||||
* 提供扩展 [PluginLoader]
|
||||
*
|
||||
* @see PluginComponentStorage.contributePluginLoader
|
||||
*
|
||||
*
|
||||
* @see Extension
|
||||
* @see PluginLoader
|
||||
*
|
||||
* @see LazyPluginLoaderProviderImpl
|
||||
*/
|
||||
public interface PluginLoaderProvider : InstanceExtension<PluginLoader<*, *>> {
|
||||
public companion object ExtensionPoint : AbstractExtensionPoint<PluginLoaderProvider>(PluginLoaderProvider::class)
|
||||
|
@ -9,8 +9,11 @@
|
||||
|
||||
package net.mamoe.mirai.console.extensions
|
||||
|
||||
import net.mamoe.mirai.console.MiraiConsole
|
||||
import net.mamoe.mirai.console.extension.AbstractExtensionPoint
|
||||
import net.mamoe.mirai.console.extension.ExtensionException
|
||||
import net.mamoe.mirai.console.extension.FunctionExtension
|
||||
import net.mamoe.mirai.console.internal.MiraiConsoleImplementationBridge
|
||||
|
||||
/**
|
||||
* 在 Console 启动完成后立即在主线程调用的扩展. 用于进行一些必要的延迟初始化.
|
||||
@ -20,7 +23,13 @@ import net.mamoe.mirai.console.extension.FunctionExtension
|
||||
public fun interface PostStartupExtension : FunctionExtension {
|
||||
/**
|
||||
* 将在 Console 主线程执行.
|
||||
*
|
||||
* @throws Exception 所有抛出的 [Exception] 都会被捕获并包装为 [ExtensionException] 抛出, 并停止 [MiraiConsole]
|
||||
*
|
||||
* #### 内部实现细节
|
||||
* 在 [MiraiConsoleImplementationBridge.doStart] 所有 [MiraiConsoleImplementationBridge.phase] 执行完成后顺序调用.
|
||||
*/
|
||||
@Throws(Exception::class)
|
||||
public operator fun invoke()
|
||||
|
||||
public companion object ExtensionPoint : AbstractExtensionPoint<PostStartupExtension>(PostStartupExtension::class)
|
||||
|
@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -41,7 +41,7 @@ import net.mamoe.mirai.console.internal.util.autoHexToBytes
|
||||
import net.mamoe.mirai.console.logging.*
|
||||
import net.mamoe.mirai.console.internal.logging.MiraiConsoleLogger
|
||||
import net.mamoe.mirai.console.permission.PermissionService
|
||||
import net.mamoe.mirai.console.permission.PermissionService.Companion.grantPermission
|
||||
import net.mamoe.mirai.console.permission.PermissionService.Companion.permit
|
||||
import net.mamoe.mirai.console.permission.RootPermission
|
||||
import net.mamoe.mirai.console.plugin.PluginManager
|
||||
import net.mamoe.mirai.console.plugin.center.PluginCenter
|
||||
@ -177,9 +177,7 @@ internal object MiraiConsoleImplementationBridge : CoroutineScope, MiraiConsoleI
|
||||
phase `load PermissionService`@{
|
||||
mainLogger.verbose { "Loading PermissionService..." }
|
||||
|
||||
PermissionService.instanceField = GlobalComponentStorage.run {
|
||||
PermissionServiceProvider.findSingletonInstance(BuiltInPermissionService)
|
||||
}
|
||||
PermissionServiceProvider.selectedInstance // init
|
||||
|
||||
PermissionService.INSTANCE.let { ps ->
|
||||
if (ps is BuiltInPermissionService) {
|
||||
@ -188,7 +186,7 @@ internal object MiraiConsoleImplementationBridge : CoroutineScope, MiraiConsoleI
|
||||
}
|
||||
}
|
||||
|
||||
ConsoleCommandSender.grantPermission(RootPermission)
|
||||
ConsoleCommandSender.permit(RootPermission)
|
||||
}
|
||||
|
||||
phase `prepare commands`@{
|
||||
@ -231,7 +229,7 @@ internal object MiraiConsoleImplementationBridge : CoroutineScope, MiraiConsoleI
|
||||
}
|
||||
|
||||
GlobalComponentStorage.run {
|
||||
PostStartupExtension.useExtensions { it() }
|
||||
PostStartupExtension.useExtensions { it() } // exceptions thrown will be caught by caller of `doStart`.
|
||||
}
|
||||
|
||||
mainLogger.info { "mirai-console started successfully." }
|
||||
|
@ -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,271 @@
|
||||
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.KType
|
||||
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<CommandSignatureFromKFunction>): 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(signatures: List<CommandSignatureFromKFunctionImpl>) {
|
||||
|
||||
data class ErasedParameterInfo(
|
||||
val index: Int,
|
||||
val name: String?,
|
||||
val type: KType, // ignore nullability
|
||||
val additional: String?,
|
||||
)
|
||||
|
||||
data class ErasedVariantInfo(
|
||||
val receiver: ErasedParameterInfo?,
|
||||
val valueParameters: List<ErasedParameterInfo>,
|
||||
)
|
||||
|
||||
fun CommandParameter<*>.toErasedParameterInfo(index: Int): ErasedParameterInfo {
|
||||
return ErasedParameterInfo(index,
|
||||
this.name,
|
||||
this.type.withNullability(false),
|
||||
if (this is AbstractCommandValueParameter.StringConstant) this.expectingValue else null)
|
||||
}
|
||||
|
||||
val candidates = signatures.map { variant ->
|
||||
variant to ErasedVariantInfo(
|
||||
variant.receiverParameter?.toErasedParameterInfo(0),
|
||||
variant.valueParameters.mapIndexed { index, parameter -> parameter.toErasedParameterInfo(index) }
|
||||
)
|
||||
}
|
||||
|
||||
val groups = candidates.groupBy { it.second }
|
||||
|
||||
val clashes = groups.entries.find { (_, value) ->
|
||||
value.size > 1
|
||||
} ?: return
|
||||
|
||||
throw CommandDeclarationClashException(command, clashes.value.map { it.first })
|
||||
}
|
||||
|
||||
@Throws(IllegalCommandDeclarationException::class)
|
||||
fun findSubCommands(): List<CommandSignatureFromKFunctionImpl> {
|
||||
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() }
|
||||
|
||||
CommandSignatureFromKFunctionImpl(
|
||||
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
|
||||
}
|
@ -1,58 +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("unused", "INVISIBLE_REFERENCE", "INVISIBLE_MEMBER")
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* 指令形式参数.
|
||||
* @see toCommandParam
|
||||
*/
|
||||
internal data class CommandParameter<T : Any>(
|
||||
/**
|
||||
* 参数名. 不允许重复.
|
||||
*/
|
||||
val name: String,
|
||||
/**
|
||||
* 参数类型. 将从 [CompositeCommand.context] 中寻找 [CommandArgumentParser] 解析.
|
||||
*/
|
||||
val type: KClass<T> // exact type
|
||||
) {
|
||||
constructor(name: String, type: KClass<T>, parser: CommandArgumentParser<T>) : this(name, type) {
|
||||
this._overrideParser = parser
|
||||
}
|
||||
|
||||
@Suppress("PropertyName")
|
||||
@JvmField
|
||||
internal var _overrideParser: CommandArgumentParser<T>? = null
|
||||
|
||||
|
||||
/**
|
||||
* 覆盖的 [CommandArgumentParser].
|
||||
*
|
||||
* 如果非 `null`, 将不会从 [CommandArgumentContext] 寻找 [CommandArgumentParser]
|
||||
*/
|
||||
val overrideParser: CommandArgumentParser<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)
|
||||
}
|
@ -18,13 +18,6 @@ import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
|
||||
|
||||
internal infix fun Array<String>.matchesBeginning(list: List<Any>): Boolean {
|
||||
this.forEachIndexed { index, any ->
|
||||
if (list[index] != any) return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
internal infix fun Array<out String>.intersectsIgnoringCase(other: Array<out String>): Boolean {
|
||||
val max = this.size.coerceAtMost(other.size)
|
||||
for (i in 0 until max) {
|
||||
|
@ -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
|
||||
@ -74,7 +73,9 @@ internal open class MultiFilePluginDataStorageImpl(
|
||||
public override fun store(holder: PluginDataHolder, instance: PluginData) {
|
||||
getPluginDataFile(holder, instance).writeText(
|
||||
kotlin.runCatching {
|
||||
yaml.encodeToString(instance.updaterSerializer, Unit)
|
||||
yaml.encodeToString(instance.updaterSerializer, Unit).also {
|
||||
yaml.decodeAnyFromString(it) // test yaml
|
||||
}
|
||||
}.recoverCatching {
|
||||
// Just use mainLogger for convenience.
|
||||
MiraiConsole.mainLogger.warning(
|
||||
|
@ -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 AbstractInstanceExtensionPoint<*, *>) {
|
||||
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, "*"),
|
||||
"The base permission"
|
||||
)
|
||||
}
|
||||
|
@ -20,6 +20,7 @@ import net.mamoe.mirai.console.internal.data.mkdir
|
||||
import net.mamoe.mirai.console.internal.extension.GlobalComponentStorage
|
||||
import net.mamoe.mirai.console.plugin.Plugin
|
||||
import net.mamoe.mirai.console.plugin.PluginManager
|
||||
import net.mamoe.mirai.console.plugin.PluginManager.INSTANCE.safeLoader
|
||||
import net.mamoe.mirai.console.plugin.description.PluginDependency
|
||||
import net.mamoe.mirai.console.plugin.description.PluginDescription
|
||||
import net.mamoe.mirai.console.plugin.jvm.JvmPlugin
|
||||
@ -60,18 +61,17 @@ internal object PluginManagerImpl : PluginManager, CoroutineScope by MiraiConsol
|
||||
override val pluginLoaders: List<PluginLoader<*, *>>
|
||||
get() = _pluginLoaders.toList()
|
||||
|
||||
override val Plugin.description: PluginDescription
|
||||
get() = if (this is JvmPlugin) {
|
||||
this.safeLoader.getPluginDescription(this)
|
||||
} else resolvedPlugins.firstOrNull { it == this }
|
||||
override fun getPluginDescription(plugin: Plugin): PluginDescription = if (plugin is JvmPlugin) {
|
||||
plugin.safeLoader.getPluginDescription(plugin)
|
||||
} else resolvedPlugins.firstOrNull { it == plugin }
|
||||
?.loader?.cast<PluginLoader<Plugin, PluginDescription>>()
|
||||
?.getPluginDescription(this)
|
||||
?.getPluginDescription(plugin)
|
||||
?: error("Plugin is unloaded")
|
||||
|
||||
|
||||
init {
|
||||
MiraiConsole.coroutineContext[Job]!!.invokeOnCompletion {
|
||||
plugins.forEach { it.disable() }
|
||||
plugins.forEach { disablePlugin(it) }
|
||||
}
|
||||
}
|
||||
|
||||
@ -98,10 +98,10 @@ internal object PluginManagerImpl : PluginManager, CoroutineScope by MiraiConsol
|
||||
this.enable(plugin as P)
|
||||
}.fold(
|
||||
onSuccess = {
|
||||
logger.info { "Successfully enabled plugin ${plugin.description.name}" }
|
||||
logger.info { "Successfully enabled plugin ${getPluginDescription(plugin).name}" }
|
||||
},
|
||||
onFailure = {
|
||||
logger.info { "Cannot enable plugin ${plugin.description.name}" }
|
||||
logger.info { "Cannot enable plugin ${getPluginDescription(plugin).name}" }
|
||||
throw it
|
||||
}
|
||||
)
|
||||
@ -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++
|
||||
}
|
||||
@ -166,7 +166,7 @@ internal object PluginManagerImpl : PluginManager, CoroutineScope by MiraiConsol
|
||||
}
|
||||
|
||||
internal fun enableAllLoadedPlugins() {
|
||||
resolvedPlugins.forEach { it.enable() }
|
||||
resolvedPlugins.forEach { enablePlugin(it) }
|
||||
}
|
||||
|
||||
@kotlin.jvm.Throws(PluginLoadException::class)
|
||||
@ -180,7 +180,7 @@ internal object PluginManagerImpl : PluginManager, CoroutineScope by MiraiConsol
|
||||
|
||||
private fun List<PluginLoader<*, *>>.listAndSortAllPlugins(): List<PluginDescriptionWithLoader> {
|
||||
return flatMap { loader ->
|
||||
loader.listPlugins().map { plugin -> plugin.description.wrapWith(loader, plugin) }
|
||||
loader.listPlugins().map { plugin -> getPluginDescription(plugin).wrapWith(loader, plugin) }
|
||||
}.sortByDependencies()
|
||||
}
|
||||
|
||||
|
@ -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.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -17,8 +17,8 @@ import net.mamoe.mirai.console.extensions.PermissionServiceProvider
|
||||
import net.mamoe.mirai.console.internal.permission.checkType
|
||||
import net.mamoe.mirai.console.permission.Permission.Companion.parentsWithSelf
|
||||
import net.mamoe.mirai.console.plugin.Plugin
|
||||
import net.mamoe.mirai.console.plugin.description
|
||||
import net.mamoe.mirai.console.plugin.name
|
||||
import net.mamoe.mirai.console.plugin.PluginManager.INSTANCE.description
|
||||
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
/**
|
||||
@ -27,7 +27,7 @@ import kotlin.reflect.KClass
|
||||
* ### 可扩展
|
||||
* 权限服务可由插件扩展并覆盖默认实现.
|
||||
*
|
||||
* [PermissionServiceProvider]
|
||||
* @see PermissionServiceProvider 相应扩展
|
||||
*/
|
||||
@PermissionImplementation
|
||||
public interface PermissionService<P : Permission> {
|
||||
@ -50,11 +50,15 @@ public interface PermissionService<P : Permission> {
|
||||
|
||||
/**
|
||||
* 获取所有已注册的指令列表. 应保证线程安全.
|
||||
*
|
||||
* 备注: Java 实现者使用 `CollectionsKt.asSequence(Collection)` 构造 [Sequence]
|
||||
*/
|
||||
public fun getRegisteredPermissions(): Sequence<P>
|
||||
|
||||
/**
|
||||
* 获取 [PermitteeId] 和其父标识的所有被授予的所有直接和间接的权限列表
|
||||
*
|
||||
* 备注: Java 实现者使用 `CollectionsKt.asSequence(Collection)` 构造 [Sequence]
|
||||
*/
|
||||
public fun getPermittedPermissions(permitteeId: PermitteeId): Sequence<P>
|
||||
|
||||
@ -83,7 +87,12 @@ public interface PermissionService<P : Permission> {
|
||||
*
|
||||
* @throws PermissionRegistryConflictException 当已存在一个 [PermissionId] 时抛出.
|
||||
*
|
||||
* @param description 描述. 将会展示给用户.
|
||||
*
|
||||
* @return 申请到的 [Permission] 实例
|
||||
*
|
||||
* @see get 获取一个已注册的权限
|
||||
* @see getOrFail 获取一个已注册的权限
|
||||
*/
|
||||
@Throws(PermissionRegistryConflictException::class)
|
||||
public fun register(
|
||||
@ -93,11 +102,14 @@ public interface PermissionService<P : Permission> {
|
||||
): P
|
||||
|
||||
/** 为 [Plugin] 分配一个 [PermissionId] */
|
||||
@ConsoleExperimentalApi
|
||||
public fun allocatePermissionIdForPlugin(
|
||||
plugin: Plugin,
|
||||
@ResolveContext(COMMAND_NAME) permissionName: String,
|
||||
reason: PluginPermissionIdRequestType
|
||||
): PermissionId = allocatePermissionIdForPluginDefaultImplement(plugin, permissionName, reason)
|
||||
): PermissionId = PermissionId(
|
||||
plugin.description.id.toLowerCase(),
|
||||
permissionName.toLowerCase()
|
||||
)
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@ -126,102 +138,184 @@ public interface PermissionService<P : Permission> {
|
||||
@Throws(UnsupportedOperationException::class)
|
||||
public fun cancel(permitteeId: PermitteeId, permission: P, recursive: Boolean)
|
||||
|
||||
/** [Plugin] 尝试分配的 [PermissionId] 来源 */
|
||||
public enum class PluginPermissionIdRequestType {
|
||||
/** For [Plugin.parentPermission] */
|
||||
ROOT_PERMISSION,
|
||||
|
||||
/** For [Plugin.permissionId] */
|
||||
PERMISSION_ID
|
||||
}
|
||||
|
||||
public companion object {
|
||||
internal var instanceField: PermissionService<*>? = null
|
||||
|
||||
/**
|
||||
* [PermissionService] 实例
|
||||
*
|
||||
* @see PermissionServiceProvider.selectedInstance
|
||||
*/
|
||||
@get:JvmName("getInstance")
|
||||
@JvmStatic
|
||||
public val INSTANCE: PermissionService<out Permission>
|
||||
get() = instanceField ?: error("PermissionService is not yet initialized therefore cannot be used.")
|
||||
get() = PermissionServiceProvider.selectedInstance
|
||||
|
||||
/**
|
||||
* 获取一个权限, 失败时抛出 [NoSuchElementException]
|
||||
*
|
||||
* @see register 申请并注册一个权限
|
||||
*/
|
||||
@JvmStatic
|
||||
@Throws(NoSuchElementException::class)
|
||||
public fun <P : Permission> PermissionService<P>.getOrFail(id: PermissionId): P =
|
||||
get(id) ?: throw NoSuchElementException("Permission not found: $id")
|
||||
|
||||
internal fun PermissionService<*>.allocatePermissionIdForPluginDefaultImplement(
|
||||
plugin: Plugin,
|
||||
@ResolveContext(COMMAND_NAME) permissionName: String,
|
||||
reason: PluginPermissionIdRequestType
|
||||
) = PermissionId(
|
||||
plugin.description.id.toLowerCase(),
|
||||
permissionName.toLowerCase()
|
||||
)
|
||||
/**
|
||||
* @see findCorrespondingPermission
|
||||
*/
|
||||
@JvmStatic
|
||||
public val PermissionId.correspondingPermission: Permission?
|
||||
get() = findCorrespondingPermission()
|
||||
|
||||
/**
|
||||
* @see get
|
||||
*/
|
||||
@JvmStatic
|
||||
public fun PermissionId.findCorrespondingPermission(): Permission? = INSTANCE[this]
|
||||
|
||||
/**
|
||||
* @see getOrFail
|
||||
* @throws NoSuchElementException
|
||||
*/
|
||||
@Throws(NoSuchElementException::class)
|
||||
@JvmStatic
|
||||
public fun PermissionId.findCorrespondingPermissionOrFail(): Permission = INSTANCE.getOrFail(this)
|
||||
|
||||
public fun PermitteeId.grantPermission(permission: Permission) {
|
||||
/**
|
||||
* @see PermissionService.permit
|
||||
*/
|
||||
@JvmStatic
|
||||
@JvmName("permit0") // clash, not JvmSynthetic to allow possible calls from Java.
|
||||
public fun PermitteeId.permit(permission: Permission) {
|
||||
INSTANCE.checkType(permission::class).permit(this, permission)
|
||||
}
|
||||
|
||||
public fun PermitteeId.grantPermission(permissionId: PermissionId) {
|
||||
grantPermission(permissionId.findCorrespondingPermissionOrFail())
|
||||
/**
|
||||
* @see PermissionService.permit
|
||||
* @throws NoSuchElementException
|
||||
*/
|
||||
@JvmStatic
|
||||
@Throws(NoSuchElementException::class)
|
||||
public fun PermitteeId.permit(permissionId: PermissionId) {
|
||||
permit(permissionId.findCorrespondingPermissionOrFail())
|
||||
}
|
||||
|
||||
public fun PermitteeId.denyPermission(permission: Permission, recursive: Boolean) {
|
||||
/**
|
||||
* @see PermissionService.cancel
|
||||
*/
|
||||
@JvmSynthetic
|
||||
@JvmStatic
|
||||
@JvmName("cancel0") // clash, not JvmSynthetic to allow possible calls from Java.
|
||||
public fun PermitteeId.cancel(permission: Permission, recursive: Boolean) {
|
||||
INSTANCE.checkType(permission::class).cancel(this, permission, recursive)
|
||||
}
|
||||
|
||||
public fun PermitteeId.denyPermission(permissionId: PermissionId, recursive: Boolean) {
|
||||
denyPermission(permissionId.findCorrespondingPermissionOrFail(), recursive)
|
||||
/**
|
||||
* @see PermissionService.cancel
|
||||
* @throws NoSuchElementException
|
||||
*/
|
||||
@JvmStatic
|
||||
@Throws(NoSuchElementException::class)
|
||||
public fun PermitteeId.cancel(permissionId: PermissionId, recursive: Boolean) {
|
||||
cancel(permissionId.findCorrespondingPermissionOrFail(), recursive)
|
||||
}
|
||||
|
||||
/**
|
||||
* @see PermissionService.testPermission
|
||||
*/
|
||||
@JvmStatic
|
||||
public fun Permittee.hasPermission(permission: Permission): Boolean =
|
||||
permission.testPermission(this@hasPermission)
|
||||
|
||||
/**
|
||||
* @see PermissionService.testPermission
|
||||
*/
|
||||
@JvmStatic
|
||||
public fun PermitteeId.hasPermission(permission: Permission): Boolean =
|
||||
permission.testPermission(this@hasPermission)
|
||||
|
||||
/**
|
||||
* @see PermissionService.testPermission
|
||||
* @throws NoSuchElementException
|
||||
*/
|
||||
@JvmStatic
|
||||
@Throws(NoSuchElementException::class)
|
||||
public fun PermitteeId.hasPermission(permissionId: PermissionId): Boolean {
|
||||
val instance = permissionId.findCorrespondingPermissionOrFail()
|
||||
return INSTANCE.checkType(instance::class).testPermission(this@hasPermission, instance)
|
||||
}
|
||||
|
||||
/**
|
||||
* @see PermissionService.testPermission
|
||||
*/
|
||||
@JvmStatic
|
||||
public fun Permittee.hasPermission(permissionId: PermissionId): Boolean =
|
||||
permissionId.testPermission(this@hasPermission)
|
||||
|
||||
|
||||
/**
|
||||
* @see PermissionService.getPermittedPermissions
|
||||
*/
|
||||
@JvmStatic
|
||||
public fun Permittee.getPermittedPermissions(): Sequence<Permission> =
|
||||
INSTANCE.getPermittedPermissions(this@getPermittedPermissions.permitteeId)
|
||||
|
||||
public fun Permittee.grantPermission(vararg permissions: Permission) {
|
||||
|
||||
/**
|
||||
* @see PermissionService.permit
|
||||
*/
|
||||
@JvmStatic
|
||||
public fun Permittee.permit(vararg permissions: Permission) {
|
||||
for (permission in permissions) {
|
||||
INSTANCE.checkType(permission::class).permit(this.permitteeId, permission)
|
||||
}
|
||||
}
|
||||
|
||||
public fun Permittee.denyPermission(vararg permissions: Permission, recursive: Boolean) {
|
||||
/**
|
||||
* @see PermissionService.cancel
|
||||
*/
|
||||
@JvmStatic
|
||||
public fun Permittee.cancel(vararg permissions: Permission, recursive: Boolean) {
|
||||
for (permission in permissions) {
|
||||
INSTANCE.checkType(permission::class).cancel(this.permitteeId, permission, recursive)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @see PermissionService.getPermittedPermissions
|
||||
*/
|
||||
@JvmSynthetic
|
||||
@JvmStatic
|
||||
@JvmName("getPermittedPermissions0") // clash, not JvmSynthetic to allow possible calls from Java.
|
||||
public fun PermitteeId.getPermittedPermissions(): Sequence<Permission> =
|
||||
INSTANCE.getPermittedPermissions(this@getPermittedPermissions)
|
||||
|
||||
/**
|
||||
* @see PermissionService.testPermission
|
||||
*/
|
||||
@JvmStatic
|
||||
public fun Permission.testPermission(permittee: Permittee): Boolean =
|
||||
INSTANCE.checkType(this::class).testPermission(permittee.permitteeId, this@testPermission)
|
||||
|
||||
/**
|
||||
* @see PermissionService.testPermission
|
||||
*/
|
||||
@JvmStatic
|
||||
public fun Permission.testPermission(permitteeId: PermitteeId): Boolean =
|
||||
INSTANCE.checkType(this::class).testPermission(permitteeId, this@testPermission)
|
||||
|
||||
/**
|
||||
* @see PermissionService.testPermission
|
||||
*/
|
||||
@JvmStatic
|
||||
public fun PermissionId.testPermission(permittee: Permittee): Boolean {
|
||||
val p = INSTANCE[this] ?: return false
|
||||
return p.testPermission(permittee)
|
||||
}
|
||||
|
||||
/**
|
||||
* @see PermissionService.testPermission
|
||||
*/
|
||||
@JvmStatic
|
||||
public fun PermissionId.testPermission(permissible: PermitteeId): Boolean {
|
||||
val p = INSTANCE[this] ?: return false
|
||||
return p.testPermission(permissible)
|
||||
|
@ -12,23 +12,22 @@
|
||||
package net.mamoe.mirai.console.plugin
|
||||
|
||||
import net.mamoe.mirai.console.command.CommandOwner
|
||||
import net.mamoe.mirai.console.plugin.PluginManager.INSTANCE.disable
|
||||
import net.mamoe.mirai.console.plugin.PluginManager.INSTANCE.enable
|
||||
import net.mamoe.mirai.console.plugin.PluginManager.INSTANCE.safeLoader
|
||||
import net.mamoe.mirai.console.plugin.PluginManager.INSTANCE.getPluginDescription
|
||||
import net.mamoe.mirai.console.plugin.description.PluginDependency
|
||||
import net.mamoe.mirai.console.plugin.description.PluginDescription
|
||||
import net.mamoe.mirai.console.plugin.jvm.JvmPlugin
|
||||
import net.mamoe.mirai.console.plugin.loader.PluginLoader
|
||||
import net.mamoe.mirai.console.util.SemVersion
|
||||
import kotlin.DeprecationLevel.ERROR
|
||||
|
||||
/**
|
||||
* 表示一个 mirai-console 插件.
|
||||
*
|
||||
* @see PluginManager.enable 启用一个插件
|
||||
* @see PluginManager.disable 禁用一个插件
|
||||
* @see PluginManager.enablePlugin 启用一个插件
|
||||
* @see PluginManager.disablePlugin 禁用一个插件
|
||||
* @see PluginManager.description 获取一个插件的 [描述][PluginDescription]
|
||||
*
|
||||
* @see PluginDescription 插件描述, 需由 [PluginLoader] 帮助提供([PluginLoader.description])
|
||||
* @see PluginDescription 插件描述, 需由 [PluginLoader] 帮助提供([PluginLoader.getPluginDescription])
|
||||
* @see JvmPlugin Java, Kotlin 或其他 JVM 平台插件
|
||||
* @see PluginFileExtensions 支持文件系统存储的扩展
|
||||
*
|
||||
@ -38,8 +37,8 @@ public interface Plugin : CommandOwner {
|
||||
/**
|
||||
* 判断此插件是否已启用
|
||||
*
|
||||
* @see PluginManager.enable 启用一个插件
|
||||
* @see PluginManager.disable 禁用一个插件
|
||||
* @see PluginManager.enablePlugin 启用一个插件
|
||||
* @see PluginManager.disablePlugin 禁用一个插件
|
||||
*/
|
||||
public val isEnabled: Boolean
|
||||
|
||||
@ -49,32 +48,42 @@ public interface Plugin : CommandOwner {
|
||||
public val loader: PluginLoader<*, *>
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 [PluginDescription]
|
||||
*/
|
||||
public inline val Plugin.description: PluginDescription get() = this.safeLoader.getPluginDescription(this)
|
||||
@Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
|
||||
@kotlin.internal.LowPriorityInOverloadResolution
|
||||
@Deprecated(
|
||||
"Moved to companion for a better Java API. ",
|
||||
ReplaceWith("this.description", "net.mamoe.mirai.console.plugin.PluginManager.INSTANCE.description"),
|
||||
level = ERROR
|
||||
)
|
||||
public inline val Plugin.description: PluginDescription
|
||||
get() = getPluginDescription(this) // resolved to net.mamoe.mirai.console.plugin.PluginManager.INSTANCE.getDescription
|
||||
|
||||
/**
|
||||
* 获取 [PluginDescription.name`]
|
||||
* 获取 [PluginDescription.name]
|
||||
*/
|
||||
public inline val Plugin.name: String get() = this.description.name
|
||||
public inline val Plugin.name: String get() = getPluginDescription(this).name
|
||||
|
||||
/**
|
||||
* 获取 [PluginDescription.id]
|
||||
*/
|
||||
public inline val Plugin.id: String get() = getPluginDescription(this).id
|
||||
|
||||
/**
|
||||
* 获取 [PluginDescription.version]
|
||||
*/
|
||||
public inline val Plugin.version: SemVersion get() = this.description.version
|
||||
public inline val Plugin.version: SemVersion get() = getPluginDescription(this).version
|
||||
|
||||
/**
|
||||
* 获取 [PluginDescription.info]
|
||||
*/
|
||||
public inline val Plugin.info: String get() = this.description.info
|
||||
public inline val Plugin.info: String get() = getPluginDescription(this).info
|
||||
|
||||
/**
|
||||
* 获取 [PluginDescription.author]
|
||||
*/
|
||||
public inline val Plugin.author: String get() = this.description.author
|
||||
public inline val Plugin.author: String get() = getPluginDescription(this).author
|
||||
|
||||
/**
|
||||
* 获取 [PluginDescription.dependencies]
|
||||
*/
|
||||
public inline val Plugin.dependencies: Set<PluginDependency> get() = this.description.dependencies
|
||||
public inline val Plugin.dependencies: Set<PluginDependency> get() = getPluginDescription(this).dependencies
|
||||
|
@ -105,45 +105,52 @@ public interface PluginManager {
|
||||
/**
|
||||
* 获取插件的 [描述][PluginDescription], 通过 [PluginLoader.getPluginDescription]
|
||||
*/
|
||||
public val Plugin.description: PluginDescription
|
||||
public fun getPluginDescription(plugin: Plugin): PluginDescription
|
||||
|
||||
/**
|
||||
* 禁用这个插件
|
||||
*
|
||||
* @see PluginLoader.disable
|
||||
*/
|
||||
public fun Plugin.disable(): Unit = safeLoader.disable(this)
|
||||
public fun disablePlugin(plugin: Plugin): Unit = plugin.safeLoader.disable(plugin)
|
||||
|
||||
/**
|
||||
* 加载这个插件
|
||||
*
|
||||
* @see PluginLoader.load
|
||||
*/
|
||||
public fun Plugin.load(): Unit = safeLoader.load(this)
|
||||
public fun loadPlugin(plugin: Plugin): Unit = plugin.safeLoader.load(plugin)
|
||||
|
||||
/**
|
||||
* 启用这个插件
|
||||
*
|
||||
* @see PluginLoader.enable
|
||||
*/
|
||||
public fun Plugin.enable(): Unit = safeLoader.enable(this)
|
||||
public fun enablePlugin(plugin: Plugin): Unit = plugin.safeLoader.enable(plugin)
|
||||
|
||||
// endregion
|
||||
|
||||
public companion object INSTANCE : PluginManager by PluginManagerImpl {
|
||||
/**
|
||||
* 经过泛型类型转换的 [Plugin.loader]
|
||||
*/
|
||||
@get:JvmSynthetic
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
public val <P : Plugin> P.safeLoader: PluginLoader<P, PluginDescription>
|
||||
public inline val <P : Plugin> P.safeLoader: PluginLoader<P, PluginDescription>
|
||||
get() = this.loader as PluginLoader<P, PluginDescription>
|
||||
|
||||
// endregion
|
||||
|
||||
public companion object INSTANCE : PluginManager by PluginManagerImpl {
|
||||
// due to Kotlin's bug
|
||||
public override val Plugin.description: PluginDescription get() = PluginManagerImpl.run { description }
|
||||
public override fun Plugin.disable(): Unit = PluginManagerImpl.run { disable() }
|
||||
public override fun Plugin.enable(): Unit = PluginManagerImpl.run { enable() }
|
||||
public override fun Plugin.load(): Unit = PluginManagerImpl.run { load() }
|
||||
public override val <P : Plugin> P.safeLoader: PluginLoader<P, PluginDescription> get() = PluginManagerImpl.run { safeLoader }
|
||||
@get:JvmSynthetic
|
||||
public inline val Plugin.description: PluginDescription
|
||||
get() = getPluginDescription(this)
|
||||
|
||||
@JvmSynthetic
|
||||
public inline fun Plugin.disable(): Unit = disablePlugin(this)
|
||||
|
||||
@JvmSynthetic
|
||||
public inline fun Plugin.enable(): Unit = enablePlugin(this)
|
||||
|
||||
@JvmSynthetic
|
||||
public inline fun Plugin.load(): Unit = loadPlugin(this)
|
||||
}
|
||||
}
|
@ -92,7 +92,7 @@ public interface PluginDescription {
|
||||
*
|
||||
* @see Semver 语义化版本. 允许 [宽松][Semver.SemverType.LOOSE] 类型版本.
|
||||
*/
|
||||
@ResolveContext(PLUGIN_VERSION)
|
||||
@ResolveContext(SEMANTIC_VERSION)
|
||||
public val version: SemVersion
|
||||
|
||||
/**
|
||||
|
@ -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)
|
||||
|
||||
/**
|
||||
* 重载 [PluginData]
|
||||
|
@ -43,7 +43,7 @@ public interface JvmPluginDescription : PluginDescription {
|
||||
/**
|
||||
* @see [PluginDescription.version]
|
||||
*/
|
||||
@ResolveContext(PLUGIN_VERSION) version: String,
|
||||
@ResolveContext(SEMANTIC_VERSION) version: String,
|
||||
/**
|
||||
* @see [PluginDescription.name]
|
||||
*/
|
||||
@ -102,7 +102,7 @@ public class JvmPluginDescriptionBuilder(
|
||||
) {
|
||||
public constructor(
|
||||
@ResolveContext(PLUGIN_ID) id: String,
|
||||
@ResolveContext(PLUGIN_VERSION) version: String,
|
||||
@ResolveContext(SEMANTIC_VERSION) version: String,
|
||||
) : this(id, SemVersion(version))
|
||||
|
||||
private var name: String = id
|
||||
@ -115,7 +115,7 @@ public class JvmPluginDescriptionBuilder(
|
||||
apply { this.name = value.trim() }
|
||||
|
||||
@ILoveKuriyamaMiraiForever
|
||||
public fun version(@ResolveContext(PLUGIN_VERSION) value: String): JvmPluginDescriptionBuilder =
|
||||
public fun version(@ResolveContext(SEMANTIC_VERSION) value: String): JvmPluginDescriptionBuilder =
|
||||
apply { this.version = SemVersion(value) }
|
||||
|
||||
@ILoveKuriyamaMiraiForever
|
||||
|
@ -14,8 +14,7 @@ package net.mamoe.mirai.console.plugin.loader
|
||||
import net.mamoe.mirai.console.extensions.PluginLoaderProvider
|
||||
import net.mamoe.mirai.console.plugin.Plugin
|
||||
import net.mamoe.mirai.console.plugin.PluginManager
|
||||
import net.mamoe.mirai.console.plugin.PluginManager.INSTANCE.disable
|
||||
import net.mamoe.mirai.console.plugin.PluginManager.INSTANCE.enable
|
||||
import net.mamoe.mirai.console.plugin.PluginManager.INSTANCE.enablePlugin
|
||||
import net.mamoe.mirai.console.plugin.description.PluginDescription
|
||||
import net.mamoe.mirai.console.plugin.jvm.JvmPluginLoader
|
||||
|
||||
@ -38,6 +37,7 @@ import net.mamoe.mirai.console.plugin.jvm.JvmPluginLoader
|
||||
* 直接实现接口 [PluginLoader] 或 [FilePluginLoader], 并注册 [PluginLoaderProvider]
|
||||
*
|
||||
* @see JvmPluginLoader Jar 插件加载器
|
||||
* @see PluginLoaderProvider 扩展
|
||||
*/
|
||||
public interface PluginLoader<P : Plugin, D : PluginDescription> {
|
||||
/**
|
||||
@ -66,9 +66,9 @@ public interface PluginLoader<P : Plugin, D : PluginDescription> {
|
||||
public fun getPluginDescription(plugin: P): D
|
||||
|
||||
/**
|
||||
* 主动加载一个插件 (实例), 但不 [启用][enable] 它. 返回加载成功的主类实例
|
||||
* 主动加载一个插件 (实例), 但不 [启用][enablePlugin] 它. 返回加载成功的主类实例
|
||||
*
|
||||
* **实现注意**: Console 不会把一个已经启用了的插件再次调用 [load] 或 [enable], 但不排除意外情况. 实现本函数时应在这种情况时立即抛出异常 [IllegalStateException].
|
||||
* **实现注意**: Console 不会把一个已经启用了的插件再次调用 [load] 或 [enablePlugin], 但不排除意外情况. 实现本函数时应在这种情况时立即抛出异常 [IllegalStateException].
|
||||
*
|
||||
* **实现细节**: 此函数只允许抛出 [PluginLoadException] 作为正常失败原因, 其他任意异常都属于意外错误.
|
||||
* 当异常发生时, 插件将会直接被放弃加载, 并影响依赖它的其他插件.
|
||||
@ -82,7 +82,7 @@ public interface PluginLoader<P : Plugin, D : PluginDescription> {
|
||||
/**
|
||||
* 主动启用这个插件.
|
||||
*
|
||||
* **实现注意**: Console 不会把一个已经启用了的插件再次调用 [load] 或 [enable], 但不排除意外情况. 实现本函数时应在这种情况时立即抛出异常 [IllegalStateException].
|
||||
* **实现注意**: Console 不会把一个已经启用了的插件再次调用 [load] 或 [enablePlugin], 但不排除意外情况. 实现本函数时应在这种情况时立即抛出异常 [IllegalStateException].
|
||||
*
|
||||
* **实现细节**: 此函数可抛出 [PluginLoadException] 作为正常失败原因, 其他任意异常都属于意外错误.
|
||||
* 当异常发生时, 插件将会直接被放弃加载, 并影响依赖它的其他插件.
|
||||
@ -90,7 +90,7 @@ public interface PluginLoader<P : Plugin, D : PluginDescription> {
|
||||
* @throws PluginLoadException 在加载插件遇到意料之中的错误时抛出 (如找不到主类等).
|
||||
* @throws IllegalStateException 在插件已经被加载时抛出. 这属于意料之外的情况.
|
||||
*
|
||||
* @see PluginManager.enable
|
||||
* @see PluginManager.enablePlugin
|
||||
*/
|
||||
@Throws(IllegalStateException::class, PluginLoadException::class)
|
||||
public fun enable(plugin: P)
|
||||
@ -103,7 +103,7 @@ public interface PluginLoader<P : Plugin, D : PluginDescription> {
|
||||
*
|
||||
* @throws PluginLoadException 在加载插件遇到意料之中的错误时抛出 (如找不到主类等).
|
||||
*
|
||||
* @see PluginManager.disable
|
||||
* @see PluginManager.disablePlugin
|
||||
*/
|
||||
@Throws(IllegalStateException::class, PluginLoadException::class)
|
||||
public fun disable(plugin: P)
|
||||
|
@ -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()
|
||||
}
|
@ -21,7 +21,7 @@ import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.Transient
|
||||
import kotlinx.serialization.builtins.serializer
|
||||
import net.mamoe.mirai.console.compiler.common.ResolveContext
|
||||
import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.PLUGIN_VERSION
|
||||
import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.SEMANTIC_VERSION
|
||||
import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.VERSION_REQUIREMENT
|
||||
import net.mamoe.mirai.console.internal.data.map
|
||||
import net.mamoe.mirai.console.internal.util.semver.SemVersionInternal
|
||||
@ -47,10 +47,10 @@ import kotlin.LazyThreadSafetyMode.PUBLICATION
|
||||
* ```
|
||||
* 其中 identifier 和 metadata 都是可选的.
|
||||
*
|
||||
* 对于核心版本号, 此实现稍微比 semver 宽松一些, 允许 x.y 的存在.
|
||||
* 对于核心版本号, 此实现稍微比语义化版本规范宽松一些, 允许 x.y 的存在.
|
||||
*
|
||||
* @see Requirement
|
||||
* @see SemVersion.invoke
|
||||
* @see Requirement 版本号要修
|
||||
* @see SemVersion.invoke 由字符串解析
|
||||
*/
|
||||
@Serializable(with = SemVersion.SemVersionAsStringSerializer::class)
|
||||
public data class SemVersion
|
||||
@ -69,6 +69,15 @@ internal constructor(
|
||||
/** 版本号元数据, 不参与版本号对比([compareTo]), 但是参与版本号严格对比([equals]) */
|
||||
public val metadata: String? = null,
|
||||
) : Comparable<SemVersion> {
|
||||
|
||||
init {
|
||||
require(major >= 0) { "major must >= 0" }
|
||||
require(minor >= 0) { "minor must >= 0" }
|
||||
if (patch != null) require(patch >= 0) { "patch must >= 0" }
|
||||
if (identifier != null) require(identifier.none(Char::isWhitespace)) { "identifier must not contain whitespace" }
|
||||
if (metadata != null) require(metadata.none(Char::isWhitespace)) { "metadata must not contain whitespace" }
|
||||
}
|
||||
|
||||
/**
|
||||
* 一条依赖规则
|
||||
* @see [parseRangeRequirement]
|
||||
@ -103,10 +112,10 @@ internal constructor(
|
||||
* - 如果不确定版本号是否合法, 可以使用 [regex101.com](https://regex101.com/r/vkijKf/1/) 进行检查
|
||||
* - 此实现使用的正则表达式为 `^(0|[1-9]\d*)\.(0|[1-9]\d*)(?:\.(0|[1-9]\d*))?(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$`
|
||||
*/
|
||||
@Throws(IllegalArgumentException::class, NumberFormatException::class)
|
||||
@JvmStatic
|
||||
@JvmName("parse")
|
||||
public operator fun invoke(@ResolveContext(PLUGIN_VERSION) version: String): SemVersion = SemVersionInternal.parse(version)
|
||||
@Throws(IllegalArgumentException::class, NumberFormatException::class)
|
||||
public operator fun invoke(@ResolveContext(SEMANTIC_VERSION) version: String): SemVersion = SemVersionInternal.parse(version)
|
||||
|
||||
/**
|
||||
* 解析一条依赖需求描述, 在无法解析的时候抛出 [IllegalArgumentException]
|
||||
@ -138,14 +147,15 @@ internal constructor(
|
||||
* - 如果目标版本号携带有先行版本号, 请不要忘记先行版本号
|
||||
* - 因为 `()` 已经用于数学区间, 使用 `{}` 替代 `()`
|
||||
*/
|
||||
@Throws(IllegalArgumentException::class)
|
||||
@JvmStatic
|
||||
@Throws(IllegalArgumentException::class)
|
||||
public fun parseRangeRequirement(@ResolveContext(VERSION_REQUIREMENT) requirement: String): Requirement =
|
||||
SemVersionInternal.parseRangeRequirement(requirement)
|
||||
|
||||
/** @see [Requirement.test] */
|
||||
@JvmStatic
|
||||
public fun Requirement.test(@ResolveContext(PLUGIN_VERSION) version: String): Boolean = test(invoke(version))
|
||||
@Throws(IllegalArgumentException::class, NumberFormatException::class)
|
||||
public fun Requirement.test(@ResolveContext(SEMANTIC_VERSION) version: String): Boolean = test(invoke(version))
|
||||
|
||||
/**
|
||||
* 当满足 [requirement] 时返回 true, 否则返回 false
|
||||
@ -157,6 +167,7 @@ internal constructor(
|
||||
* 当满足 [requirement] 时返回 true, 否则返回 false
|
||||
*/
|
||||
@JvmStatic
|
||||
@Throws(IllegalArgumentException::class)
|
||||
public fun SemVersion.satisfies(@ResolveContext(VERSION_REQUIREMENT) requirement: String): Boolean = parseRangeRequirement(requirement).test(this)
|
||||
|
||||
/** for Kotlin only */
|
||||
@ -167,7 +178,7 @@ internal constructor(
|
||||
/** for Kotlin only */
|
||||
@JvmStatic
|
||||
@JvmSynthetic
|
||||
public operator fun Requirement.contains(@ResolveContext(PLUGIN_VERSION) version: String): Boolean = test(version)
|
||||
public operator fun Requirement.contains(@ResolveContext(SEMANTIC_VERSION) version: String): Boolean = test(version)
|
||||
}
|
||||
|
||||
@Transient
|
||||
@ -185,7 +196,10 @@ internal constructor(
|
||||
}
|
||||
}
|
||||
|
||||
override fun toString(): String = toString
|
||||
/**
|
||||
* 返回类似 `1.0.0-M4+c25733b8` 的字符串.
|
||||
*/
|
||||
public override fun toString(): String = toString
|
||||
|
||||
/**
|
||||
* 将 [SemVersion] 转为 Kotlin data class 风格的 [String]
|
||||
@ -194,27 +208,60 @@ internal constructor(
|
||||
return "SemVersion(major=$major, minor=$minor, patch=$patch, identifier=$identifier, metadata=$metadata)"
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
|
||||
other as SemVersion
|
||||
|
||||
return compareTo(other) == 0 && other.identifier == identifier && other.metadata == metadata
|
||||
/**
|
||||
* 比较 `this` 和 [other].
|
||||
*
|
||||
* @param deep 为 `true` 时进行深度比较, 相当于 [equals]. 为 `false` 时相当于 `compareTo(other) == 0`
|
||||
* @see compareTo
|
||||
*/
|
||||
public fun equals(other: SemVersion, deep: Boolean): Boolean {
|
||||
return if (deep) {
|
||||
(other.major == major
|
||||
&& other.minor == minor
|
||||
&& other.patch == patch
|
||||
&& other.identifier == identifier
|
||||
&& other.metadata == metadata)
|
||||
} else {
|
||||
this.compareTo(other) == 0
|
||||
}
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = major shl minor
|
||||
result *= (patch ?: 1)
|
||||
/**
|
||||
* 深度比较 `this` 和 [other], 当且仅当 [major], [patch], [minor], [identifier], [metadata] 完全相同时返回 `true`.
|
||||
*
|
||||
* 如: `1.0.0-RC` != `1.0-RC`
|
||||
*
|
||||
* @see compareTo
|
||||
*/
|
||||
public override fun equals(other: Any?): Boolean {
|
||||
if (other === null) return false
|
||||
if (this === other) return true
|
||||
if (javaClass != other.javaClass) return false
|
||||
return equals(other as SemVersion, deep = true)
|
||||
}
|
||||
|
||||
public override fun hashCode(): Int {
|
||||
var result = major.hashCode()
|
||||
result = 31 * result + minor.hashCode()
|
||||
result = 31 * result + (patch?.hashCode() ?: 0)
|
||||
result = 31 * result + (identifier?.hashCode() ?: 0)
|
||||
result = 31 * result + (metadata?.hashCode() ?: 0)
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares this object with the specified object for order. Returns zero if this object is equal
|
||||
* to the specified [other] object, a negative number if it's less than [other], or a positive number
|
||||
* if it's greater than [other].
|
||||
* 比较 `this` 和 [other] 的实际版本大小.
|
||||
*
|
||||
* 如:
|
||||
* - `SemVersion("1.0.0-RC").compareTo(SemVersion("1.0-RC")) == 0` (然而对他们进行 [equals] 判断会返回 `false`)
|
||||
* - `SemVersion("1.3.0") > SemVersion("1.1.0") == true` (因为 1.3.0 比 1.1.0 更高)
|
||||
*
|
||||
*
|
||||
* @return 当 `this` 比 [other] 更高时返回一个正数.
|
||||
* 当 `this` 比 [other] 更低时返回一个负数.
|
||||
* 当 `this` 与 [other] 版本大小相等时返回 0.
|
||||
*
|
||||
* @see equals
|
||||
*/
|
||||
public override operator fun compareTo(other: SemVersion): Int {
|
||||
return SemVersionInternal.run { compareInternal(this@SemVersion, other) }
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
@ -22,7 +22,8 @@ internal class TestSemVersion {
|
||||
internal fun testCompare() {
|
||||
fun String.sem(): SemVersion = SemVersion.invoke(this)
|
||||
assert("1.0".sem() < "1.0.1".sem())
|
||||
assert("1.0.0".sem() == "1.0".sem())
|
||||
assert("1.0.0".sem() != "1.0".sem())
|
||||
assert("1.0.0".sem().compareTo("1.0".sem()) == 0)
|
||||
assert("1.1".sem() > "1.0.0".sem())
|
||||
assert("1.0-M4".sem() < "1.0-M5".sem())
|
||||
assert("1.0-M5-dev-7".sem() < "1.0-M5-dev-15".sem())
|
||||
|
@ -8,8 +8,8 @@
|
||||
*/
|
||||
|
||||
object Versions {
|
||||
const val core = "1.3.0"
|
||||
const val console = "1.0-RC-dev-31"
|
||||
const val core = "1.3.2"
|
||||
const val console = "1.0-RC-dev-32"
|
||||
const val consoleGraphical = "0.0.7"
|
||||
const val consoleTerminal = console
|
||||
|
||||
@ -19,7 +19,7 @@ object Versions {
|
||||
const val coroutines = "1.3.9"
|
||||
const val collectionsImmutable = "0.3.2"
|
||||
const val serialization = "1.0.0-RC"
|
||||
const val ktor = "1.4.0"
|
||||
const val ktor = "1.4.1"
|
||||
const val atomicFU = "0.14.4"
|
||||
|
||||
const val androidGradle = "3.6.2"
|
||||
|
@ -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
|
||||
|
@ -13,6 +13,7 @@ import net.mamoe.mirai.console.compiler.common.castOrNull
|
||||
import net.mamoe.mirai.console.compiler.common.firstValue
|
||||
import org.jetbrains.kotlin.descriptors.annotations.Annotated
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.resolve.constants.ArrayValue
|
||||
import org.jetbrains.kotlin.resolve.constants.EnumValue
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
@ -73,11 +74,14 @@ enum class ResolveContextKind {
|
||||
}
|
||||
}
|
||||
|
||||
fun Annotated.isResolveContext(kind: ResolveContextKind) = this.resolveContextKind == kind
|
||||
|
||||
val Annotated.resolveContextKind: ResolveContextKind?
|
||||
val Annotated.resolveContextKinds: List<ResolveContextKind>?
|
||||
get() {
|
||||
val ann = this.findAnnotation(RESOLVE_CONTEXT_FQ_NAME) ?: return null
|
||||
val (_, enumEntryName) = ann.allValueArguments.firstValue().castOrNull<EnumValue>()?.value ?: return null // undetermined kind
|
||||
return ResolveContextKind.valueOf(enumEntryName.asString())
|
||||
val kinds =
|
||||
ann.allValueArguments.firstValue().castOrNull<ArrayValue>()?.value?.mapNotNull { it.castOrNull<EnumValue>()?.value }
|
||||
?: return null // undetermined kind
|
||||
|
||||
return kinds.map { (_, enumEntryName) ->
|
||||
ResolveContextKind.valueOf(enumEntryName.asString())
|
||||
}
|
||||
}
|
@ -10,6 +10,6 @@
|
||||
package net.mamoe.mirai.console.gradle
|
||||
|
||||
internal object VersionConstants {
|
||||
const val CONSOLE_VERSION = "1.0-RC-dev-30" // value is written here automatically during build
|
||||
const val CORE_VERSION = "1.3.0" // value is written here automatically during build
|
||||
const val CONSOLE_VERSION = "1.0-RC-dev-32" // value is written here automatically during build
|
||||
const val CORE_VERSION = "1.3.2" // value is written here automatically during build
|
||||
}
|
@ -12,7 +12,7 @@ package net.mamoe.mirai.console.intellij.diagnostics
|
||||
import com.intellij.psi.PsiElement
|
||||
import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors.*
|
||||
import net.mamoe.mirai.console.compiler.common.resolve.ResolveContextKind
|
||||
import net.mamoe.mirai.console.compiler.common.resolve.resolveContextKind
|
||||
import net.mamoe.mirai.console.compiler.common.resolve.resolveContextKinds
|
||||
import net.mamoe.mirai.console.intellij.resolve.resolveAllCalls
|
||||
import net.mamoe.mirai.console.intellij.resolve.resolveStringConstantValues
|
||||
import net.mamoe.mirai.console.intellij.resolve.valueParametersWithArguments
|
||||
@ -134,12 +134,18 @@ class ContextualParametersChecker : DeclarationChecker {
|
||||
context: DeclarationCheckerContext,
|
||||
) {
|
||||
declaration.resolveAllCalls(context.bindingContext)
|
||||
.asSequence()
|
||||
.flatMap { call ->
|
||||
call.valueParametersWithArguments().asSequence()
|
||||
}
|
||||
.mapNotNull { (p, a) ->
|
||||
p.resolveContextKind?.let(checkersMap::get)?.let { it to a }
|
||||
p.resolveContextKinds
|
||||
?.map(checkersMap::get)
|
||||
?.mapNotNull {
|
||||
if (it == null) null else it to a
|
||||
}
|
||||
}
|
||||
.flatMap { it.asSequence() }
|
||||
.mapNotNull { (kind, argument) ->
|
||||
argument.resolveStringConstantValues()?.let { const ->
|
||||
Triple(kind, argument, const)
|
||||
|
@ -31,12 +31,12 @@ class PluginDataValuesChecker : DeclarationChecker {
|
||||
declaration.resolveAllCallsWithElement(bindingContext)
|
||||
.filter { (call) -> call.isCalling(PLUGIN_DATA_VALUE_FUNCTIONS_FQ_FQ_NAME) }
|
||||
.filter { (call) ->
|
||||
call.resultingDescriptor.resolveContextKind == ResolveContextKind.RESTRICTED_NO_ARG_CONSTRUCTOR
|
||||
call.resultingDescriptor.resolveContextKinds?.contains(ResolveContextKind.RESTRICTED_NO_ARG_CONSTRUCTOR) == true
|
||||
}.flatMap { (call, element) ->
|
||||
call.typeArguments.entries.associateWith { element }.asSequence()
|
||||
}.filter { (e, _) ->
|
||||
val (p, t) = e
|
||||
(p.isReified || p.resolveContextKind == ResolveContextKind.RESTRICTED_NO_ARG_CONSTRUCTOR)
|
||||
(p.isReified || p.resolveContextKinds?.contains(ResolveContextKind.RESTRICTED_NO_ARG_CONSTRUCTOR) == true)
|
||||
&& t is SimpleType
|
||||
}.forEach { (e, callExpr) ->
|
||||
val (_, type) = e
|
||||
|
@ -15,7 +15,6 @@ import org.jetbrains.kotlin.diagnostics.Diagnostic
|
||||
import org.jetbrains.kotlin.psi.KtElement
|
||||
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
|
||||
import org.jetbrains.kotlin.resolve.checkers.DeclarationCheckerContext
|
||||
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
|
||||
|
||||
fun DeclarationCheckerContext.report(diagnostic: Diagnostic) {
|
||||
return this.trace.report(diagnostic)
|
||||
@ -25,7 +24,6 @@ val DeclarationCheckerContext.bindingContext get() = this.trace.bindingContext
|
||||
|
||||
fun KtElement?.getResolvedCallOrResolveToCall(
|
||||
context: DeclarationCheckerContext,
|
||||
bodyResolveMode: BodyResolveMode = BodyResolveMode.PARTIAL,
|
||||
): ResolvedCall<out CallableDescriptor>? {
|
||||
return this.getResolvedCallOrResolveToCall(context.bindingContext, bodyResolveMode)
|
||||
return this.getResolvedCallOrResolveToCall(context.bindingContext)
|
||||
}
|
@ -32,7 +32,6 @@ import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
|
||||
import org.jetbrains.kotlin.resolve.constants.ArrayValue
|
||||
import org.jetbrains.kotlin.resolve.constants.ConstantValue
|
||||
import org.jetbrains.kotlin.resolve.constants.StringValue
|
||||
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
|
||||
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstance
|
||||
|
||||
|
||||
@ -125,9 +124,8 @@ inline fun <reified E> PsiElement.findChild(): E? = this.children.find { it is E
|
||||
|
||||
fun KtElement?.getResolvedCallOrResolveToCall(
|
||||
context: BindingContext,
|
||||
bodyResolveMode: BodyResolveMode = BodyResolveMode.PARTIAL,
|
||||
): ResolvedCall<out CallableDescriptor>? {
|
||||
return this?.getCall(context)?.getResolvedCall(context)// ?: this?.resolveToCall(bodyResolveMode)
|
||||
return this?.getCall(context)?.getResolvedCall(context)
|
||||
}
|
||||
|
||||
val ResolvedCall<out CallableDescriptor>.valueParameters: List<ValueParameterDescriptor> get() = this.resultingDescriptor.valueParameters
|
||||
|
Loading…
Reference in New Issue
Block a user