mirror of
https://github.com/mamoe/mirai.git
synced 2025-01-10 18:40:15 +08:00
Change SubCommandDescriptor calling from callSuspend
to callSuspendBy
- Support optional argument now.
This commit is contained in:
parent
48f5c947b6
commit
5f6873e347
@ -20,6 +20,7 @@ import net.mamoe.mirai.console.permission.PermissionId
|
|||||||
import net.mamoe.mirai.console.permission.PermitteeId
|
import net.mamoe.mirai.console.permission.PermitteeId
|
||||||
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
|
import net.mamoe.mirai.console.util.ConsoleExperimentalApi
|
||||||
import net.mamoe.mirai.contact.*
|
import net.mamoe.mirai.contact.*
|
||||||
|
import net.mamoe.mirai.message.data.MessageContent
|
||||||
import kotlin.internal.LowPriorityInOverloadResolution
|
import kotlin.internal.LowPriorityInOverloadResolution
|
||||||
import kotlin.reflect.KClass
|
import kotlin.reflect.KClass
|
||||||
import kotlin.reflect.full.isSubclassOf
|
import kotlin.reflect.full.isSubclassOf
|
||||||
@ -84,6 +85,8 @@ public interface CommandArgumentContext {
|
|||||||
|
|
||||||
PermissionId::class with PermissionIdArgumentParser
|
PermissionId::class with PermissionIdArgumentParser
|
||||||
PermitteeId::class with PermitteeIdArgumentParser
|
PermitteeId::class with PermitteeIdArgumentParser
|
||||||
|
|
||||||
|
MessageContent::class with RawContentArgumentParser
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -19,10 +19,7 @@ import net.mamoe.mirai.console.permission.PermitteeId
|
|||||||
import net.mamoe.mirai.contact.*
|
import net.mamoe.mirai.contact.*
|
||||||
import net.mamoe.mirai.getFriendOrNull
|
import net.mamoe.mirai.getFriendOrNull
|
||||||
import net.mamoe.mirai.getGroupOrNull
|
import net.mamoe.mirai.getGroupOrNull
|
||||||
import net.mamoe.mirai.message.data.At
|
import net.mamoe.mirai.message.data.*
|
||||||
import net.mamoe.mirai.message.data.MessageContent
|
|
||||||
import net.mamoe.mirai.message.data.SingleMessage
|
|
||||||
import net.mamoe.mirai.message.data.content
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -86,9 +83,9 @@ public object StringArgumentParser : InternalCommandArgumentParserExtensions<Str
|
|||||||
public object BooleanArgumentParser : InternalCommandArgumentParserExtensions<Boolean> {
|
public object BooleanArgumentParser : InternalCommandArgumentParserExtensions<Boolean> {
|
||||||
public override fun parse(raw: String, sender: CommandSender): Boolean = raw.trim().let { str ->
|
public override fun parse(raw: String, sender: CommandSender): Boolean = raw.trim().let { str ->
|
||||||
str.equals("true", ignoreCase = true)
|
str.equals("true", ignoreCase = true)
|
||||||
|| str.equals("yes", ignoreCase = true)
|
|| str.equals("yes", ignoreCase = true)
|
||||||
|| str.equals("enabled", ignoreCase = true)
|
|| str.equals("enabled", ignoreCase = true)
|
||||||
|| str.equals("on", ignoreCase = true)
|
|| str.equals("on", ignoreCase = true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -331,6 +328,12 @@ public object PermitteeIdArgumentParser : CommandArgumentParser<PermitteeId> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 直接返回原始参数 [MessageContent] */
|
||||||
|
public object RawContentArgumentParser : CommandArgumentParser<MessageContent> {
|
||||||
|
override fun parse(raw: String, sender: CommandSender): MessageContent = PlainText(raw)
|
||||||
|
override fun parse(raw: MessageContent, sender: CommandSender): MessageContent = raw
|
||||||
|
}
|
||||||
|
|
||||||
internal interface InternalCommandArgumentParserExtensions<T : Any> : CommandArgumentParser<T> {
|
internal interface InternalCommandArgumentParserExtensions<T : Any> : CommandArgumentParser<T> {
|
||||||
fun String.parseToLongOrFail(): Long = toLongOrNull() ?: illegalArgument("无法解析 $this 为整数")
|
fun String.parseToLongOrFail(): Long = toLongOrNull() ?: illegalArgument("无法解析 $this 为整数")
|
||||||
|
|
||||||
@ -365,10 +368,10 @@ internal interface InternalCommandArgumentParserExtensions<T : Any> : CommandArg
|
|||||||
} else {
|
} else {
|
||||||
var index = 1
|
var index = 1
|
||||||
illegalArgument("无法找到成员 $idOrCard。 多个成员满足搜索结果或匹配度不足: \n\n" +
|
illegalArgument("无法找到成员 $idOrCard。 多个成员满足搜索结果或匹配度不足: \n\n" +
|
||||||
candidates.joinToString("\n", limit = 6) {
|
candidates.joinToString("\n", limit = 6) {
|
||||||
val percentage = (it.second * 100).toDecimalPlace(0)
|
val percentage = (it.second * 100).toDecimalPlace(0)
|
||||||
"#${index++}(${percentage}%)${it.first.nameCardOrNick.truncate(10)}(${it.first.id})" // #1 15.4%
|
"#${index++}(${percentage}%)${it.first.nameCardOrNick.truncate(10)}(${it.first.id})" // #1 15.4%
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,20 +14,25 @@ package net.mamoe.mirai.console.internal.command
|
|||||||
import net.mamoe.mirai.console.command.CompositeCommand
|
import net.mamoe.mirai.console.command.CompositeCommand
|
||||||
import net.mamoe.mirai.console.command.description.CommandArgumentParser
|
import net.mamoe.mirai.console.command.description.CommandArgumentParser
|
||||||
import java.lang.reflect.Parameter
|
import java.lang.reflect.Parameter
|
||||||
|
import kotlin.contracts.ExperimentalContracts
|
||||||
|
import kotlin.contracts.contract
|
||||||
import kotlin.reflect.KClass
|
import kotlin.reflect.KClass
|
||||||
|
import kotlin.reflect.KParameter
|
||||||
|
|
||||||
|
/*
|
||||||
internal fun Parameter.toCommandParam(): CommandParameter<*> {
|
internal fun Parameter.toCommandParam(): CommandParameter<*> {
|
||||||
val name = getAnnotation(CompositeCommand.Name::class.java)
|
val name = getAnnotation(CompositeCommand.Name::class.java)
|
||||||
return CommandParameter(
|
return CommandParameter(
|
||||||
name?.value ?: this.name
|
name?.value ?: this.name
|
||||||
?: throw IllegalArgumentException("Cannot construct CommandParam from a unnamed param"),
|
?: throw IllegalArgumentException("Cannot construct CommandParam from a unnamed param"),
|
||||||
this.type.kotlin
|
this.type.kotlin,
|
||||||
|
null
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 指令形式参数.
|
* 指令形式参数.
|
||||||
* @see toCommandParam
|
|
||||||
*/
|
*/
|
||||||
internal data class CommandParameter<T : Any>(
|
internal data class CommandParameter<T : Any>(
|
||||||
/**
|
/**
|
||||||
@ -37,9 +42,12 @@ internal data class CommandParameter<T : Any>(
|
|||||||
/**
|
/**
|
||||||
* 参数类型. 将从 [CompositeCommand.context] 中寻找 [CommandArgumentParser] 解析.
|
* 参数类型. 将从 [CompositeCommand.context] 中寻找 [CommandArgumentParser] 解析.
|
||||||
*/
|
*/
|
||||||
val type: KClass<T> // exact type
|
val type: KClass<T>, // exact type
|
||||||
|
val parameter: KParameter, // source parameter
|
||||||
) {
|
) {
|
||||||
constructor(name: String, type: KClass<T>, parser: CommandArgumentParser<T>) : this(name, type) {
|
constructor(name: String, type: KClass<T>, parameter: KParameter, parser: CommandArgumentParser<T>) : this(
|
||||||
|
name, type, parameter
|
||||||
|
) {
|
||||||
this._overrideParser = parser
|
this._overrideParser = parser
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,7 +21,8 @@ import net.mamoe.mirai.message.data.*
|
|||||||
import kotlin.reflect.KAnnotatedElement
|
import kotlin.reflect.KAnnotatedElement
|
||||||
import kotlin.reflect.KClass
|
import kotlin.reflect.KClass
|
||||||
import kotlin.reflect.KFunction
|
import kotlin.reflect.KFunction
|
||||||
import kotlin.reflect.full.callSuspend
|
import kotlin.reflect.KParameter
|
||||||
|
import kotlin.reflect.full.callSuspendBy
|
||||||
import kotlin.reflect.full.declaredFunctions
|
import kotlin.reflect.full.declaredFunctions
|
||||||
import kotlin.reflect.full.findAnnotation
|
import kotlin.reflect.full.findAnnotation
|
||||||
import kotlin.reflect.full.isSubclassOf
|
import kotlin.reflect.full.isSubclassOf
|
||||||
@ -131,8 +132,9 @@ internal abstract class AbstractReflectionCommand
|
|||||||
val params: Array<CommandParameter<*>>,
|
val params: Array<CommandParameter<*>>,
|
||||||
val description: String,
|
val description: String,
|
||||||
val permission: Permission,
|
val permission: Permission,
|
||||||
val onCommand: suspend (sender: CommandSender, parsedArgs: Array<out Any>) -> Boolean,
|
val onCommand: suspend (sender: CommandSender, parsedArgs: Map<KParameter, Any?>) -> Boolean,
|
||||||
val context: CommandArgumentContext,
|
val context: CommandArgumentContext,
|
||||||
|
val argumentBuilder: (sender: CommandSender) -> MutableMap<KParameter, Any?>,
|
||||||
) {
|
) {
|
||||||
val usage: String = createUsage(this@AbstractReflectionCommand)
|
val usage: String = createUsage(this@AbstractReflectionCommand)
|
||||||
|
|
||||||
@ -151,21 +153,40 @@ internal abstract class AbstractReflectionCommand
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun KParameter.isOptional(): Boolean {
|
||||||
|
return isOptional || this.type.isMarkedNullable
|
||||||
|
}
|
||||||
|
|
||||||
|
val minimalArgumentsSize = params.count {
|
||||||
|
!it.parameter.isOptional()
|
||||||
|
}
|
||||||
|
|
||||||
@JvmField
|
@JvmField
|
||||||
internal val bakedSubNames: Array<Array<String>> = names.map { it.bakeSubName() }.toTypedArray()
|
internal val bakedSubNames: Array<Array<String>> = names.map { it.bakeSubName() }.toTypedArray()
|
||||||
private fun parseArgs(sender: CommandSender, rawArgs: MessageChain, offset: Int): Array<out Any>? {
|
private fun parseArgs(sender: CommandSender, rawArgs: MessageChain, offset: Int): MutableMap<KParameter, Any?>? {
|
||||||
if (rawArgs.size < offset + this.params.size)
|
if (rawArgs.size < offset + minimalArgumentsSize)
|
||||||
return null
|
return null
|
||||||
//require(rawArgs.size >= offset + this.params.size) { "No enough args. Required ${params.size}, but given ${rawArgs.size - offset}" }
|
//require(rawArgs.size >= offset + this.params.size) { "No enough args. Required ${params.size}, but given ${rawArgs.size - offset}" }
|
||||||
|
return argumentBuilder(sender).also { result ->
|
||||||
return Array(this.params.size) { index ->
|
params.forEachIndexed { index, parameter ->
|
||||||
val param = params[index]
|
val rawArg = rawArgs.getOrNull(offset + index)
|
||||||
val rawArg = rawArgs[offset + index]
|
result[parameter.parameter] = when (rawArg) {
|
||||||
when (rawArg) {
|
null -> {
|
||||||
is PlainText -> context[param.type]?.parse(rawArg.content, sender)
|
val p = parameter.parameter
|
||||||
is MessageContent -> context[param.type]?.parse(rawArg, sender)
|
when {
|
||||||
else -> throw IllegalArgumentException("Illegal Message kind: ${rawArg.kClassQualifiedNameOrTip}")
|
p.isOptional -> return@forEachIndexed
|
||||||
} ?: error("Cannot find a parser for $rawArg")
|
p.type.isMarkedNullable -> {
|
||||||
|
result[parameter.parameter] = null
|
||||||
|
return@forEachIndexed
|
||||||
|
}
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is PlainText -> context[parameter.type]?.parse(rawArg.content, sender)
|
||||||
|
is MessageContent -> context[parameter.type]?.parse(rawArg, sender)
|
||||||
|
else -> throw IllegalArgumentException("Illegal Message kind: ${rawArg.kClassQualifiedNameOrTip}")
|
||||||
|
} ?: error("Cannot find a parser for $rawArg")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -250,6 +271,10 @@ internal fun AbstractReflectionCommand.SubCommandDescriptor.createUsage(baseComm
|
|||||||
appendLine()
|
appendLine()
|
||||||
}.trimEnd()
|
}.trimEnd()
|
||||||
|
|
||||||
|
internal fun <T1, R1, R2> ((T1) -> R1).then(then: (T1, R1) -> R2): ((T1) -> R2) {
|
||||||
|
return { a -> then.invoke(a, (this@then(a))) }
|
||||||
|
}
|
||||||
|
|
||||||
internal fun AbstractReflectionCommand.createSubCommand(
|
internal fun AbstractReflectionCommand.createSubCommand(
|
||||||
function: KFunction<*>,
|
function: KFunction<*>,
|
||||||
context: CommandArgumentContext,
|
context: CommandArgumentContext,
|
||||||
@ -273,12 +298,17 @@ internal fun AbstractReflectionCommand.createSubCommand(
|
|||||||
check(!function.returnType.isMarkedNullable) {
|
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})")
|
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})")
|
||||||
}
|
}
|
||||||
|
var argumentBuilder: (sender: CommandSender) -> MutableMap<KParameter, Any?> = { HashMap() }
|
||||||
val parameters = function.parameters.toMutableList()
|
val parameters = function.parameters.toMutableList()
|
||||||
|
|
||||||
if (notStatic) parameters.removeAt(0) // instance
|
if (notStatic) {
|
||||||
|
val type = parameters.removeAt(0) // instance
|
||||||
|
argumentBuilder = argumentBuilder.then { _, map ->
|
||||||
|
map[type] = this@createSubCommand
|
||||||
|
map
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var hasSenderParam = false
|
|
||||||
check(parameters.isNotEmpty()) {
|
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 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})"
|
||||||
}
|
}
|
||||||
@ -291,8 +321,11 @@ internal fun AbstractReflectionCommand.createSubCommand(
|
|||||||
|
|
||||||
(parameters.first()).let { receiver ->
|
(parameters.first()).let { receiver ->
|
||||||
if ((receiver.type.classifier as? KClass<*>)?.isSubclassOf(CommandSender::class) == true) {
|
if ((receiver.type.classifier as? KClass<*>)?.isSubclassOf(CommandSender::class) == true) {
|
||||||
hasSenderParam = true
|
val senderType = parameters.removeAt(0)
|
||||||
parameters.removeAt(0)
|
argumentBuilder = argumentBuilder.then { sender, map ->
|
||||||
|
map[senderType] = sender
|
||||||
|
map
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -313,37 +346,32 @@ internal fun AbstractReflectionCommand.createSubCommand(
|
|||||||
//map parameter
|
//map parameter
|
||||||
val params = parameters.map { param ->
|
val params = parameters.map { param ->
|
||||||
|
|
||||||
if (param.isOptional) error("optional parameters are not yet supported. (at ${this::class.qualifiedNameOrTip}.${function.name}.$param)")
|
// if (param.isOptional) error("optional parameters are not yet supported. (at ${this::class.qualifiedNameOrTip}.${function.name}.$param)")
|
||||||
|
|
||||||
val paramName = param.findAnnotation<CompositeCommand.Name>()?.value ?: param.name ?: "unknown"
|
val paramName = param.findAnnotation<CompositeCommand.Name>()?.value ?: param.name ?: "unknown"
|
||||||
CommandParameter(
|
CommandParameter(
|
||||||
paramName,
|
paramName,
|
||||||
(param.type.classifier as? KClass<*>)
|
(param.type.classifier as? KClass<*>)
|
||||||
?: throw IllegalArgumentException("unsolved type reference from param " + param.name + ". (at ${this::class.qualifiedNameOrTip}.${function.name}.$param)")
|
?: throw IllegalArgumentException("unsolved type reference from param " + param.name + ". (at ${this::class.qualifiedNameOrTip}.${function.name}.$param)"),
|
||||||
|
param
|
||||||
)
|
)
|
||||||
}.toTypedArray()
|
}.toTypedArray()
|
||||||
|
|
||||||
|
// TODO: 2020/09/19 检查 optional/nullable 是否都在最后
|
||||||
|
|
||||||
return SubCommandDescriptor(
|
return SubCommandDescriptor(
|
||||||
commandName,
|
commandName,
|
||||||
params,
|
params,
|
||||||
subDescription, // overridePermission?.value
|
subDescription, // overridePermission?.value
|
||||||
permission,//overridePermission?.value?.let { PermissionService.INSTANCE[PermissionId.parseFromString(it)] } ?: permission,
|
permission,//overridePermission?.value?.let { PermissionService.INSTANCE[PermissionId.parseFromString(it)] } ?: permission,
|
||||||
onCommand = { sender: CommandSender, args: Array<out Any> ->
|
onCommand = { sender: CommandSender, args: Map<KParameter, Any?> ->
|
||||||
val result = if (notStatic) {
|
val result = function.callSuspendBy(args)
|
||||||
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})" }
|
checkNotNull(result) { "sub command return value is null (at ${this::class.qualifiedName}.${function.name})" }
|
||||||
|
|
||||||
result as? Boolean ?: true // Unit, void is considered as true.
|
result as? Boolean ?: true // Unit, void is considered as true.
|
||||||
},
|
},
|
||||||
context = context
|
context = context,
|
||||||
|
argumentBuilder = argumentBuilder
|
||||||
)
|
)
|
||||||
}
|
}
|
@ -238,8 +238,34 @@ 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?) {
|
||||||
|
println(arg1)
|
||||||
|
println(arg2)
|
||||||
|
println(arg3)
|
||||||
|
// println(arg3)
|
||||||
|
Testing.ok(Unit)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
optionCommand.withRegistration {
|
||||||
|
withTesting<Unit> {
|
||||||
|
assertSuccess(sender.executeCommand("/testOptional optional 1"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun assertSuccess(result: CommandExecuteResult) {
|
internal fun assertSuccess(result: CommandExecuteResult) {
|
||||||
assertTrue(result.isSuccess(), result.toString())
|
if (result.isFailure()) {
|
||||||
|
throw result.exception ?: AssertionError(result.toString())
|
||||||
|
}
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user