Вывести имя вызывающего ящика для заполнения документации в процедурном макросе

Я создаю процедурный макрос, который автоматически генерирует библиотеку из некоторого файла конфигурации (это макет регистра, но это не важно для вопроса).

Я хотел бы, чтобы библиотека автоматически генерировала документацию, сопровождающую автоматическую библиотеку, и включала тесты документации, которые должны запускаться с 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);

Если ваш макрос вызывается только один раз на ящик, это, вероятно, предпочтительное решение. Если ваш макрос вызывается часто, повторение имени ящика может раздражать.

Другие вопросы по тегам