6 个版本
0.1.5 | 2024 年 3 月 13 日 |
---|---|
0.1.4 | 2023 年 7 月 16 日 |
0.1.3 | 2023 年 6 月 28 日 |
0.1.0 | 2023 年 4 月 6 日 |
在 过程宏 中排名第 1637
被 cl-format 使用
19KB
236 行
cl-format
cl-format
是 Common Lisp format 函数的 Rust 实现。
TL;DR:使用像 ~a ~{~}
这样的指令和灵活的条件和循环控制字符串来从参数格式化字符串。
如果你不熟悉 Common Lisp,这里有几个页面可以让你有个大致的了解
注意:我还没有实现所有的格式指令。我正在努力。下面可以找到已经完成的那些。
顺便说一句:我正在尝试复制 Common Lisp 的 format 函数的行为,但可能有一些妥协。
用法
有两种方式可以使用这个库。你可以使用 cl_format!
宏,或者生成控制字符串并自己格式化参数以获得更多灵活性。
首先,在你的 Cargo.toml
中添加 cl-format = "0.2"
。
使用宏
~a
是我最常用的指令之一,所以让我们从普通的 ~a
开始。
let a = cl_format!("~a, ~a, ~a", &1_i32, &2, &3);
assert_eq!(String::from("1, 2, 3"), a.unwrap());
所有用于格式化的参数都必须是借用来的,并且必须实现 TildeAble
特性。有关详细信息,请参阅 为自定义类型实现 部分。
这里是宏的更多用法。字符串中的双引号符号转义
let s = String::from("abc");
let a = cl_format!("~a, ~a, ~a, ~S", &1_i32, &2, &3, &s);
assert_eq!(String::from("1, 2, 3, \"abc\""), a.unwrap());
或者不转义
let a = cl_format!("start ~a, ~a, ~a, ~a, here", &1_i32, &2, &3, &s);
assert_eq!(String::from("start 1, 2, 3, abc, here"), a.unwrap());
让我们在控制字符串中做一些类似于 Lispers 的循环
let ll: Vec<&dyn TildeAble> = vec![&1, &2, &3];
let a = cl_format!("~a, ~a, ~a, ~{~a,~}", &1_i32, &2, &3, &ll);
assert_eq!(String::from("1, 2, 3, 1,2,3,"), a.unwrap());
等等,结果末尾有一个不必要的逗号,让我们清理一下
let a = cl_format!("~a, ~a, ~a, ~{~a~^,~}", &1_i32, &2, &3, &ll);
assert_eq!(String::from("1, 2, 3, 1,2,3"), a.unwrap());
我突然不想再循环 Vec 了
let l = vec![&1 as &dyn TildeAble, &2, &3];
let a = cl_format!("The value is:\n ~a", &l);
assert_eq!(String::from("The value is:\n [1, 2, 3]"), a.unwrap());
现在,Common Lisp 和 Rust 之间存在一些不一致性。在 Common Lisp 中,控制字符串中的 ~%
表示换行,但现在是 Rust,所以 \n
将会生效。
我觉得展示类型为 &dyn TildeAble
给 Vec 内的元素有点烦了。但我还没有找到避免它的方法。如果你知道,请告诉我。所以我添加了一些宏
let l = vec![tilde!(&1), &2, &3];
let a = cl_format!("The value is:\n ~a", &l);
assert_eq!(String::from("The value is:\n [1, 2, 3]"), a.unwrap());
就像在 Common Lisp 中一样,我们可以遍历所有参数,而不是将它们放在 Vec 中
let a = cl_format!("~@{~a~^, ~}", &1, &2, &3);
assert_eq!(String::from("1, 2, 3"), a.unwrap());
现在,让我们尝试一些条件控制(你可以在 A Few FORMAT Recipes 的 Conditional Formatting
章节中找到条件控制字符串的含义)
let l = vec![tilde!(&1), &2, &3];
let a = cl_format!("~{~a~#[~;, and ~:;, ~]~}", &l);
assert_eq!(String::from("1, 2, and 3"), a.unwrap());
let l = vec![tilde!(&1), &2, &3, &4];
let a = cl_format!("~{~a~#[~;, and ~:;, ~]~}", &l);
assert_eq!(String::from("1, 2, 3, and 4"), a.unwrap());
手动
使用宏会在每次使用时生成控制字符串实例。如果你试图在各个地方使用控制字符串,因为它足够灵活,可以用于多种用途,这可能会造成浪费。
我们可以自己生成它
let cs = cl_format::ControlStr::from("~{~#[~;~a~;~a and ~a~:;~@{~a~#[~;, and ~:;, ~]~}~]~}").unwrap();
然后我们可以生成控制字符串的 Args 以揭示它们
let mut list = vec![];
let args = Args::new(vec![&list]);
让我们通过提供不同长度的参数来多次使用它
// this equal cl_format!(cs, &list)
assert_eq!(cs.reveal(args).unwrap(), "".to_string());
list.push(&1);
let args = Args::new(vec![&list]);
assert_eq!(cs.reveal(args).unwrap(), "1".to_string());
list.push(&2);
let args = Args::new(vec![&list]);
assert_eq!(cs.reveal(args).unwrap(), "1 and 2".to_string());
list.push(&3);
let args = Args::new(vec![&list]);
assert_eq!(cs.reveal(args).unwrap(), "1, 2, and 3".to_string());
list.push(&4);
let args = Args::new(vec![&list]);
assert_eq!(cs.reveal(args).unwrap(), "1, 2, 3, and 4".to_string());
让我们尝试一个混合示例
let my_team = String::from("STeam");
let my_stars = vec![
String::from("Adam Lambert"),
String::from("Queen"),
String::from("snoop dogg"),
];
let stars = my_stars
.iter()
.map(|s| tilde!(s))
.collect::<Vec<&dyn TildeAble>>();
assert_eq!(
String::from("my favorite team \"STeam\" will win the superbowl LVIII. And Adam Lambert, Queen, and snoop dogg will in half time show. And the scores should be 38:35"),
cl_format!(
"my favorite team ~S will win the superbowl ~@R. And ~{~#[~;~a~;~a and ~a~:;~@{~a~#[~;, and ~:;, ~]~}~]~} will in half time show. And the scores should be ~d:~d",
&my_team,
&58,
&stars,
&38,
&35
)
.unwrap()
);
为自定义类型实现
到目前为止,我们只展示了基本类型。如果能让我们自己的类型也能被揭示,那就更好了。
这里有一个关于如何实现的演示
use cl_format::*;
// has to derive to Debug
#[derive(Debug)]
struct MyStruct {
a: usize,
b: String,
}
impl TildeAble for MyStruct {
// there are a lot methods inside, but not every of them
// we are need.
// ~a is good enough
fn into_tildekind_va(&self) -> Option<&dyn TildeKindVa> {
Some(self)
}
// ~d just for show case
fn into_tildekind_digit(&self) -> Option<&dyn TildeKindDigit> {
Some(self)
}
// how many elements you want cl_format treat this type
// 1 is enough. And this one has to implement
fn len(&self) -> usize {
1
}
}
到目前为止,你的 IDE 应该会给出一些错误,让你实现 TildeKindVa
和 TildeKindDigit
。
impl TildeKindVa for MyStruct {
fn format(&self, tkind: &TildeKind, buf: &mut String) -> Result<(), TildeError> {
buf.push_str(&format!("a: {}, b: {}", self.a, self.b));
Ok(())
}
}
impl TildeKindDigit for MyStruct {
fn format(&self, tkind: &TildeKind, buf: &mut String) -> Result<(), TildeError> {
buf.push_str(&format!("{}", self.a));
Ok(())
}
}
现在 MyStruct
可以被 cl_format
使用,但正如你所猜测的,只有 ~a
和 ~d
let s = MyStruct {
a: 1,
b: "b".to_string(),
};
assert_eq!("a: 1, b: b".to_string(), cl_format!("~a", &s).unwrap());
assert_eq!(
"a: 1, b: b lalalal a: 1, b: b".to_string(),
cl_format!("~a lalalal ~a", &s, &s).unwrap()
);
assert_eq!("1".to_string(), cl_format!("~d", &s).unwrap());
assert_eq!(
"First: a: 1, b: b; Second: 1".to_string(),
cl_format!("First: ~a; Second: ~d", &s, &s).unwrap()
);
格式指令
这是已实现指令的表格
波浪号 | Rust 类型 |
---|---|
~a |
f32, f64, char, i8, i16, i32, i64, i128, isize, bool, u8, u16, u32, u64, u128, usize, String |
~s |
f32, f64, char, i32, i64, usize, bool, u32, u64, String |
~d |
i8, i16, i32, i64, i128, u8, u16, u32, u64, u128, usize, isize |
~C |
char |
~[~] (常规条件) |
bool, usize |
~R |
i8, i16, i32, i64, i128, u8, u16, u32, u64, u128, usize, isize |
依赖关系
~265–710KB
~17K SLoC