2个版本
0.0.2 | 2022年5月30日 |
---|---|
0.0.1 | 2022年4月16日 |
#1549 in 编码
31KB
401 行
TPK for Rust
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