From febb9bc9cbae4b0ed699892a6ca7dbab6854843d Mon Sep 17 00:00:00 2001 From: Manuel Cillero Date: Thu, 2 Jan 2025 09:24:56 +0100 Subject: [PATCH] =?UTF-8?q?=F0=9F=8E=A8=20[macros]=20Redefine=20#[fn=5Fbui?= =?UTF-8?q?lder]=20con=20coherencia?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit La macro genera automáticamente un método "alter_", que modifica la instancia actual usando "&mut self", y redefine el método "with_" para delegar la lógica en el nuevo método "alter_". --- helpers/pagetop-macros/src/lib.rs | 158 +++++++++++++++---------- packages/pagetop-hljs/src/config.rs | 37 +++--- packages/pagetop-hljs/src/context.rs | 25 ++-- packages/pagetop-hljs/src/snippet.rs | 4 +- pagetop/src/core/component/children.rs | 51 ++++---- pagetop/src/core/theme/regions.rs | 6 +- pagetop/src/html/opt_classes.rs | 2 +- pagetop/src/html/opt_component.rs | 2 +- pagetop/src/html/opt_id.rs | 2 +- pagetop/src/html/opt_name.rs | 2 +- pagetop/src/html/opt_string.rs | 2 +- pagetop/src/html/opt_translated.rs | 2 +- pagetop/src/response/page.rs | 20 ++-- 13 files changed, 176 insertions(+), 137 deletions(-) diff --git a/helpers/pagetop-macros/src/lib.rs b/helpers/pagetop-macros/src/lib.rs index d427ffad..d802672e 100644 --- a/helpers/pagetop-macros/src/lib.rs +++ b/helpers/pagetop-macros/src/lib.rs @@ -22,105 +22,135 @@ mod smart_default; use proc_macro::TokenStream; use proc_macro_error::proc_macro_error; -use quote::{quote, quote_spanned, ToTokens}; -use syn::{parse_macro_input, parse_str, DeriveInput, ItemFn}; +use quote::{quote, quote_spanned}; +use syn::{parse_macro_input, spanned::Spanned, DeriveInput, ItemFn}; -/// Macro (*attribute*) que asocia a un método `alter_` su correspondiente método `with_` para -/// aplicar el patrón *builder*. +/// Macro (*attribute*) que asocia un método *builder* `with_` con un método `alter_` equivalente +/// que modifica la instancia actual usando una única implementación. +/// +/// La macro genera automáticamente un método `alter_`, que modifica la instancia actual usando +/// `&mut self`, y redefine el método `with_` para delegar la lógica en el nuevo método `alter_`. /// /// # Panics /// -/// Esta función provocará un *panic* si no encuentra identificadores en la lista de argumentos. +/// Esta macro provocará un *panic* en tiempo de compilación si la función anotada no cumple con la +/// declaración `pub fn with_...(mut self, ...) -> Self`. /// /// # Ejemplos /// +/// Si defines un método `with_` como este: +/// /// ```rust#ignore /// #[fn_builder] -/// pub fn alter_example(&mut self) -> &mut Self { -/// // implementación -/// } -/// ``` -/// -/// Añadirá al código el siguiente método: -/// -/// ```rust#ignore -/// #[inline] -/// pub fn with_example(mut self) -> Self { -/// self.alter_example(); +/// pub fn with_example(mut self, value: impl Into) -> Self { +/// self.value = Some(value.into()); /// self /// } /// ``` +/// +/// La macro generará automáticamente el siguiente método `alter_`: +/// +/// ```rust#ignore +/// pub fn alter_example(&mut self, value: impl Into) -> &mut Self { +/// self.value = Some(value.into()); +/// self +/// } +/// ``` +/// +/// Y redefinirá el método `with_` para que delegue en el método `alter_`: +/// +/// ```rust#ignore +/// pub fn with_example(mut self, value: impl Into) -> Self { +/// self.alter_example(value); +/// self +/// } +/// ``` +/// +/// Así, cada método *builder* `with_...()` generará automáticamente su correspondiente método +/// asociado `alter_...()`, permitiendo aplicar modificaciones en instancias existentes. #[proc_macro_attribute] pub fn fn_builder(_: TokenStream, item: TokenStream) -> TokenStream { - let fn_alter = parse_macro_input!(item as ItemFn); - let fn_alter_name = fn_alter.sig.ident.to_string(); + let fn_with = parse_macro_input!(item as ItemFn); + let fn_with_name = fn_with.sig.ident.clone(); + let fn_with_name_str = fn_with.sig.ident.to_string(); - if !fn_alter_name.starts_with("alter_") { + // Valida el nombre del método. + if !fn_with_name_str.starts_with("with_") { let expanded = quote_spanned! { - fn_alter.sig.ident.span() => - compile_error!("expected a \"pub fn alter_...() -> &mut Self\" method"); + fn_with.sig.ident.span() => + compile_error!("expected a \"pub fn with_...(mut self, ...) -> Self\" method"); }; return expanded.into(); } - let fn_with_name = fn_alter_name.replace("alter_", "with_"); - let fn_with_generics = if fn_alter.sig.generics.params.is_empty() { - fn_with_name.clone() + // Valida que el primer argumento sea `mut self`. + if let Some(syn::FnArg::Receiver(receiver)) = fn_with.sig.inputs.first() { + if !receiver.mutability.is_some() { + return quote_spanned! { + receiver.span() => compile_error!("expected `mut self` as the first argument"); + } + .into(); + } } else { - let g = &fn_alter.sig.generics; - format!("{fn_with_name}{}", quote! { #g }.to_string()) - }; + return quote_spanned! { + fn_with.sig.ident.span() => compile_error!("expected `mut self` as the first argument"); + } + .into(); + } - let where_clause = fn_alter - .sig - .generics - .where_clause - .as_ref() - .map_or(String::new(), |where_clause| { - format!("{} ", quote! { #where_clause }.to_string()) - }); + // Genera el nombre del método alter_...(). + let fn_alter_name_str = fn_with_name_str.replace("with_", "alter_"); + let fn_alter_name = syn::Ident::new(&fn_alter_name_str, fn_with.sig.ident.span()); - let args: Vec = fn_alter + // Extrae genéricos y cláusulas where. + let fn_generics = &fn_with.sig.generics; + let where_clause = &fn_with.sig.generics.where_clause; + + // Extrae argumentos y parámetros de llamada. + let args: Vec<_> = fn_with.sig.inputs.iter().skip(1).collect(); + let params: Vec<_> = fn_with .sig .inputs .iter() .skip(1) - .map(|arg| arg.to_token_stream().to_string()) - .collect(); - - let params: Vec = args - .iter() - .map(|arg| { - arg.split_whitespace() - .next() - .unwrap() - .trim_end_matches(':') - .to_string() + .map(|arg| match arg { + syn::FnArg::Typed(pat) => &pat.pat, + _ => panic!("unexpected argument type"), }) .collect(); - #[rustfmt::skip] - let fn_with = parse_str::(format!(r##" - pub fn {fn_with_generics}(mut self, {}) -> Self {where_clause} {{ - self.{fn_alter_name}({}); + // Extrae bloque del método. + let fn_with_block = &fn_with.block; + + // Extrae documentación y otros atributos del método. + let fn_with_attrs = &fn_with.attrs; + + // Genera el método alter_...() con el código del método with_...(). + let fn_alter_doc = format!( + "Permite modificar la instancia actual en los mismos términos que el método \ + builder `{}()` al que está asociado.", + fn_with_name_str, + ); + let fn_alter = quote! { + #[doc = #fn_alter_doc] + pub fn #fn_alter_name #fn_generics(&mut self, #(#args),*) -> &mut Self #where_clause { + #fn_with_block + } + }; + + // Redefine el método with_...() para que llame a alter_...(). + let fn_with = quote! { + #(#fn_with_attrs)* + pub fn #fn_with_name #fn_generics(mut self, #(#args),*) -> Self #where_clause { + self.#fn_alter_name(#(#params),*); self - }} - "##, args.join(", "), params.join(", ") - ).as_str()).unwrap(); - - #[rustfmt::skip] - let fn_alter_doc = format!(r##" -

