推断调用包的名称以在过程宏中填充一个 doctest
Infer the name of the calling crate to populate a doctest in a procedural macro
我正在创建一个程序宏,它从一些配置文件自动生成一个库(这是一个寄存器布局,但这对问题并不重要)。
我希望图书馆自动生成自动图书馆附带的文档,并包括应该 运行 和 cargo test
的文档测试。现在,我已经实现了其中的大部分,但是有一个问题我看不到解决方案。
假设我们有一个名为 my_lib
的库,我们在其中调用宏来填充它:
use my_macro_lib::hello;
hello!();
扩展为:
/// `foo` will always return `true`
/// ```
/// use my_lib;
/// assert!(my_lib::foo());
/// ```
pub fn foo() -> bool {
true
}
这将 运行 如预期的那样 - cargo doc
将做正确的事情并且 cargo test
将 运行 如预期的那样进行 doctests。
问题是,在这个例子中,use my_lib
被硬编码到 my_macro_lib
中,这显然是不可取的。
我如何创建一个宏来推断执行调用的 crate 的名称?
我尝试在程序宏中使用 macro_rules!
来扩展 $crate
,但这违反了卫生规则。
您可以通过读取 CARGO_PKG_NAME
环境变量来获取正在使用您的宏的 crate 的名称。请注意,您必须通过阅读它
std::env
(at "runtime" of your macro) and not via env!
(当你的 proc macro crate 被编译时)。
#[proc_macro]
pub fn hello(input: TokenStream) -> TokenStream {
let crate_name = std::env::var("CARGO_PKG_NAME").unwrap();
let use_statement = format!("use {}::foo;", crate_name);
let output = quote! {
/// `foo` will always return `true`
/// ```
#[doc = #use_statement]
/// assert!(foo());
/// ```
pub fn foo() -> bool {
true
}
};
output.into()
}
这里有一些关于在文档注释中插入内容的复杂问题。像 /// #an_ident
这样的插值不起作用,因为文档注释以特殊方式解析。我们可以做到这一点的唯一方法是创建一个字符串并使用 #[doc = ...]
语法。这有点烦人,因为您必须在 quote!
调用之前创建字符串,但它有效。
但是,我认为不能保证这有效。目前 proc 宏可以访问所有环境(包括文件系统、网络...)。据我所知,proc 宏不能保证此访问权限,并且 proc 宏将来可能会被沙盒化。所以这个解决方案还不完美,但它现在有效(可能还会持续一段时间)。
另一种方法是让用户将 crate 名称传递给您的宏:
hello!(my_lib);
如果您的宏在每个 crate 中只被调用一次,这可能是首选的解决方案。如果您的宏被多次调用,那么重复 crate 名称可能会很烦人。
我正在创建一个程序宏,它从一些配置文件自动生成一个库(这是一个寄存器布局,但这对问题并不重要)。
我希望图书馆自动生成自动图书馆附带的文档,并包括应该 运行 和 cargo test
的文档测试。现在,我已经实现了其中的大部分,但是有一个问题我看不到解决方案。
假设我们有一个名为 my_lib
的库,我们在其中调用宏来填充它:
use my_macro_lib::hello;
hello!();
扩展为:
/// `foo` will always return `true`
/// ```
/// use my_lib;
/// assert!(my_lib::foo());
/// ```
pub fn foo() -> bool {
true
}
这将 运行 如预期的那样 - cargo doc
将做正确的事情并且 cargo test
将 运行 如预期的那样进行 doctests。
问题是,在这个例子中,use my_lib
被硬编码到 my_macro_lib
中,这显然是不可取的。
我如何创建一个宏来推断执行调用的 crate 的名称?
我尝试在程序宏中使用 macro_rules!
来扩展 $crate
,但这违反了卫生规则。
您可以通过读取 CARGO_PKG_NAME
环境变量来获取正在使用您的宏的 crate 的名称。请注意,您必须通过阅读它
std::env
(at "runtime" of your macro) and not via env!
(当你的 proc macro crate 被编译时)。
#[proc_macro]
pub fn hello(input: TokenStream) -> TokenStream {
let crate_name = std::env::var("CARGO_PKG_NAME").unwrap();
let use_statement = format!("use {}::foo;", crate_name);
let output = quote! {
/// `foo` will always return `true`
/// ```
#[doc = #use_statement]
/// assert!(foo());
/// ```
pub fn foo() -> bool {
true
}
};
output.into()
}
这里有一些关于在文档注释中插入内容的复杂问题。像 /// #an_ident
这样的插值不起作用,因为文档注释以特殊方式解析。我们可以做到这一点的唯一方法是创建一个字符串并使用 #[doc = ...]
语法。这有点烦人,因为您必须在 quote!
调用之前创建字符串,但它有效。
但是,我认为不能保证这有效。目前 proc 宏可以访问所有环境(包括文件系统、网络...)。据我所知,proc 宏不能保证此访问权限,并且 proc 宏将来可能会被沙盒化。所以这个解决方案还不完美,但它现在有效(可能还会持续一段时间)。
另一种方法是让用户将 crate 名称传递给您的宏:
hello!(my_lib);
如果您的宏在每个 crate 中只被调用一次,这可能是首选的解决方案。如果您的宏被多次调用,那么重复 crate 名称可能会很烦人。