,
+ resource_dir: ResourceDir,
}
impl StaticFilesBundle {
- /// Crea el paquete de recursos con los archivos del directorio indicado.
+ /// Prepara el conjunto de recursos con los archivos de un directorio. Opcionalmente se puede
+ /// aplicar un filtro para seleccionar un subconjunto de los archivos.
///
/// # Argumentos
///
- /// * `dir` - Ruta al directorio con los archivos a incluir, normalmente `static/` o un
- /// directorio dentro de este.
- /// * `filter` - Función opcional para seleccionar qué archivos incluir en el paquete.
+ /// * `dir` - Directorio que contiene los archivos.
+ /// * `filter` - Una función opcional para aceptar o no un archivo según su ruta.
///
/// # Ejemplo
///
@@ -177,227 +145,124 @@ impl StaticFilesBundle {
/// use std::path::Path;
///
/// fn main() -> std::io::Result<()> {
+ /// fn only_images(path: &Path) -> bool {
+ /// matches!(
+ /// path.extension().and_then(|ext| ext.to_str()),
+ /// Some("jpg" | "png" | "gif")
+ /// )
+ /// }
+ ///
/// StaticFilesBundle::from_dir("./static", Some(only_images))
/// .with_name("images")
/// .build()
/// }
- ///
- /// fn only_images(path: &Path) -> bool {
- /// matches!(
- /// path.extension().and_then(|ext| ext.to_str()),
- /// Some("jpg" | "png" | "gif")
- /// )
- /// }
/// ```
pub fn from_dir(dir: P, filter: Option bool>) -> Self
where
P: AsRef,
{
- Self {
- dir: dir.as_ref().to_path_buf(),
- filter,
- name: None,
+ let dir_path = dir.as_ref();
+ let dir_str = dir_path.to_str().unwrap_or_else(|| {
+ panic!(
+ "Resource directory path is not valid UTF-8: {}",
+ dir_path.display()
+ );
+ });
+
+ let mut resource_dir = resource_dir(dir_str);
+
+ // Aplica el filtro si está definido.
+ if let Some(f) = filter {
+ resource_dir.with_filter(f);
+ }
+
+ // Identifica el directorio temporal de recursos.
+ StaticFilesBundle { resource_dir }
+ }
+
+ /// Prepara un recurso CSS minimizado a partir de la compilación de un archivo SCSS (que puede a
+ /// su vez importar otros archivos SCSS).
+ ///
+ /// # Argumentos
+ ///
+ /// * `path` - Archivo SCSS a compilar.
+ /// * `target_name` - Nombre para el archivo CSS.
+ ///
+ /// # Ejemplo
+ ///
+ /// ```rust,no_run
+ /// use pagetop_build::StaticFilesBundle;
+ ///
+ /// fn main() -> std::io::Result<()> {
+ /// StaticFilesBundle::from_scss("./bootstrap/scss/main.scss", "bootstrap.min.css")
+ /// .with_name("bootstrap_css")
+ /// .build()
+ /// }
+ /// ```
+ pub fn from_scss(path: P, target_name: &str) -> Self
+ where
+ P: AsRef,
+ {
+ // Crea un directorio temporal único para el archivo CSS (basado en su nombre, para que
+ // varias llamadas a from_scss en el mismo build.rs no se pisen).
+ let out_dir = std::env::var("OUT_DIR").unwrap();
+ let safe_name = target_name.replace(['.', '-'], "_");
+ let temp_dir = Path::new(&out_dir).join(format!("from_scss_{safe_name}"));
+
+ // Limpia el directorio temporal de ejecuciones previas, si existe.
+ if temp_dir.exists() {
+ remove_dir_all(&temp_dir).unwrap_or_else(|e| {
+ panic!(
+ "Failed to clean temporary directory `{}`: {e}",
+ temp_dir.display()
+ );
+ });
+ }
+ create_dir_all(&temp_dir).unwrap_or_else(|e| {
+ panic!(
+ "Failed to create temporary directory `{}`: {e}",
+ temp_dir.display()
+ );
+ });
+
+ // Compila SCSS a CSS.
+ let css_content = from_path(
+ path.as_ref(),
+ &Options::default().style(OutputStyle::Compressed),
+ )
+ .unwrap_or_else(|e| {
+ panic!(
+ "Failed to compile SCSS file `{}`: {e}",
+ path.as_ref().display(),
+ )
+ });
+
+ // Guarda el archivo CSS compilado en el directorio temporal.
+ let css_path = temp_dir.join(target_name);
+ File::create(&css_path)
+ .unwrap_or_else(|_| panic!("Failed to create CSS file `{}`", css_path.display()))
+ .write_all(css_content.as_bytes())
+ .unwrap_or_else(|_| panic!("Failed to write CSS content to `{}`", css_path.display()));
+
+ // Identifica el directorio temporal de recursos.
+ StaticFilesBundle {
+ resource_dir: resource_dir(temp_dir.to_str().unwrap()),
}
}
- /// Asigna un nombre al paquete de recursos.
- ///
- /// El nombre debe ser un identificador Rust válido que se convertirá en nombre del módulo y de
- /// la función del archivo `.rs` generado en `OUT_DIR`. Si no se llama a este método, el nombre
- /// por defecto será `"bundle"`.
- ///
- /// Este nombre es el que hay que declarar en
- /// [`serve_static_files!`](https://docs.rs/pagetop/latest/pagetop/macro.serve_static_files.html)
- /// para configurar la ruta del servicio:
- ///
- /// ```rust,ignore
- /// serve_static_files!(router, ["./static/css", app_css] => "/public/css");
- /// // ^^^^^^^
- /// // debe coincidir con .with_name("app_css")
- /// ```
+ /// Asigna un nombre al conjunto de recursos.
pub fn with_name(mut self, name: impl AsRef) -> Self {
- self.name = Some(name.as_ref().to_string());
+ let name = name.as_ref();
+ let out_dir = std::env::var("OUT_DIR").unwrap();
+ let filename = Path::new(&out_dir).join(format!("{name}.rs"));
+ self.resource_dir.with_generated_filename(filename);
+ self.resource_dir.with_module_name(format!("bundle_{name}"));
+ self.resource_dir.with_generated_fn(name);
self
}
- /// Genera el archivo `.rs` en `OUT_DIR` para incluir los recursos del directorio en el binario.
+ /// Contruye finalmente el conjunto de recursos para incluir en el binario de la aplicación.
pub fn build(self) -> std::io::Result<()> {
- let out_dir = std::env::var("OUT_DIR").unwrap();
- let name = self.name.as_deref().unwrap_or("bundle");
-
- let mut rd = resource_dir(&self.dir);
- if let Some(f) = self.filter {
- rd.with_filter(f);
- }
-
- let generated_filename = PathBuf::from(&out_dir).join(format!("{name}.rs"));
- rd.with_generated_filename(generated_filename);
- rd.with_module_name(format!("bundle_{name}"));
- rd.with_generated_fn(name);
- rd.build()
+ self.resource_dir.build()
}
}
-
-// **< compile_scss / copy_dir / copy_file / copy_file_replacing / minify_js >**********************
-
-/// Compila un archivo SCSS a CSS minificado y lo escribe en la ruta de destino.
-///
-/// Crea el directorio padre del destino si no existe.
-///
-/// # Ejemplo
-///
-/// ```rust,no_run
-/// fn main() -> std::io::Result<()> {
-/// pagetop_build::compile_scss("assets/main.scss", "static/css/main.min.css")
-/// }
-/// ```
-pub fn compile_scss(src: P, dst: Q) -> io::Result<()>
-where
- P: AsRef,
- Q: AsRef,
-{
- let src = src.as_ref();
- let dst = dst.as_ref();
-
- if let Some(parent) = dst.parent() {
- create_dir_all(parent)?;
- }
-
- let options = Options::default().style(OutputStyle::Compressed);
- let css = from_path(src, &options)
- .map_err(|e| io::Error::other(format!("failed to compile `{}`: {e}", src.display())))?;
- File::create(dst)?.write_all(css.as_bytes())
-}
-
-/// Copia recursivamente el contenido de un directorio a otro destino.
-///
-/// Crea el directorio destino y todos los subdirectorios necesarios.
-///
-/// # Ejemplo
-///
-/// ```rust,no_run
-/// fn main() -> std::io::Result<()> {
-/// pagetop_build::copy_dir("assets", "static")
-/// }
-/// ```
-pub fn copy_dir(src: P, dst: Q) -> io::Result<()>
-where
- P: AsRef,
- Q: AsRef,
-{
- let src = src.as_ref();
- let dst = dst.as_ref();
- create_dir_all(dst)?;
- for entry in read_dir(src)? {
- let entry = entry?;
- let src_path = entry.path();
- let dst_path = dst.join(entry.file_name());
- if src_path.is_dir() {
- copy_dir(&src_path, &dst_path)?;
- } else {
- fs_copy(&src_path, &dst_path)?;
- }
- }
- Ok(())
-}
-
-/// Copia un archivo a su destino.
-///
-/// Crea el directorio padre del destino si no existe.
-///
-/// # Ejemplo
-///
-/// ```rust,no_run
-/// fn main() -> std::io::Result<()> {
-/// pagetop_build::copy_file("assets/fonts/icon.woff2", "static/fonts/icon.woff2")
-/// }
-/// ```
-pub fn copy_file(src: P, dst: Q) -> io::Result<()>
-where
- P: AsRef,
- Q: AsRef,
-{
- let src = src.as_ref();
- let dst = dst.as_ref();
-
- if let Some(parent) = dst.parent() {
- create_dir_all(parent)?;
- }
-
- fs_copy(src, dst)?;
- Ok(())
-}
-
-/// Copia un archivo a su destino con una lista de sustituciones de texto en su contenido.
-///
-/// El archivo fuente se lee como texto UTF-8; no debe usarse con archivos binarios. Las
-/// sustituciones de texto se aplican en orden y de forma encadenada: el resultado de cada
-/// sustitución puede ser entrada de la siguiente.
-///
-/// Crea el directorio padre del destino si no existe.
-///
-/// # Ejemplo
-///
-/// ```rust,no_run
-/// fn main() -> std::io::Result<()> {
-/// pagetop_build::copy_file_replacing(
-/// "assets/adminlte.min.js",
-/// "static/js/myapp.min.js",
-/// &[("adminlte.min.js.map", "myapp.min.js.map")],
-/// )
-/// }
-/// ```
-pub fn copy_file_replacing(src: P, dst: Q, replacements: &[(&str, &str)]) -> io::Result<()>
-where
- P: AsRef,
- Q: AsRef,
-{
- let src = src.as_ref();
- let dst = dst.as_ref();
-
- if let Some(parent) = dst.parent() {
- create_dir_all(parent)?;
- }
-
- let content = std::fs::read_to_string(src)?;
- let patched = replacements
- .iter()
- .fold(content, |acc, (old, new)| acc.replace(old, new));
- File::create(dst)?.write_all(patched.as_bytes())
-}
-
-/// Minifica un archivo JavaScript y lo escribe en la ruta de destino.
-///
-/// El archivo se procesa en modo de ámbito global (`TopLevelMode::Global`), adecuado para scripts
-/// sin `import`/`export`. Los archivos con sintaxis de módulo ES deben procesarse con
-/// `TopLevelMode::Module`, que el *crate* subyacente (`minify-js`) también soporta pero esta
-/// función no expone actualmente.
-///
-/// Crea el directorio padre del destino si no existe.
-///
-/// # Ejemplo
-///
-/// ```rust,no_run
-/// fn main() -> std::io::Result<()> {
-/// pagetop_build::minify_js("assets/shell.js", "static/js/shell.min.js")
-/// }
-/// ```
-pub fn minify_js(src: P, dst: Q) -> io::Result<()>
-where
- P: AsRef,
- Q: AsRef,
-{
- let src = src.as_ref();
- let dst = dst.as_ref();
-
- if let Some(parent) = dst.parent() {
- create_dir_all(parent)?;
- }
-
- let source = std::fs::read(src)?;
- let session = Session::new();
- let mut output = Vec::new();
- minify(&session, TopLevelMode::Global, &source, &mut output)
- .map_err(|e| io::Error::other(format!("failed to minify `{}`: {e:?}", src.display())))?;
- File::create(dst)?.write_all(&output)
-}
diff --git a/helpers/pagetop-macros/README.md b/helpers/pagetop-macros/README.md
index 599f81bb..9b0174a6 100644
--- a/helpers/pagetop-macros/README.md
+++ b/helpers/pagetop-macros/README.md
@@ -11,13 +11,14 @@
-## Sobre PageTop
+## 🧭 Sobre PageTop
[PageTop](https://docs.rs/pagetop) es un entorno de desarrollo que reivindica la esencia de la web
clásica para crear soluciones web SSR (*renderizadas en el servidor*) modulares, extensibles y
configurables, basadas en HTML, CSS y JavaScript.
-## Créditos
+
+## 📚 Créditos
Este *crate* incluye entre sus macros una adaptación de
[maud-macros](https://crates.io/crates/maud_macros)
@@ -28,13 +29,15 @@ Este *crate* incluye entre sus macros una adaptación de
necesidad de referenciar `maud` o `smart_default` en las dependencias del archivo `Cargo.toml` de
cada proyecto PageTop.
-## Advertencia
+
+## 🚧 Advertencia
**PageTop** es un proyecto personal para aprender [Rust](https://www.rust-lang.org/es) y conocer su
ecosistema. Su API está sujeta a cambios frecuentes. No se recomienda su uso en producción, al menos
hasta que se libere la versión **1.0.0**.
-## Licencia
+
+## 📜 Licencia
El código está disponible bajo una doble licencia:
diff --git a/helpers/pagetop-macros/src/lib.rs b/helpers/pagetop-macros/src/lib.rs
index 4557196e..63349aa0 100644
--- a/helpers/pagetop-macros/src/lib.rs
+++ b/helpers/pagetop-macros/src/lib.rs
@@ -31,7 +31,7 @@ cada proyecto PageTop.
*/
#![doc(
- html_favicon_url = "https://git.cillero.es/manuelcillero/pagetop/raw/branch/main/assets/favicon.ico"
+ html_favicon_url = "https://git.cillero.es/manuelcillero/pagetop/raw/branch/main/static/favicon.ico"
)]
mod maud;
@@ -126,7 +126,7 @@ pub fn derive_auto_default(input: TokenStream) -> TokenStream {
///
/// Si defines un método `with_` como este:
///
-/// ```rust,no_run
+/// ```rust
/// # use pagetop_macros::builder_fn;
/// # struct Example {value: Option};
/// # impl Example {
@@ -140,7 +140,7 @@ pub fn derive_auto_default(input: TokenStream) -> TokenStream {
///
/// la macro reescribirá el método `with_` y generará un nuevo método `alter_`:
///
-/// ```rust,no_run
+/// ```rust
/// # struct Example {value: Option};
/// # impl Example {
/// #[inline]
diff --git a/helpers/pagetop-minimal/README.md b/helpers/pagetop-minimal/README.md
index b7a17bc0..1f8ec148 100644
--- a/helpers/pagetop-minimal/README.md
+++ b/helpers/pagetop-minimal/README.md
@@ -11,19 +11,21 @@
-## Sobre PageTop
+## 🧭 Sobre PageTop
[PageTop](https://docs.rs/pagetop) es un entorno de desarrollo que reivindica la esencia de la web
clásica para crear soluciones web SSR (*renderizadas en el servidor*) modulares, extensibles y
configurables, basadas en HTML, CSS y JavaScript.
-## Descripción general
+
+## 🗺️ Descripción general
Este *crate* proporciona un conjunto básico de macros que se integran en las utilidades de PageTop
para optimizar operaciones habituales relacionadas con la composición estructurada de texto, la
concatenación de cadenas y el uso rápido de colecciones clave-valor.
-## Créditos
+
+## 📚 Créditos
Las macros para texto multilínea **`indoc!`**, **`formatdoc!`** y **`concatdoc!`** se reexportan del
*crate* [indoc](https://crates.io/crates/indoc) de [David Tolnay](https://crates.io/users/dtolnay).
@@ -37,13 +39,15 @@ La macro para generar identificadores dinámicos **`paste!`** se reexporta del *
[pastey](https://crates.io/crates/pastey), una implementación avanzada y soportada del popular
`paste!` de [David Tolnay](https://crates.io/users/dtolnay).
-## Advertencia
+
+## 🚧 Advertencia
**PageTop** es un proyecto personal para aprender [Rust](https://www.rust-lang.org/es) y conocer su
ecosistema. Su API está sujeta a cambios frecuentes. No se recomienda su uso en producción, al menos
hasta que se libere la versión **1.0.0**.
-## Licencia
+
+## 📜 Licencia
El código está disponible bajo una doble licencia:
diff --git a/helpers/pagetop-minimal/src/lib.rs b/helpers/pagetop-minimal/src/lib.rs
index eab70c84..3b8c9036 100644
--- a/helpers/pagetop-minimal/src/lib.rs
+++ b/helpers/pagetop-minimal/src/lib.rs
@@ -40,7 +40,7 @@ La macro para generar identificadores dinámicos **`paste!`** se reexporta del *
*/
#![doc(
- html_favicon_url = "https://git.cillero.es/manuelcillero/pagetop/raw/branch/main/assets/favicon.ico"
+ html_favicon_url = "https://git.cillero.es/manuelcillero/pagetop/raw/branch/main/static/favicon.ico"
)]
#[doc(hidden)]
diff --git a/helpers/pagetop-statics/README.md b/helpers/pagetop-statics/README.md
index 92541096..3184f095 100644
--- a/helpers/pagetop-statics/README.md
+++ b/helpers/pagetop-statics/README.md
@@ -31,13 +31,15 @@ Para ello, adapta el código de [static-files](https://crates.io/crates/static_f
se integra en PageTop para evitar que cada proyecto tenga que declarar `static-files` manualmente
como dependencia en su `Cargo.toml`.
-## Advertencia
+
+## 🚧 Advertencia
**PageTop** es un proyecto personal para aprender [Rust](https://www.rust-lang.org/es) y conocer su
ecosistema. Su API está sujeta a cambios frecuentes. No se recomienda su uso en producción, al menos
hasta que se libere la versión **1.0.0**.
-## Licencia
+
+## 📜 Licencia
El código está disponible bajo una doble licencia:
diff --git a/helpers/pagetop-statics/src/lib.rs b/helpers/pagetop-statics/src/lib.rs
index 426991d5..d72176c6 100644
--- a/helpers/pagetop-statics/src/lib.rs
+++ b/helpers/pagetop-statics/src/lib.rs
@@ -35,14 +35,12 @@ como dependencia en su `Cargo.toml`.
#![doc(test(no_crate_inject))]
#![doc(
- html_favicon_url = "https://git.cillero.es/manuelcillero/pagetop/raw/branch/main/assets/favicon.ico"
+ html_favicon_url = "https://git.cillero.es/manuelcillero/pagetop/raw/branch/main/static/favicon.ico"
)]
#![allow(clippy::needless_doctest_main)]
/// Resource definition and single module based generation.
pub mod resource;
-
-#[doc(inline)]
pub use resource::Resource as StaticFile;
mod resource_dir;
diff --git a/src/base/action/component/after_render_component.rs b/src/base/action/component/after_render_component.rs
index 08eefdb5..0778e9c8 100644
--- a/src/base/action/component/after_render_component.rs
+++ b/src/base/action/component/after_render_component.rs
@@ -6,7 +6,7 @@ use super::FnActionWithComponent;
pub struct AfterRender {
f: FnActionWithComponent,
referer_type_id: Option,
- referer_id: Option,
+ referer_id: AttrId,
weight: Weight,
}
@@ -19,7 +19,7 @@ impl ActionDispatcher for AfterRender {
/// Devuelve el identificador del componente.
fn referer_id(&self) -> Option {
- self.referer_id.clone()
+ self.referer_id.get()
}
/// Devuelve el peso para definir el orden de ejecución.
@@ -34,7 +34,7 @@ impl AfterRender {
AfterRender {
f,
referer_type_id: Some(UniqueId::of::()),
- referer_id: None,
+ referer_id: AttrId::default(),
weight: 0,
}
}
@@ -42,8 +42,7 @@ impl AfterRender {
/// Afina el registro para ejecutar la acción [`FnActionWithComponent`] sólo para el componente
/// `C` con identificador `id`.
pub fn filter_by_referer_id(mut self, id: impl AsRef) -> Self {
- let id = id.as_ref().trim().to_ascii_lowercase().replace(' ', "_");
- self.referer_id = if id.is_empty() { None } else { Some(id) };
+ self.referer_id.alter_id(id);
self
}
diff --git a/src/base/action/component/before_render_component.rs b/src/base/action/component/before_render_component.rs
index e91589a2..051a3dd6 100644
--- a/src/base/action/component/before_render_component.rs
+++ b/src/base/action/component/before_render_component.rs
@@ -6,7 +6,7 @@ use super::FnActionWithComponent;
pub struct BeforeRender