Use - pub fn {fn_with_name}(self, …) -> Self - for the builder pattern. -

- "##); + } + }; + // Genera el código final. let expanded = quote! { - #[doc(hidden)] #fn_with #[inline] - #[doc = #fn_alter_doc] #fn_alter }; expanded.into() diff --git a/packages/pagetop-hljs/src/config.rs b/packages/pagetop-hljs/src/config.rs index e12b719c..3da7243f 100644 --- a/packages/pagetop-hljs/src/config.rs +++ b/packages/pagetop-hljs/src/config.rs @@ -1,6 +1,6 @@ -//! Configuration settings for package. +//! Opciones de configuración. //! -//! Example: +//! Ejemplo: //! //! ```toml //! [hljs] @@ -9,14 +9,16 @@ //! tabsize = 8 //! ``` //! -//! Usage: +//! Uso: //! //! ```rust //! use pagetop_hljs::config; //! //! assert_eq!(config::SETTINGS.hljs.theme, "zenburn"); //! ``` -//! See [`pagetop::config`] to learn how PageTop reads configuration files and uses settings. +//! +//! Consulta [`pagetop::config`] para aprender cómo `PageTop` lee los archivos de opciones y aplica +//! los valores de configuración. use pagetop::prelude::*; @@ -33,26 +35,27 @@ include_config!(SETTINGS: Settings => [ ]); #[derive(Debug, Deserialize)] -/// Configuration settings for the [`[hljs]`](Hljs) section (see [`SETTINGS`] package). +/// Opciones de configuración para la sección [`[hljs]`](Hljs) (ver [`SETTINGS`]). pub struct Settings { pub hljs: Hljs, } #[derive(Debug, Deserialize)] -/// Section `[hljs]` of the configuration settings. +/// Sección `[hljs]` de la configuración. /// -/// See [`Settings`]. +/// Ver [`Settings`]. pub struct Hljs { - /// Use ***core*** to import a minimal library and load only the languages added via - /// [`add_hljs_language()`](crate::hljs_context::HljsContext::add_hljs_language). Alternatively, - /// ***common*** imports an extended library containing around 40 popular languages (see - /// [`HljsLang`](crate::hljs_lang::HljsLang)). Note that using the *common* library restricts - /// you to the languages that are preloaded. - /// Default value: *"core"* + /// Usa ***core*** para importar una librería mínima y cargar solo los lenguajes añadidos vía + /// [`add_hljs_language()`](crate::context::HljsContext::add_hljs_language). Por otro lado, usa + /// ***common*** para importar una librería extendida con los 40 lenguajes más habituales según + /// [`HljsLang`](crate::lang::HljsLang). Ten en cuenta que al usar la librería *common* te + /// limitas a los lenguajes que vienen precargados. + /// Valor por defecto: *"core"* pub mode: HljsMode, - /// Default theme in kebab-case used to display code snippets on web pages (see [`HljsTheme`]). - /// Default value: *"default"* + /// Tema por defecto en formato *kebab-case* para mostrar los fragmentos de código en las + /// páginas web (ver [`HljsTheme`]). + /// Valor por defecto: *"default"* pub theme: HljsTheme, - /// Number of spaces for *tab* character. - /// Default value: *4* + /// Número de espacios para el carácter tabulador. + /// Valor por defecto: *4* pub tabsize: usize, } diff --git a/packages/pagetop-hljs/src/context.rs b/packages/pagetop-hljs/src/context.rs index e517f54a..3c23d8f2 100644 --- a/packages/pagetop-hljs/src/context.rs +++ b/packages/pagetop-hljs/src/context.rs @@ -7,32 +7,33 @@ use crate::theme::HljsTheme; use std::collections::HashSet; -// Context parameters. +// Parámetros para el contexto. const PARAM_HLJS_ENABLED: &str = "hljs.enabled"; const PARAM_HLJS_MODE: &str = "hljs.mode"; const PARAM_HLJS_LANGS: &str = "hljs.langs"; const PARAM_HLJS_THEME: &str = "hljs.theme"; -/// Extend Context with HighlightJS features. +/// Extiende el contexto de renderizado con funcionalidades de HighlightJS. pub trait HljsContext { - /// Enable syntax highlighting in current context. + /// Habilita el resaltado de sintaxis en el contexto actual. fn enable_hljs(&mut self); - /// Preventing syntax highlighting in current context. + /// Deshabilita el resaltado de sintaxis en el contexto actual. fn disable_hljs(&mut self); - /// Force the use of the *highlight.js* ***core*** or ***common*** mode in current context, - /// ignoring the [`config::SETTINGS.hljs.mode`](crate::config::Hljs#structfield.mode) - /// configuration setting. + /// Fuerza el uso del modo ***core*** o ***common*** de *highlight.js* en el contexto actual, + /// ignorando [`config::SETTINGS.hljs.mode`](crate::config::Hljs#structfield.mode) de las + /// opciones de configuración. fn force_hljs_mode(&mut self, mode: &HljsMode); - /// Add a new language to the context for processing code snippets. It is necessary to add at - /// least one language to load the *highlight.js* library. Each - /// [`Snippet`](crate::snippet::Snippet) component automatically adds its required language. + /// Añade un nuevo lenguaje al contexto actual para el resaltado de fragmentos de código. Se + /// requiere al menos un lenguaje para cargar la librería *highlight.js*. Recuerda que cada + /// componente [`Snippet`](crate::snippet::HljsSnippet) añade automáticamente el lenguaje que + /// necesita. Solo aplica cuando el contexto está configurado en el modo ***core***. fn add_hljs_language(&mut self, language: &HljsLang); - /// Change the theme in current context for displaying code snippets. The same theme is used for - /// all snippets in the given context. + /// Cambia el tema del contexto actual para mostrar los fragmentos de código. Ten en cuenta que + /// *highlight.js* utilizará el mismo tema para todos los framentos en este contexto. fn set_hljs_theme(&mut self, theme: &HljsTheme); fn is_hljs_enabled(&self) -> bool; diff --git a/packages/pagetop-hljs/src/snippet.rs b/packages/pagetop-hljs/src/snippet.rs index 3499887b..60a05d10 100644 --- a/packages/pagetop-hljs/src/snippet.rs +++ b/packages/pagetop-hljs/src/snippet.rs @@ -42,13 +42,13 @@ impl HljsSnippet { // Hljs BUILDER. #[fn_builder] - pub fn alter_language(&mut self, language: HljsLang) -> &mut Self { + pub fn with_language(mut self, language: HljsLang) -> Self { self.language = language; self } #[fn_builder] - pub fn alter_snippet(&mut self, snippet: impl Into) -> &mut Self { + pub fn with_snippet(mut self, snippet: impl Into) -> Self { self.snippet = snippet.into().trim().to_string(); self } diff --git a/pagetop/src/core/component/children.rs b/pagetop/src/core/component/children.rs index 6f90456d..d9ad8c4a 100644 --- a/pagetop/src/core/component/children.rs +++ b/pagetop/src/core/component/children.rs @@ -88,7 +88,7 @@ impl Children { } pub fn with(child: ChildComponent) -> Self { - Children::default().with_value(ChildOp::Add(child)) + Children::default().with_child(ChildOp::Add(child)) } pub(crate) fn merge(mixes: &[Option<&Children>]) -> Self { @@ -102,7 +102,7 @@ impl Children { // Children BUILDER. #[fn_builder] - pub fn alter_value(&mut self, op: ChildOp) -> &mut Self { + pub fn with_child(mut self, op: ChildOp) -> Self { match op { ChildOp::Add(any) => self.add(any), ChildOp::InsertAfterId(id, any) => self.insert_after_id(id, any), @@ -111,12 +111,11 @@ impl Children { ChildOp::RemoveById(id) => self.remove_by_id(id), ChildOp::ReplaceById(id, any) => self.replace_by_id(id, any), ChildOp::Reset => self.reset(), - }; - self + } } #[fn_builder] - pub fn alter_typed(&mut self, op: TypedOp) -> &mut Self { + pub fn with_typed(mut self, op: TypedOp) -> Self { match op { TypedOp::Add(typed) => self.add(typed.to_child()), TypedOp::InsertAfterId(id, typed) => self.insert_after_id(id, typed.to_child()), @@ -125,56 +124,62 @@ impl Children { TypedOp::RemoveById(id) => self.remove_by_id(id), TypedOp::ReplaceById(id, typed) => self.replace_by_id(id, typed.to_child()), TypedOp::Reset => self.reset(), + } + } + + #[inline] + pub fn add(&mut self, child: ChildComponent) -> &mut Self { + self.0.push(child); + self + } + + #[inline] + fn insert_after_id(&mut self, id: &str, child: ChildComponent) -> &mut Self { + match self.0.iter().position(|c| c.id() == id) { + Some(index) => self.0.insert(index + 1, child), + _ => self.0.push(child), }; self } #[inline] - fn add(&mut self, child: ChildComponent) { - self.0.push(child); - } - - #[inline] - fn insert_after_id(&mut self, id: &str, child: ChildComponent) { - match self.0.iter().position(|c| c.id() == id) { - Some(index) => self.0.insert(index + 1, child), - _ => self.0.push(child), - }; - } - - #[inline] - fn insert_before_id(&mut self, id: &str, child: ChildComponent) { + fn insert_before_id(&mut self, id: &str, child: ChildComponent) -> &mut Self { match self.0.iter().position(|c| c.id() == id) { Some(index) => self.0.insert(index, child), _ => self.0.insert(0, child), }; + self } #[inline] - fn prepend(&mut self, child: ChildComponent) { + fn prepend(&mut self, child: ChildComponent) -> &mut Self { self.0.insert(0, child); + self } #[inline] - fn remove_by_id(&mut self, id: &str) { + fn remove_by_id(&mut self, id: &str) -> &mut Self { if let Some(index) = self.0.iter().position(|c| c.id() == id) { self.0.remove(index); } + self } #[inline] - fn replace_by_id(&mut self, id: &str, child: ChildComponent) { + fn replace_by_id(&mut self, id: &str, child: ChildComponent) -> &mut Self { for c in &mut self.0 { if c.id() == id { *c = child; break; } } + self } #[inline] - fn reset(&mut self) { + fn reset(&mut self) -> &mut Self { self.0.clear(); + self } // Children GETTERS. diff --git a/pagetop/src/core/theme/regions.rs b/pagetop/src/core/theme/regions.rs index 71e3c70a..d0fec339 100644 --- a/pagetop/src/core/theme/regions.rs +++ b/pagetop/src/core/theme/regions.rs @@ -24,11 +24,11 @@ impl ChildrenInRegions { } #[fn_builder] - pub fn alter_in_region(&mut self, region: &'static str, op: ChildOp) -> &mut Self { + pub fn with_in_region(mut self, region: &'static str, op: ChildOp) -> Self { if let Some(region) = self.0.get_mut(region) { - region.alter_value(op); + region.alter_child(op); } else { - self.0.insert(region, Children::new().with_value(op)); + self.0.insert(region, Children::new().with_child(op)); } self } diff --git a/pagetop/src/html/opt_classes.rs b/pagetop/src/html/opt_classes.rs index c754719e..0ac60afc 100644 --- a/pagetop/src/html/opt_classes.rs +++ b/pagetop/src/html/opt_classes.rs @@ -31,7 +31,7 @@ impl OptionClasses { // OptionClasses BUILDER. #[fn_builder] - pub fn alter_value(&mut self, op: ClassesOp, classes: impl Into) -> &mut Self { + pub fn with_value(mut self, op: ClassesOp, classes: impl Into) -> Self { let classes: String = classes.into(); let classes: Vec<&str> = classes.split_ascii_whitespace().collect(); diff --git a/pagetop/src/html/opt_component.rs b/pagetop/src/html/opt_component.rs index ea18890e..858685a4 100644 --- a/pagetop/src/html/opt_component.rs +++ b/pagetop/src/html/opt_component.rs @@ -18,7 +18,7 @@ impl OptionComponent { // OptionComponent BUILDER. #[fn_builder] - pub fn alter_value(&mut self, component: Option) -> &mut Self { + pub fn with_value(mut self, component: Option) -> Self { if let Some(component) = component { self.0 = Some(TypedComponent::with(component)); } else { diff --git a/pagetop/src/html/opt_id.rs b/pagetop/src/html/opt_id.rs index 6500c14a..47c965e0 100644 --- a/pagetop/src/html/opt_id.rs +++ b/pagetop/src/html/opt_id.rs @@ -11,7 +11,7 @@ impl OptionId { // OptionId BUILDER. #[fn_builder] - pub fn alter_value(&mut self, value: impl Into) -> &mut Self { + pub fn with_value(mut self, value: impl Into) -> Self { self.0 = Some(value.into().trim().replace(' ', "_")); self } diff --git a/pagetop/src/html/opt_name.rs b/pagetop/src/html/opt_name.rs index 9a668e3a..cd0432bb 100644 --- a/pagetop/src/html/opt_name.rs +++ b/pagetop/src/html/opt_name.rs @@ -11,7 +11,7 @@ impl OptionName { // OptionName BUILDER. #[fn_builder] - pub fn alter_value(&mut self, value: impl Into) -> &mut Self { + pub fn with_value(mut self, value: impl Into) -> Self { self.0 = Some(value.into().trim().replace(' ', "_")); self } diff --git a/pagetop/src/html/opt_string.rs b/pagetop/src/html/opt_string.rs index 36c467ac..0f1abcf2 100644 --- a/pagetop/src/html/opt_string.rs +++ b/pagetop/src/html/opt_string.rs @@ -11,7 +11,7 @@ impl OptionString { // OptionString BUILDER. #[fn_builder] - pub fn alter_value(&mut self, value: impl Into) -> &mut Self { + pub fn with_value(mut self, value: impl Into) -> Self { self.0 = Some(value.into().trim().to_owned()); self } diff --git a/pagetop/src/html/opt_translated.rs b/pagetop/src/html/opt_translated.rs index acefac08..4459161b 100644 --- a/pagetop/src/html/opt_translated.rs +++ b/pagetop/src/html/opt_translated.rs @@ -13,7 +13,7 @@ impl OptionTranslated { // OptionTranslated BUILDER. #[fn_builder] - pub fn alter_value(&mut self, value: L10n) -> &mut Self { + pub fn with_value(mut self, value: L10n) -> Self { self.0 = value; self } diff --git a/pagetop/src/response/page.rs b/pagetop/src/response/page.rs index 65589fa5..984bfb2b 100644 --- a/pagetop/src/response/page.rs +++ b/pagetop/src/response/page.rs @@ -42,61 +42,61 @@ impl Page { // Page BUILDER. #[fn_builder] - pub fn alter_title(&mut self, title: L10n) -> &mut Self { + pub fn with_title(mut self, title: L10n) -> Self { self.title.alter_value(title); self } #[fn_builder] - pub fn alter_description(&mut self, description: L10n) -> &mut Self { + pub fn with_description(mut self, description: L10n) -> Self { self.description.alter_value(description); self } #[fn_builder] - pub fn alter_metadata(&mut self, name: &'static str, content: &'static str) -> &mut Self { + pub fn with_metadata(mut self, name: &'static str, content: &'static str) -> Self { self.metadata.push((name, content)); self } #[fn_builder] - pub fn alter_property(&mut self, property: &'static str, content: &'static str) -> &mut Self { + pub fn with_property(mut self, property: &'static str, content: &'static str) -> Self { self.metadata.push((property, content)); self } #[fn_builder] - pub fn alter_assets(&mut self, op: AssetsOp) -> &mut Self { + pub fn with_assets(mut self, op: AssetsOp) -> Self { self.context.alter_assets(op); self } #[fn_builder] - pub fn alter_body_id(&mut self, id: impl Into) -> &mut Self { + pub fn with_body_id(mut self, id: impl Into) -> Self { self.body_id.alter_value(id); self } #[fn_builder] - pub fn alter_body_classes(&mut self, op: ClassesOp, classes: impl Into) -> &mut Self { + pub fn with_body_classes(mut self, op: ClassesOp, classes: impl Into) -> Self { self.body_classes.alter_value(op, classes); self } #[fn_builder] - pub fn alter_theme(&mut self, theme: &'static str) -> &mut Self { + pub fn with_theme(mut self, theme: &'static str) -> Self { self.context.alter_assets(AssetsOp::Theme(theme)); self } #[fn_builder] - pub fn alter_layout(&mut self, layout: &'static str) -> &mut Self { + pub fn with_layout(mut self, layout: &'static str) -> Self { self.context.alter_assets(AssetsOp::Layout(layout)); self } #[fn_builder] - pub fn alter_in_region(&mut self, region: &'static str, op: ChildOp) -> &mut Self { + pub fn with_in_region(mut self, region: &'static str, op: ChildOp) -> Self { self.context.alter_in_region(region, op); self }