Skip to content

Commit

Permalink
feat: hits when type a wrong sub commands
Browse files Browse the repository at this point in the history
  • Loading branch information
asforest committed Feb 20, 2022
1 parent 844573a commit 8e8732b
Show file tree
Hide file tree
Showing 7 changed files with 109 additions and 64 deletions.
12 changes: 6 additions & 6 deletions src/main/kotlin/command/MiraiTreeCommand.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package com.github.asforest.mshell.command

import com.github.asforest.mshell.command.resolver.PrefixedCommandSignature
import com.github.asforest.mshell.command.mshell.MShellCommand
import com.github.asforest.mshell.command.resolver.TreeCommand
import com.github.asforest.mshell.util.MShellUtils.buildUsage
import net.mamoe.mirai.console.command.*
import net.mamoe.mirai.console.command.descriptor.*
import net.mamoe.mirai.console.permission.Permission
Expand All @@ -25,18 +27,16 @@ abstract class MiraiTreeCommand (
override val prefixOptional: Boolean = false,
) : Command {

abstract val subCommandFunctions: List<PrefixedCommandSignature>
abstract val subCommandFunctions: List<TreeCommand.PrefixedCommandSignature>

override val usage: String by lazy {
subCommandFunctions.joinToString("\n") { pfun ->
val label = pfun.prefix
val prefix = pfun.prefix
val func = pfun.signature

buildString {
append(CommandManager.commandPrefix)
append("$primaryName $label ")
append(func.parameters.joinToString(" ") { parameter -> parameter.identity})
append(" # ${func.description}")
append(buildUsage(MShellCommand.rootLabal + if (prefix.isNotEmpty()) " $prefix" else "", func))
}
}
}
Expand Down
41 changes: 31 additions & 10 deletions src/main/kotlin/command/mshell/MShellCommand.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@ package com.github.asforest.mshell.command.mshell
import com.github.asforest.mshell.MShellPlugin
import com.github.asforest.mshell.command.MiraiTreeCommand
import com.github.asforest.mshell.command.resolver.AbstractArgumentParsers
import com.github.asforest.mshell.command.resolver.CommandSignature
import com.github.asforest.mshell.command.resolver.TreeCommand
import com.github.asforest.mshell.command.resolver.PrefixedCommandSignature
import com.github.asforest.mshell.permission.MShellPermissions
import com.github.asforest.mshell.session.SessionUser
import com.github.asforest.mshell.util.MShellUtils.buildUsage
import com.github.asforest.mshell.util.MShellUtils.toSessionUser
import net.mamoe.mirai.console.command.CommandSender
import net.mamoe.mirai.console.permission.PermissionService.Companion.testPermission
Expand All @@ -21,12 +22,14 @@ object MShellCommand : MiraiTreeCommand(
description = "MShell插件指令",
permission = MShellPermissions.use
) {
val rootLabal = secondaryNames.first()

const val Nobody = 0
const val User = 1 shl 0
const val Admin = 1 shl 1
const val All = User or Admin

override val subCommandFunctions: List<PrefixedCommandSignature> by lazy {
override val subCommandFunctions: List<TreeCommand.PrefixedCommandSignature> by lazy {
MainCommand.allCommands
}

Expand All @@ -46,21 +49,27 @@ object MShellCommand : MiraiTreeCommand(
Nobody
}

val afun = MainCommand.resolveCommandText(commandText.split(" "), senderPermission, "")
afun.callSuspend(CallContext(this, senderPermission))
val prefix = rootLabal
val arguments = commandText.split(" ").filter { it.isNotEmpty() }

if (arguments.isNotEmpty())
{
val afun = MainCommand.resolveCommandText(prefix, arguments, senderPermission)
afun.callSuspend(CallContext(this, senderPermission))
} else {
sendMessage("输入 /$prefix help 来查看帮助信息")
}
} catch (e: TreeCommand.MissingSubCommandException) {
sendMessage("可用的子指令:\n${e.availables.generateUsage(e.prefix)}")
} catch (e: TreeCommand.NoFunctionMatchedException) {
if (commandText.isEmpty())
sendMessage("输入 /${secondaryNames.first()} help 来查看帮助信息")
else
sendMessage("未知指令。输入 /${secondaryNames.first()} help 来查看帮助信息")
sendMessage("未知子指令 ${e.label} 。可用的子指令:${e.availables.joinToString(", ") { it.name }}")
} catch (e: TreeCommand.TooFewArgumentsException) {
val signature = listOf("/${secondaryNames.first()}", e.prefix, e.signature.name, e.signature.params)
val signature = listOf(e.prefix, e.signature.name, e.signature.params)
.filter { it.isNotEmpty() }
.joinToString(" ")
sendMessage("参数太少。正确的参数列表:$signature")
} catch (e: TreeCommand.TooManyArgumentsException) {
val signature = listOf("/${secondaryNames.first()}", e.prefix, e.signature.name, e.signature.params)
val signature = listOf(e.prefix, e.signature.name, e.signature.params)
.filter { it.isNotEmpty() }
.joinToString(" ")
sendMessage("参数太多。正确的参数列表:$signature")
Expand All @@ -71,6 +80,18 @@ object MShellCommand : MiraiTreeCommand(
}
}

private fun List<CommandSignature>.generateUsage(prefix: String): String
{
return buildString {
for (funcation in this@generateUsage)
{
append("/")
append(buildUsage(prefix, funcation))
append("\n")
}
}.trim()
}

data class CallContext(val sender: CommandSender, val permission: Int)
{
val isUser = permission and User
Expand Down
12 changes: 6 additions & 6 deletions src/main/kotlin/command/mshell/MainCommand.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,16 @@ package com.github.asforest.mshell.command.mshell

import com.github.asforest.mshell.MShellPlugin
import com.github.asforest.mshell.command.mshell.MShellCommand.Admin
import com.github.asforest.mshell.command.mshell.MShellCommand.User
import com.github.asforest.mshell.command.mshell.MShellCommand.CallContext
import com.github.asforest.mshell.command.mshell.MShellCommand.User
import com.github.asforest.mshell.command.resolver.TreeCommand
import com.github.asforest.mshell.configuration.MShellConfig
import com.github.asforest.mshell.exception.business.*
import com.github.asforest.mshell.model.Preset
import com.github.asforest.mshell.permission.PresetGrants
import com.github.asforest.mshell.session.Session
import com.github.asforest.mshell.session.SessionManager
import com.github.asforest.mshell.util.MShellUtils.buildUsage

object MainCommand : TreeCommand()
{
Expand All @@ -27,17 +28,16 @@ object MainCommand : TreeCommand()
suspend fun CallContext.help()
{
sendMessage(buildString {
for ((label, func) in allCommands)
for ((prefix, func) in allCommands)
{
if (func.permissionMask and permission == 0)
continue

append(listOf("/${MShellCommand.primaryName}", label, func.params).joinToString(" "))
if(func.description.isNotEmpty())
append(": ${func.description}")
append("/")
append(buildUsage(MShellCommand.rootLabal + if (prefix.isNotEmpty()) " $prefix" else "", func))
append("\n")
}
})
}.trim())
}

@Command(desc = "开启一个会话并将当前用户连接到这个会话", permission = Admin or User)
Expand Down
17 changes: 16 additions & 1 deletion src/main/kotlin/command/resolver/CommandSignature.kt
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
package com.github.asforest.mshell.command.resolver

import kotlin.reflect.*
import kotlin.reflect.full.extensionReceiverParameter
import kotlin.reflect.full.isSubtypeOf
import kotlin.reflect.full.valueParameters
import kotlin.reflect.jvm.jvmErasure

/**
* @param name: 指令名字
* @param name: callable 标识符名字
* @param callable: 指令KFunction对象
* @param parameters: value parameter
* @param extensionReceiverParameter: extension receiver parameter
Expand All @@ -20,8 +22,21 @@ data class CommandSignature(
val permissionMask: Int,
val description: String
) {
/**
* 字符串化后的parameters
*/
val params = parameters.joinToString(" ") { p -> p.identity }

companion object {
@JvmStatic
fun CreateFromKF(kf: KFunction<*>, prefix: String, permissionMask: Int, description: String): CommandSignature
{
val parameters = kf.valueParameters.map { Parameter(it.name ?: "", it.isOptional, it.isVararg, it.type) }

return CommandSignature(prefix, kf, parameters, kf.extensionReceiverParameter, permissionMask, description)
}
}

data class Parameter(
val name: String,
val isOptional: Boolean,
Expand Down
9 changes: 0 additions & 9 deletions src/main/kotlin/command/resolver/PrefixedCommandSignature.kt

This file was deleted.

71 changes: 39 additions & 32 deletions src/main/kotlin/command/resolver/TreeCommand.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,14 @@ import kotlin.reflect.KVisibility
import kotlin.reflect.full.*
import kotlin.reflect.jvm.javaField

/**
* 指令指令的抽象类(一些变量命名说明:
*
* labal:指命令字符串用空格分割后的第一个元素,比如ms auth add中的ms。用来匹配方法标识符
*
* arguments:指命令字符串用空格分割后从第二个开始的后面的所有元素,比如ms auth add 123中的auth add 123。用来匹配方法形参
*
*/
abstract class TreeCommand
{
/**
Expand All @@ -17,37 +25,25 @@ abstract class TreeCommand
val functions = mutableListOf<PrefixedCommandSignature>()

for (command in this.commands)
{
functions += PrefixedCommandSignature(command.prefix, command.signature)
}
functions += PrefixedCommandSignature("", command)

fun collectFromMemberProperty(parent: TreeCommand)
{
for (subCommand in parent.subCommandObjects.filter { !it.isAlias })
for (command in subCommand.subtree.commands)
functions += PrefixedCommandSignature(subCommand.field + " " + command.prefix, command.signature)
}

collectFromMemberProperty(this)
for (subCommand in subCommandObjects.filter { !it.isAlias })
for (command in subCommand.subtree.commands)
functions += PrefixedCommandSignature(subCommand.field + " " + command.name, command)

functions
}

/**
* 所有定义在本类/子类里的指令函数列表
*/
protected val commands: List<PrefixedCommandSignature> by lazy {
protected val commands: List<CommandSignature> by lazy {
this::class.functions
.filter { it.isSubCommandFunction() }
.onEach { it.checkModifiers() }
.map { func ->
val annotation = func.findAnnotation<Command>()!!

val signature = CommandSignature(func.name, func, func.valueParameters.map {
CommandSignature.Parameter(it.name ?: "", it.isOptional, it.isVararg, it.type)
}, func.extensionReceiverParameter, annotation.permission, annotation.desc)

PrefixedCommandSignature(func.name, signature)
.map { funcation ->
val annotation = funcation.findAnnotation<Command>()!!
CommandSignature.CreateFromKF(funcation, funcation.name, annotation.permission, annotation.desc)
}
}

Expand All @@ -74,31 +70,32 @@ abstract class TreeCommand

/**
* 尝试解析Arguments
* @param commands 要被解析的Arguments字符串列表
* @param callerPermission 调用者拥有的权限,用来判断有无权限执行指令
* @param prefix 指令前缀。用来知道当前函数是第几层子指令
* @param arguments 要被解析的Arguments字符串列表
* @param callerPermission 调用者拥有的权限,用来判断有无权限执行指令
* @return 解析结果
* @throws MissingSubCommandException 没有任何实参被传进来
* @throws NoFunctionMatchedException 没有对应的函数(解析失败)
* @throws TooFewArgumentsException 实参太少
* @throws TooManyArgumentsException 实参太多
* @throws AbstractArgumentParsers.ArgumentParserException 解析成功,但参数类型不正确(不匹配)时
* @throws PermissionDeniedException 解析成功,但权限不够时
*/
fun resolveCommandText(commands: List<String>, callerPermission: Int, prefix: String): ArgumentedFunction
fun resolveCommandText(prefix: String, arguments: List<String>, callerPermission: Int): ArgumentedFunction
{
if(commands.isEmpty())
throw NoFunctionMatchedException()
if(arguments.isEmpty())
throw MissingSubCommandException(prefix, this.commands)

val label = commands[0]
val arguments = commands.drop(1)
val label = arguments[0]
val arguments = arguments.drop(1)

val resolveResult = resolveWithinSelf(label, arguments)

if (resolveResult == null)
{
val subCommand = subCommandObjects.firstOrNull { it.field == label } ?: throw NoFunctionMatchedException()
val subCommand = subCommandObjects.firstOrNull { it.field == label } ?: throw NoFunctionMatchedException(prefix, label, this.commands)
val pfx = prefix + (if(prefix.isNotEmpty()) " " else "") + subCommand.field
return subCommand.subtree.resolveCommandText(arguments, callerPermission, pfx)
return subCommand.subtree.resolveCommandText(pfx, arguments, callerPermission)
}

when(resolveResult)
Expand All @@ -118,9 +115,9 @@ abstract class TreeCommand
{
var tempResult: CommandCallResolver.ResolveResult? = null

for (function in this.commands.filter { it.prefix == label })
for (function in this.commands.filter { it.name == label })
{
tempResult = CommandCallResolver.resolve(function.signature, arguments, this)
tempResult = CommandCallResolver.resolve(function, arguments, this)

if (tempResult is CommandCallResolver.ResolveResult.ResolveCorrect)
return tempResult
Expand All @@ -140,14 +137,24 @@ abstract class TreeCommand

class IllegalDeclarationException(message: String) : Exception(message)

class NoFunctionMatchedException : Exception()
class NoFunctionMatchedException(val prefix: String, val label: String, val availables: List<CommandSignature>) : Exception()

class MissingSubCommandException(val prefix: String, val availables: List<CommandSignature>) : Exception()

class TooFewArgumentsException(val prefix: String, val signature: CommandSignature) : Exception()

class TooManyArgumentsException(val prefix: String, val signature: CommandSignature) : Exception()

class PermissionDeniedException(val prefix: String, val function: ArgumentedFunction) : Exception()

/**
* 带前缀版本的CommandSignature
*/
data class PrefixedCommandSignature(
val prefix: String,
val signature: CommandSignature,
)

/**
* 代表一个定义在子对象里的指令函数列表
*/
Expand Down
11 changes: 11 additions & 0 deletions src/main/kotlin/util/MShellUtils.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.github.asforest.mshell.util

import com.github.asforest.mshell.command.resolver.CommandSignature
import com.github.asforest.mshell.session.SessionUser
import net.mamoe.mirai.Bot
import net.mamoe.mirai.console.command.CommandSender
Expand Down Expand Up @@ -61,4 +62,14 @@ object MShellUtils

return SessionUser.FriendUser(user!!)
}

fun buildUsage(prefix: String, function: CommandSignature, separator: String = ":"): String
{
return listOf(
prefix,
function.name,
function.params,
).filter { it.isNotEmpty() }
.joinToString(" ") + if(function.description.isNotEmpty()) "$separator ${function.description}" else ""
}
}

0 comments on commit 8e8732b

Please sign in to comment.