0.20220802.0-dev |
|
---|---|
0.20220725.0-dev |
|
0.20220724.0-dev |
|
0.0.0-已删除 |
|
#79 in #smart-pointers
30KB
476 代码行
🗑️ litter
使你的字面量可通过智能指针变为可变,指向源代码。
这些可用于快照测试,或将状态内联到脚本中的基本方式。这仅适用于通过 Cargo 运行的代码,因为它依赖于 CARGO_
环境变量在需要写入时定位源代码。实现使用 #[track_caller]
属性(无宏)。
待办事项
- 实现简单的非同步写入
- 实现更好的写入
- 支持外部数据(?)
- 未来工作?!上述内容几乎都没有实际实现!
- 使用可失败替代方案而不是恐慌,包括在源文件不存在时写入值的情况(它们仍然可以保存在内存中)。
- 支持外部数据,而不仅仅是内联。
- 虽然值只被读取,但我们可以保留其状态的 Weak Arc 并让它被释放。一旦写入,我们就需要将其保留在内存中,因此泄露了一个引用。
基本用法
类型 litter::Litter
封装了字面量值及其在源代码中的位置信息,允许对其进行修改,同时原始脚本文件中的更改也会反映出来。支持的字面量类型包括整数(1
、2
、2_usize
、-1i16
)、浮点数(1.5
、2e6f64
)、布尔值(true
、false
)、静态字符串("hello"
、r##"world##"
)、静态字节字符串(b"one two \x12"
、br"hell\x00"
)。这些类型由 litter::Literal
特性描述。
.edit()
修改 Literal
或 Litter
会生成一个 LitterHandle
。它实现了 Deref
和 DerefMut
,暴露了内部值,以及各种其他特性。如果内部值被修改,它将在 Litter
被丢弃时写回文件。
以下是一个基本示例,每次脚本运行时都会修改字符串
use litter::LiteralExt;
fn main() {
let mut p = "and I say hello!".edit();
*p += " hello!";
}
let mut p = "and I say hello! hello!".edit();
*p += " hello!";
let mut p = "and I say hello! hello! hello!".edit();
*p += " hello!";
组合
Litter
构造函数使用 #[track_caller]
而不是宏,因此可以将其封装以创建自己的函数来修改字面量,但有一个重要的限制:要修改的字面量必须是函数调用中作为第一个参数出现的字面量。因此 f(x, "literal")
是可行的,但 f(2, "literal")
则不行。因此,我们通常倾向于将字面量作为函数的第一个参数。
例如,我们可以使用此功能来实现类似于 expect_test
风格的快照测试。
#[test]
fn test_the_ultimate_question() {
assert_eq_u64(42, 6 * 9);
}
#[track_caller] // <- in order to look for literal at this function's call site instead
fn assert_eq_u64(expected: u64, actual: u64) {
let expected = litter::new(expected);
if expected != actual {
if std::env::get("UPDATE_EXPECT").unwrap_or("0") != "0" {
*expected = actual; // <- updated in memory, written to source at end of scope
} else {
panic!("\
Expected {expected:?} but actual value was {actual:?}.\n\
\n\
To update the expected value, run this again with UPDATE_EXPECT=1.\
");
}
}
}
运行此测试将最初因我们的 panic 消息而失败,但再次运行带有 UPDATE_EXPECT=1 cargo test
的测试将通过并更新我们的源代码以反映正确值
#[test]
fn test_the_ultimate_question() {
assert_eq_u64(54, 6 * 9);
}
尽管您不需要自己编写此特定函数:一个泛型版本包含在 litter::assert_eq(literal, actual)
中。
序列化
如果您启用了 json
、yaml
、postcard
或 toml
功能,字符串和字节的 LiteralExt
将获得相应的 .edit_json()
、.edit_yaml()
、.edit_toml()
或 .edit_postcard()
方法,可用于内联实现 serde::{ Serialize, Deserialize }
的非原始值,以及 Debug
、Clone
和 Default
。 (如果您的类型没有实现 Default
,您可以考虑将其包装在 Option<...>
中。)
作为一个方便的特殊情况,如果字面字符串为空但反序列化失败,则将返回类型的 Default::default()
值。任何其他(反)序列化错误将导致程序崩溃。
fn main() {
// empty string interpreted as default, like "[]"
let json_vec = "".edit_json::<Vec<usize>>();
json_vec.push(json_vec.len());
// empty byte string interpreted as default, like b"\x00"
let postcard_vec = b"".edit_postcard::<Vec<usize>>();
postcard_vec.push(postcard_vec.len());
}
let json_vec = "[0]".edit_json::<Vec<usize>>();
json_vec.push(json_vec.len());
let postcard_vec = b"\x01\x00".edit_postcard::<Vec<usize>>();
postcard_vec.postcard_vec(postcard_vec.len());
let json_vec = "[0, 1]".edit_json::<Vec<usize>>();
json_vec.push(json_vec.len());
let postcard_vec = b"\x02\x00\x01".edit_postcard::<Vec<usize>>();
postcard_vec.push(postcard_vec.len());
如果没有指定类型或无法推断类型,则 .edit_json()
、.edit_yaml()
和 .edit_toml()
方法默认为动态值(serde_json::Value
、serde_yaml::Value
和 toml::Value
)。但是 .edit_postcard()
总是需要已知类型(它不是自我描述的,也不能动态处理)。
对于文本格式,将编码到字符串字面量将是格式化的/冗长的,而将编码到字节字符串字面量将使用紧凑的表示。 (这显然与仅二进制格式的 postcard
和 rkyv
无关。)
性能和可靠性
这明显是为了方便,而不是为了性能。它应该足够快,可以用于测试快照或脚本中的某些状态的转储,但您肯定不会想用它来构建高吞吐量的Web服务器。对每个字面量的访问都由 RwLock
控制,如果并发使用可能会阻塞。
只有在需要将更改后的值写回时才会访问文件系统,所以如果值从未被修改,文件系统就不会被访问,在这种情况下,程序可以在没有源文件的不同系统上正常运行。
如果您的程序多个副本同时运行并尝试修改同一文件,则可能会出现逻辑错误。
许可证
litter
版权所有 Jeremy Banks,根据熟悉的 MIT OR Apache-2.0
许可。
litter
从 expect-test 库 中借鉴了很多内容,该库也采用 MIT OR Apache-2.0
许可协议,并由 rust-analyzer 开发者(包括 Aleksey Kladov 和 Dylan MacKenzie)版权所有。
依赖项
~1–12MB
~106K SLoC