♻️ (macros): Deja main/test sin depender de Tokio

Antes delegaban directamente en `#[tokio::main]`/`#[tokio::test]`; ahora
construyen su propio runtime vía `pagetop::util::build_runtime()`, con
el mismo comportamiento (`Builder::new_multi_thread().enable_all()`).
Las extensiones que usan estas macros ya no necesitan declarar Tokio
como dependencia directa en su `Cargo.toml`.
This commit is contained in:
Manuel Cillero 2026-07-14 00:44:21 +02:00
parent 24bd13414c
commit 30151b6254
2 changed files with 58 additions and 17 deletions

View file

@ -39,7 +39,7 @@ mod smart_default;
use proc_macro::TokenStream; use proc_macro::TokenStream;
use quote::{quote, quote_spanned}; use quote::{quote, quote_spanned};
use syn::{DeriveInput, parse_macro_input, spanned::Spanned}; use syn::{DeriveInput, ItemFn, parse_macro_input, spanned::Spanned};
/// Macro para escribir plantillas HTML (basada en [Maud](https://docs.rs/maud)). /// Macro para escribir plantillas HTML (basada en [Maud](https://docs.rs/maud)).
#[proc_macro] #[proc_macro]
@ -450,19 +450,14 @@ pub fn builder_fn(_: TokenStream, item: TokenStream) -> TokenStream {
/// ``` /// ```
#[proc_macro_attribute] #[proc_macro_attribute]
pub fn main(_: TokenStream, item: TokenStream) -> TokenStream { pub fn main(_: TokenStream, item: TokenStream) -> TokenStream {
let mut output: TokenStream = (quote! { let input = parse_macro_input!(item as ItemFn);
#[::tokio::main] expand_entry(input, false)
})
.into();
output.extend(item);
output
} }
/// Define funciones de prueba asíncronas para usar con PageTop. /// Define funciones de prueba asíncronas para usar con PageTop.
/// ///
/// Usa el *runtime* multi-hilo de **Tokio**, igual que [`#[pagetop::main]`](macro@main), para /// Usa el mismo *runtime* multi-hilo que [`#[pagetop::main]`](macro@main), para garantizar
/// garantizar compatibilidad con extensiones que ejecutan código asíncrono de forma síncrona. /// compatibilidad con extensiones que ejecutan código asíncrono de forma síncrona.
/// ///
/// # Ejemplo /// # Ejemplo
/// ///
@ -474,11 +469,43 @@ pub fn main(_: TokenStream, item: TokenStream) -> TokenStream {
/// ``` /// ```
#[proc_macro_attribute] #[proc_macro_attribute]
pub fn test(_: TokenStream, item: TokenStream) -> TokenStream { pub fn test(_: TokenStream, item: TokenStream) -> TokenStream {
let mut output: TokenStream = (quote! { let input = parse_macro_input!(item as ItemFn);
#[::tokio::test(flavor = "multi_thread")] expand_entry(input, true)
}) }
.into();
// Genera la función síncrona que envuelve el cuerpo asíncrono original, común a `main` y `test`.
output.extend(item); fn expand_entry(input: ItemFn, is_test: bool) -> TokenStream {
output if input.sig.asyncness.is_none() {
return syn::Error::new_spanned(&input.sig.fn_token, "the function must be `async`")
.to_compile_error()
.into();
}
let ItemFn {
attrs,
vis,
mut sig,
block,
} = input;
sig.asyncness = None;
// Ruta absoluta para evitar ambigüedad con `pagetop::test` bajo `use pagetop::prelude::*;`.
let test_attr = is_test.then(|| quote! { #[::core::prelude::v1::test] });
let expanded = quote! {
#test_attr
#(#attrs)*
#vis #sig {
#[allow(
clippy::expect_used,
clippy::diverging_sub_expression,
clippy::needless_return,
clippy::unwrap_in_result
)]
{
return ::pagetop::util::build_runtime().block_on(async move #block);
}
}
};
expanded.into()
} }

View file

@ -25,6 +25,20 @@ pub use pagetop_minimal::paste;
// **< FUNCIONES ÚTILES >*************************************************************************** // **< FUNCIONES ÚTILES >***************************************************************************
/// Código requerido por las macros [`pagetop::main`](crate::main) y [`pagetop::test`](crate::test).
///
/// Equivale a `Builder::new_multi_thread().enable_all().build()`, el mismo runtime que construye
/// `#[tokio::main]`/`#[tokio::test(flavor = "multi_thread")]` sin argumentos. Las aplicaciones o
/// extensiones que usen estas macros no necesitan declarar `tokio` como dependencia directa en su
/// `Cargo.toml`.
///
/// Esta función no forma parte de la API pública; es `pub` para ser llamada por el código generado
/// por las macros.
#[doc(hidden)]
pub fn build_runtime() -> tokio::runtime::Runtime {
tokio::runtime::Runtime::new().expect("Failed to build the Tokio runtime")
}
/// Errores posibles al normalizar una cadena ASCII con [`normalize_ascii()`]. /// Errores posibles al normalizar una cadena ASCII con [`normalize_ascii()`].
#[derive(Clone, Copy, Debug, Eq, PartialEq)] #[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum NormalizeAsciiError { pub enum NormalizeAsciiError {