如何漂亮地打印 Syn AST?
How to pretty print Syn AST?
我正在尝试使用 syn to create an AST from a Rust file and then using quote 将其写入另一个。但是,当我写它时,它会在所有内容之间放置额外的空格。
请注意,下面的示例只是为了演示我遇到的最小可重现问题。我意识到,如果我只是想复制代码,我可以复制文件,但它不适合我的情况,我需要使用 AST。
pub fn build_file() {
let current_dir = std::env::current_dir().expect("Unable to get current directory");
let rust_file = std::fs::read_to_string(current_dir.join("src").join("lib.rs")).expect("Unable to read rust file");
let ast = syn::parse_file(&rust_file).expect("Unable to create AST from rust file");
match std::fs::write("src/utils.rs", quote::quote!(#ast).to_string());
}
它创建 AST 的文件是这样的:
#[macro_use]
extern crate foo;
mod test;
fn init(handle: foo::InitHandle) {
handle.add_class::<Test::test>();
}
它输出的是这样的:
# [macro_use] extern crate foo ; mod test ; fn init (handle : foo :: InitHandle) { handle . add_class :: < Test :: test > () ; }
我什至试过 运行 通过 rustfmt
将其写入文件后,如下所示:
utils::write_file("src/utils.rs", quote::quote!(#ast).to_string());
match std::process::Command::new("cargo").arg("fmt").output() {
Ok(_v) => (),
Err(e) => std::process::exit(1),
}
不过好像也没什么区别
quote
crate is not really concerned with pretty printing the generated code. You can run it through rustfmt,你只需要执行rustfmt src/utils.rs
或cargo fmt -- src/utils.rs
。
use std::fs;
use std::io;
use std::path::Path;
use std::process::Command;
fn write_and_fmt<P: AsRef<Path>, S: ToString>(path: P, code: S) -> io::Result<()> {
fs::write(&path, code.to_string())?;
Command::new("rustfmt")
.arg(path.as_ref())
.spawn()?
.wait()?;
Ok(())
}
现在你可以执行:
write_and_fmt("src/utils.rs", quote::quote!(#ast)).expect("unable to save or format");
另请参阅 Rust 论坛上的 "Any interest in a pretty-printing crate for Syn?"。
请查看新的 prettyplease crate。优点:
- 可以直接作为库使用
- 它可以处理代码片段,而
rustfmt
只能处理完整文件。
- 速度很快,因为它使用了更简单的算法。
因为 mentioned in his answer, prettyplease 可用于格式化代码片段,这在测试 proc 宏时非常有用,因为 proc_macro2::TokenStream
上的标准 to_string()
很难阅读。
这里是一个代码示例,用于漂亮地打印一个 proc_macro2::TokenStream
可解析为 syn::Item
:
fn pretty_print_item(item: proc_macro2::TokenStream) -> String {
let item = syn::parse2(item).unwrap();
let file = syn::File {
attrs: vec![],
items: vec![item],
shebang: None,
};
prettyplease::unparse(&file)
}
我在测试中使用它来帮助我了解错误生成的代码在哪里:
assert_eq!(
expected.to_string(),
generate_event().to_string(),
"\n\nActual:\n {}",
pretty_print_item(generate_event())
);
我正在尝试使用 syn to create an AST from a Rust file and then using quote 将其写入另一个。但是,当我写它时,它会在所有内容之间放置额外的空格。
请注意,下面的示例只是为了演示我遇到的最小可重现问题。我意识到,如果我只是想复制代码,我可以复制文件,但它不适合我的情况,我需要使用 AST。
pub fn build_file() {
let current_dir = std::env::current_dir().expect("Unable to get current directory");
let rust_file = std::fs::read_to_string(current_dir.join("src").join("lib.rs")).expect("Unable to read rust file");
let ast = syn::parse_file(&rust_file).expect("Unable to create AST from rust file");
match std::fs::write("src/utils.rs", quote::quote!(#ast).to_string());
}
它创建 AST 的文件是这样的:
#[macro_use]
extern crate foo;
mod test;
fn init(handle: foo::InitHandle) {
handle.add_class::<Test::test>();
}
它输出的是这样的:
# [macro_use] extern crate foo ; mod test ; fn init (handle : foo :: InitHandle) { handle . add_class :: < Test :: test > () ; }
我什至试过 运行 通过 rustfmt
将其写入文件后,如下所示:
utils::write_file("src/utils.rs", quote::quote!(#ast).to_string());
match std::process::Command::new("cargo").arg("fmt").output() {
Ok(_v) => (),
Err(e) => std::process::exit(1),
}
不过好像也没什么区别
quote
crate is not really concerned with pretty printing the generated code. You can run it through rustfmt,你只需要执行rustfmt src/utils.rs
或cargo fmt -- src/utils.rs
。
use std::fs;
use std::io;
use std::path::Path;
use std::process::Command;
fn write_and_fmt<P: AsRef<Path>, S: ToString>(path: P, code: S) -> io::Result<()> {
fs::write(&path, code.to_string())?;
Command::new("rustfmt")
.arg(path.as_ref())
.spawn()?
.wait()?;
Ok(())
}
现在你可以执行:
write_and_fmt("src/utils.rs", quote::quote!(#ast)).expect("unable to save or format");
另请参阅 Rust 论坛上的 "Any interest in a pretty-printing crate for Syn?"。
请查看新的 prettyplease crate。优点:
- 可以直接作为库使用
- 它可以处理代码片段,而
rustfmt
只能处理完整文件。 - 速度很快,因为它使用了更简单的算法。
因为 proc_macro2::TokenStream
上的标准 to_string()
很难阅读。
这里是一个代码示例,用于漂亮地打印一个 proc_macro2::TokenStream
可解析为 syn::Item
:
fn pretty_print_item(item: proc_macro2::TokenStream) -> String {
let item = syn::parse2(item).unwrap();
let file = syn::File {
attrs: vec![],
items: vec![item],
shebang: None,
};
prettyplease::unparse(&file)
}
我在测试中使用它来帮助我了解错误生成的代码在哪里:
assert_eq!(
expected.to_string(),
generate_event().to_string(),
"\n\nActual:\n {}",
pretty_print_item(generate_event())
);