8 个版本

0.1.1 2022年12月26日
0.1.0 2022年12月26日
0.0.8 2019年6月24日
0.0.5 2018年6月11日
0.0.2 2017年2月6日

#756 in 解析器实现

每月 22 次下载
用于 4 个 crate (3 直接)

MIT/Apache

765KB
20K SLoC

Yamlette

Rust 实现的全面 YAML 1.2 处理器

Current Version on crates.io MIT / Apache2 License GitHub Actions Travis Documentation

特性

  • 完全支持 YAML 1.2 规范
  • 覆盖 YAML 1.2 规范中每个示例的测试
  • 方便的宏,用于 YAML 读取和写入
  • 控制写入模式中的输出格式
  • 通过 trait 实现,轻松反序列化自己的类型(FromPointer<'a>
  • 尽管不稳定,但可以通过 trait 实现序列化自己的类型(orchestra::chord::Chord)
  • 实验性的多线程模型(尽管目前比单线程实现慢得多,不推荐使用)

示例

yamlette! 宏执行所有工作,因此您可以使用库而无需了解其内部工作方式。

主要思想是描述您正在处理的数据结构,而不是玩弄对象和方法。

示例之后有格式说明,因为 Reader 和 Writer 都有类似的格式。

基本读取示例

#[macro_use]
extern crate yamlette;

const SRC_YAML: &'static str = r#"
sequence:
- one
- two
mapping:
  ? sky
  : blue
  sea : green
"#;

fn main () {
    yamlette! ( read ; SRC_YAML ; [[
        {
            "sequence" => (list seq:Vec<String>),
            "mapping" => {
                "sky" => (sky_color:&str),
                "sea" => (sea_color:String)
            }
        }
    ]] );

    assert! (seq.is_some ());
    let seq = seq.unwrap ();

    assert_eq! (seq.len (), 2);
    assert_eq! (seq[0], "one");
    assert_eq! (seq[1], "two");

    assert_eq! (sky_color, Some ("blue"));
    assert_eq! (sea_color, Some (String::from ("green")));
}

其第一个参数不是一个变量,而是一个文字,表示我们需要在这里调用读取功能。

第二个参数是读取的数据源。它可以有以下类型

  • &'static str
  • String
  • Vec<u8>
  • &[u8]
  • std::fs::File
  • std::os::unix::net::UnixStream
  • & std::os::unix::net::UnixStream
  • std::net::TcpStream
  • & std::net::TcpStream
  • Box<std::io::Read>
  • &mut std::io::Read
  • std::io::BufReader
  • 任何其他实现了 skimmer::reader::IntoReader trait 的对象

第三个参数是数据结构描述。其格式在以下示例之后描述。

yamlette! 宏在作为读取器调用时默认不返回任何内容。

基本写入示例

#[macro_use]
extern crate yamlette;

use std::collections::BTreeMap;

const TGT_YAML: &'static str =
r#"name: Martin D'vloper
job: Developer
employed: true
foods:
  - Apple
  - Orange
  - Strawberry
  - Mango
languages:
  pascal: Lame
  perl: Elite
  python: Elite
education: "4 GCSEs\n3 A-Levels\nBSc in the Internet of Things"
"#;

fn main () {
    let name = "Martin D'vloper";
    let employed = true;

    let foods = vec! ["Apple", "Orange", "Strawberry", "Mango"];

    let mut languages = BTreeMap::new ();
    languages.insert ("pascal", "Lame");
    languages.insert ("perl", "Elite");
    languages.insert ("python", "Elite");

    let education = "4 GCSEs\n3 A-Levels\nBSc in the Internet of Things";

    let string = yamlette! ( write ; [[ {
        "name": name,
        "job": "Developer",
        "employed": employed,
        "foods": foods,
        "languages": languages,
        "education": education
    } ]] ).ok ().unwrap ();

    assert_eq! (string, TGT_YAML);
}

其第一个参数不是一个变量,而是一个文字,表示我们需要在这里调用写入功能。

第二个参数是数据结构描述。其格式在以下示例之后描述。

yamlette! 宏在作为写入器调用时,默认返回 Result<String, yamlette::orchestra::OrchError> 实例。

自定义类型读取示例

您只需为您的类型实现 FromPointer<'a> 特性

extern crate yamlette;

use self::yamlette::book::extractor::pointer::Pointer;
use self::yamlette::book::extractor::traits::FromPointer;

#[derive (PartialEq, Eq, Debug)]
struct State {
    pub state: bool,
    pub transition: u8,
    pub brightness: u8
}

impl State {
    pub fn new (state: bool, transition: u8, brightness: u8) -> State {
        State {
            state: state,
            transition: transition,
            brightness: brightness
        }
    }
}

impl<'a> FromPointer<'a> for State {
    fn from_pointer (pointer: Pointer<'a>) -> Option<Self> {
        yamlette_reckon! ( ptr ; Some (pointer) ; {
            (state:bool),
            (transition:u8),
            (brightness:u8)
        } );

        Some (State {
            state: if let Some (s) = state { s } else { false },
            transition: if let Some (t) = transition { t } else { 0u8 },
            brightness: if let Some (b) = brightness { b } else { 0u8 }
        })
    }
}

格式描述

常见事项

格式背后的主要思想是,您可以以类似JSON的格式表达您数据的结构。然而,YAML流可能包含多个文档,因此我们也将它们列举为顶级列表,并用方括号括起来。还有一个方括号的顶层,以便Rust宏引擎可以读取所有数据描述作为一个单独的标记树。

这就是为什么我们总是有两个方括号在顶部的原因

  • 第一个是为Rust宏引擎
  • 第二个是包含一个YAML文档的(我们可以有很多这样的,但我们始终至少有一个)

这意味着我们可以用以下结构来描述一个空YAML文档: [[]]。两个空YAML文档将是: [ [], [] ] 等。

在文档中,我们开始描述实际的数据节点。有三种可能的节点类型

  • 字典(Map),可以用花括号描述: {}
  • 序列(List),可以用方括号描述: []
  • 标量(Node),可以用括号描述(有时甚至不需要): ()

数据类型之间的特定差异在读取和写入时不同。

格式的读取器特定

当我们读取一个字典时,我们会列举其键和值(这些值本身也是节点)。

有两种方式可以列举一个字典

  • { key => value } - 通过键值查找值(应用 <key as PartialEq>::eq
  • { key > value } - 简单地遍历字典的键和值,并按原始顺序分配它们的值

有意未实现JSONish形式的 { key : value },因为在项目当前阶段尚不清楚哪种行为应该是默认的。

当涉及到标量值时,有几个选项:我们将它用括号括起来

  • (var:type) - 变量名及其类型,读取器应尝试将其转换为读取的值
  • (list var:type) - 将节点作为集合处理;类型应该实现 yamlette::book::extractor::traits::List 特性
  • (dict var:type) - 将节点作为集合处理;类型应该实现 yamlette::book::extractor::traits::Dict 特性
  • (call FnOnce [, FnOnce[, ...]])] - 适用于高级用户;在节点上调用多个自定义回调函数
  • (foreach FnOnce [, FnOnce[, ...]]] - 适用于高级用户;在节点的所有兄弟节点上调用多个自定义回调函数

幸运的是,序列没有特别之处。

编写格式的具体细节

当我们编写字典时,我们只需用冒号和逗号枚举其键和值,就像在JSON中一样

  • {:}

当涉及到标量值时,我们只需提供一个值,无需任何语法糖。避免将表达式或代码块传递给宏是一个好习惯,因为它可能会干扰宏的格式本身。然而,如果您需要提供一个表达式,您可能希望用括号将其括起来。

当我们编写内容时,我们可能希望选择一些样式,以便处理器生成输出。

还有两件事需要知道

  • 指令
  • 样式

样式适用于所有内容,并且它们可以放在

  • 宏标记打开的方括号之后(这将适用于所有文档)
  • 文档打开的方括号之后(这将适用于文档中的所有节点)
  • 列表打开的方括号之后(这将适用于列表中的所有节点,但不包括列表本身)
  • 字典打开的大括号之后(这将适用于字典中的所有节点,但不包括字典本身)
  • 括号之后(对于节点)

指令适用于YAML文档,并且可以放在

  • 宏标记打开的方括号之后(这将适用于所有文档)
  • 文档打开的方括号之后(这将适用于特定文档)

如果您需要在同一级别设置样式和指令,则样式先于指令

  • [ % YAML, BORDER_TOP, NO_BORDER_BOT => # FLOW => [ ... ]]

指令列表应以百分号(%)开头,以等于加大于号(=>)结尾,这是由于某些Rust宏引擎的限制,并且不会干扰现有的Rust语言语法。

样式列表应以井号(#)开头,以等于加大于号(=>)结尾。

可能的指令

  • YAML - 打印出 %YAML 指令
  • BORDER_TOP - 打印出文档的上边框(---)
  • 文档底部边框 - 打印文档的底部边框 (...), 在存在多个 YAML 文档的情况下自动生成
  • NO_YAML - 忽略 YAML 指令
  • NO_BORDER_TOP - 忽略 BORDER_TOP 指令
  • NO_BORDER_BOT - 忽略 BORDER_BOT 指令
  • (TAG ; handle , prefix ) - 发布自定义 %TAG 指令

有两种类型的样式:通用样式和模型样式。模型样式仅适用于单一数据类型(数据模型),如 !!str 或 !!int。通用样式适用于多个数据类型(数据模型)。

已经实现了一些通用样式

  • yamlette::model::style::Indent - 允许更改缩进大小(默认为2个空格)
  • yamlette::model::style::Flow - 流模式(类似 JSON 模式)
  • yamlette::model::style::Compact - 尽可能进行紧凑输出(眼睛刺痛模式)
  • yamlette::model::style::Multiline - 尽可能多地使用换行符(与 FLOW 模式一起使用很有用)
  • yamlette::model::style::IssueTag - 发布节点标签(是的,你说对了,!!str、!!int、!!map、!!timezone 等)
  • yamlette::model::style::RespectThreshold - 如果行太长,则换行
  • yamlette::model::style::Threshold - RespectThreshold 模式下每行的最大字符数

模型样式可能会更改某些格式,主要取决于使用案例。尽管如此,已经实现了一些

  • yamlette::model::yaml::str::ForceQuotes - 用引号括住字符串(即使没有特殊字符或换行符)
  • yamlette::model::yaml::str::PreferDoubleQuotes - 如果 ForceQuotes,则使用 " 而不是 '
  • 其他一些可能在库的新版本中实现(例如,为 !!int、!!float 和 !!timestamp 实现数字格式化)

以下是一个描述的示例

#[macro_use]
extern crate yamlette;

use std::collections::BTreeMap;

const TGT_YAML: &'static str =
r#"%YAML 1.2
%TAG !custom-bool-tag! tag:yaml.org,2002:boo
---
name: Martin D'vloper
job: 'Developer'
employed: !custom-bool-tag!l true
foods: [ Apple, Orange, Strawberry, Mango ]
languages: {
    pascal: Lame,
    perl: Elite,
    python: Elite
}
...
"#;

fn main () {
    let name = "Martin D'vloper";
    let employed = true;

    let foods = vec! ["Apple", "Orange", "Strawberry", "Mango"];

    let mut languages = BTreeMap::new ();
    languages.insert ("pascal", "Lame");
    languages.insert ("perl", "Elite");
    languages.insert ("python", "Elite");

    use yamlette::model::style::{ FLOW, MULTILINE, ISSUE_TAG, Indent };
    use yamlette::model::yaml::str::FORCE_QUOTES;

    let string = yamlette! ( write ; [
        % YAML, ( TAG ; "!custom-bool-tag!" , "tag:yaml.org,2002:boo" ) =>
        [
            { # FLOW =>
                "name": name,
                "job": ( # FORCE_QUOTES => "Developer" ),
                "employed": ( # ISSUE_TAG => employed ),
                "foods": foods,
                "languages": ( # MULTILINE, Indent (4) => languages )
            }
        ]
    ] ).ok ().unwrap ();

    assert_eq! (string, TGT_YAML);
}

许可协议

许可协议:Double: MIT / Apache 许可证, 版本 2.0

依赖项

~1.5MB
~32K SLoC