Вывести имя вызывающего ящика для заполнения документации в процедурном макросе
Я создаю процедурный макрос, который автоматически генерирует библиотеку из некоторого файла конфигурации (это макет регистра, но это не важно для вопроса).
Я хотел бы, чтобы библиотека автоматически генерировала документацию, сопровождающую автоматическую библиотеку, и включала тесты документации, которые должны запускаться с 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
как и ожидалось, запустит тесты.
Проблема в том, что в этом примере use my_lib
жестко закодирован в my_macro_lib
что явно нежелательно.
Как я могу создать макрос, который определяет имя контейнера, который выполняет вызов?
Я пробовал использовать macro_rules!
внутри процедурного макроса для расширения $crate
, но это нарушает правила гигиены.
1 ответ
Вы можете получить имя ящика, в котором используется ваш макрос, прочитав CARGO_PKG_NAME
переменная окружения. Обратите внимание, что вы должны прочитать его через
std::env
("во время выполнения" вашего макроса), а не через env!
(что будет, когда ваш ящик макроса proc скомпилирован).
#[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 могут быть изолированы в песочнице. Таким образом, это решение еще не идеально, но пока оно работает (и, вероятно, еще некоторое время).
Альтернативой может быть просто позволить пользователю передать имя ящика вашему макросу:
hello!(my_lib);
Если ваш макрос вызывается только один раз на ящик, это, вероятно, предпочтительное решение. Если ваш макрос вызывается часто, повторение имени ящика может раздражать.