2个版本

0.0.2 2022年5月30日
0.0.1 2022年4月16日

#1549 in 编码

MIT/Apache

31KB
401

TPK for Rust   Build Coverage Crate

Rust实现的TPK格式


此存储库包含TPK数据格式的Rust实现的工作进度代码。

在撰写本文时,规范尚未最终确定,此实现本身也不完全符合。因此,我强烈建议不要使用此库,甚至不要使用TPK数据,用于任何重要的项目。

用法

目前,仅支持手动编写/读取元素和条目。这意味着大多数数据都需要手动编写和读取。

基于元素的编写/读取

例如,要编写以下JSON结构的TPK等价物

{
  "format": "TPK",
  "version": {
    "name": "First Development Release",
    "major": 0,
    "minor": 1,
    "patch": 0
  }
}

你需要做以下事情

use tpk::{Element, Writer};

fn main() {
    // "output" is an already created `Write` implementor
    let mut writer = Writer::new(output);
    writer.write_element(&Element::Marker("format".into()));
    writer.write_element(&Element::String("TPK".into()));
    writer.write_element(&Element::Marker("version".into()));
    writer.write_element(&Element::Folder);
    writer.write_element(&Element::Marker("name".into()));
    writer.write_element(&Element::String("First Development Release".into()));
    writer.write_element(&Element::Marker("major".into()));
    writer.write_element(&Element::UInteger8(0));
    writer.write_element(&Element::Marker("minor".into()));
    writer.write_element(&Element::UInteger8(1));
    writer.write_element(&Element::Marker("patch".into()));
    writer.write_element(&Element::UInteger8(0));
}

这看起来相当冗长。读取甚至更糟

use tpk::{Element, Reader};

#[inline(always)]
fn print_string(name: &'static str, element: Element) {
  match element {
    Element::String(string) => println!("The {} is {}", name, string),
    _ => panic!("Expected string element, got something else"),
  };
}

#[inline(always)]
fn print_uint8(name: &'static str, element: Element) {
  match element {
    Element::UInteger8(number) => println!("The {} is {}", name, number),
    _ => panic!("Expected unsigned integer element, got something else"),
  };
}

fn main() {
    // "input" is an already created `Read` implementor
    let mut reader = Reader::new(input);

    let mut in_version = false;
    while let Ok(Some(element)) = reader.read_element() {
        if in_version {
            match element {
                Element::Marker(name) if name == "name" => {
                    print_string("version name", reader.read_element().unwrap().unwrap());
                }
                Element::Marker(name) if name == "major" => {
                    print_uint8("major version", reader.read_element().unwrap().unwrap());
                }
                Element::Marker(name) if name == "minor" => {
                    print_uint8("minor version", reader.read_element().unwrap().unwrap());
                }
                Element::Marker(name) if name == "patch" => {
                    print_uint8("patch version", reader.read_element().unwrap().unwrap());
                }
                _ => panic!("Unrecognized entry"),
            }
        } else {
            match element {
                Element::Marker(name) if name == "format" => {
                    print_string("format", reader.read_element().unwrap().unwrap());
                }
                Element::Marker(name) if name == "version" => {
                    in_version = true;
                    // Oops, we're not checking that version is a folder!
                    reader.read_element().unwrap().unwrap();
                }
                _ => panic!("Unrecognized entry"),
            };
        }
    }
}

哎呀,这太粗糙了!而且我们还没有支持所有边缘情况...我们很容易在某些有效TPK数据上恐慌(例如,此格式的../文件夹标记),或者错过无效数据(例如,对于version的另一个元素)。

这种编写和读取文件的方式称为“元素模式”。这是处理TPK数据的最低级方式,并且仅应由需要操作原始TPK元数据的工具使用。这是目前由tpk-rust支持的唯一方式。

如果你的需求是轻松地将数据从TPK文件中读取和写入,例如,最好等待树模式或甚至serde支持被实现。

基于条目的编写/读取

让我们尝试使用基于条目的编写来将前面提到的结构写入TPK数据

use tpk::{Element, Entry, Writer};

