詳解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開始


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM