diff --git a/CHANGELOG.md b/CHANGELOG.md index b01b988..9e18714 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,32 @@ Resume la evolución del proyecto para usuarios y colaboradores, destacando nuev correcciones, mejoras durante el desarrollo o cambios en la documentación. Cambios menores o internos pueden omitirse si no afectan al uso del proyecto. +## 0.3.0 (2025-08-16) + +### Cambiado + +- Redefine función para directorios absolutos +- Mejora la integración de archivos estáticos + +### Documentado + +- Cambia el formato para la documentación (#4) + +## 0.2.0 (2025-08-09) + +### Añadido + +- Añade librería para gestionar recursos estáticos (#1) +- Añade soporte a changelog de `pagetop-statics` (#2) + +### Documentado + +- Corrige enlace del botón de licencia en la documentación + +### Otros cambios + +- Afina Cargo.toml para buscar la mejor categoría + ## 0.1.0 (2025-08-06) - Versión inicial diff --git a/Cargo.lock b/Cargo.lock index 8427dd2..944027d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1557,7 +1557,7 @@ checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" [[package]] name = "pagetop" -version = "0.1.0" +version = "0.3.0" dependencies = [ "actix-files", "actix-session", @@ -1587,7 +1587,7 @@ dependencies = [ [[package]] name = "pagetop-build" -version = "0.1.1" +version = "0.3.0" dependencies = [ "grass", "pagetop-statics", @@ -1595,7 +1595,7 @@ dependencies = [ [[package]] name = "pagetop-macros" -version = "0.1.0" +version = "0.1.1" dependencies = [ "proc-macro2", "proc-macro2-diagnostics", @@ -1605,7 +1605,7 @@ dependencies = [ [[package]] name = "pagetop-statics" -version = "0.1.0" +version = "0.1.1" dependencies = [ "actix-web", "change-detection", diff --git a/Cargo.toml b/Cargo.toml index ff45ad3..8ccd69e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pagetop" -version = "0.1.0" +version = "0.3.0" edition = "2021" description = """ @@ -71,6 +71,6 @@ authors = ["Manuel Cillero "] [workspace.dependencies] actix-web = { version = "4.11.0", default-features = false } -pagetop-build = { version = "0.1", path = "helpers/pagetop-build" } +pagetop-build = { version = "0.3", path = "helpers/pagetop-build" } pagetop-macros = { version = "0.1", path = "helpers/pagetop-macros" } pagetop-statics = { version = "0.1", path = "helpers/pagetop-statics" } diff --git a/README.md b/README.md index fe85e66..e7fab94 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,7 @@ [![Crates.io](https://img.shields.io/crates/v/pagetop.svg?style=for-the-badge&logo=ipfs)](https://crates.io/crates/pagetop) [![Descargas](https://img.shields.io/crates/d/pagetop.svg?label=Descargas&style=for-the-badge&logo=transmission)](https://crates.io/crates/pagetop) +
`PageTop` reivindica la esencia de la web clásica usando [Rust](https://www.rust-lang.org/es) para @@ -32,7 +33,7 @@ según las necesidades de cada proyecto, incluyendo: La aplicación más sencilla de `PageTop` se ve así: -```rust +```rust,no_run use pagetop::prelude::*; #[pagetop::main] @@ -46,7 +47,7 @@ de bienvenida accesible desde un navegador local en la dirección `http://localh Para personalizar el servicio, se puede crear una extensión de `PageTop` de la siguiente manera: -```rust +```rust,no_run use pagetop::prelude::*; struct HelloWorld; @@ -83,9 +84,14 @@ El código se organiza en un *workspace* donde actualmente se incluyen los sigui ## Auxiliares + * **[pagetop-statics](https://git.cillero.es/manuelcillero/pagetop/src/branch/main/helpers/pagetop-statics)**, + es la librería que permite incluir archivos estáticos en el ejecutable de las aplicaciones + `PageTop` para servirlos de forma eficiente, con detección de cambios que optimizan el tiempo + de compilación. + * **[pagetop-build](https://git.cillero.es/manuelcillero/pagetop/src/branch/main/helpers/pagetop-build)**, - permite incluir fácilmente archivos estáticos o archivos SCSS compilados directamente en el - binario de las aplicaciones `PageTop`. + prepara los archivos estáticos o archivos SCSS compilados para incluirlos en el binario de las + aplicaciones `PageTop` durante la compilación de los ejecutables. * **[pagetop-macros](https://git.cillero.es/manuelcillero/pagetop/src/branch/main/helpers/pagetop-macros)**, proporciona una colección de macros que mejoran la experiencia de desarrollo con `PageTop`. diff --git a/helpers/pagetop-build/CHANGELOG.md b/helpers/pagetop-build/CHANGELOG.md index 2242969..3e3801e 100644 --- a/helpers/pagetop-build/CHANGELOG.md +++ b/helpers/pagetop-build/CHANGELOG.md @@ -8,6 +8,28 @@ Resume la evolución del proyecto para usuarios y colaboradores, destacando nuev correcciones, mejoras durante el desarrollo o cambios en la documentación. Cambios menores o internos pueden omitirse si no afectan al uso del proyecto. +## 0.3.0 (2025-08-16) + +### Cambiado + +- Mejora función `from_dir` por compatibilidad (#3) +- Mejora la integración de archivos estáticos + +### Documentado + +- Cambia el formato para la documentación (#4) + +## 0.2.0 (2025-08-09) + +### Añadido + +- Añade librería propia para gestionar recursos estáticos (#1) + +### Otros cambios + +- 🩹 Corrige enlace del botón de licencia en la documentación +- 🚩 Afina Cargo.toml para buscar la mejor categoría + ## 0.1.1 (2025-08-05) - Depura la edición de CHANGELOGs y publicación de nuevas versiones diff --git a/helpers/pagetop-build/Cargo.toml b/helpers/pagetop-build/Cargo.toml index 41e826e..e0f5ef7 100644 --- a/helpers/pagetop-build/Cargo.toml +++ b/helpers/pagetop-build/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pagetop-build" -version = "0.1.1" +version = "0.3.0" edition = "2021" description = """ diff --git a/helpers/pagetop-build/README.md b/helpers/pagetop-build/README.md index d9d5257..80d6bba 100644 --- a/helpers/pagetop-build/README.md +++ b/helpers/pagetop-build/README.md @@ -18,6 +18,99 @@ clásica para crear soluciones web SSR (*renderizadas en el servidor*) modulares configurables, basadas en HTML, CSS y JavaScript. +# ⚡️ Guía rápida + +Añadir en el archivo `Cargo.toml` del proyecto: + +```toml +[build-dependencies] +pagetop-build = { ... } +``` + +Y crear un archivo `build.rs` a la altura de `Cargo.toml` para indicar cómo se van a incluir los +archivos estáticos o cómo se van a compilar los archivos SCSS para el proyecto. Casos de uso: + +## Incluir archivos estáticos desde un directorio + +Hay que preparar una carpeta en el proyecto con todos los archivos que se quieren incluir, por +ejemplo `static`, y añadir el siguiente código en `build.rs` para crear el conjunto de recursos: + +```rust,no_run +use pagetop_build::StaticFilesBundle; + +fn main() -> std::io::Result<()> { + StaticFilesBundle::from_dir("./static", None) + .with_name("guides") + .build() +} +``` + +Si es necesario, se puede añadir un filtro para seleccionar archivos específicos de la carpeta, por +ejemplo: + +```rust,no_run +use pagetop_build::StaticFilesBundle; +use std::path::Path; + +fn main() -> std::io::Result<()> { + fn only_pdf_files(path: &Path) -> bool { + // Selecciona únicamente los archivos con extensión `.pdf`. + path.extension().map_or(false, |ext| ext == "pdf") + } + + StaticFilesBundle::from_dir("./static", Some(only_pdf_files)) + .with_name("guides") + .build() +} +``` + +## Compilar archivos SCSS a CSS + +Se puede compilar un archivo SCSS, que podría importar otros a su vez, para preparar un recurso con +el archivo CSS minificado obtenido. Por ejemplo: + +```rust,no_run +use pagetop_build::StaticFilesBundle; + +fn main() -> std::io::Result<()> { + StaticFilesBundle::from_scss("./styles/main.scss", "styles.min.css") + .with_name("main_styles") + .build() +} +``` + +Este código compila el archivo `main.scss` de la carpeta `static` del proyecto, y prepara un recurso +llamado `main_styles` que contiene el archivo `styles.min.css` obtenido. + + +# 📦 Archivos generados + +Cada conjunto de recursos [`StaticFilesBundle`] genera un archivo en el directorio estándar +[OUT_DIR](https://doc.rust-lang.org/cargo/reference/environment-variables.html#environment-variables-cargo-sets-for-build-scripts) +donde se incluye el código necesario para compilar el proyecto. Por ejemplo, para +`with_name("guides")` se genera un archivo llamado `guides.rs`. + +No hay ningún problema en generar más de un conjunto de recursos para cada proyecto siempre que se +usen nombres diferentes. + +Normalmente no habrá que acceder a estos módulos; sólo declarar el nombre del conjunto de recursos +en [`static_files_service!`](https://docs.rs/pagetop/latest/pagetop/macro.static_files_service.html) +para configurar un servicio web que sirva los archivos desde la ruta indicada. Por ejemplo: + +```rust,ignore +use pagetop::prelude::*; + +pub struct MyExtension; + +impl Extension for MyExtension { + // Servicio web que publica los recursos de `guides` en `/ruta/a/guides`. + fn configure_service(&self, scfg: &mut service::web::ServiceConfig) { + static_files_service!(scfg, guides => "/ruta/a/guides"); + } +} +``` + + # 🚧 Advertencia `PageTop` es un proyecto personal para aprender [Rust](https://www.rust-lang.org/es) y conocer su diff --git a/helpers/pagetop-build/src/lib.rs b/helpers/pagetop-build/src/lib.rs index b360377..c6e7236 100644 --- a/helpers/pagetop-build/src/lib.rs +++ b/helpers/pagetop-build/src/lib.rs @@ -1,122 +1,116 @@ -//!
-//! -//!

PageTop Build

-//! -//!

Prepara un conjunto de archivos estáticos o archivos SCSS compilados para ser incluidos en el binario de un proyecto PageTop.

-//! -//! [![Licencia](https://img.shields.io/badge/license-MIT%2FApache-blue.svg?label=Licencia&style=for-the-badge)](#-licencia) -//! [![Doc API](https://img.shields.io/docsrs/pagetop-build?label=Doc%20API&style=for-the-badge&logo=Docs.rs)](https://docs.rs/pagetop-build) -//! [![Crates.io](https://img.shields.io/crates/v/pagetop-build.svg?style=for-the-badge&logo=ipfs)](https://crates.io/crates/pagetop-build) -//! [![Descargas](https://img.shields.io/crates/d/pagetop-build.svg?label=Descargas&style=for-the-badge&logo=transmission)](https://crates.io/crates/pagetop-build) -//! -//!
-//! -//! ## 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. -//! -//! -//! # ⚡️ Guía rápida -//! -//! Añadir en el archivo `Cargo.toml` del proyecto: -//! -//! ```toml -//! [build-dependencies] -//! pagetop-build = { ... } -//! ``` -//! -//! Y crear un archivo `build.rs` a la altura de `Cargo.toml` para indicar cómo se van a incluir los -//! archivos estáticos o cómo se van a compilar los archivos SCSS para el proyecto. Casos de uso: -//! -//! ## Incluir archivos estáticos desde un directorio -//! -//! Hay que preparar una carpeta en el proyecto con todos los archivos que se quieren incluir, por -//! ejemplo `static`, y añadir el siguiente código en `build.rs` para crear el conjunto de recursos: -//! -//! ```rust,no_run -//! use pagetop_build::StaticFilesBundle; -//! -//! fn main() -> std::io::Result<()> { -//! StaticFilesBundle::from_dir("./static", None) -//! .with_name("guides") -//! .build() -//! } -//! ``` -//! -//! Si es necesario, se puede añadir un filtro para seleccionar archivos específicos de la carpeta, -//! por ejemplo: -//! -//! ```rust,no_run -//! use pagetop_build::StaticFilesBundle; -//! use std::path::Path; -//! -//! fn main() -> std::io::Result<()> { -//! fn only_pdf_files(path: &Path) -> bool { -//! // Selecciona únicamente los archivos con extensión `.pdf`. -//! path.extension().map_or(false, |ext| ext == "pdf") -//! } -//! -//! StaticFilesBundle::from_dir("./static", Some(only_pdf_files)) -//! .with_name("guides") -//! .build() -//! } -//! ``` -//! -//! ## Compilar archivos SCSS a CSS -//! -//! Se puede compilar un archivo SCSS, que podría importar otros a su vez, para preparar un recurso -//! con el archivo CSS minificado obtenido. Por ejemplo: -//! -//! ```rust,no_run -//! use pagetop_build::StaticFilesBundle; -//! -//! fn main() -> std::io::Result<()> { -//! StaticFilesBundle::from_scss("./styles/main.scss", "styles.min.css") -//! .with_name("main_styles") -//! .build() -//! } -//! ``` -//! -//! Este código compila el archivo `main.scss` de la carpeta `static` del proyecto, y prepara un -//! recurso llamado `main_styles` que contiene el archivo `styles.min.css` obtenido. -//! -//! -//! # 📦 Módulos generados -//! -//! Cada conjunto de recursos [`StaticFilesBundle`] genera un archivo en el directorio estándar -//! [OUT_DIR](https://doc.rust-lang.org/cargo/reference/environment-variables.html#environment-variables-cargo-sets-for-build-scripts) -//! donde se incluyen los recursos necesarios para compilar el proyecto. Por ejemplo, para -//! `with_name("guides")` se crea un archivo llamado `guides.rs`. -//! -//! No hay ningún problema en generar más de un conjunto de recursos para cada proyecto. -//! -//! Normalmente no habrá que acceder a estos módulos; bastará con incluirlos en el proyecto con -//! [`include_files!`](https://docs.rs/pagetop/latest/pagetop/macro.include_files.html), y luego con -//! [`include_files_service!`](https://docs.rs/pagetop/latest/pagetop/macro.include_files_service.html) -//! configurar un servicio web para servir los recursos desde la ruta indicada: -//! -//! ```rust,ignore -//! use pagetop::prelude::*; -//! -//! include_files!(guides); -//! -//! pub struct MyExtension; -//! -//! impl Extension for MyExtension { -//! // Servicio web que publica los recursos de `guides` en `/ruta/a/guides`. -//! fn configure_service(&self, scfg: &mut service::web::ServiceConfig) { -//! include_files_service!(scfg, guides => "/ruta/a/guides"); -//! } -//! } -//! ``` -//! -//! También se puede asignar el conjunto de recursos a una variable global; p.ej. `GUIDES`: -//! -//! ```rust,ignore -//! include_files!(GUIDES => guides); -//! ``` +/*! +
+ +

PageTop Build

+ +

Prepara un conjunto de archivos estáticos o archivos SCSS compilados para ser incluidos en el binario de un proyecto PageTop.

+ +[![Licencia](https://img.shields.io/badge/license-MIT%2FApache-blue.svg?label=Licencia&style=for-the-badge)](#-licencia) +[![Doc API](https://img.shields.io/docsrs/pagetop-build?label=Doc%20API&style=for-the-badge&logo=Docs.rs)](https://docs.rs/pagetop-build) +[![Crates.io](https://img.shields.io/crates/v/pagetop-build.svg?style=for-the-badge&logo=ipfs)](https://crates.io/crates/pagetop-build) +[![Descargas](https://img.shields.io/crates/d/pagetop-build.svg?label=Descargas&style=for-the-badge&logo=transmission)](https://crates.io/crates/pagetop-build) + +
+ +## 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. + + +# ⚡️ Guía rápida + +Añadir en el archivo `Cargo.toml` del proyecto: + +```toml +[build-dependencies] +pagetop-build = { ... } +``` + +Y crear un archivo `build.rs` a la altura de `Cargo.toml` para indicar cómo se van a incluir los +archivos estáticos o cómo se van a compilar los archivos SCSS para el proyecto. Casos de uso: + +## Incluir archivos estáticos desde un directorio + +Hay que preparar una carpeta en el proyecto con todos los archivos que se quieren incluir, por +ejemplo `static`, y añadir el siguiente código en `build.rs` para crear el conjunto de recursos: + +```rust,no_run +use pagetop_build::StaticFilesBundle; + +fn main() -> std::io::Result<()> { + StaticFilesBundle::from_dir("./static", None) + .with_name("guides") + .build() +} +``` + +Si es necesario, se puede añadir un filtro para seleccionar archivos específicos de la carpeta, por +ejemplo: + +```rust,no_run +use pagetop_build::StaticFilesBundle; +use std::path::Path; + +fn main() -> std::io::Result<()> { + fn only_pdf_files(path: &Path) -> bool { + // Selecciona únicamente los archivos con extensión `.pdf`. + path.extension().map_or(false, |ext| ext == "pdf") + } + + StaticFilesBundle::from_dir("./static", Some(only_pdf_files)) + .with_name("guides") + .build() +} +``` + +## Compilar archivos SCSS a CSS + +Se puede compilar un archivo SCSS, que podría importar otros a su vez, para preparar un recurso con +el archivo CSS minificado obtenido. Por ejemplo: + +```rust,no_run +use pagetop_build::StaticFilesBundle; + +fn main() -> std::io::Result<()> { + StaticFilesBundle::from_scss("./styles/main.scss", "styles.min.css") + .with_name("main_styles") + .build() +} +``` + +Este código compila el archivo `main.scss` de la carpeta `static` del proyecto, y prepara un recurso +llamado `main_styles` que contiene el archivo `styles.min.css` obtenido. + + +# 📦 Archivos generados + +Cada conjunto de recursos [`StaticFilesBundle`] genera un archivo en el directorio estándar +[OUT_DIR](https://doc.rust-lang.org/cargo/reference/environment-variables.html#environment-variables-cargo-sets-for-build-scripts) +donde se incluye el código necesario para compilar el proyecto. Por ejemplo, para +`with_name("guides")` se genera un archivo llamado `guides.rs`. + +No hay ningún problema en generar más de un conjunto de recursos para cada proyecto siempre que se +usen nombres diferentes. + +Normalmente no habrá que acceder a estos módulos; sólo declarar el nombre del conjunto de recursos +en [`static_files_service!`](https://docs.rs/pagetop/latest/pagetop/macro.static_files_service.html) +para configurar un servicio web que sirva los archivos desde la ruta indicada. Por ejemplo: + +```rust,ignore +use pagetop::prelude::*; + +pub struct MyExtension; + +impl Extension for MyExtension { + // Servicio web que publica los recursos de `guides` en `/ruta/a/guides`. + fn configure_service(&self, scfg: &mut service::web::ServiceConfig) { + static_files_service!(scfg, guides => "/ruta/a/guides"); + } +} +``` +*/ #![doc( html_favicon_url = "https://git.cillero.es/manuelcillero/pagetop/raw/branch/main/static/favicon.ico" @@ -129,8 +123,7 @@ use std::fs::{create_dir_all, remove_dir_all, File}; use std::io::Write; use std::path::Path; -/// Prepara un conjunto de recursos para ser incluidos en el binario del proyecto utilizando -/// [static_files](https://docs.rs/static-files/). +/// Prepara un conjunto de recursos para ser incluidos en el binario del proyecto. pub struct StaticFilesBundle { resource_dir: ResourceDir, } @@ -163,8 +156,19 @@ impl StaticFilesBundle { /// .build() /// } /// ``` - pub fn from_dir(dir: impl AsRef, filter: Option bool>) -> Self { - let mut resource_dir = resource_dir(dir.as_ref()); + pub fn from_dir

(dir: P, filter: Option bool>) -> Self + where + P: AsRef, + { + 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 { diff --git a/helpers/pagetop-macros/CHANGELOG.md b/helpers/pagetop-macros/CHANGELOG.md index b01b988..b61471a 100644 --- a/helpers/pagetop-macros/CHANGELOG.md +++ b/helpers/pagetop-macros/CHANGELOG.md @@ -8,6 +8,17 @@ Resume la evolución del proyecto para usuarios y colaboradores, destacando nuev correcciones, mejoras durante el desarrollo o cambios en la documentación. Cambios menores o internos pueden omitirse si no afectan al uso del proyecto. +## 0.1.1 (2025-08-16) + +### Documentado + +- Cambia el formato para la documentación (#4) +- Corrige enlaces de licencia en la documentación + +### Otros cambios + +- Afina Cargo.toml para buscar la mejor categoría + ## 0.1.0 (2025-08-06) - Versión inicial diff --git a/helpers/pagetop-macros/Cargo.toml b/helpers/pagetop-macros/Cargo.toml index 64f1cf4..d5ceb1b 100644 --- a/helpers/pagetop-macros/Cargo.toml +++ b/helpers/pagetop-macros/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pagetop-macros" -version = "0.1.0" +version = "0.1.1" edition = "2021" description = """ diff --git a/helpers/pagetop-macros/README.md b/helpers/pagetop-macros/README.md index c5006a8..e58d24c 100644 --- a/helpers/pagetop-macros/README.md +++ b/helpers/pagetop-macros/README.md @@ -11,9 +11,16 @@ -## Descripción general +## Sobre PageTop -Entre sus macros se incluye una adaptación de [maud-macros](https://crates.io/crates/maud_macros) +[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 + +Esta librería incluye entre sus macros una adaptación de +[maud-macros](https://crates.io/crates/maud_macros) ([0.27.0](https://github.com/lambda-fairy/maud/tree/v0.27.0/maud_macros)) de [Chris Wong](https://crates.io/users/lambda-fairy) y una versión renombrada de [SmartDefault](https://crates.io/crates/smart_default) (0.7.1) de @@ -21,12 +28,6 @@ Entre sus macros se incluye una adaptación de [maud-macros](https://crates.io/c necesidad de referenciar `maud` o `smart_default` en las dependencias del archivo `Cargo.toml` de cada proyecto `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. - # 🚧 Advertencia diff --git a/helpers/pagetop-macros/src/lib.rs b/helpers/pagetop-macros/src/lib.rs index 496ce82..6421ca6 100644 --- a/helpers/pagetop-macros/src/lib.rs +++ b/helpers/pagetop-macros/src/lib.rs @@ -1,21 +1,34 @@ -//!

-//! -//!

PageTop Macros

-//! -//!

Una colección de macros que mejoran la experiencia de desarrollo con PageTop.

-//! -//! [![Licencia](https://img.shields.io/badge/license-MIT%2FApache-blue.svg?label=Licencia&style=for-the-badge)](#-licencia) -//! [![Doc API](https://img.shields.io/docsrs/pagetop-macros?label=Doc%20API&style=for-the-badge&logo=Docs.rs)](https://docs.rs/pagetop-macros) -//! [![Crates.io](https://img.shields.io/crates/v/pagetop-macros.svg?style=for-the-badge&logo=ipfs)](https://crates.io/crates/pagetop-macros) -//! [![Descargas](https://img.shields.io/crates/d/pagetop-macros.svg?label=Descargas&style=for-the-badge&logo=transmission)](https://crates.io/crates/pagetop-macros) -//! -//!
-//! -//! ## 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. +/*! +
+ +

PageTop Macros

+ +

Una colección de macros que mejoran la experiencia de desarrollo con PageTop.

+ +[![Licencia](https://img.shields.io/badge/license-MIT%2FApache-blue.svg?label=Licencia&style=for-the-badge)](#-licencia) +[![Doc API](https://img.shields.io/docsrs/pagetop-macros?label=Doc%20API&style=for-the-badge&logo=Docs.rs)](https://docs.rs/pagetop-macros) +[![Crates.io](https://img.shields.io/crates/v/pagetop-macros.svg?style=for-the-badge&logo=ipfs)](https://crates.io/crates/pagetop-macros) +[![Descargas](https://img.shields.io/crates/d/pagetop-macros.svg?label=Descargas&style=for-the-badge&logo=transmission)](https://crates.io/crates/pagetop-macros) + +
+ +## 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 + +Esta librería incluye entre sus macros una adaptación de +[maud-macros](https://crates.io/crates/maud_macros) +([0.27.0](https://github.com/lambda-fairy/maud/tree/v0.27.0/maud_macros)) de +[Chris Wong](https://crates.io/users/lambda-fairy) y una versión renombrada de +[SmartDefault](https://crates.io/crates/smart_default) (0.7.1) de +[Jane Doe](https://crates.io/users/jane-doe), llamada `AutoDefault`. Estas macros eliminan la +necesidad de referenciar `maud` o `smart_default` en las dependencias del archivo `Cargo.toml` de +cada proyecto `PageTop`. +*/ #![doc( html_favicon_url = "https://git.cillero.es/manuelcillero/pagetop/raw/branch/main/static/favicon.ico" diff --git a/helpers/pagetop-statics/CHANGELOG.md b/helpers/pagetop-statics/CHANGELOG.md index 62ad97e..811a77f 100644 --- a/helpers/pagetop-statics/CHANGELOG.md +++ b/helpers/pagetop-statics/CHANGELOG.md @@ -8,6 +8,12 @@ Resume la evolución del proyecto para usuarios y colaboradores, destacando nuev correcciones, mejoras durante el desarrollo o cambios en la documentación. Cambios menores o internos pueden omitirse si no afectan al uso del proyecto. +## 0.1.1 (2025-08-16) + +### Documentado + +- Cambia el formato para la documentación (#4) + ## 0.1.0 (2025-08-09) ### Añadido diff --git a/helpers/pagetop-statics/Cargo.toml b/helpers/pagetop-statics/Cargo.toml index e5c58d5..2afe373 100644 --- a/helpers/pagetop-statics/Cargo.toml +++ b/helpers/pagetop-statics/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pagetop-statics" -version = "0.1.0" +version = "0.1.1" edition = "2021" description = """ diff --git a/helpers/pagetop-statics/README.md b/helpers/pagetop-statics/README.md index c053e95..92999c0 100644 --- a/helpers/pagetop-statics/README.md +++ b/helpers/pagetop-statics/README.md @@ -8,12 +8,21 @@ +## 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 -Permite a `PageTop` incluir archivos estáticos en el ejecutable de la aplicación para servirlos de -forma eficiente vía web, con detección de cambios que optimiza el tiempo de compilación. +Esta librería permite incluir archivos estáticos en el ejecutable de las aplicaciones `PageTop` para +servirlos de forma eficiente vía web, con detección de cambios que optimizan el tiempo de +compilación. -Para ello, reúne el código de los *crates* [static-files](https://crates.io/crates/static_files) +## Créditos + +Para ello, adapta el código de los *crates* [static-files](https://crates.io/crates/static_files) (versión [0.2.5](https://github.com/static-files-rs/static-files/tree/v0.2.5)) y [actix-web-static-files](https://crates.io/crates/actix_web_static_files) (versión [4.0.1](https://github.com/kilork/actix-web-static-files/tree/v4.0.1)), desarrollados ambos por @@ -22,12 +31,6 @@ Para ello, reúne el código de los *crates* [static-files](https://crates.io/cr Estas implementaciones se integran en `PageTop` para evitar que cada proyecto tenga que declarar `static-files` manualmente como dependencia en su `Cargo.toml`. -## 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. - # 🚧 Advertencia diff --git a/helpers/pagetop-statics/src/lib.rs b/helpers/pagetop-statics/src/lib.rs index fffd1ae..dab50d9 100644 --- a/helpers/pagetop-statics/src/lib.rs +++ b/helpers/pagetop-statics/src/lib.rs @@ -1,18 +1,37 @@ -//!
-//! -//!

PageTop Statics

-//! -//!

Librería para automatizar la recopilación de recursos estáticos en PageTop.

-//! -//! [![Licencia](https://img.shields.io/badge/license-MIT%2FApache-blue.svg?label=Licencia&style=for-the-badge)](#-licencia) -//! -//!
-//! -//! ## 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. +/*! +
+ +

PageTop Statics

+ +

Librería para automatizar la recopilación de recursos estáticos en PageTop.

+ +[![Licencia](https://img.shields.io/badge/license-MIT%2FApache-blue.svg?label=Licencia&style=for-the-badge)](#-licencia) + +
+ +## 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 + +Esta librería permite incluir archivos estáticos en el ejecutable de las aplicaciones `PageTop` para +servirlos de forma eficiente vía web, con detección de cambios que optimizan el tiempo de +compilación. + +## Créditos + +Para ello, adapta el código de los *crates* [static-files](https://crates.io/crates/static_files) +(versión [0.2.5](https://github.com/static-files-rs/static-files/tree/v0.2.5)) y +[actix-web-static-files](https://crates.io/crates/actix_web_static_files) (versión +[4.0.1](https://github.com/kilork/actix-web-static-files/tree/v4.0.1)), desarrollados ambos por +[Alexander Korolev](https://crates.io/users/kilork). + +Estas implementaciones se integran en `PageTop` para evitar que cada proyecto tenga que declarar +`static-files` manualmente como dependencia en su `Cargo.toml`. +*/ #![doc(test(no_crate_inject))] #![doc( diff --git a/src/config.rs b/src/config.rs index 08067fe..27cf630 100644 --- a/src/config.rs +++ b/src/config.rs @@ -110,11 +110,13 @@ //! } //! ``` +use crate::util; + use config::builder::DefaultState; use config::{Config, ConfigBuilder, File}; use std::env; -use std::path::{Path, PathBuf}; +use std::path::PathBuf; use std::sync::LazyLock; // Nombre del directorio de configuración por defecto. @@ -125,25 +127,12 @@ const DEFAULT_RUN_MODE: &str = "default"; /// Valores originales cargados desde los archivos de configuración como pares `clave = valor`. pub static CONFIG_VALUES: LazyLock> = LazyLock::new(|| { - // Determina el directorio de configuración: - // - Usa CONFIG_DIR si está definido en el entorno (p.ej.: CONFIG_DIR=/etc/myapp ./myapp). - // - Si no, intenta DEFAULT_CONFIG_DIR dentro del proyecto (en CARGO_MANIFEST_DIR). - // - Si nada de esto aplica, entonces usa DEFAULT_CONFIG_DIR relativo al ejecutable. - let config_dir: PathBuf = if let Ok(env_dir) = env::var("CONFIG_DIR") { - env_dir.into() - } else if let Ok(manifest_dir) = env::var("CARGO_MANIFEST_DIR") { - let manifest_config = Path::new(&manifest_dir).join(DEFAULT_CONFIG_DIR); - if manifest_config.exists() { - manifest_config - } else { - DEFAULT_CONFIG_DIR.into() - } - } else { - DEFAULT_CONFIG_DIR.into() - }; + // CONFIG_DIR (si existe) o DEFAULT_CONFIG_DIR. Si no se puede resolver, se usa tal cual. + let dir = env::var_os("CONFIG_DIR").unwrap_or_else(|| DEFAULT_CONFIG_DIR.into()); + let config_dir = util::resolve_absolute_dir(&dir).unwrap_or_else(|_| PathBuf::from(&dir)); - // Determina el modo de ejecución según la variable de entorno PAGETOP_RUN_MODE. Por defecto usa - // DEFAULT_RUN_MODE si no está definida (p.ej.: PAGETOP_RUN_MODE=production ./myapp). + // Modo de ejecución según la variable de entorno PAGETOP_RUN_MODE. Si no está definida, se usa + // por defecto, DEFAULT_RUN_MODE (p.ej.: PAGETOP_RUN_MODE=production). let rm = env::var("PAGETOP_RUN_MODE").unwrap_or_else(|_| DEFAULT_RUN_MODE.into()); Config::builder() diff --git a/src/core/extension/all.rs b/src/core/extension/all.rs index 93b5c4b..a243778 100644 --- a/src/core/extension/all.rs +++ b/src/core/extension/all.rs @@ -1,7 +1,7 @@ use crate::core::action::add_action; use crate::core::extension::ExtensionRef; use crate::core::theme::all::THEMES; -use crate::{global, include_files, include_files_service, service, trace}; +use crate::{global, service, static_files_service, trace}; use parking_lot::RwLock; @@ -125,8 +125,6 @@ pub fn initialize_extensions() { // CONFIGURA LOS SERVICIOS ************************************************************************* -include_files!(assets); - pub fn configure_services(scfg: &mut service::web::ServiceConfig) { // Sólo compila durante el desarrollo, para evitar errores 400 en la traza de eventos. #[cfg(debug_assertions)] @@ -140,7 +138,5 @@ pub fn configure_services(scfg: &mut service::web::ServiceConfig) { extension.configure_service(scfg); } - include_files_service!( - scfg, assets => "/", [&global::SETTINGS.dev.pagetop_project_dir, "static"] - ); + static_files_service!(scfg, [&global::SETTINGS.dev.pagetop_static_dir, assets] => "/"); } diff --git a/src/global.rs b/src/global.rs index ea659b8..8a03589 100644 --- a/src/global.rs +++ b/src/global.rs @@ -13,7 +13,7 @@ include_config!(SETTINGS: Settings => [ "app.startup_banner" => "Slant", // [dev] - "dev.pagetop_project_dir" => "", + "dev.pagetop_static_dir" => "", // [log] "log.enabled" => true, @@ -68,11 +68,15 @@ pub struct App { #[derive(Debug, Deserialize)] /// Sección `[Dev]` de la configuración. Forma parte de [`Settings`]. pub struct Dev { - /// Los archivos estáticos requeridos por `PageTop` se integran por defecto en el binario - /// ejecutable. Sin embargo, durante el desarrollo puede resultar útil servirlos desde su propio - /// directorio para evitar recompilar cada vez que se modifican. En ese caso, este ajuste debe - /// indicar la ruta absoluta al directorio raíz del proyecto. - pub pagetop_project_dir: String, + /// Directorio desde el que servir los archivos estáticos de `PageTop`. + /// + /// Por defecto, los archivos se integran en el binario de la aplicación. Si aquí se indica una + /// ruta válida, ya sea absoluta o relativa al directorio del proyecto o del binario en + /// ejecución, se servirán desde el sistema de ficheros en su lugar. Esto es especialmente útil + /// en desarrollo, ya que evita recompilar el proyecto por cambios en estos archivos. + /// + /// Si la cadena está vacía, se ignora este ajuste. + pub pagetop_static_dir: String, } #[derive(Debug, Deserialize)] diff --git a/src/html/assets/favicon.rs b/src/html/assets/favicon.rs index 2af0173..1a8b29e 100644 --- a/src/html/assets/favicon.rs +++ b/src/html/assets/favicon.rs @@ -12,8 +12,7 @@ use crate::AutoDefault; /// /// > **Nota** /// > Los archivos de los iconos deben estar disponibles en el servidor web de la aplicación. Pueden -/// > incluirse en el proyecto utilizando [`include_files!`](crate::include_files) y servirse con -/// > [`include_files_service!`](crate::include_files_service). +/// > servirse usando [`static_files_service!`](crate::static_files_service). /// /// # Ejemplo /// diff --git a/src/html/assets/javascript.rs b/src/html/assets/javascript.rs index 604e85a..db5754e 100644 --- a/src/html/assets/javascript.rs +++ b/src/html/assets/javascript.rs @@ -30,8 +30,7 @@ enum Source { /// /// > **Nota** /// > Los archivos de los *scripts* deben estar disponibles en el servidor web de la aplicación. -/// > Pueden incluirse en el proyecto utilizando [`include_files!`](crate::include_files) y servirse -/// > con [`include_files_service!`](crate::include_files_service). +/// > Pueden servirse usando [`static_files_service!`](crate::static_files_service). /// /// # Ejemplo /// diff --git a/src/html/assets/stylesheet.rs b/src/html/assets/stylesheet.rs index 72a79a1..bb60b01 100644 --- a/src/html/assets/stylesheet.rs +++ b/src/html/assets/stylesheet.rs @@ -55,8 +55,7 @@ impl TargetMedia { /// /// > **Nota** /// > Las hojas de estilo CSS deben estar disponibles en el servidor web de la aplicación. Pueden -/// > incluirse en el proyecto utilizando [`include_files!`](crate::include_files) y servirse con -/// > [`include_files_service!`](crate::include_files_service). +/// > servirse usando [`static_files_service!`](crate::static_files_service). /// /// # Ejemplo /// diff --git a/src/lib.rs b/src/lib.rs index bbc4530..90ea462 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,86 +1,89 @@ -//!
-//! -//! -//! -//!

PageTop

-//! -//!

Un entorno de desarrollo para crear soluciones web modulares, extensibles y configurables.

-//! -//! [![Licencia](https://img.shields.io/badge/license-MIT%2FApache-blue.svg?label=Licencia&style=for-the-badge)](#-licencia) -//! [![Doc API](https://img.shields.io/docsrs/pagetop?label=Doc%20API&style=for-the-badge&logo=Docs.rs)](https://docs.rs/pagetop) -//! [![Crates.io](https://img.shields.io/crates/v/pagetop.svg?style=for-the-badge&logo=ipfs)](https://crates.io/crates/pagetop) -//! [![Descargas](https://img.shields.io/crates/d/pagetop.svg?label=Descargas&style=for-the-badge&logo=transmission)](https://crates.io/crates/pagetop) -//! -//!
-//!
-//! -//! `PageTop` reivindica la esencia de la web clásica usando [Rust](https://www.rust-lang.org/es) -//! para la creación de soluciones web SSR (*renderizadas en el servidor*) basadas en HTML, CSS y -//! JavaScript. Ofrece un conjunto de herramientas que los desarrolladores pueden implementar, -//! extender o adaptar según las necesidades de cada proyecto, incluyendo: -//! -//! * **Acciones** (*actions*): alteran la lógica interna de una funcionalidad interceptando su -//! flujo de ejecución. -//! * **Componentes** (*components*): encapsulan HTML, CSS y JavaScript en unidades funcionales, -//! configurables y reutilizables. -//! * **Extensiones** (*extensions*): añaden, extienden o personalizan funcionalidades usando las -//! APIs de `PageTop` o de terceros. -//! * **Temas** (*themes*): son extensiones que permiten modificar la apariencia de páginas y -//! componentes sin comprometer su funcionalidad. -//! -//! # ⚡️ Guía rápida -//! -//! La aplicación más sencilla de `PageTop` se ve así: -//! -//! ```rust,no_run -//! use pagetop::prelude::*; -//! -//! #[pagetop::main] -//! async fn main() -> std::io::Result<()> { -//! Application::new().run()?.await -//! } -//! ``` -//! -//! Este código arranca el servidor de `PageTop`. Con la -//! [configuración por defecto](crate::global::SETTINGS), muestra una página de bienvenida accesible -//! desde un navegador local en la dirección `http://localhost:8080`. -//! -//! Para personalizar el servicio, se puede crear una extensión de `PageTop` de la siguiente manera: -//! -//! ```rust,no_run -//! use pagetop::prelude::*; -//! -//! struct HelloWorld; -//! -//! impl Extension for HelloWorld { -//! fn configure_service(&self, scfg: &mut service::web::ServiceConfig) { -//! scfg.route("/", service::web::get().to(hello_world)); -//! } -//! } -//! -//! async fn hello_world(request: HttpRequest) -> ResultPage { -//! Page::new(Some(request)) -//! .with_component(Html::with(move |_| html! { h1 { "Hello world!" } })) -//! .render() -//! } -//! -//! #[pagetop::main] -//! async fn main() -> std::io::Result<()> { -//! Application::prepare(&HelloWorld).run()?.await -//! } -//! ``` -//! -//! Este programa implementa una extensión llamada `HelloWorld` que sirve una página web en la ruta -//! raíz (`/`) mostrando el texto "Hello world!" dentro de un elemento HTML `

`. -//! -//! # 🧩 Gestión de Dependencias -//! -//! Los proyectos que utilizan `PageTop` gestionan las dependencias con `cargo`, como cualquier otro -//! proyecto en Rust. -//! -//! Sin embargo, es fundamental que cada extensión declare explícitamente sus -//! [dependencias](core::extension::Extension::dependencies), si las tiene, para que `PageTop` pueda -//! estructurar e inicializar la aplicación de forma modular. +/*! +
+ + + +

PageTop

+ +

Un entorno para el desarrollo de soluciones web modulares, extensibles y configurables.

+ +[![Licencia](https://img.shields.io/badge/license-MIT%2FApache-blue.svg?label=Licencia&style=for-the-badge)](#-licencia) +[![Doc API](https://img.shields.io/docsrs/pagetop?label=Doc%20API&style=for-the-badge&logo=Docs.rs)](https://docs.rs/pagetop) +[![Crates.io](https://img.shields.io/crates/v/pagetop.svg?style=for-the-badge&logo=ipfs)](https://crates.io/crates/pagetop) +[![Descargas](https://img.shields.io/crates/d/pagetop.svg?label=Descargas&style=for-the-badge&logo=transmission)](https://crates.io/crates/pagetop) + +
+
+ +`PageTop` reivindica la esencia de la web clásica usando [Rust](https://www.rust-lang.org/es) para +la creación de soluciones web SSR (*renderizadas en el servidor*) basadas en HTML, CSS y JavaScript. +Ofrece un conjunto de herramientas que los desarrolladores pueden implementar, extender o adaptar +según las necesidades de cada proyecto, incluyendo: + + * **Acciones** (*actions*): alteran la lógica interna de una funcionalidad interceptando su flujo + de ejecución. + * **Componentes** (*components*): encapsulan HTML, CSS y JavaScript en unidades funcionales, + configurables y reutilizables. + * **Extensiones** (*extensions*): añaden, extienden o personalizan funcionalidades usando las APIs + de `PageTop` o de terceros. + * **Temas** (*themes*): son extensiones que permiten modificar la apariencia de páginas y + componentes sin comprometer su funcionalidad. + + +# ⚡️ Guía rápida + +La aplicación más sencilla de `PageTop` se ve así: + +```rust,no_run +use pagetop::prelude::*; + +#[pagetop::main] +async fn main() -> std::io::Result<()> { + Application::new().run()?.await +} +``` + +Este código arranca el servidor de `PageTop`. Con la configuración por defecto, muestra una página +de bienvenida accesible desde un navegador local en la dirección `http://localhost:8080`. + +Para personalizar el servicio, se puede crear una extensión de `PageTop` de la siguiente manera: + +```rust,no_run +use pagetop::prelude::*; + +struct HelloWorld; + +impl Extension for HelloWorld { + fn configure_service(&self, scfg: &mut service::web::ServiceConfig) { + scfg.route("/", service::web::get().to(hello_world)); + } +} + +async fn hello_world(request: HttpRequest) -> ResultPage { + Page::new(Some(request)) + .with_component(Html::with(move |_| html! { h1 { "Hello World!" } })) + .render() +} + +#[pagetop::main] +async fn main() -> std::io::Result<()> { + Application::prepare(&HelloWorld).run()?.await +} +``` + +Este programa implementa una extensión llamada `HelloWorld` que sirve una página web en la ruta raíz +(`/`) mostrando el texto "Hello world!" dentro de un elemento HTML `

`. + + +# 🧩 Gestión de Dependencias + +Los proyectos que utilizan `PageTop` gestionan las dependencias con `cargo`, como cualquier otro +proyecto en Rust. + +Sin embargo, es fundamental que cada extensión declare explícitamente sus +[dependencias](core::extension::Extension::dependencies), si las tiene, para que `PageTop` pueda +estructurar e inicializar la aplicación de forma modular. +*/ #![cfg_attr(docsrs, feature(doc_cfg))] #![doc( @@ -100,7 +103,8 @@ pub use pagetop_macros::{builder_fn, html, main, test, AutoDefault}; pub use pagetop_statics::{resource, StaticResource}; -/// Conjunto de recursos asociados a `$STATIC` en [`include_files!`](crate::include_files). +/// Contenedor para un conjunto de recursos embebidos. +#[derive(AutoDefault)] pub struct StaticResources { bundle: HashMap<&'static str, StaticResource>, } diff --git a/src/prelude.rs b/src/prelude.rs index 6bacaf3..9072dec 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -15,7 +15,8 @@ pub use crate::include_config; // crate::locale pub use crate::include_locales; // crate::service -pub use crate::{include_files, include_files_service}; +#[allow(deprecated)] +pub use crate::{include_files, include_files_service, static_files_service}; // crate::core::action pub use crate::actions_boxed; diff --git a/src/service.rs b/src/service.rs index 47f1420..288e1eb 100644 --- a/src/service.rs +++ b/src/service.rs @@ -15,6 +15,9 @@ pub use pagetop_statics::ResourceFiles; #[doc(hidden)] pub use actix_web::test; +/// **Obsoleto desde la versión 0.3.0**: usar [`static_files_service!`](crate::static_files_service) +/// en su lugar. +/// /// Incluye en código un conjunto de recursos previamente preparado con `build.rs`. /// /// # Formas de uso @@ -39,6 +42,7 @@ pub use actix_web::test; /// /// include_files!(STATIC_ASSETS => assets); /// ``` +#[deprecated(since = "0.3.0", note = "Use `static_files_service!` instead")] #[macro_export] macro_rules! include_files { // Forma 1: incluye un conjunto de recursos por nombre. @@ -63,6 +67,9 @@ macro_rules! include_files { }; } +/// **Obsoleto desde la versión 0.3.0**: usar [`static_files_service!`](crate::static_files_service) +/// en su lugar. +/// /// Configura un servicio web para publicar los recursos embebidos con [`include_files!`]. /// /// El código expandido de la macro decide durante el arranque de la aplicación si debe servir los @@ -104,6 +111,7 @@ macro_rules! include_files { /// // También desde el directorio actual de ejecución. /// include_files_service!(cfg, assets => "/public", ["", "static"]); /// ``` +#[deprecated(since = "0.3.0", note = "Use `static_files_service!` instead")] #[macro_export] macro_rules! include_files_service { ( $scfg:ident, $bundle:ident => $route:expr $(, [$root:expr, $relative:expr])? ) => {{ @@ -137,3 +145,114 @@ macro_rules! include_files_service { } }}; } + +/// Configura un servicio web para publicar archivos estáticos. +/// +/// La macro ofrece tres modos para configurar el servicio: +/// +/// - **Sistema de ficheros o embebido** (`[$path, $bundle]`): trata de servir los archivos desde +/// `$path`; y si es una cadena vacía, no existe o no es un directorio, entonces usará el conjunto +/// de recursos `$bundle` integrado en el binario. +/// - **Sólo embebido** (`[$bundle]`): sirve siempre desde el conjunto de recursos `$bundle` +/// integrado en el binario. +/// - **Sólo sistema de ficheros** (`$path`): sin usar corchetes, sirve únicamente desde el sistema +/// de ficheros si existe; en otro caso no registra el servicio. +/// +/// # Argumentos +/// +/// * `$scfg` – Instancia de [`ServiceConfig`](crate::service::web::ServiceConfig) donde aplicar la +/// configuración. +/// * `$path` – Ruta al directorio local con los archivos estáticos. +/// * `$bundle` – Nombre del conjunto de recursos que esta macro integra en el binario. +/// * `$route` – Ruta URL base desde la que se servirán los archivos. +/// +/// # Ejemplos +/// +/// ```rust,ignore +/// use pagetop::prelude::*; +/// +/// pub struct MyExtension; +/// +/// impl Extension for MyExtension { +/// fn configure_service(&self, scfg: &mut service::web::ServiceConfig) { +/// // Forma 1) Sistema de ficheros o embebido. +/// static_files_service!(scfg, ["/var/www/static", assets] => "/public"); +/// +/// // Forma 2) Siempre embebido. +/// static_files_service!(scfg, [assets] => "/public"); +/// +/// // Forma 3) Sólo sistema de ficheros (no requiere `assets`). +/// static_files_service!(scfg, "/var/www/static" => "/public"); +/// } +/// } +/// ``` +#[macro_export] +macro_rules! static_files_service { + // Forma 1: primero intenta servir desde el sistema de ficheros; si falla, sirve embebido. + ( $scfg:ident, [$path:expr, $bundle:ident] => $route:expr $(,)? ) => {{ + let span = $crate::trace::debug_span!( + "Configuring static files (file system or embedded)", + mode = "fs_or_embedded", + route = $route, + ); + let _ = span.in_scope(|| { + let mut serve_embedded: bool = true; + if !::std::path::Path::new(&$path).as_os_str().is_empty() { + if let Ok(absolute) = $crate::util::resolve_absolute_dir($path) { + $scfg.service($crate::service::ActixFiles::new($route, absolute)); + serve_embedded = false; + } + } + if serve_embedded { + $crate::util::paste! { + mod [] { + include!(concat!(env!("OUT_DIR"), "/", stringify!($bundle), ".rs")); + } + $scfg.service($crate::service::ResourceFiles::new( + $route, + []::$bundle(), + )); + } + } + }); + }}; + // Forma 2: sirve siempre embebido. + ( $scfg:ident, [$bundle:ident] => $route:expr $(,)? ) => {{ + let span = $crate::trace::debug_span!( + "Configuring static files (using embedded only)", + mode = "embedded", + route = $route, + ); + let _ = span.in_scope(|| { + $crate::util::paste! { + mod [] { + include!(concat!(env!("OUT_DIR"), "/", stringify!($bundle), ".rs")); + } + $scfg.service($crate::service::ResourceFiles::new( + $route, + []::$bundle(), + )); + } + }); + }}; + // Forma 3: intenta servir desde el sistema de ficheros. + ( $scfg:ident, $path:expr => $route:expr $(,)? ) => {{ + let span = $crate::trace::debug_span!( + "Configuring static files (file system only)", + mode = "fs", + route = $route, + ); + let _ = span.in_scope(|| match $crate::util::resolve_absolute_dir($path) { + Ok(absolute) => { + $scfg.service($crate::service::ActixFiles::new($route, absolute)); + } + Err(e) => { + $crate::trace::warn!( + "Static dir not found or invalid for route `{}`: {:?} ({e})", + $route, + $path, + ); + } + }); + }}; +} diff --git a/src/util.rs b/src/util.rs index b605125..21537c5 100644 --- a/src/util.rs +++ b/src/util.rs @@ -2,35 +2,44 @@ use crate::trace; +use std::env; use std::io; use std::path::{Path, PathBuf}; // FUNCIONES ÚTILES ******************************************************************************** -/// Devuelve la ruta absoluta a un directorio existente. +/// Resuelve y valida la ruta de un directorio existente, devolviendo una ruta absoluta. /// -/// * Si `relative_path` es una ruta absoluta, entonces se ignora `root_path`. -/// * Si la ruta final es relativa, se convierte en absoluta respecto al directorio actual. -/// * Devuelve error si la ruta no existe o no es un directorio. +/// - Si la ruta es relativa, se resuelve respecto al directorio del proyecto según la variable de +/// entorno `CARGO_MANIFEST_DIR` (si existe) o, en su defecto, respecto al directorio actual de +/// trabajo. +/// - Normaliza y valida la ruta final (resuelve `.`/`..` y enlaces simbólicos). +/// - Devuelve error si la ruta no existe o no es un directorio. /// -/// # Ejemplo +/// # Ejemplos /// /// ```rust,no_run /// use pagetop::prelude::*; /// -/// let root = "/home/user"; -/// let rel = "documents"; -/// println!("{:#?}", util::absolute_dir(root, rel)); +/// // Ruta relativa, se resuelve respecto a CARGO_MANIFEST_DIR o al directorio actual (`cwd`). +/// println!("{:#?}", util::resolve_absolute_dir("documents")); +/// +/// // Ruta absoluta, se normaliza y valida tal cual. +/// println!("{:#?}", util::resolve_absolute_dir("/var/www")); /// ``` -pub fn absolute_dir(root_path: P, relative_path: Q) -> io::Result -where - P: AsRef, - Q: AsRef, -{ - // Une ambas rutas: - // - Si `relative_path` es absoluta, el `join` la devuelve tal cual, descartando `root_path`. - // - Si el resultado es aún relativo, lo será respecto al directorio actual. - let candidate = root_path.as_ref().join(relative_path.as_ref()); +pub fn resolve_absolute_dir>(path: P) -> io::Result { + let path = path.as_ref(); + + let candidate = if path.is_absolute() { + path.to_path_buf() + } else { + // Directorio base CARGO_MANIFEST_DIR si está disponible; o current_dir() en su defecto. + env::var_os("CARGO_MANIFEST_DIR") + .map(PathBuf::from) + .or_else(|| env::current_dir().ok()) + .unwrap_or_else(|| PathBuf::from(".")) + .join(path) + }; // Resuelve `.`/`..`, enlaces simbólicos y obtiene la ruta absoluta en un único paso. let absolute_dir = candidate.canonicalize()?; @@ -47,6 +56,16 @@ where } } +/// Devuelve la ruta absoluta a un directorio existente. +#[deprecated(since = "0.3.0", note = "Use [`resolve_absolute_dir`] instead")] +pub fn absolute_dir(root_path: P, relative_path: Q) -> io::Result +where + P: AsRef, + Q: AsRef, +{ + resolve_absolute_dir(root_path.as_ref().join(relative_path.as_ref())) +} + // MACROS ÚTILES *********************************************************************************** #[doc(hidden)] diff --git a/tests/util.rs b/tests/util.rs index 6e0896f..70699a7 100644 --- a/tests/util.rs +++ b/tests/util.rs @@ -1,6 +1,6 @@ use pagetop::prelude::*; -use std::{fs, io}; +use std::{env, fs, io}; use tempfile::TempDir; #[cfg(unix)] @@ -13,15 +13,36 @@ mod unix { // /tmp//sub let td = TempDir::new()?; - let root = td.path(); - let sub = root.join("sub"); + let sub = td.path().join("sub"); fs::create_dir(&sub)?; - let abs = util::absolute_dir(root, "sub")?; + let abs = util::resolve_absolute_dir(&sub)?; assert_eq!(abs, std::fs::canonicalize(&sub)?); Ok(()) } + #[pagetop::test] + async fn ok_relative_dir_with_manifest() -> io::Result<()> { + let _app = service::test::init_service(Application::new().test()).await; + + let td = TempDir::new()?; + let sub = td.path().join("sub"); + fs::create_dir(&sub)?; + + // Fija CARGO_MANIFEST_DIR para que "sub" se resuelva contra td.path() + let prev_manifest_dir = env::var_os("CARGO_MANIFEST_DIR"); + env::set_var("CARGO_MANIFEST_DIR", td.path()); + let res = util::resolve_absolute_dir("sub"); + // Restaura entorno. + match prev_manifest_dir { + Some(v) => env::set_var("CARGO_MANIFEST_DIR", v), + None => env::remove_var("CARGO_MANIFEST_DIR"), + } + + assert_eq!(res?, std::fs::canonicalize(&sub)?); + Ok(()) + } + #[pagetop::test] async fn error_not_a_directory() -> io::Result<()> { let _app = service::test::init_service(Application::new().test()).await; @@ -30,7 +51,7 @@ mod unix { let file = td.path().join("foo.txt"); fs::write(&file, b"data")?; - let err = util::absolute_dir(td.path(), "foo.txt").unwrap_err(); + let err = util::resolve_absolute_dir(&file).unwrap_err(); assert_eq!(err.kind(), io::ErrorKind::InvalidInput); Ok(()) } @@ -46,15 +67,36 @@ mod windows { // C:\Users\...\Temp\... let td = TempDir::new()?; - let root = td.path(); - let sub = root.join("sub"); + let sub = td.path().join("sub"); fs::create_dir(&sub)?; - let abs = util::absolute_dir(root, sub.as_path())?; + let abs = util::resolve_absolute_dir(&sub)?; assert_eq!(abs, std::fs::canonicalize(&sub)?); Ok(()) } + #[pagetop::test] + async fn ok_relative_dir_with_manifest() -> io::Result<()> { + let _app = service::test::init_service(Application::new().test()).await; + + let td = TempDir::new()?; + let sub = td.path().join("sub"); + fs::create_dir(&sub)?; + + // Fija CARGO_MANIFEST_DIR para que "sub" se resuelva contra td.path() + let prev_manifest_dir = env::var_os("CARGO_MANIFEST_DIR"); + env::set_var("CARGO_MANIFEST_DIR", td.path()); + let res = util::resolve_absolute_dir("sub"); + // Restaura entorno. + match prev_manifest_dir { + Some(v) => env::set_var("CARGO_MANIFEST_DIR", v), + None => env::remove_var("CARGO_MANIFEST_DIR"), + } + + assert_eq!(res?, std::fs::canonicalize(&sub)?); + Ok(()) + } + #[pagetop::test] async fn error_not_a_directory() -> io::Result<()> { let _app = service::test::init_service(Application::new().test()).await; @@ -63,7 +105,7 @@ mod windows { let file = td.path().join("foo.txt"); fs::write(&file, b"data")?; - let err = util::absolute_dir(td.path(), "foo.txt").unwrap_err(); + let err = util::resolve_absolute_dir(&file).unwrap_err(); assert_eq!(err.kind(), io::ErrorKind::InvalidInput); Ok(()) }