详解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的优缺点可以参考:

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发现第三行末尾没换行符
    在读取YAML时,上面的字符串会被读取为:
    蛋吸吃掉了第一行的换行符tny吃掉了第二行的换行符Norman发现第三行末尾没换行符

YAML允许你用直接输入数字的方式来保存一个数字,并且会自动识别是整数还是浮点小数。YAML支持以下的数字表达方式:

  • 直接输入整数表示十进制整数,例如:233
  • 用“0”开头的一串0~7数字表示八进制整数,例如: 0351
  • 用“0x”开头的一串0~9或A~F字符表示十六进制整数,例如:0xE9
  • 直接输入小数表示浮点数,例如:233.333
  • 类似于科学计数法的指数,例如:2.33e+332e-33

另外,关于布尔值,YAML中使用truefalse来表示。空的值可以使用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
View Code

由于一个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;
    }
}
View Code

通过分析以上的代码,我们知道了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"}));
                            }
View Code

这段代码属于插件管理器,通过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开始

posted @ 2015-09-27 17:10  粉鞋大妈  阅读(4256)  评论(0编辑  收藏  举报