use crate::locale::{L10n, LangId}; use crate::{AutoDefault, builder_fn, util}; /// Valor opcional para atributos HTML. /// /// `Attr` encapsula un `Option` y sirve como tipo base para representar atributos HTML /// opcionales, uniformes y tipados. /// /// Este tipo **no impone ninguna normalización ni semántica concreta**; dichas reglas se definen en /// implementaciones concretas como `Attr` y `Attr`, o en tipos específicos como /// [`AttrName`]. #[derive(AutoDefault, Clone, Debug)] pub struct Attr(Option); impl Attr { /// Crea un atributo vacío. pub fn empty() -> Self { Self(None) } /// Crea un atributo con valor. pub fn some(value: T) -> Self { Self(Some(value)) } // **< Attr BUILDER >************************************************************************ /// Establece un valor opcional para el atributo. #[builder_fn] pub fn with_opt(mut self, opt: Option) -> Self { self.0 = opt; self } /// Establece un valor para el atributo. #[builder_fn] pub fn with_value(mut self, value: T) -> Self { self.0 = Some(value); self } /// Elimina el valor del atributo. #[builder_fn] pub fn with_none(mut self) -> Self { self.0 = None; self } // **< Attr GETTERS >************************************************************************ /// Devuelve el valor (clonado), si existe. pub fn get(&self) -> Option where T: Clone, { self.0.clone() } /// Devuelve una referencia al valor, si existe. pub fn as_ref(&self) -> Option<&T> { self.0.as_ref() } /// Devuelve el valor (propiedad), si existe. pub fn into_inner(self) -> Option { self.0 } /// `true` si no hay valor. pub fn is_empty(&self) -> bool { self.0.is_none() } } // **< Attr >********************************************************************************* /// Extiende [`Attr`] para [texto localizado](crate::locale) en atributos HTML. /// /// Encapsula un [`L10n`] para manejar traducciones de forma segura en atributos. /// /// # Ejemplo /// /// ```rust /// # use pagetop::prelude::*; /// // Traducción por clave en las locales por defecto de PageTop. /// let hello = Attr::::new(L10n::l("test_hello_world")); /// /// // Español disponible. /// assert_eq!( /// hello.lookup(&Locale::resolve("es-ES")), /// Some("¡Hola mundo!".to_string()) /// ); /// /// // Japonés no disponible, traduce al idioma de respaldo (`"en-US"`). /// assert_eq!( /// hello.lookup(&Locale::resolve("ja-JP")), /// Some("Hello world!".to_string()) /// ); /// /// // Uso típico en un atributo: /// let title = hello.value(&Locale::resolve("es-ES")); /// // Ejemplo: html! { a title=(title) { "Link" } } /// ``` impl Attr { /// Crea una nueva instancia `Attr`. pub fn new(value: L10n) -> Self { Self::some(value) } /// Devuelve la traducción para `language` si puede resolverse. pub fn lookup(&self, language: &impl LangId) -> Option { self.0.as_ref()?.lookup(language) } /// Devuelve la traducción para `language` o una cadena vacía si no existe. pub fn value(&self, language: &impl LangId) -> String { self.lookup(language).unwrap_or_default() } } // **< Attr >******************************************************************************* /// Extiende [`Attr`] para cadenas de texto. impl Attr { /// Devuelve el texto como `&str` si existe. pub fn as_str(&self) -> Option<&str> { self.0.as_deref() } } // **< AttrName >*********************************************************************************** /// Nombre normalizado para el atributo `name` o similar de HTML. /// /// Este tipo encapsula `Option` garantizando un valor normalizado para su uso: /// /// - Se eliminan los espacios al principio y al final. /// - Se convierte a minúsculas. /// - Se sustituyen los espacios (`' '`) intermedios por guiones bajos (`_`). /// - Si el resultado es una cadena vacía, se guarda `None`. /// /// # Ejemplo /// /// ```rust /// # use pagetop::prelude::*; /// let name = AttrName::new(" DISplay name "); /// assert_eq!(name.as_str(), Some("display_name")); /// /// let empty = AttrName::default(); /// assert_eq!(empty.get(), None); /// ``` #[derive(AutoDefault, Clone, Debug)] pub struct AttrName(Attr); impl AttrName { /// Crea un nuevo `AttrName` normalizando el valor. pub fn new(name: impl AsRef) -> Self { Self::default().with_name(name) } // **< AttrName BUILDER >*********************************************************************** /// Establece un nombre nuevo normalizando el valor. #[builder_fn] pub fn with_name(mut self, name: impl AsRef) -> Self { self.0 = match util::normalize_token(name) { Some(name) => Attr::some(name), None => Attr::default(), }; self } // **< AttrName GETTERS >*********************************************************************** /// Devuelve el nombre normalizado, si existe. pub fn get(&self) -> Option { self.0.get() } /// Devuelve el nombre normalizado (sin clonar), si existe. pub fn as_str(&self) -> Option<&str> { self.0.as_str() } /// Devuelve el nombre normalizado (propiedad), si existe. pub fn into_inner(self) -> Option { self.0.into_inner() } /// `true` si no hay valor. pub fn is_empty(&self) -> bool { self.0.is_empty() } } // **< AttrValue >********************************************************************************** /// Cadena normalizada para renderizar en atributos HTML. /// /// Este tipo encapsula `Option` garantizando un valor normalizado para su uso: /// /// - Se eliminan los espacios al principio y al final. /// - Si el resultado es una cadena vacía, se guarda `None`. /// /// # Ejemplo /// /// ```rust /// # use pagetop::prelude::*; /// let s = AttrValue::new(" a new string "); /// assert_eq!(s.as_str(), Some("a new string")); /// /// let empty = AttrValue::default(); /// assert_eq!(empty.get(), None); /// ``` #[derive(AutoDefault, Clone, Debug)] pub struct AttrValue(Attr); impl AttrValue { /// Crea un nuevo `AttrValue` normalizando el valor. pub fn new(value: impl AsRef) -> Self { Self::default().with_str(value) } // **< AttrValue BUILDER >********************************************************************** /// Establece una cadena nueva normalizando el valor. #[builder_fn] pub fn with_str(mut self, value: impl AsRef) -> Self { self.0 = match util::non_blank(value.as_ref()) { Some(value) => Attr::some(value.to_string()), None => Attr::default(), }; self } // **< AttrValue GETTERS >********************************************************************** /// Devuelve la cadena normalizada, si existe. pub fn get(&self) -> Option { self.0.get() } /// Devuelve la cadena normalizada (sin clonar), si existe. pub fn as_str(&self) -> Option<&str> { self.0.as_str() } /// Devuelve la cadena normalizada (propiedad), si existe. pub fn into_inner(self) -> Option { self.0.into_inner() } /// `true` si no hay valor. pub fn is_empty(&self) -> bool { self.0.is_empty() } }