Merge remote-tracking branch 'origin/reborn' into reborn

# Conflicts:
#	backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/Command.kt
#	backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/command/CommandPermission.kt
This commit is contained in:
jiahua.liu 2020-05-17 13:30:45 +08:00
commit 80dd007c9b
17 changed files with 491 additions and 417 deletions

View File

@ -1,4 +1,4 @@
package net.mamoe.mirai.console.events;
package net.mamoe.mirai.console.event;
import net.mamoe.mirai.console.plugins.PluginBase;
import net.mamoe.mirai.event.Event;

View File

@ -7,7 +7,7 @@
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
package net.mamoe.mirai.console.events;
package net.mamoe.mirai.console.event;
import net.mamoe.mirai.event.Event;
import org.jetbrains.annotations.NotNull;

View File

@ -11,12 +11,17 @@
package net.mamoe.mirai.console.command
import net.mamoe.mirai.console.command.CommandDescriptor.SubCommandDescriptor
import net.mamoe.mirai.message.data.Message
import net.mamoe.mirai.console.command.description.CommandParam
import net.mamoe.mirai.console.command.description.CommandParserContext
import net.mamoe.mirai.console.command.description.EmptyCommandParserContext
import net.mamoe.mirai.console.command.description.plus
import net.mamoe.mirai.console.plugins.MyArg
import net.mamoe.mirai.message.data.PlainText
import net.mamoe.mirai.message.data.SingleMessage
import kotlin.reflect.KAnnotatedElement
import kotlin.reflect.KClass
import kotlin.reflect.KProperty
internal const val FOR_BINARY_COMPATIBILITY = "for binary compatibility"
import kotlin.reflect.full.declaredFunctions
import kotlin.reflect.full.findAnnotation
/**
* 指令
@ -24,20 +29,15 @@ internal const val FOR_BINARY_COMPATIBILITY = "for binary compatibility"
* @see register 注册这个指令
*/
interface Command {
val descriptor: CommandDescriptor
/**
* Permission of the command
*/
@Target(AnnotationTarget.FUNCTION)
annotation class Permission(val permission:KClass<*>)
val names: Array<out String>
val usage: String
val description: String
val permission: CommandPermission
val prefixOptional: Boolean
/**
* If a command is prefix optional
* e.g
* mute work as (/mute) if prefix optional or vise versa
*/
@Target(AnnotationTarget.CLASS)
annotation class PrefixOptional()
val owner: CommandOwner
suspend fun onCommand(sender: CommandSender, args: Array<out Any>)
}
/**
@ -49,120 +49,188 @@ interface Command {
* /mute addandremove (sub is case insensitive, lowercase are recommend)
* /mute add and remove('add and remove' consider as a sub)
*/
abstract class CompositeCommand(val name:String, val alias:Array<String> = arrayOf()):Command{
/**
* 你应当使用 @SubCommand 来注册 sub 指令
*/
abstract class CompositeCommand @JvmOverloads constructor(
override val owner: CommandOwner,
vararg names: String,
override val description: String = "",
override val permission: CommandPermission = CommandPermission.Default,
override val prefixOptional: Boolean = false,
overrideContext: CommandParserContext = EmptyCommandParserContext
) : Command {
override val names: Array<out String> =
names.map(String::trim).filterNot(String::isEmpty).map(String::toLowerCase).toTypedArray()
val context: CommandParserContext = CommandParserContext.Builtins + overrideContext
override val usage: String by lazy { TODO() }
/** 指定子指令要求的权限 */
@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.FUNCTION)
annotation class SubCommand(val name:String)
annotation class Permission(val permission: KClass<out CommandPermission>)
/**
* Usage of the sub command
* you should not include arg names, which will be insert automatically
*/
/** 标记一个函数为子指令 */
@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.FUNCTION)
annotation class Usage(val usage:String)
annotation class SubCommand(val name: String)
/**
* name of the parameter
*
* by default available
*
*/
/** 指令描述 */
@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.FUNCTION)
annotation class Description(val description: String)
/** 参数名, 将参与构成 [usage] */
@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.VALUE_PARAMETER)
annotation class Name(val name:String)
annotation class Name(val name: String)
override val descriptor: CommandDescriptor by lazy {
CommandDescriptor()
}
}
abstract class SimpleCommand(val name: String, val alias: Array<String> = arrayOf()
):Command{
override val descriptor: CommandDescriptor by lazy {
CommandDescriptor()
final override suspend fun onCommand(sender: CommandSender, args: Array<out Any>) {
matchSubCommand(args)?.parseAndExecute(sender, args) ?: kotlin.run {
defaultSubCommand.onCommand(sender, args)
}
subCommands
}
/**
* 你必须实现onCommand方法
*/
}
abstract class RawCommand(name:String, alias: Array<String> = arrayOf()):Command{
override val descriptor: CommandDescriptor by lazy {
CommandDescriptor()
internal val defaultSubCommand: DefaultSubCommandDescriptor by lazy {
DefaultSubCommandDescriptor(
"",
CommandPermission.Default,
onCommand = block { sender: CommandSender, args: Array<out Any> ->
println("default finally got args: ${args.joinToString()}")
true
}
)
}
internal val subCommands: Array<SubCommandDescriptor> by lazy {
this::class.declaredFunctions.filter { it.hasAnnotation<SubCommand>() }.map { function ->
SubCommandDescriptor(
arrayOf(function.name),
arrayOf(CommandParam("p", MyArg::class)),
"",
CommandPermission.Default,
onCommand = block { sender: CommandSender, args: Array<out Any> ->
println("subname finally gor args: ${args.joinToString()}")
true
}
)
}.toTypedArray()
}
@Command.Permission(CommandPermission.Default::class)
abstract fun onCommand(list: List<Message>)
private fun block(block: suspend (CommandSender, Array<out Any>) -> Boolean): suspend (CommandSender, Array<out Any>) -> Boolean {
return block
}
}
/**
* 解析完成的指令实际参数列表. 参数顺序与 [Command.descriptor] [CommandDescriptor.params] 相同.
*/
class CommandArgs private constructor(
@JvmField
internal val values: List<Any>,
private val fromCommand: SubCommandDescriptor
) : List<Any> by values {
/**
* 获取第一个类型为 [R] 的参数
*/
@JvmSynthetic
inline fun <reified R> getReified(): R {
for (value in this) {
if (value is R) {
return value
internal val bakedCommandNameToSubDescriptorArray: Map<Array<String>, SubCommandDescriptor> = kotlin.run {
val map = LinkedHashMap<Array<String>, SubCommandDescriptor>(subCommands.size * 2)
for (descriptor in subCommands) {
for (name in descriptor.bakedSubNames) {
map[name] = descriptor
}
}
error("Cannot find argument typed ${R::class.qualifiedName}")
map.toSortedMap(Comparator { o1, o2 -> o1!!.contentHashCode() - o2!!.contentHashCode() })
}
/**
* 获取名称为 [name] 的参数.
*
* [name] `null` 则获取第一个匿名参数
* @throws NoSuchElementException 找不到这个名称的参数时抛出
*/
operator fun get(name: String?): Any {
val index = fromCommand.params.indexOfFirst { it.name == name }
if (index == -1) {
throw NoSuchElementException("Cannot find argument named $name")
}
return values[index]
}
internal inner class DefaultSubCommandDescriptor(
val description: String,
val permission: CommandPermission,
val onCommand: suspend (sender: CommandSender, rawArgs: Array<out Any>) -> Boolean
)
/**
* 获取名称为 [name] 的参数. 并强转为 [R].
*
* [name] `null` 则获取第一个匿名参数
* @throws IllegalStateException 无法强转时抛出
*/
fun <R> getAs(name: String?): R {
@Suppress("UNCHECKED_CAST")
return this[name] as? R ?: error("Argument $name has a type $")
}
/** 获取第一个类型为 [R] 的参数并提供委托 */
inline operator fun <reified R : Any> getValue(thisRef: Any?, property: KProperty<*>): R = getReified()
companion object {
fun parseFrom(command: SubCommandDescriptor, sender: CommandSender, rawArgs: List<Any>): CommandArgs {
val params = command.params
require(rawArgs.size >= params.size) { "No enough rawArgs: required ${params.size}, found only ${rawArgs.size}" }
command.params.asSequence().zip(rawArgs.asSequence()).map { (commandParam, any) ->
command.parserFor(commandParam)?.parse(any, sender)
?: error("Could not find a parser for param named ${commandParam.name}, typed ${commandParam.type.qualifiedName}")
}.toList().let { bakedArgs ->
return CommandArgs(bakedArgs, command)
internal inner class SubCommandDescriptor(
val names: Array<String>,
val params: Array<CommandParam<*>>,
val description: String,
val permission: CommandPermission,
val onCommand: suspend (sender: CommandSender, parsedArgs: Array<out Any>) -> Boolean
) {
internal suspend inline fun parseAndExecute(
sender: CommandSender,
argsWithSubCommandNameNotRemoved: Array<out Any>
) {
if (!onCommand(sender, parseArgs(sender, argsWithSubCommandNameNotRemoved, names.size))) {
sender.sendMessage(usage)
}
}
@JvmField
internal val bakedSubNames: Array<Array<String>> = names.map { it.bakeSubName() }.toTypedArray()
private fun parseArgs(sender: CommandSender, rawArgs: Array<out Any>, offset: Int): Array<out Any> {
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 String -> context[param.type]?.parse(rawArg, sender)
is SingleMessage -> context[param.type]?.parse(rawArg, sender)
else -> throw IllegalArgumentException("Illegal argument type: ${rawArg::class.qualifiedName}")
} ?: error("Cannot find a parser for $rawArg")
}
}
}
/**
* @param rawArgs 元素类型必须为 [SingleMessage] [String], 且已经经过扁平化处理. 否则抛出异常 [IllegalArgumentException]
*/
internal fun matchSubCommand(rawArgs: Array<out Any>): SubCommandDescriptor? {
val maxCount = rawArgs.size - 1
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
}
}
abstract class SimpleCommand(
override val owner: CommandOwner,
val name: String,
val alias: Array<String> = arrayOf()
) : Command {
abstract suspend fun CommandSender.onCommand(args: List<Any>)
}
abstract class RawCommand(
final override val owner: CommandOwner,
name: String,
alias: Array<String> = arrayOf()
) : Command {
final override val names: Array<String> = arrayOf(name, *alias)
}
private fun <T> Array<T>.contentEqualsOffset(other: Array<out Any>, length: Int): Boolean {
repeat(length) { index ->
if (other[index].toString() != this[index]) {
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(): ArrayList<Any> {
val list = ArrayList<Any>()
when (this::class.java) { // faster than is
String::class.java -> (this as String).splitToSequence(' ').filterNot { it.isBlank() }.forEach { list.add(it) }
PlainText::class.java -> (this as PlainText).content.splitToSequence(' ').filterNot { it.isBlank() }
.forEach { list.add(it) }
SingleMessage::class.java -> list.add(this as SingleMessage)
Array<Any>::class.java -> (this as Array<*>).forEach { if (it != null) list.addAll(it.flattenCommandComponents()) }
Iterable::class.java -> (this as Iterable<*>).forEach { if (it != null) list.addAll(it.flattenCommandComponents()) }
else -> list.add(this.toString())
}
return list
}
internal inline fun <reified T : Annotation> KAnnotatedElement.hasAnnotation(): Boolean =
findAnnotation<T>() != null

View File

@ -1,97 +0,0 @@
@file:Suppress("NOTHING_TO_INLINE", "INVISIBLE_MEMBER", "INVISIBLE_REFERENCE", "unused", "MemberVisibilityCanBePrivate")
package net.mamoe.mirai.console.command
import net.mamoe.mirai.message.data.MessageChain
import net.mamoe.mirai.message.data.PlainText
import net.mamoe.mirai.message.data.SingleMessage
/**
* 指令描述. 包含名称, 权限要求, 参数解析器环境, 参数列表.
*
* 这是指令系统较低级的 API. 大部分情况下请使用 [Command]
*/
class CommandDescriptor(
val names: Array<String> = arrayOf(),
val subCommands: List<SubCommandDescriptor>,
val prefixOptional: Boolean = false,
/** 覆盖内建的指令参数解析器环境. */
overrideContext: CommandParserContext = CommandParserContext.Empty
) {
/** 子指令描述 */
inner class SubCommandDescriptor(
/** 子指令名, 如 "/mute group add" 中的 "group add". 当表示默认指令时为父指令名. 包含别名*/
val names: Array<String> = arrayOf(),
/** 用法说明 */
val usage: String,
/** 指令参数列表, 有顺序. */
val params: List<CommandParam<*>>,
/** 指令说明 */
val description: String,
/**
* 指令权限
* @see CommandPermission.or 要求其中一个权限
* @see CommandPermission.and 同时要求两个权限
*/
val permission: CommandPermission = CommandPermission.Default,
/** 指令执行器 */
val onCommand: suspend (sender: CommandSender, args: CommandArgs) -> Boolean
) {
init {
names.forEach {subName ->
require(!(subName.startsWith(' ') || subName.endsWith(' '))) { "subName mustn't start or end with a caret" }
require(subName.isValidSubName()) { "subName mustn't contain any of $ILLEGAL_SUB_NAME_CHARS" }
}
}
@JvmField
internal val bakedSubNames: Array<Array<String>> = names.map { it.bakeSubName() }.toTypedArray()
internal inline val parent: CommandDescriptor get() = this@CommandDescriptor
}
/**
* 指令参数解析器环境.
*/
val context: CommandParserContext = CommandParserContext.Builtins + overrideContext
}
internal val CommandDescriptor.base: CommandDescriptor.SubCommandDescriptor get() = subCommands[0]
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()
/**
* 检查指令参数数量是否足够, 类型是否匹配.
* @throws IllegalArgumentException
*/
fun CommandDescriptor.SubCommandDescriptor.checkArgs(args: CommandArgs) {
require(args.size >= this.params.size) { "No enough args. Required ${params.size}, but given ${args.size}" }
params.forEachIndexed { index, commandParam ->
require(commandParam.type.isInstance(args[index])) {
"Illegal arg #$index, required ${commandParam.type.qualifiedName}, but given ${args[index]::class.qualifiedName}"
}
}
}
internal fun Any.flattenCommandComponents(): List<String> {
val list = ArrayList<String>()
when (this) {
is String -> list.addAll(split(' ').filterNot { it.isBlank() })
is PlainText -> list.addAll(content.flattenCommandComponents())
is SingleMessage -> list.add(this.toString())
is MessageChain -> this.asSequence().forEach { list.addAll(it.flattenCommandComponents()) }
else -> throw IllegalArgumentException("Illegal component: $this")
}
return list
}
internal fun Any.checkFullName(errorHint: String): Array<String> {
return flattenCommandComponents().toList().also {
require(it.isNotEmpty()) { "$errorHint must not be empty" }
}.toTypedArray()
}

View File

@ -7,21 +7,25 @@ import kotlinx.atomicfu.locks.withLock
import net.mamoe.mirai.console.plugins.PluginBase
import net.mamoe.mirai.message.data.Message
import net.mamoe.mirai.message.data.MessageChain
import net.mamoe.mirai.utils.MiraiExperimentalAPI
import java.util.concurrent.locks.ReentrantLock
import kotlin.reflect.KClass
import net.mamoe.mirai.message.data.SingleMessage
sealed class CommandOwner
object TestCommandOwner : CommandOwner()
abstract class PluginCommandOwner(plugin: PluginBase) : CommandOwner()
// 由前端实现
internal abstract class ConsoleCommandOwner : CommandOwner()
/**
* 获取已经注册了的指令列表
*/
val CommandOwner.registeredCommands: List<Command> get() = InternalCommandManager.registeredCommands.filter { it.owner == this }
@get:JvmName("getCommandPrefix")
val CommandPrefix: String get() = InternalCommandManager.COMMAND_PREFIX
val CommandPrefix: String
get() = InternalCommandManager.COMMAND_PREFIX
fun CommandOwner.unregisterAllCommands() {
for (registeredCommand in registeredCommands) {
@ -33,136 +37,65 @@ fun CommandOwner.unregisterAllCommands() {
* 注册一个指令. 若此指令已经注册或有已经注册的指令与 [SubCommandDescriptor] 重名, 返回 `false`
*/
fun Command.register(): Boolean = InternalCommandManager.modifyLock.withLock {
if (findDuplicate() != null) {
return false
}
if (findDuplicate() != null) return false
InternalCommandManager.registeredCommands.add(this@register)
if (this.prefixOptional) {
for (name in this.names) {
InternalCommandManager.optionalPrefixCommandMap[name] = this
}
} else {
for (name in this.names) {
InternalCommandManager.requiredPrefixCommandMap[name] = this
}
}
return true
}
/**
* 查找是否有重名的指令. 返回重名的指令.
*/
fun Command.findDuplicate(): Command? {
return InternalCommandManager.registeredCommands.firstOrNull {
it.descriptor.base.bakedSubNames intersects this.descriptor.base.bakedSubNames
}
}
private infix fun <T> Array<T>.intersects(other: Array<T>): Boolean {
val max = this.size.coerceAtMost(other.size)
for (i in 0 until max) {
if (this[i] == other[i]) return true
}
return false
}
fun Command.findDuplicate(): Command? =
InternalCommandManager.registeredCommands.firstOrNull { it.names intersects this.names }
/**
* 取消注册这个指令. 若指令未注册, 返回 `false`
*/
fun Command.unregister(): Boolean = InternalCommandManager.modifyLock.withLock {
return InternalCommandManager.registeredCommands.remove(this)
InternalCommandManager.registeredCommands.remove(this)
}
//// executing
/**
* 解析并执行一个指令
* @param args 接受 [String] [Message]
*
* @param messages 接受 [String] [Message], 其他对象将会被 [Any.toString]
* @return 是否成功解析到指令. 返回 `false` 代表无任何指令匹配
*/
@MiraiExperimentalAPI
suspend fun CommandSender.executeCommand(vararg args: Any): Boolean {
val command = InternalCommandManager.matchCommand(args[0].toString()) ?: return false
TODO()
//return args.flattenCommandComponents().executeCommand(this)
suspend fun CommandSender.executeCommand(vararg messages: Any): Boolean {
if (messages.isEmpty()) return false
return executeCommandInternal(
messages,
messages[0].let { if (it is SingleMessage) it.toString() else it.toString().substringBefore(' ') })
}
internal inline fun <reified T> List<T>.dropToTypedArray(n: Int): Array<T> = Array(size - n) { this[n + it] }
/**
* 解析并执行一个指令
* @return 是否成功解析到指令. 返回 `false` 代表无任何指令匹配
*/
suspend fun MessageChain.executeAsCommand(sender: CommandSender): Boolean {
TODO()
/// return this.flattenCommandComponents().executeCommand(sender)
suspend fun CommandSender.executeCommand(message: MessageChain): Boolean {
if (message.isEmpty()) return false
return executeCommandInternal(message, message[0].toString())
}
/**
* 检查指令参数并直接执行一个指令.
*/
suspend inline fun CommandSender.execute(command: CommandDescriptor.SubCommandDescriptor, args: CommandArgs): Boolean {
command.checkArgs(args)
return command.onCommand(this@execute, args)
}
/**
* 检查指令参数并直接执行一个指令.
*/
suspend inline fun Command.execute(sender: CommandSender, args: CommandArgs): Boolean =
TODO()
//sender.execute(this, args)
/**
* 核心执行指令
*/
internal suspend fun List<Any>.executeCommand(origin: String, sender: CommandSender): Boolean {
if (this.isEmpty()) return false
val command = InternalCommandManager.matchCommand(origin) ?: return false
TODO()
}
internal infix fun Array<String>.matchesBeginning(list: List<Any>): Boolean {
this.forEachIndexed { index, any ->
if (list[index] != any) return false
}
internal suspend inline fun CommandSender.executeCommandInternal(
messages: Any,
commandName: String
): Boolean {
val command = InternalCommandManager.matchCommand(commandName) ?: return false
val rawInput = messages.flattenCommandComponents()
command.onCommand(this, rawInput.dropToTypedArray(1))
return true
}
internal object InternalCommandManager {
const val COMMAND_PREFIX = "/"
/**
* 全部注册的指令
* /mute -> MuteCommand
* /jinyan -> MuteCommand
*/
@JvmField
internal val registeredCommands: MutableMap<String, Command> = mutableMapOf()
/**
* Command name of commands that are prefix optional
* mute -> MuteCommand
*/
private val quickMatchCommands: MutableMap<String, Command> = mutableMapOf()
@JvmField
internal val modifyLock = ReentrantLock()
/**
* 从原始的command中解析出Command对象
*/
internal fun matchCommand(rawCommand: String): Command? {
if(!rawCommand.startsWith('/')){
return quickMatchCommands[rawCommand
.substringBefore(' ')
.trim()
]
}
return registeredCommands[rawCommand
.substringBefore(' ')
.trim()
]
}
/**
* 从解析好的第一个字节来获取Command对象
*/
internal fun findCommand(name: String): Command? {
if(!name.startsWith('/')){
return quickMatchCommands[name]
}
return registeredCommands[name]
}
}

View File

@ -28,6 +28,7 @@ interface CommandPermission {
*/
fun CommandSender.hasPermission(): Boolean
/**
* 满足两个权限其中一个即可使用指令
*/ // no extension for Java
@ -38,6 +39,7 @@ interface CommandPermission {
*/ // no extension for Java
infix fun and(another: CommandPermission): CommandPermission = AndCommandPermission(this, another)
/**
* 任何人都可以使用这个指令
*/
@ -53,7 +55,7 @@ interface CommandPermission {
}
/**
* 来自任何 [Bot] 的任何一个管理员或群主都可以使用这个指令
* 来自任何 [Bot] 的任何一个管理员或群主都可以使用这个指令
*/
object Operator : CommandPermission {
override fun CommandSender.hasPermission(): Boolean {
@ -95,10 +97,7 @@ interface CommandPermission {
override fun CommandSender.hasPermission(): Boolean = false
}
companion object {
@JvmStatic
val Default: CommandPermission = Manager or Console
}
object Default : CommandPermission by (Manager or Console)
}
/**
@ -115,7 +114,9 @@ inline fun AnonymousCommandPermission(crossinline block: CommandSender.() -> Boo
inline fun CommandSender.hasPermission(permission: CommandPermission): Boolean =
permission.run { this@hasPermission.hasPermission() }
inline fun CommandPermission.hasPermission(sender: CommandSender): Boolean = this.run { sender.hasPermission() }
inline fun CommandPermission.testPermission(sender: CommandSender): Boolean = this.run { sender.hasPermission() }
internal class OrCommandPermission(
private val first: CommandPermission,

View File

@ -0,0 +1,63 @@
@file:Suppress("NOTHING_TO_INLINE")
package net.mamoe.mirai.console.command.description
import net.mamoe.mirai.console.command.CommandSender
import net.mamoe.mirai.message.data.SingleMessage
import net.mamoe.mirai.message.data.content
import kotlin.contracts.contract
/**
* this output type of that arg
* input is always String
*/
abstract class CommandArgParser<out T : Any> {
abstract fun parse(raw: String, sender: CommandSender): T
open fun parse(raw: SingleMessage, sender: CommandSender): T = parse(raw.content, sender)
}
fun <T : Any> CommandArgParser<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}")
}
}
@Suppress("unused")
@JvmSynthetic
inline fun CommandArgParser<*>.illegalArgument(message: String, cause: Throwable? = null): Nothing {
throw ParserException(message, cause)
}
@JvmSynthetic
inline fun CommandArgParser<*>.checkArgument(
condition: Boolean,
crossinline message: () -> String = { "Check failed." }
) {
contract {
returns() implies condition
}
if (!condition) illegalArgument(message())
}
/**
* 创建匿名 [CommandArgParser]
*/
@Suppress("FunctionName")
@JvmSynthetic
inline fun <T : Any> CommandArgParser(
crossinline parser: CommandArgParser<T>.(s: String, sender: CommandSender) -> T
): CommandArgParser<T> = object : CommandArgParser<T>() {
override fun parse(raw: String, sender: CommandSender): T = parser(raw, sender)
}
/**
* 在解析参数时遇到的 _正常_ 错误. 如参数不符合规范.
*/
class ParserException(message: String, cause: Throwable? = null) : RuntimeException(message, cause)

View File

@ -1,8 +1,19 @@
@file:Suppress("NOTHING_TO_INLINE")
/*
* 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
package net.mamoe.mirai.console.command.description
import net.mamoe.mirai.Bot
import net.mamoe.mirai.console.command.BotAwareCommandSender
import net.mamoe.mirai.console.command.CommandSender
import net.mamoe.mirai.console.command.MemberCommandSender
import net.mamoe.mirai.console.command.UserCommandSender
import net.mamoe.mirai.console.utils.fuzzySearchMember
import net.mamoe.mirai.contact.Friend
import net.mamoe.mirai.contact.Group
@ -10,62 +21,6 @@ import net.mamoe.mirai.contact.Member
import net.mamoe.mirai.message.data.At
import net.mamoe.mirai.message.data.SingleMessage
import net.mamoe.mirai.message.data.content
import kotlin.contracts.contract
/**
* this output type of that arg
* input is always String
*/
abstract class CommandArgParser<out T : Any> {
abstract fun parse(raw: String, sender: CommandSender): T
open fun parse(raw: SingleMessage, sender: CommandSender): T = parse(raw.content, sender)
}
fun <T : Any> CommandArgParser<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}")
}
}
@Suppress("unused")
@JvmSynthetic
inline fun CommandArgParser<*>.illegalArgument(message: String, cause: Throwable? = null): Nothing {
throw ParserException(message, cause)
}
@JvmSynthetic
inline fun CommandArgParser<*>.checkArgument(
condition: Boolean,
crossinline message: () -> String = { "Check failed." }
) {
contract {
returns() implies condition
}
if (!condition) illegalArgument(message())
}
/**
* 创建匿名 [CommandArgParser]
*/
@Suppress("FunctionName")
@JvmSynthetic
inline fun <T : Any> CommandArgParser(
crossinline parser: CommandArgParser<T>.(s: String, sender: CommandSender) -> T
): CommandArgParser<T> = object : CommandArgParser<T>() {
override fun parse(raw: String, sender: CommandSender): T = parser(raw, sender)
}
/**
* 在解析参数时遇到的 _正常_ 错误. 如参数不符合规范.
*/
class ParserException(message: String, cause: Throwable? = null) : RuntimeException(message, cause)
object IntArgParser : CommandArgParser<Int>() {
@ -99,7 +54,10 @@ object FloatArgParser : CommandArgParser<Float>() {
}
object StringArgParser : CommandArgParser<String>() {
override fun parse(raw: String, sender: CommandSender): String = raw
override fun parse(raw: String, sender: CommandSender): String {
println("STRING PARSER! $raw")
return raw
}
}
object BooleanArgParser : CommandArgParser<Boolean>() {

View File

@ -9,13 +9,15 @@
@file:Suppress("NOTHING_TO_INLINE", "INVISIBLE_MEMBER", "INVISIBLE_REFERENCE", "unused", "MemberVisibilityCanBePrivate")
package net.mamoe.mirai.console.command
package net.mamoe.mirai.console.command.description
import net.mamoe.mirai.Bot
import net.mamoe.mirai.console.command.CommandParserContext.ParserPair
import net.mamoe.mirai.console.command.CommandSender
import net.mamoe.mirai.console.command.description.CommandParserContext.ParserPair
import net.mamoe.mirai.contact.Friend
import net.mamoe.mirai.contact.Group
import net.mamoe.mirai.contact.Member
import net.mamoe.mirai.utils.MiraiExperimentalAPI
import kotlin.internal.LowPriorityInOverloadResolution
import kotlin.reflect.KClass
import kotlin.reflect.full.isSubclassOf
@ -53,28 +55,16 @@ interface CommandParserContext {
Bot::class with ExistBotArgParser
Friend::class with ExistFriendArgParser
})
object Empty : CommandParserContext by CustomCommandParserContext(listOf())
}
fun <T : Any> CommandParserContext.parserFor(param: CommandParam<T>): CommandArgParser<T>? =
param.overrideParser ?: this[param.type]
fun <T : Any> CommandDescriptor.parserFor(param: CommandParam<T>): CommandArgParser<T>? =
this.context.parserFor(param)
fun <T : Any> CommandDescriptor.SubCommandDescriptor.parserFor(param: CommandParam<T>): CommandArgParser<T>? =
this.parent.parserFor(param)
fun <T : Any> Command.parserFor(param: CommandParam<T>): CommandArgParser<T>? =
this.descriptor.parserFor(param)
object EmptyCommandParserContext : CommandParserContext by CustomCommandParserContext(listOf())
/**
* 合并两个 [CommandParserContext], [replacer] 将会替换 [this] 中重复的 parser.
*/
operator fun CommandParserContext.plus(replacer: CommandParserContext): CommandParserContext {
if (replacer == CommandParserContext.Empty) return this
if (this == CommandParserContext.Empty) return replacer
if (replacer == EmptyCommandParserContext) return this
if (this == EmptyCommandParserContext) return replacer
return object : CommandParserContext {
override fun <T : Any> get(klass: KClass<out T>): CommandArgParser<T>? = replacer[klass] ?: this@plus[klass]
override fun toList(): List<ParserPair<*>> = replacer.toList() + this@plus.toList()
@ -86,7 +76,7 @@ operator fun CommandParserContext.plus(replacer: CommandParserContext): CommandP
*/
operator fun CommandParserContext.plus(replacer: List<ParserPair<*>>): CommandParserContext {
if (replacer.isEmpty()) return this
if (this == CommandParserContext.Empty) return CustomCommandParserContext(replacer)
if (this == EmptyCommandParserContext) return CustomCommandParserContext(replacer)
return object : CommandParserContext {
@Suppress("UNCHECKED_CAST")
override fun <T : Any> get(klass: KClass<out T>): CommandArgParser<T>? =
@ -137,6 +127,9 @@ class CommandParserContextBuilder : MutableList<ParserPair<*>> by mutableListOf(
inline infix fun <T : Any> KClass<T>.with(parser: CommandArgParser<T>): ParserPair<*> =
ParserPair(this, parser).also { add(it) }
inline infix fun <reified T : Any> auto(parser: CommandArgParser<T>): ParserPair<*> =
ParserPair(T::class, parser).also { add(it) }
/**
* 添加一个指令解析器
*/
@ -153,6 +146,25 @@ class CommandParserContextBuilder : MutableList<ParserPair<*>> by mutableListOf(
inline infix fun <T : Any> KClass<T>.with(
crossinline parser: CommandArgParser<T>.(s: String) -> T
): ParserPair<*> = ParserPair(this, CommandArgParser { s: String, _: CommandSender -> parser(s) }).also { add(it) }
/**
* 添加一个指令解析器
*/
@MiraiExperimentalAPI
@JvmSynthetic
inline infix fun <reified T : Any> auto(
crossinline parser: CommandArgParser<*>.(s: String) -> T
): ParserPair<*> = T::class with CommandArgParser { s: String, _: CommandSender -> parser(s) }
/**
* 添加一个指令解析器
*/
@MiraiExperimentalAPI
@JvmSynthetic
@LowPriorityInOverloadResolution
inline infix fun <reified T : Any> auto(
crossinline parser: CommandArgParser<*>.(s: String, sender: CommandSender) -> T
): ParserPair<*> = T::class with CommandArgParser(parser)
}

View File

@ -1,13 +1,25 @@
@file:Suppress("unused")
package net.mamoe.mirai.console.command
package net.mamoe.mirai.console.command.description
import net.mamoe.mirai.console.command.Command
import net.mamoe.mirai.console.command.CompositeCommand
import java.lang.reflect.Parameter
import kotlin.reflect.KClass
internal fun Parameter.toCommandParam(): CommandParam<*> {
val name = getAnnotation(CompositeCommand.Name::class.java)
return CommandParam(
name?.name ?: this.name ?: throw IllegalArgumentException("Cannot construct CommandParam from a unnamed param"),
this.type.kotlin
)
}
/**
* 指令形式参数.
* @see toCommandParam
*/
data class CommandParam<T : Any>(
internal data class CommandParam<T : Any>(
/**
* 参数名. 不允许重复.
*/
@ -36,12 +48,3 @@ data class CommandParam<T : Any>(
val overrideParser: CommandArgParser<T>? get() = _overrideParser
}
fun <T : Any> CommandParam<T>.parserFrom(command: Command): CommandArgParser<T>? = command.parserFor(this)
fun <T : Any> CommandParam<T>.parserFrom(descriptor: CommandDescriptor): CommandArgParser<T>? =
descriptor.parserFor(this)
fun <T : Any> CommandParam<T>.parserFrom(descriptor: CommandDescriptor.SubCommandDescriptor): CommandArgParser<T>? =
descriptor.parserFor(this)
fun <T : Any> CommandParam<T>.parserFrom(context: CommandParserContext): CommandArgParser<T>? = context.parserFor(this)

View File

@ -0,0 +1,64 @@
/*
* 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
import java.util.concurrent.locks.ReentrantLock
internal infix fun Array<String>.matchesBeginning(list: List<Any>): Boolean {
this.forEachIndexed { index, any ->
if (list[index] != any) return false
}
return true
}
internal object InternalCommandManager {
const val COMMAND_PREFIX = "/"
@JvmField
internal val registeredCommands: MutableList<Command> = mutableListOf()
/**
* 全部注册的指令
* /mute -> MuteCommand
* /jinyan -> MuteCommand
*/
@JvmField
internal val requiredPrefixCommandMap: MutableMap<String, Command> = mutableMapOf()
/**
* Command name of commands that are prefix optional
* mute -> MuteCommand
*/
@JvmField
internal val optionalPrefixCommandMap: MutableMap<String, Command> = mutableMapOf()
@JvmField
internal val modifyLock = ReentrantLock()
/**
* 从原始的 command 中解析出 Command 对象
*/
internal fun matchCommand(rawCommand: String): Command? {
if (rawCommand.startsWith(COMMAND_PREFIX)) {
return requiredPrefixCommandMap[rawCommand.substringAfter(COMMAND_PREFIX)]
}
return optionalPrefixCommandMap[rawCommand]
}
}
internal infix fun <T> Array<out T>.intersects(other: Array<out T>): Boolean {
val max = this.size.coerceAtMost(other.size)
for (i in 0 until max) {
if (this[i] == other[i]) return true
}
return false
}

View File

@ -0,0 +1,32 @@
package net.mamoe.mirai.console.event
import net.mamoe.mirai.console.command.Command
import net.mamoe.mirai.console.command.CommandSender
import net.mamoe.mirai.event.AbstractEvent
import net.mamoe.mirai.event.CancellableEvent
data class CommandExecutionEvent(
val sender: CommandSender,
val command: Command,
val rawArgs: Array<Any>
) : AbstractEvent(), CancellableEvent, ConsoleEvent {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as CommandExecutionEvent
if (sender != other.sender) return false
if (command != other.command) return false
if (!rawArgs.contentEquals(other.rawArgs)) return false
return true
}
override fun hashCode(): Int {
var result = sender.hashCode()
result = 31 * result + command.hashCode()
result = 31 * result + rawArgs.contentHashCode()
return result
}
}

View File

@ -0,0 +1,8 @@
package net.mamoe.mirai.console.event
import net.mamoe.mirai.event.Event
/**
* 表示来自 mirai-console 的事件
*/
interface ConsoleEvent : Event

View File

@ -9,7 +9,7 @@
@file:Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
package net.mamoe.mirai.console.events
package net.mamoe.mirai.console.event
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.runBlocking

View File

@ -14,7 +14,7 @@ package net.mamoe.mirai.console.plugins
import kotlinx.coroutines.*
import net.mamoe.mirai.console.command.Command
import net.mamoe.mirai.console.command.CommandSender
import net.mamoe.mirai.console.events.EventListener
import net.mamoe.mirai.console.event.EventListener
import net.mamoe.mirai.console.scheduler.PluginScheduler
import net.mamoe.mirai.utils.MiraiLogger
import java.io.File

View File

@ -0,0 +1,29 @@
/*
* 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
import org.junit.jupiter.api.Test
object TestCompositeCommand : CompositeCommand(
TestCommandOwner,
"name1", "name2",
description = """
desc
""".trimIndent()
)
internal class TestComposite {
@Test
fun testRegister() {
TestCompositeCommand.register()
}
}