Separate Command.names into Command.primaryName and Command.secondaryNames such that primaryName is compulsory while secondaryNames are optional.

This commit is contained in:
Him188 2020-09-18 22:55:19 +08:00
parent 899c6266dd
commit 7762ea2f65
22 changed files with 110 additions and 68 deletions

View File

@ -132,12 +132,12 @@ public object BuiltInCommands {
onFailure = { throwable ->
sendMessage(
"Login failed: ${throwable.localizedMessage ?: throwable.message ?: throwable.toString()}" +
if (this is CommandSenderOnMessage<*>) {
CommandManagerImpl.launch(CoroutineName("stacktrace delayer from Login")) {
fromEvent.nextMessageOrNull(60.secondsToMillis) { it.message.contentEquals("stacktrace") }
}
"\n 1 分钟内发送 stacktrace 以获取堆栈信息"
} else ""
if (this is CommandSenderOnMessage<*>) {
CommandManagerImpl.launch(CoroutineName("stacktrace delayer from Login")) {
fromEvent.nextMessageOrNull(60.secondsToMillis) { it.message.contentEquals("stacktrace") }
}
"\n 1 分钟内发送 stacktrace 以获取堆栈信息"
} else ""
)
throw throwable

View File

@ -16,8 +16,8 @@ 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.internal.command.createOrFindCommandPermission
import net.mamoe.mirai.console.internal.command.isValidSubName
import net.mamoe.mirai.console.permission.Permission
import net.mamoe.mirai.console.permission.PermissionId
import net.mamoe.mirai.message.data.MessageChain
/**
@ -33,13 +33,17 @@ import net.mamoe.mirai.message.data.MessageChain
*/
public interface Command {
/**
* 指令名. 需要至少有一个元素. 所有元素都不能带有空格
*
* 第一个元素会作为主指令名.
* 主指令名. 将会参与构成 [Permission.id].
*
* 不允许包含 [空格][Char.isWhitespace], '.', ':'.
*/
public val primaryName: String
/**
* 次要指令名
* @see Command.primaryName 获取主指令名
*/
public val names: Array<out String>
public val secondaryNames: Array<out String>
/**
* 用法说明, 用于发送给用户. [usage] 一般包含 [description].
@ -52,14 +56,18 @@ public interface Command {
public val description: String
/**
* 指令权限
* 此指令所分配的权限.
*
* ### 实现约束
* - [Permission.id] 应由 [CommandOwner.permissionId] 创建. 因此保证相同的 [PermissionId.namespace]
* - [PermissionId.name] 应为 [主指令名][primaryName]
*/
public val permission: Permission
/**
* `true` 时表示 [指令前缀][CommandManager.commandPrefix] 可选.
*
* 会影响消息语境中的解析.
* 会影响聊天语境中的解析.
*/
public val prefixOptional: Boolean
@ -80,17 +88,34 @@ public interface Command {
public suspend fun CommandSender.onCommand(args: MessageChain)
public companion object {
/**
* 主要指令名. [Command.names] 的第一个元素.
* 获取所有指令名称 (包含 [primaryName] [secondaryNames]).
*
* @return 数组大小至少为 1. 第一个元素总是 [primaryName]. 随后是保持原顺序的 [secondaryNames]
*/
@JvmStatic
public val Command.primaryName: String
get() = names[0]
public val Command.allNames: Array<String>
get() = arrayOf(primaryName, *secondaryNames)
/**
* 检查指令名的合法性. 在非法时抛出 [IllegalArgumentException]
*/
@Throws(IllegalArgumentException::class)
public fun checkCommandName(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.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 =
@ -105,19 +130,14 @@ public suspend inline fun Command.onCommand(sender: CommandSender, args: Message
*/
public abstract class AbstractCommand
@JvmOverloads constructor(
/** 指令拥有者. */
public override val owner: CommandOwner,
vararg names: String,
description: String = "<no description available>",
public final override val owner: CommandOwner,
public final override val primaryName: String,
public final override val secondaryNames: Array<out String>,
public override val description: String = "<no description available>",
parentPermission: Permission = owner.parentPermission,
/** 为 `true` 时表示 [指令前缀][CommandManager.commandPrefix] 可选 */
public override val prefixOptional: Boolean = false,
) : Command {
public override val description: String = description.trimIndent()
public final override val names: Array<out String> =
names.map(String::trim).filterNot(String::isEmpty).map(String::toLowerCase).also { list ->
list.firstOrNull { !it.isValidSubName() }?.let { error("Invalid name: $it") }
}.toTypedArray()
public override val usage: String get() = description
public override val permission: Permission by lazy { createOrFindCommandPermission(parentPermission) }
}

View File

@ -11,7 +11,6 @@
package net.mamoe.mirai.console.command
import net.mamoe.mirai.console.command.Command.Companion.primaryName
import net.mamoe.mirai.console.command.CommandManager.INSTANCE.executeCommand
/**

View File

@ -53,8 +53,8 @@ public interface CommandManager {
*
* @param override 是否覆盖重名指令.
*
* 若原有指令 P, [Command.names] 'a', 'b', 'c'.
* 新指令 Q, [Command.names] 'b', 将会覆盖原指令 A 注册的 'b'.
* 若原有指令 P, [Command.secondaryNames] 'a', 'b', 'c'.
* 新指令 Q, [Command.secondaryNames] 'b', 将会覆盖原指令 A 注册的 'b'.
*
* 即注册完成后, 'a' 'c' 将会解析到指令 P, 'b' 会解析到指令 Q.
*

View File

@ -36,5 +36,5 @@ public interface CommandOwner : PermissionIdNamespace {
internal object ConsoleCommandOwner : CommandOwner {
override val parentPermission: Permission get() = BuiltInCommands.parentPermission
override fun permissionId(name: String): PermissionId = PermissionId("console", "command.$name")
override fun permissionId(name: String): PermissionId = PermissionId("console", name)
}

View File

@ -9,7 +9,6 @@
package net.mamoe.mirai.console.command
import net.mamoe.mirai.console.command.Command.Companion.primaryName
import net.mamoe.mirai.console.command.CommandManager.INSTANCE.executeCommand
/**

View File

@ -81,12 +81,13 @@ import kotlin.annotation.AnnotationTarget.FUNCTION
*/
public abstract class CompositeCommand(
owner: CommandOwner,
vararg names: String,
primaryName: String,
vararg secondaryNames: String,
description: String = "no description available",
parentPermission: Permission = owner.parentPermission,
prefixOptional: Boolean = false,
overrideContext: CommandArgumentContext = EmptyCommandArgumentContext,
) : Command, AbstractReflectionCommand(owner, names, description, parentPermission, prefixOptional),
) : Command, AbstractReflectionCommand(owner, primaryName, secondaryNames = secondaryNames, description, parentPermission, prefixOptional),
CommandArgumentContextAware {
/**

View File

@ -35,8 +35,10 @@ public abstract class RawCommand(
* @see CommandOwner
*/
public override val owner: CommandOwner,
/** 指令名. 需要至少有一个元素. 所有元素都不能带有空格 */
public override vararg val names: String,
/** 主指令名. */
public override val primaryName: String,
/** 次要指令名. */
public override vararg val secondaryNames: String,
/** 用法说明, 用于发送给用户 */
public override val usage: String = "<no usages given>",
/** 指令描述, 用于显示在 [BuiltInCommands.Help] */

View File

@ -50,12 +50,13 @@ import net.mamoe.mirai.message.data.MessageChain
*/
public abstract class SimpleCommand(
owner: CommandOwner,
vararg names: String,
primaryName: String,
vararg secondaryNames: String,
description: String = "no description available",
parentPermission: Permission = owner.parentPermission,
prefixOptional: Boolean = false,
overrideContext: CommandArgumentContext = EmptyCommandArgumentContext,
) : Command, AbstractReflectionCommand(owner, names, description, parentPermission, prefixOptional),
) : Command, AbstractReflectionCommand(owner, primaryName, secondaryNames = secondaryNames, description, parentPermission, prefixOptional),
CommandArgumentContextAware {
/**

View File

@ -69,11 +69,12 @@ import net.mamoe.mirai.console.permission.Permission
public abstract class JCompositeCommand
@JvmOverloads constructor(
owner: CommandOwner,
vararg names: String,
primaryName: String,
vararg secondaryNames: String,
parentPermission: Permission = owner.parentPermission,
) : CompositeCommand(owner, *names, parentPermission = parentPermission) {
/** 指令描述, 用于显示在 [BuiltInCommands.Help] */
public final override var description: String = "<no descriptions given>"
) : CompositeCommand(owner, primaryName, secondaryNames = secondaryNames, parentPermission = parentPermission) {
/** 指令描述, 用于显示在 [BuiltInCommands.HelpCommand] */
public final override var description: String = "<no descriptions available>"
protected set
public final override var permission: Permission = super.permission

View File

@ -52,7 +52,7 @@ public abstract class JRawCommand
*/
public override val owner: CommandOwner,
/** 指令名. 需要至少有一个元素. 所有元素都不能带有空格 */
public override vararg val names: String,
public override vararg val secondaryNames: String,
parentPermission: Permission = owner.parentPermission,
) : Command {
/** 用法说明, 用于发送给用户 */

View File

@ -41,9 +41,10 @@ import net.mamoe.mirai.console.permission.Permission
*/
public abstract class JSimpleCommand(
owner: CommandOwner,
vararg names: String,
primaryName: String,
vararg secondaryNames: String,
basePermission: Permission,
) : SimpleCommand(owner, *names, parentPermission = basePermission) {
) : SimpleCommand(owner, primaryName, secondaryNames = secondaryNames, parentPermission = basePermission) {
public override var description: String = super.description
protected set

View File

@ -23,7 +23,6 @@ import net.mamoe.mirai.console.MiraiConsole
import net.mamoe.mirai.console.MiraiConsoleFrontEndDescription
import net.mamoe.mirai.console.MiraiConsoleImplementation
import net.mamoe.mirai.console.command.BuiltInCommands
import net.mamoe.mirai.console.command.Command.Companion.primaryName
import net.mamoe.mirai.console.command.CommandManager
import net.mamoe.mirai.console.command.ConsoleCommandSender
import net.mamoe.mirai.console.data.PluginDataStorage
@ -191,6 +190,10 @@ internal object MiraiConsoleImplementationBridge : CoroutineScope, MiraiConsoleI
PluginManagerImpl.enableAllLoadedPlugins()
for (registeredCommand in CommandManager.allRegisteredCommands) {
registeredCommand.permission // init
}
mainLogger.info { "${PluginManagerImpl.plugins.size} plugin(s) enabled." }
}

View File

@ -14,7 +14,6 @@ import kotlinx.coroutines.CoroutineExceptionHandler
import kotlinx.coroutines.CoroutineScope
import net.mamoe.mirai.console.MiraiConsole
import net.mamoe.mirai.console.command.*
import net.mamoe.mirai.console.command.Command.Companion.primaryName
import net.mamoe.mirai.console.command.CommandSender.Companion.toCommandSender
import net.mamoe.mirai.event.Listener
import net.mamoe.mirai.event.subscribeAlways
@ -101,7 +100,7 @@ internal object CommandManagerImpl : CommandManager, CoroutineScope by Coroutine
override fun Command.register(override: Boolean): Boolean {
if (this is CompositeCommand) this.subCommands // init lazy
this.permission // init lazy
this.names // init lazy
this.secondaryNames // init lazy
this.description // init lazy
this.usage // init lazy
@ -111,13 +110,13 @@ internal object CommandManagerImpl : CommandManager, CoroutineScope by Coroutine
}
registeredCommands.add(this@register)
if (this.prefixOptional) {
for (name in this.names) {
for (name in this.secondaryNames) {
val lowerCaseName = name.toLowerCase()
optionalPrefixCommandMap[lowerCaseName] = this
requiredPrefixCommandMap[lowerCaseName] = this
}
} else {
for (name in this.names) {
for (name in this.secondaryNames) {
val lowerCaseName = name.toLowerCase()
optionalPrefixCommandMap.remove(lowerCaseName) // ensure resolution consistency
requiredPrefixCommandMap[lowerCaseName] = this
@ -128,15 +127,15 @@ internal object CommandManagerImpl : CommandManager, CoroutineScope by Coroutine
}
override fun Command.findDuplicate(): Command? =
registeredCommands.firstOrNull { it.names intersectsIgnoringCase this.names }
registeredCommands.firstOrNull { it.secondaryNames intersectsIgnoringCase this.secondaryNames }
override fun Command.unregister(): Boolean = modifyLock.withLock {
if (this.prefixOptional) {
this.names.forEach {
this.secondaryNames.forEach {
optionalPrefixCommandMap.remove(it)
}
}
this.names.forEach {
this.secondaryNames.forEach {
requiredPrefixCommandMap.remove(it)
}
registeredCommands.remove(this)

View File

@ -12,7 +12,6 @@
package net.mamoe.mirai.console.internal.command
import net.mamoe.mirai.console.command.*
import net.mamoe.mirai.console.command.Command.Companion.primaryName
import net.mamoe.mirai.console.command.description.CommandArgumentContext
import net.mamoe.mirai.console.command.description.CommandArgumentContextAware
import net.mamoe.mirai.console.internal.data.kClassQualifiedNameOrTip
@ -42,19 +41,21 @@ internal object SimpleCommandSubCommandAnnotationResolver :
function.hasAnnotation<SimpleCommand.Handler>()
override fun getSubCommandNames(baseCommand: AbstractReflectionCommand, function: KFunction<*>): Array<out String> =
baseCommand.names
baseCommand.secondaryNames
}
internal abstract class AbstractReflectionCommand
@JvmOverloads constructor(
owner: CommandOwner,
names: Array<out String>,
primaryName: String,
secondaryNames: Array<out String>,
description: String = "<no description available>",
parentPermission: Permission = owner.parentPermission,
prefixOptional: Boolean = false,
) : Command, AbstractCommand(
owner,
names = names,
primaryName = primaryName,
secondaryNames = secondaryNames,
description = description,
parentPermission = parentPermission,
prefixOptional = prefixOptional
@ -251,7 +252,7 @@ internal fun AbstractReflectionCommand.SubCommandDescriptor.createUsage(baseComm
internal fun AbstractReflectionCommand.createSubCommand(
function: KFunction<*>,
context: CommandArgumentContext
context: CommandArgumentContext,
): AbstractReflectionCommand.SubCommandDescriptor {
val notStatic = !function.hasAnnotation<JvmStatic>()
//val overridePermission = null//function.findAnnotation<CompositeCommand.PermissionId>()//optional

View File

@ -10,7 +10,6 @@
package net.mamoe.mirai.console.internal.command
import net.mamoe.mirai.console.command.Command
import net.mamoe.mirai.console.command.Command.Companion.primaryName
import net.mamoe.mirai.console.permission.Permission
import net.mamoe.mirai.console.permission.PermissionService
import net.mamoe.mirai.contact.Group

View File

@ -13,9 +13,9 @@ import net.mamoe.mirai.console.command.BuiltInCommands
import net.mamoe.mirai.console.command.Command
/**
* 一个权限.
* 一个抽象的权限. [PermissionService] 实现不同, [Permission] 可能会有多种实例. 但一个权限总是拥有确定的 [id].
*
* [PermissionService] 实现不同, [Permission] 可能会有多种实例. 但一个权限总是拥有确定的 [id].
* 在匹配权限时, 应使用唯一的 [id] 作为依据. 而不应该使用 [Permission] 实例. 同时, [Permission] 也不适合存储.
*
* **注意**: 请不要手动实现这个接口. 总是从 [PermissionService.register] 获得实例.
*
@ -32,6 +32,7 @@ import net.mamoe.mirai.console.command.Command
* #### 手动申请权限
* [PermissionService.register]
*/
@PermissionImplementation
public interface Permission {
/**
* 唯一识别 ID. 所有权限的 [id] 都互不相同.
@ -49,6 +50,8 @@ public interface Permission {
/**
* 父权限.
*
* 在检查权限时, 若一个 [Permittee] 拥有父
*
* [RootPermission] parent 为自身
*/
public val parent: Permission

View File

@ -9,12 +9,16 @@
package net.mamoe.mirai.console.permission
import net.mamoe.mirai.console.command.Command
/**
* [PermissionId] 的命名空间. 用于提供 [PermissionId.namespace].
*/
public interface PermissionIdNamespace {
/**
* 创建一个此命名空间下的 [PermitteeId]
* 创建一个此命名空间下的 [PermitteeId].
*
* 在指令初始化时, 会申请对应权限. 此时 [name] "command.$primaryName` 其中 [primaryName][Command.primaryName].
*/
public fun permissionId(name: String): PermissionId
}

View File

@ -12,10 +12,12 @@ package net.mamoe.mirai.console.permission
import kotlin.annotation.AnnotationTarget.*
/**
* 表示一个应该由权限插件实现的类.
* 表示一个应该由专有的权限插件 (提供 [PermissionService] 的插件) 实现的类.
*
* 这样的类不能被用户手动实现或者继承, 也不能使用属性委托或者类委托, 或者其他任意改变实现类的手段.
* 用户仅应该使用从 [PermissionService] 或其他途径获取这些对象, 而不能自行实现它们.
*
* 这样的类不能被用户手动实现或者继承, 也不能使用属性委托或者类委托, 或者其他任意直接或间接实现他们的手段 (否则会导致 [PermissionService] 处理异常).
*
* 普通插件仅应该使用从 [PermissionService] 或其他途径获取这些对象.
*/
@Retention(AnnotationRetention.BINARY)
@Target(CLASS, TYPEALIAS, FUNCTION, PROPERTY, FIELD, CONSTRUCTOR)

View File

@ -77,6 +77,8 @@ public interface PermissionService<P : Permission> {
* 申请并注册一个权限 [Permission].
*
* @throws PermissionRegistryConflictException 当已存在一个 [PermissionId] 时抛出.
*
* @return 申请到的 [Permission] 实例
*/
@Throws(PermissionRegistryConflictException::class)
public fun register(
@ -90,16 +92,17 @@ public interface PermissionService<P : Permission> {
/**
* 授予 [permitteeId] [permission] 权限
*
* Console 内建的权限服务支持授予操作. 但插件扩展的权限服务可能不支持.
* Console 内建的权限服务支持操作. 但插件扩展的权限服务可能不支持.
*
* @throws UnsupportedOperationException 当插件扩展的 [PermissionService] 不支持这样的操作时抛出.
*/
@Throws(UnsupportedOperationException::class)
public fun permit(permitteeId: PermitteeId, permission: P)
/**
* 撤销 [permitteeId] [permission] 授权
*
* Console 内建的权限服务支持授予操作. 但插件扩展的权限服务可能不支持.
* Console 内建的权限服务支持操作. 但插件扩展的权限服务可能不支持.
*
* @param recursive `true` 时递归撤销所有子权限.
* 例如, [permission] "*:*",
@ -108,6 +111,7 @@ public interface PermissionService<P : Permission> {
*
* @throws UnsupportedOperationException 当插件扩展的 [PermissionService] 不支持这样的操作时抛出.
*/
@Throws(UnsupportedOperationException::class)
public fun cancel(permitteeId: PermitteeId, permission: P, recursive: Boolean)
public companion object {
@ -118,6 +122,10 @@ public interface PermissionService<P : Permission> {
public val INSTANCE: PermissionService<out Permission>
get() = instanceField ?: error("PermissionService is not yet initialized therefore cannot be used.")
/**
* 获取一个权限, 失败时抛出 [NoSuchElementException]
*/
@Throws(NoSuchElementException::class)
public fun <P : Permission> PermissionService<P>.getOrFail(id: PermissionId): P =
get(id) ?: throw NoSuchElementException("Permission not found: $id")

View File

@ -37,7 +37,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 = PermissionId(description.id, "command.$name")
public final override fun permissionId(name: String): PermissionId = PermissionId(description.id, name)
/**
* 重载 [PluginData]

View File

@ -16,7 +16,6 @@ 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.Command.Companion.primaryName
import net.mamoe.mirai.console.command.CommandExecuteStatus
import net.mamoe.mirai.console.command.CommandManager
import net.mamoe.mirai.console.command.CommandManager.INSTANCE.executeCommand