前言
在食用本文前你需要了解 Kotlin语言,及用到的特性——带接收者的 lambda、内联 reified 泛型函数、扩展函数、中缀函数等。当然这些大部分都是在 Kotlin 中想要写出 DSL 语言结构的必要知识。
DSL 构造命令
关于注册命令的写法大部分都是远古判断法或者注解反射法。关于注解反射这个东西,虽然外表看起来简洁美观,但背后十分邪恶,我个人也非常不喜欢。因此我们从 CommandMap
注册命令开始入手。
首先需要拿到 CommandMap
,它位于 Server
类中。
1 | fun getCommandMap(): CommandMap = |
此处没有对非空做出处理,因为我们知道 Server
类中肯定是存在这个东西的。之后我们就要把抽象类 Command
给实现一下他的 execute
方法。在构造 DSL 过程中,我们把这个方法中执行的内容封装成一个 lambda (CommandSender, String, Array<out String>) -> Unit
,用 result
作为最终返回结果。我们定义一个类继承 Command
,并封装上下文及 execute
函数。
1 | class PackingCommand |
name
、description
、usageMessage
、aliases
对应 plugin.yml 下对命令的配置;action
会在execute
被调用时执行result
对应处理命令时返回的布尔结果(这里在 runtime 前已经固定)
定义完 PackingCommand
后,可以实现其 DSL Builder。代码如下:
1 | class CommandBuilderDsl(val name: String) { |
写完 CommandBuilderDsl
后还需要提供一另一个 CommandScope
来构造 Builder
,在这之前构造好的 Command
需要存起来,在插件启用时调用,这部分不贴代码了,就是一个 MutableList<PackingCommand>
,向外提供注册方法。CommandScope
的代码如下:
1 | class CommandScopeDsl { |
目前 CommandScope
中就一个函数,也可以省略 CommandScope
,直接将 command()
改为顶层函数,不过未来肯定还会加东西,而且这样写是不合理的。贴一下前面的注册方法,commands
是那个持有 PackingCommand
的 list:
1 | fun register(commandMap: CommandMap) { |
再加上把 CommandScope
开放出来的函数:
1 | fun buildCommands(block: CommandScope.() -> Unit) { |
至此就完成了,在插件启用时调用 CommandHolder.register(getCommandMap())
注册就好。
随便写个命令举个例子:
1 | buildCommands { |
总结
有几个问题:
- 命令多了看起来很乱,大括号越写越多
- 封装性欠缺
- 无法美观处理子命令
除了第一个问题是 Kotlin DSL 的硬伤外,其余都可以自己慢慢优化、实现。emerald 主要思想来源于本文,可以在 这里 找到我进一步封装的代码。