详解YAML与Nukkit配置文件 - Nukkit插件教程番外篇
请认准本教程目录篇永久链接:http://www.cnblogs.com/xtypr/p/nukkit_plugin_start_from_0.html ,未经作者许可转载本系列文章任何内容的,视为已向作者支付50元人民币稿酬。作者支付宝账号:237356812@qq.com。
系列作者:粉鞋大妈 文章出处:http://www.cnblogs.com/xtypr 欢迎转载、翻译、收录,也请保留这段声明,不胜感激。
详解YAML与Nukkit配置文件
目录:Nukkit插件从0开始
这篇文章我将从YAML开始,详细解析nukkit的各类配置文件,包括plugin.yml、server.properties、nukkit.yml。
YAML与在Java中的实现
YAML是一种数据存储格式,与XML以及其它格式不同,YAML更容易被人类和机器阅读。YAML类似于XML和JSON,但是语法比后两者简单很多。关于YAML的优缺点可以参考:
- 官网 http://www.yaml.org/
- IBM的文章 http://www.ibm.com/developerworks/cn/xml/x-cn-yamlintro/
- 从360doc找到的文章 http://www.360doc.com/content/15/0228/17/12090552_451540006.shtml。
- 从标点符找到的文章 http://www.biaodianfu.com/yaml.html
YAML较多的用于配置文件中储存一系列常量,此类配置文件多以.yml或者.yaml为扩展名。YAML使用可打印的Unicode字符,可使用UTF-8或UTF-16。
下面简单讲一讲它的语法。
注解与档案
在YAML中,常常要给一个变量或者整个档案做注解。可以在YAML文档的任意一行的末尾或者一个空行中,使用井号#加上注释的文字来加以注解。这些注解会被编译器忽略,但是让人类更容易理解这篇YAML文档。和一些编程语言不同,YAML不支持多行注释,你需要在每一行之前加上井号。这有一个例子:
#这是一个单行注解
#又来一个单行注解
var: 233 #这是一行之后的注解
一篇正常的YAML文档,可以分为几个档案。一篇档案由三个短横---开始,三个句点...结束,中间包围着YAML变量。一般的配置文件只有一个档案,所以档案开始和结束符号常常省略。
基本变量类型
YAML中的字符串常量有许多表达方式。虽然YAML字符串不常使用引号,但是仍然可以用单引号''或双引号""包围来表达一个字符串常量。YAML还有用|或者>开头的独特字符串形式。
- 最简单的形式是不使用任何符号包围,这时YAML会保留所有原生的字符串。URL字符串因为常有'"/:这样的字符,用其它格式不适合保存,最适合这种保存方式。如果你保存的字符串是类似于1.0.0的版本号,不用符号包围时可能会被误认为是数字而影响读取,这时候应该使用其它的保存方法。
- 在用单引号包围的字符串中,表达一个单引号必须用另一个单引号来转义。比如这个:
'这是一个单引号→ '' ←不是两个单引号'
- 而使用双引号包围字符串时,不同于单引号包围或者不用引号,YAML允许你在双引号包围字符串时使用转义字符反斜杠\来表达类似于Java的转义字符的丰富的形式。比如这些:
"双引号可以使用 \\表示一个反斜杠 \"表示一个双引号 \'表示一个单引号 \r \n 表示换行符 "
- |可以用来表达多行字符串。这时候YAML会保留换行符。这种情况下,\|这样的转义字符不会被转义。比如这个:
| 大家好,我不是girlbook
这篇文章的出处:http://www.cnblogs.com/xtypr/
很高兴你来翻阅粉鞋大妈的文章 - >也可以用来表达多行字符串,但是YAML在读取时会删除换行符,将每一行连在一起。比如这个:
> 蛋吸吃掉了第一行的换行符 tny吃掉了第二行的换行符 Norman发现第三行末尾没换行符
蛋吸吃掉了第一行的换行符tny吃掉了第二行的换行符Norman发现第三行末尾没换行符
YAML允许你用直接输入数字的方式来保存一个数字,并且会自动识别是整数还是浮点小数。YAML支持以下的数字表达方式:
- 直接输入整数表示十进制整数,例如:233
- 用“0”开头的一串0~7数字表示八进制整数,例如: 0351
- 用“0x”开头的一串0~9或A~F字符表示十六进制整数,例如:0xE9
- 直接输入小数表示浮点数,例如:233.333
- 类似于科学计数法的指数,例如:2.33e+33,2e-33
另外,关于布尔值,YAML中使用true或false来表示。空的值可以使用null或~来表示。
序列与散列
YAML允许你使用序列(数组)或者散列表(哈希表)来表示一系列变量。在Java中,这两种变量会被转化为List对象和Map对象。YAML允许你使用一个短横加空格加变量的值来表示一个序列的一个值,使用键值加冒号加空格加值来表示一个数组的一组对应关系。请看以下的例子:
- 序列的第一个值
- 序列的第二个值
- 序列的第三个值
var1: 散列中var1的值
var2: 这是var2的值
当你认为换行太占空间时,YAML允许你把序列简化成[]包围的形式,散列表简化为{}包围的形式。空的[]或{}表示一个空的序列或散列。比如这个:
[序列的第一个值,序列的第二个值,序列的第三个值]
[] #这是一个空的序列
{var1: 散列中var1的值,var2: 这是var2的值}
{} #这是一个空的散列
一个序列/散列的值可以是一个基本变量类型,也可以是另一个序列/散列。当一个序列/散列包含另外的序列的时候,应该使用一个空格的缩进(不是tab键)来说明一个序列/散列属于这个序列/散列。如果使用|或>引导的字符串,你不需要在每行前面加上很多空格来保持缩进。所以这样的片段在YAML中是合法的:
room1: #需要保持缩进
contents: {bed: 1} #这种方式可以表达散列
lighted: false
description: > #下两行不需要对齐空格
This is a room
with a large bed.
comments: #散列和散列嵌套的形式
girlbook: ok
marcus: awesome
size: #散列和序列嵌套的形式
- 6
- 5.5
- 5
users: #序列和散列嵌套的形式
- name: xtypr
age: 17
- name: miss_orange
age: 18
room2: false
由于一个YAML档案应该包含一个散列,所以一个YAML文档的结构应该是这样的层次:
变量/序列/散列 -> 档案 -> 文档
至此,我们已经可以分析一般的YAML文档和结构。
Nukkit插件与plugin.yml
Nukkit通过插件的plugin.yml来识别插件的主类、作者、版本等信息。plugin.yml在Nukkit加载插件时会被识别为一个PluginDescription类。我们看看PluginDescription类需要读取哪些信息:
package cn.nukkit.plugin; /* 此处省略若干行 */ /** * Author: iNevet & MagicDroidX * Nukkit Project */ public class PluginDescription { /* 此处省略若干行*/ private void loadMap(Map<String, Object> plugin) throws PluginException { this.name = ((String) plugin.get("name")).replaceAll("[^A-Za-z0-9 _.-]", ""); if (this.name.equals("")) { throw new PluginException("Invalid PluginDescription name"); } this.name = this.name.replace(" ", "_"); this.version = (String) plugin.get("version"); this.main = (String) plugin.get("main"); Object api = plugin.get("api"); if (api instanceof List) { this.api = (List<String>) api; } else { List<String> list = new ArrayList<>(); list.add((String) api); this.api = list; } if (this.main.startsWith("cn.nukkit.")) { throw new PluginException("Invalid PluginDescription main, cannot start within the cn.nukkit. package"); } if (plugin.containsKey("commands") && plugin.get("commands") instanceof Map) { this.commands = (Map<String, Object>) plugin.get("commands"); } if (plugin.containsKey("depend")) { this.depend = (List<String>) plugin.get("depend"); } if (plugin.containsKey("softdepend")) { this.softDepend = (List<String>) plugin.get("softdepend"); } if (plugin.containsKey("loadbefore")) { this.loadBefore = (List<String>) plugin.get("loadbefore"); } if (plugin.containsKey("website")) { this.website = (String) plugin.get("website"); } if (plugin.containsKey("description")) { this.description = (String) plugin.get("description"); } if (plugin.containsKey("prefix")) { this.prefix = (String) plugin.get("prefix"); } if (plugin.containsKey("load")) { String order = (String) plugin.get("load"); try { this.order = PluginLoadOrder.valueOf(order); } catch (Exception e) { throw new PluginException("Invalid PluginDescription load"); } } if (plugin.containsKey("author")) { this.authors.add((String) plugin.get("author")); } if (plugin.containsKey("authors")) { this.authors.addAll((Collection<? extends String>) plugin.get("authors")); } if (plugin.containsKey("permissions")) { this.permissions = Permission.loadPermissions((Map<String, Object>) plugin.get("permissions")); } } public String getFullName() { return this.name + " v" + this.version; } public List<String> getCompatibleApis() { return api; } public List<String> getAuthors() { return authors; } public String getPrefix() { return prefix; } public Map<String, Object> getCommands() { return commands; } public List<String> getDepend() { return depend; } public String getDescription() { return description; } public List<String> getLoadBefore() { return loadBefore; } public String getMain() { return main; } public String getName() { return name; } public PluginLoadOrder getOrder() { return order; } public List<Permission> getPermissions() { return permissions; } public List<String> getSoftDepend() { return softDepend; } public String getVersion() { return version; } public String getWebsite() { return website; } }
通过分析以上的代码,我们知道了Nukkit需要读取插件以下的字段:
- name
- version
- api
- main
- author/authors
- website
- description
- depend
- softdepend
- loadbefore
- prefix
- load
- commands
- permissions
要能够编写符合要求的plugin.yml,我们需要从代码中分析以上字段的功能。比如我们要分析api字段的功能,我们发现在PluginDescription类中有getCompatibleApis()这个方法使用了api序列,进一步在整个Nukkit中寻找使用这个方法的代码,我们在PluginManager类里发现了这样的代码段:
boolean compatible = false; for (String version : description.getCompatibleApis()) { //Check the format: majorVersion.minorVersion.patch if (!Pattern.matches("[0-9]\\.[0-9]\\.[0-9]", version)) { this.server.getLogger().error(this.server.getLanguage().translateString("nukkit.plugin.loadError", new String[]{name, "Wrong API format"})); continue; } String[] versionArray = version.split("\\."); String[] apiVersion = this.server.getApiVersion().split("\\."); //Completely different API version if (!Objects.equals(Integer.valueOf(versionArray[0]), Integer.valueOf(apiVersion[0]))) { continue; } //If the plugin requires new API features, being backwards compatible if (Integer.valueOf(versionArray[1]) > Integer.valueOf(apiVersion[1])) { continue; } compatible = true; break; } if (!compatible) { this.server.getLogger().error(this.server.getLanguage().translateString("nukkit.plugin.loadError", new String[]{name, "%nukkit.plugin.incompatibleAPI"})); }
这段代码属于插件管理器,通过getCompatibleApis()方法得到适合的api列表,然后根据与自己的api版本进行对比,如发现可以兼容,就加载这个插件,否则输出错误。至此,我们已经推导出了api字段的作用:用来表示这个插件支持的Nukkit api版本列表。
同理,我们可以得到plugin.yml中所有字段的用途:
- name(字符串),必需,表示这个插件的名字,名字是区分不同插件的标准之一。插件的名字不能包含“nukkit”“minecraft”“mojang”这几个字符串,而且不应该包含空格。
- version(字符串),必需,表示这个插件的版本号。使用类似于1.0.0这样的版本号时,应该使用引号包围来防止误识别。
- api(字符串序列),必需,表示这个插件支持的Nukkit api版本号列表。插件作者应该调试能支持的api,然后把版本号添加到这个列表。
- main(字符串),必需,表示这个插件的主类。插件的主类不能放在“cn.nukkit”包下。
- author(字符串)/authors(字符串序列),两个任选一个,表示这个插件的作者/作者列表。
- website(字符串),表示这个插件的网站,插件使用者或者开发者可以访问这个网站来获取插件更多的信息,可以是插件发布帖子或者插件官网之类的。
- description(字符串),表示这个插件的一些描述。使用 /version 插件名 查看插件的版本时,会显示这个描述。
- depend(字符串序列),表示这个插件依赖的插件列表。如果依赖的插件没有安装,Nukkit会提示用户去安装。
- softdepend
- loadbefore
- prefix(字符串),表示这个插件的消息头衔,如果不填写表示这个插件消息头衔为插件的名字。在Logger输出日志信息的时候,会用到插件的消息头衔。
- load
- commands
- permissions
Nukkit与nukkit.yml
Nukkit与server.properties
目录:Nukkit插件从0开始