1个不稳定版本
0.1.0 | 2023年3月13日 |
---|
#5 在 #overload
43KB
767 行
Toml路径和指针
概述
根据json路径语法实现TOML指针,类型为Option<&toml::Value>
,依赖于crate toml
。重载/
作为路径操作符以指向TOML树中的节点,以及一些其他有意义的操作符重载。例如,管道操作符|
用于从标量叶节点获取原始值,推入操作符<<
用于覆盖标量节点或将新项目推入数组或表,以及推入赋值操作符<<=
用于无条件地重新分配给TOML节点。而/
或操作符<<
可能会使指针失效,我们可以使用!
操作符或is_none()
方法来测试这种失败情况。
示例
use toml_ops::PathOperator;
let tv = r#"
[host]
ip="127.0.0.1"
port=8080
proto=["tcp", "udp"]
"#;
let mut v: toml::Value = tv.parse().unwrap();
let port = v.path() / "host" / "port" | 0;
assert_eq!(port, 8080);
let node = v.path_mut() / "host" / "port" << 8989;
let port = node | 0;
assert_eq!(port, 8989);
let proto = v.path() / "host" / "proto" / 0 | "";
assert_eq!(proto, "tcp");
let host = v.path_mut() / "host";
let host = host << ("newkey", "newval") << ("morekey", 1234);
assert_eq!(host.is_none(), false);
assert_eq!(!host, false);
let mut proto = v.path_mut() / "host" / "proto";
proto = proto << ("json", ) << ["protobuf"];
assert_eq!(proto.as_ref().unwrap().as_array().unwrap().len(), 4);
proto <<= "default";
assert_eq!(proto.as_ref().unwrap().is_str(), true);
let proto = v.path() / "host" / "proto" | "";
assert_eq!(proto, "default");
let invalid = v.path() / "host" / "no-key";
assert_eq!(!invalid, true);
assert_eq!(invalid.is_none(), true);
路径语法
类似于Unix文件系统中的文件路径,每个组件通过斜杠/
分隔,特别是对于数组索引也使用/
而不是[]
。因此,您不应在表中使用数字键以避免混淆。
请参阅详细或标准json路径语法,因为TOML在数据模型上与JSON大致相同。
Toml操作符重载指南
操作符触发
我们不能在 toml::Value
的外部直接重载操作符形式。因此,我在 toml_ops
包中定义了一个名为 PathOperator
的 trait,并为 toml::Value
实现了它。然后,您可以从 toml::Value
调用以下方法来创建 toml 指针
path()
:指向自身的 toml 值。pathto(subpath: &str)
:指向自身 toml 值的某个子节点。path_mut()
:path()
的可变版本。pathto_mut(subpath: &str)
:pathto()
的可变版本。
指针只是 Option<&toml::Value>
或可变版本的 Option<&mut toml::Value>
的结构包装。但通常不需要关心这一点,只需使用重载运算符。
可变版本指针不能实现 Copy
trait,请注意,许多后续运算符会消耗(移动)它。
路径操作符 /
它是 toml 路径指针的核心操作符,重载 /
(除法)作为路径操作符的目的非常直接。
例如,以下语句大致等价
let node = toml_value.path() / "path" / "to" / "node";
let node = toml_value.pathto("path/to/node");
let node = toml_value.path() / "path/to/node";
let node = toml_value.path() / "path.to.node";
请注意,后两种使用单个字符串表示的长路径在性能上略低效,因为它涉及到拆分和解析路径语法,并且在表节点中有数字键时可能会造成混淆(现在只有可变版本存在细微的错误)。
最好像这样指向数组项
let node = toml_value.path() / "sub-array" / 0 / "sub-key";
// not so good to use:
// let node = toml_value.path() / "sub-array/0/sub-key";
虽然不能重载操作符 .
,但点 .
也可以在长单路径形式中用作路径分隔符。但不要混用斜杠和点来混淆自己。例如,path/../to/node
并不指向父节点,就像在文件系统中一样,它只是等于 path/to/node
或 path.to.node
,因为路径操作符忽略了连续的分隔符。
管道操作符 |
用户使用管道操作符 |
从 toml 节点获取原始标量值。您可以将 |
视为 /
的垂直形式,并读作 "或默认",意味着如果节点无效或类型错误,则返回 |
右侧的默认值。
let str_val: &str = toml_value().path() / "path" / "to" / "node" | "";
let int_val: i64 = toml_value().path() / "path" / "to" / "node" | 0;
let float_val: f64 = toml_value().path() / "path" / "to" / "node" | 0.0;
let bool_val: bool = toml_value().path() / "path" / "to" / "node" | false;
类型符号,如: i64
并非必要,因为它可以从|
的右侧推导出来。
显然,|
将结束/
运算符链,因为它返回一个原始类型的值。
Put和Push运算符<<
它在可变指针版本中使用,用于修改其所引用的节点。
Put运算符<<
适用于叶节点。因为TOML叶节点只能存储一个标量值,所以Put运算符会覆盖它所持有的值,前提是值类型与原始类型匹配。
let node = toml_vaule.path_mut() / "path" / "to" / "int-node" << 314;
let node = toml_vaule.path_mut() / "path" / "to" / "float-node" << 3.14;
let _ = toml_vaule.path_mut() / "path" / "to" / "string-node" << "PI";
let _ = toml_vaule.path_mut() / "path" / "to" / "bool-node" << true;
Put运算符消耗左指针并返回一个新指针,如果节点不存在或类型不匹配,则可能返回None
指针。如果不希望使用返回的指针,可以使用let _
来抑制警告。
Push运算符也重载了<<
,它可以将新项目推送到数组节点或表节点。
let node = toml_vaule.path_mut() / "table" << ("int", 314) << ("float", 3.14);
let _ = toml_vaule.path_mut() / "array" << (314,) << [3.14];
由于<<
也返回一个指针,因此Push运算符可以链式调用以向数组或表追加更多项目,前提是原始引用的数组或表是有效的。您可以推送任何可以转换为toml::Value
的值类型。
Push赋值运算符<<=
因为在Rust中不能重载赋值运算符=
,所以我们选择<<=
,它与之前的Put或Push运算符具有一些一致的含义。它会无条件地将节点重新赋值给任何可以转换为toml::Value
的值。
let node = toml_vaule.path_mut() / "path" / "to" / "bool-node" << true;
node <<= "true";
请注意,<<=
接收对自身的引用而不会返回新指针,并且不应该像<<
那样链式调用。此外,以下两个语句在叶节点上具有相同的效果,如果<<
成功
let node = node << val; // may fail when mistype
node <<= val; // always sucesse expect node already invalid pointer.
有效运算符!
重载非运算符!
可以用来测试指针是否无效。在以下情况下将返回无效指针
- 路径运算符
/
当指向不存在的节点时。 - Put运算符
<<
当尝试放入错误类型的值时。
Deref运算符*
也被重载,支持隐式地将指针用作Option<&toml::Value>
。因此,is_none()
方法的行为将与运算符!
相同。
let node = toml_value.path() / "path" / "to" / "nil";
if (!node) { return; }
if (node.is_none()) { return; }
然而,在可变版本指针中,操作符 !
会消耗指针,因此最好使用 is_none()
方法。
保存中间指针
您可以按需保存中间指针,原因如下
let root = toml_value.path();
let table = root / "path" / "to" / "table";
let val1 = table / "key1" | 0;
let val2 = table / "key2" | "";
尽管可变指针可能存在一些限制,但请遵循编译器的提示来修复任何问题。
路径操作符与嵌套结构
处理简单的 toml 时,将其反序列化为嵌套结构相当方便,然后可以使用链式点 .
操作符来访问深层字段。
rustletnode=toml_value.path.to.node;
在上面的例子中,假设 path
、to
和 node
都是不同级别的某个结构的字段,可以映射到 toml 键。
然而,这种简单的方法在某些实践案例中也可能被轻易破坏
- 当某些键(字段)在编译时未确定但在运行时更改,或者只是在其运行时更改其类型。
- 当您想以严格的方式处理可选字段,并将许多字段包裹在
Option<>
中。
在这种情况下,再也不能保持最简单的 path.to.node
风格,而且比 "path"/"to"/"node"
方式更繁琐。
而在任何情况下,都可以以一致的方式使用路径操作符,根据样本数据结构编写业务代码可能更直接,json 路径语法在一定程度上是语言中立的。
这种灵活性的代价是编码中的错误和可能丢失 IDE 完成提示。
许可证
该项目受以下任一许可证的许可
- Apache 许可证 2.0 版,(LICENSE-APACHE 或 https://apache.ac.cn/licenses/LICENSE-2.0)
- MIT 许可证 (LICENSE-MIT 或 http://opensource.org/licenses/MIT)
任您选择。
依赖关系
~285–540KB
~12K SLoC