fn main() {
  // "output" is an already created `Write` implementor
  let mut writer = Writer::new(file);
    writer.write_entry(&Entry {
        name: "format".into(),
        elements: vec![Element::String("TPK".into())],
    });
    writer.write_entry(&Entry {
        name: "version".into(),
        elements: vec![Element::Folder],
    });
    writer.write_entry(&Entry {
        name: "name".into(),
        elements: vec![Element::String("First Development Release".into())],
    });
    writer.write_entry(&Entry {
        name: "major".into(),
        elements: vec![Element::UInteger8(0)],
    });
    writer.write_entry(&Entry {
        name: "minor".into(),
        elements: vec![Element::UInteger8(1)],
    });
    writer.write_entry(&Entry {
        name: "patch".into(),
        elements: vec![Element::UInteger8(0)],
    });
}

它稍微不那么冗长,但更重要的是它更有结构,这允许我们稍微分解代码

use tpk::{Element, Entry, Writer};

#[inline(always)]
fn create_entry(name: &str, element: Element) -> Entry {
    Entry {
        name: name.into(),
        elements: vec![element],
    }
}

fn main() {
    // "output" is an already created `Write` implementor
    let mut writer = Writer::new(output);
    writer.write_entry(&create_entry("format", Element::String("TPK".into())));
    writer.write_entry(&create_entry("version", Element::Folder));
    writer.write_entry(&create_entry(
        "name",
        Element::String("First Development Release".into()),
    ));
    writer.write_entry(&create_entry("major", Element::UInteger8(0)));
    writer.write_entry(&create_entry("minor", Element::UInteger8(1)));
    writer.write_entry(&create_entry("patch", Element::UInteger8(0)));
}

好多了!正如所示,基于条目的编写模式在需要以低级模式操作但不想自己处理标记/元素关联,并且可以接受少量开销时特别有用。

使用基于条目的模式进行读取也容易一些。

use tpk::{Element, Reader};

#[inline(always)]
fn print_string(name: &'static str, element: &Element) {
  match element {
    Element::String(string) => println!("The {} is {}", name, string),
    _ => panic!("Expected string element, got something else"),
  };
}

#[inline(always)]
fn print_uint8(name: &'static str, element: &Element) {
  match element {
    Element::UInteger8(number) => println!("The {} is {}", name, number),
    _ => panic!("Expected unsigned integer element, got something else"),
  };
}

fn main() {
  // "input" is an already created `Read` implementor
  let mut reader = Reader::new(input);

    let mut in_version = false;
    while let Ok(Some(element)) = reader.read_entry() {
        if in_version {
            match element.name.as_str() {
                "name" => print_string("version name", &element.elements[0]),
                "major" => print_uint8("major version", &element.elements[0]),
                "minor" => print_uint8("minor version", &element.elements[0]),
                "patch" => print_uint8("patch version", &element.elements[0]),
                _ => panic!("Unrecognized entry"),
            }
        } else {
            match element.name.as_str() {
                "format" => print_string("format", &element.elements[0]),
                "version" => {
                    in_version = true;
                }
                _ => panic!("Unrecognized entry"),
            }
        }
    }
}

遗憾的是,这个实现只是不那么冗长:我们仍然没有处理一些边缘情况,例如 ../* 文件夹,并且我们仍然没有对 version 文件夹条目进行类型检查。

路线图

由于 tpk-rust 计划成为 TPK 数据格式的参考实现,主版本和次要版本将遵循规范。

0.1 - 首个开发版本

先决条件

  • TPK 0.1 已发布

待办事项列表

  • 对规范进行读写时的完全合规
    • 标记 TPK 元素
    • 原始 TPK 元素
    • 文件夹和集合
    • 扩展元素
    • 依赖管理
    • 大端支持
    • 解析器提示(例如数据大小)
  • 条目模式的读写
  • CI/CD
    • CI
    • CD
  • 发布 crate

0.1.x - 与格式无关的计划增强

先决条件

  • TPK-Rust 0.1 已发布

待办事项列表

  • 树模式读写
  • Serde 支持
  • 解析器提示优化
  • 与其他格式和解析器的性能报告

依赖

~0.4–1MB
~21K SLoC