diff --git a/.cargo/cliff.toml b/.cargo/cliff.toml index cbbb57b..ff362c4 100644 --- a/.cargo/cliff.toml +++ b/.cargo/cliff.toml @@ -21,16 +21,15 @@ body = """ {% else %} ## Pendiente de publicación {% endif %}\ +{% set base = "https://git.cillero.es/manuelcillero/pagetop" %}\ {% for group, commits in commits | group_by(attribute="group") %} ### {{ group | upper_first }} - {% for commit in commits %} {%- set msg = commit.message | split(pat="\n") | first | replace(from="✨ ", to="") | replace(from="🐛 ", to="") - | replace(from="🚑 ", to="") | replace(from="⬆️ ", to="") | replace(from="🚧 ", to="") | replace(from="♻️ ", to="") @@ -41,8 +40,8 @@ body = """ | replace(from="📝 ", to="") | replace(from="💡 ", to="") -%} - -- {{ msg | trim }} {% if commit.author.name != "Manuel Cillero" %} - {{ commit.author.name }}{% endif %} +{% set sha7 = commit.id | truncate(length=7, end="") %} +- {{ msg | trim }} ([{{ sha7 }}]({{ base }}/commit/{{ commit.id }}){% if commit.author.name != "Manuel Cillero" %} - {{ commit.author.name }}{% endif %}) {% endfor %}{% endfor %} """ @@ -55,7 +54,6 @@ sort_commits = "oldest" commit_parsers = [ { message = "^✨", group = "Añadido" }, { message = "^🐛", group = "Corregido" }, - { message = "^🚑", group = "Corregido" }, { message = "^🚧", group = "Cambiado" }, { message = "^♻️", group = "Cambiado" }, { message = "^✏️", group = "Cambiado" }, diff --git a/.cargo/release.toml b/.cargo/release.toml index 68f7a9c..9c07379 100644 --- a/.cargo/release.toml +++ b/.cargo/release.toml @@ -1,7 +1,7 @@ # release.toml # Etiqueta por crate: `pagetop-macros-v0.2.0` -tag-prefix = "{{crate_name}}-" +tag-prefix = "{{crate_name}}-v" # Confirmaciones firmadas (no requeridas) sign-commit = false @@ -13,13 +13,33 @@ push = true # Publica en crates.io (puedes desactivarlo para pruebas) publish = true +# Actualiza todos los dependientes internos +update-dependencies = true + # Solo permite publicar estos crates (los que forman parte del workspace) allow-branch = ["main"] consolidate-commits = false +consolidate-pushes = true # Mensaje personalizado para el commit de versión pre-release-commit-message = "🔖 Prepara publicación de {{crate_name}} {{version}}" -pre-release-hook = [ - "sh", "-c", "ROOT=$(git rev-parse --show-toplevel) && \"$ROOT/tools/changelog.sh\" {{crate_name}} {{version}} --stage" +[workspace] +# Lista de crates que se pueden publicar dentro del workspace +# Puedes añadir extensiones más adelante +allow-publish = [ + "pagetop", + "pagetop-build", + "pagetop-macros" +] + +# Opcional: ordena la publicación de dependencias internas +publish-order = [ + "pagetop-build", + "pagetop-macros", + "pagetop" +] + +pre-release-hook = [ + "./tools/changelog.sh", "{{crate_name}}", "{{version}}", "--stage" ] diff --git a/CHANGELOG.md b/CHANGELOG.md deleted file mode 100644 index 9e18714..0000000 --- a/CHANGELOG.md +++ /dev/null @@ -1,39 +0,0 @@ -# CHANGELOG - -Este archivo documenta los cambios más relevantes realizados en cada versión. El formato está basado -en [Keep a Changelog](https://keepachangelog.com/es-ES/1.0.0/), y las versiones se numeran siguiendo -las reglas del [Versionado Semántico](https://semver.org/lang/es/). - -Resume la evolución del proyecto para usuarios y colaboradores, destacando nuevas funcionalidades, -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 944027d..1d32d89 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -225,6 +225,18 @@ dependencies = [ "syn", ] +[[package]] +name = "actix-web-static-files" +version = "4.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adf6d1ef6d7a60e084f9e0595e2a5234abda14e76c105ecf8e2d0e8800c41a1f" +dependencies = [ + "actix-web", + "derive_more 0.99.20", + "futures-util", + "static-files", +] + [[package]] name = "addr2line" version = "0.24.2" @@ -646,9 +658,9 @@ dependencies = [ [[package]] name = "crc32fast" -version = "1.5.0" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" dependencies = [ "cfg-if", ] @@ -1064,9 +1076,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.3.27" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0beca50380b1fc32983fc1cb4587bfa4bb9e78fc259aad4a0032d2080309222d" +checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" dependencies = [ "bytes", "fnv", @@ -1557,11 +1569,12 @@ checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" [[package]] name = "pagetop" -version = "0.3.0" +version = "0.0.61" dependencies = [ "actix-files", "actix-session", "actix-web", + "actix-web-static-files", "chrono", "colored", "concat-string", @@ -1571,10 +1584,10 @@ dependencies = [ "itoa", "pagetop-build", "pagetop-macros", - "pagetop-statics", "parking_lot", "pastey", "serde", + "static-files", "substring", "tempfile", "terminal_size", @@ -1587,15 +1600,15 @@ dependencies = [ [[package]] name = "pagetop-build" -version = "0.3.0" +version = "0.1.0" dependencies = [ "grass", - "pagetop-statics", + "static-files", ] [[package]] name = "pagetop-macros" -version = "0.1.1" +version = "0.0.18" dependencies = [ "proc-macro2", "proc-macro2-diagnostics", @@ -1603,18 +1616,6 @@ dependencies = [ "syn", ] -[[package]] -name = "pagetop-statics" -version = "0.1.1" -dependencies = [ - "actix-web", - "change-detection", - "derive_more 0.99.20", - "futures-util", - "mime_guess", - "path-slash", -] - [[package]] name = "parking_lot" version = "0.12.4" @@ -2177,6 +2178,17 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" +[[package]] +name = "static-files" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9c425c07353535ef55b45420f5a8b0a397cd9bc3d7e5236497ca0d90604aa9b" +dependencies = [ + "change-detection", + "mime_guess", + "path-slash", +] + [[package]] name = "strsim" version = "0.11.1" diff --git a/Cargo.toml b/Cargo.toml index 8ccd69e..dcf191d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,12 +1,12 @@ [package] name = "pagetop" -version = "0.3.0" +version = "0.0.61" edition = "2021" description = """ Un entorno de desarrollo para crear soluciones web modulares, extensibles y configurables. """ -categories = ["web-programming::http-server"] +categories = ["web-programming", "gui", "development-tools", "asynchronous"] keywords = ["pagetop", "web", "framework", "frontend", "ssr"] repository.workspace = true @@ -34,14 +34,15 @@ tracing-actix-web = "0.7.19" fluent-templates = "0.13.0" unic-langid = { version = "0.9.6", features = ["macros"] } -actix-web = { workspace = true, default-features = true } +actix-web = "4.11.0" actix-session = { version = "0.10.1", features = ["cookie-session"] } actix-web-files = { package = "actix-files", version = "0.6.6" } +actix-web-static-files = "4.0.1" +static-files.workspace = true serde = { version = "1.0", features = ["derive"] } pagetop-macros.workspace = true -pagetop-statics.workspace = true [features] default = [] @@ -59,7 +60,6 @@ resolver = "2" members = [ "helpers/pagetop-build", "helpers/pagetop-macros", - "helpers/pagetop-statics", ] [workspace.package] @@ -69,8 +69,7 @@ license = "MIT OR Apache-2.0" authors = ["Manuel Cillero "] [workspace.dependencies] -actix-web = { version = "4.11.0", default-features = false } +static-files = "0.2.5" -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" } +pagetop-build = { version = "0.1", path = "helpers/pagetop-build" } +pagetop-macros = { version = "0.0", path = "helpers/pagetop-macros" } diff --git a/README.md b/README.md index e7fab94..87f8b8d 100644 --- a/README.md +++ b/README.md @@ -6,12 +6,11 @@

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) +[![Licencia](https://img.shields.io/badge/license-MIT%2FApache-blue.svg?label=Licencia&style=for-the-badge)](#-license) [![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 @@ -33,7 +32,7 @@ según las necesidades de cada proyecto, incluyendo: La aplicación más sencilla de `PageTop` se ve así: -```rust,no_run +```rust use pagetop::prelude::*; #[pagetop::main] @@ -47,7 +46,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,no_run +```rust use pagetop::prelude::*; struct HelloWorld; @@ -84,14 +83,9 @@ 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)**, - 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. + permite incluir fácilmente archivos estáticos o archivos SCSS compilados directamente en el + binario de las aplicaciones `PageTop`. * **[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 deleted file mode 100644 index 3e3801e..0000000 --- a/helpers/pagetop-build/CHANGELOG.md +++ /dev/null @@ -1,39 +0,0 @@ -# CHANGELOG - -Este archivo documenta los cambios más relevantes realizados en cada versión. El formato está basado -en [Keep a Changelog](https://keepachangelog.com/es-ES/1.0.0/), y las versiones se numeran siguiendo -las reglas del [Versionado Semántico](https://semver.org/lang/es/). - -Resume la evolución del proyecto para usuarios y colaboradores, destacando nuevas funcionalidades, -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 - -## 0.1.0 (2025-08-05) - -- Versión inicial diff --git a/helpers/pagetop-build/Cargo.toml b/helpers/pagetop-build/Cargo.toml index e0f5ef7..7bf6048 100644 --- a/helpers/pagetop-build/Cargo.toml +++ b/helpers/pagetop-build/Cargo.toml @@ -1,13 +1,13 @@ [package] name = "pagetop-build" -version = "0.3.0" +version = "0.1.0" edition = "2021" description = """ Prepara un conjunto de archivos estáticos o archivos SCSS compilados para ser incluidos en el binario de un proyecto PageTop. """ -categories = ["development-tools::build-utils"] +categories = ["development-tools::build-utils", "web-programming"] keywords = ["pagetop", "build", "assets", "resources", "static"] repository.workspace = true @@ -17,4 +17,4 @@ authors.workspace = true [dependencies] grass = "0.13.4" -pagetop-statics.workspace = true +static-files.workspace = true diff --git a/helpers/pagetop-build/README.md b/helpers/pagetop-build/README.md index 80d6bba..27c9814 100644 --- a/helpers/pagetop-build/README.md +++ b/helpers/pagetop-build/README.md @@ -4,7 +4,7 @@

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) +[![Licencia](https://img.shields.io/badge/license-MIT%2FApache-blue.svg?label=Licencia&style=for-the-badge)](#-license) [![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) @@ -18,99 +18,6 @@ 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 c6e7236..90898dd 100644 --- a/helpers/pagetop-build/src/lib.rs +++ b/helpers/pagetop-build/src/lib.rs @@ -1,129 +1,136 @@ -/*! -
- -

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"); - } -} -``` -*/ +//!
+//! +//!

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)](#-license) +//! [![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); +//! ``` #![doc( html_favicon_url = "https://git.cillero.es/manuelcillero/pagetop/raw/branch/main/static/favicon.ico" )] use grass::{from_path, Options, OutputStyle}; -use pagetop_statics::{resource_dir, ResourceDir}; +use static_files::{resource_dir, ResourceDir}; 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. +/// Prepara un conjunto de recursos para ser incluidos en el binario del proyecto utilizando +/// [static_files](https://docs.rs/static-files/). pub struct StaticFilesBundle { resource_dir: ResourceDir, } @@ -156,19 +163,8 @@ impl StaticFilesBundle { /// .build() /// } /// ``` - 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); + pub fn from_dir(dir: impl AsRef, filter: Option bool>) -> Self { + let mut resource_dir = resource_dir(dir.as_ref()); // 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 deleted file mode 100644 index b61471a..0000000 --- a/helpers/pagetop-macros/CHANGELOG.md +++ /dev/null @@ -1,24 +0,0 @@ -# CHANGELOG - -Este archivo documenta los cambios más relevantes realizados en cada versión. El formato está basado -en [Keep a Changelog](https://keepachangelog.com/es-ES/1.0.0/), y las versiones se numeran siguiendo -las reglas del [Versionado Semántico](https://semver.org/lang/es/). - -Resume la evolución del proyecto para usuarios y colaboradores, destacando nuevas funcionalidades, -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 d5ceb1b..27325b8 100644 --- a/helpers/pagetop-macros/Cargo.toml +++ b/helpers/pagetop-macros/Cargo.toml @@ -1,12 +1,12 @@ [package] name = "pagetop-macros" -version = "0.1.1" +version = "0.0.18" edition = "2021" description = """ Una colección de macros que mejoran la experiencia de desarrollo con PageTop. """ -categories = ["development-tools::procedural-macro-helpers"] +categories = ["development-tools::procedural-macro-helpers", "web-programming"] keywords = ["pagetop", "macros", "proc-macros", "codegen"] repository.workspace = true @@ -21,4 +21,4 @@ proc-macro = true proc-macro2 = "1.0.95" proc-macro2-diagnostics = { version = "0.10.1", default-features = false } quote = "1.0.40" -syn = { version = "2.0.104", features = ["full", "extra-traits"] } +syn = { version = "2.0.104", features = ["full"] } diff --git a/helpers/pagetop-macros/README.md b/helpers/pagetop-macros/README.md index e58d24c..826698a 100644 --- a/helpers/pagetop-macros/README.md +++ b/helpers/pagetop-macros/README.md @@ -4,23 +4,16 @@

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) +[![Licencia](https://img.shields.io/badge/license-MIT%2FApache-blue.svg?label=Licencia&style=for-the-badge)](#-license) [![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 +## Descripción general -[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) +Entre sus macros se incluye 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 @@ -28,6 +21,12 @@ Esta librería 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`. +## 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 6421ca6..fedd682 100644 --- a/helpers/pagetop-macros/src/lib.rs +++ b/helpers/pagetop-macros/src/lib.rs @@ -1,34 +1,21 @@ -/*! -
- -

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`. -*/ +//!
+//! +//!

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)](#-license) +//! [![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. #![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 deleted file mode 100644 index 811a77f..0000000 --- a/helpers/pagetop-statics/CHANGELOG.md +++ /dev/null @@ -1,21 +0,0 @@ -# CHANGELOG - -Este archivo documenta los cambios más relevantes realizados en cada versión. El formato está basado -en [Keep a Changelog](https://keepachangelog.com/es-ES/1.0.0/), y las versiones se numeran siguiendo -las reglas del [Versionado Semántico](https://semver.org/lang/es/). - -Resume la evolución del proyecto para usuarios y colaboradores, destacando nuevas funcionalidades, -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 - -- Versión inicial diff --git a/helpers/pagetop-statics/Cargo.toml b/helpers/pagetop-statics/Cargo.toml deleted file mode 100644 index 2afe373..0000000 --- a/helpers/pagetop-statics/Cargo.toml +++ /dev/null @@ -1,33 +0,0 @@ -[package] -name = "pagetop-statics" -version = "0.1.1" -edition = "2021" - -description = """ - Librería para automatizar la recopilación de recursos estáticos en PageTop. -""" -categories = ["development-tools::build-utils"] -keywords = ["pagetop", "build", "static", "resources", "file"] - -repository.workspace = true -homepage.workspace = true -license.workspace = true -authors.workspace = true - -[features] -default = ["change-detection"] -sort = [] - -[dependencies] -change-detection = { version = "1.2", optional = true } -mime_guess = "2.0" -path-slash = "0.1" - -actix-web.workspace = true -derive_more = "0.99.17" -futures-util = { version = "0.3", default-features = false, features = ["std"] } - -[build-dependencies] -change-detection = { version = "1.2", optional = true } -mime_guess = "2.0" -path-slash = "0.1" diff --git a/helpers/pagetop-statics/LICENSE-APACHE b/helpers/pagetop-statics/LICENSE-APACHE deleted file mode 100644 index 263ddac..0000000 --- a/helpers/pagetop-statics/LICENSE-APACHE +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright 2022 Manuel Cillero - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. \ No newline at end of file diff --git a/helpers/pagetop-statics/LICENSE-MIT b/helpers/pagetop-statics/LICENSE-MIT deleted file mode 100644 index cd8af3d..0000000 --- a/helpers/pagetop-statics/LICENSE-MIT +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2022 Manuel Cillero - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. \ No newline at end of file diff --git a/helpers/pagetop-statics/README.md b/helpers/pagetop-statics/README.md deleted file mode 100644 index 92999c0..0000000 --- a/helpers/pagetop-statics/README.md +++ /dev/null @@ -1,53 +0,0 @@ -
- -

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`. - - -# 🚧 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 - -El código está disponible bajo una doble licencia: - - * **Licencia MIT** - ([LICENSE-MIT](LICENSE-MIT) o también https://opensource.org/licenses/MIT) - - * **Licencia Apache, Versión 2.0** - ([LICENSE-APACHE](LICENSE-APACHE) o también https://www.apache.org/licenses/LICENSE-2.0) - -Puedes elegir la licencia que prefieras. Este enfoque de doble licencia es el estándar de facto en -el ecosistema Rust. diff --git a/helpers/pagetop-statics/build.rs b/helpers/pagetop-statics/build.rs deleted file mode 100644 index fcd009c..0000000 --- a/helpers/pagetop-statics/build.rs +++ /dev/null @@ -1,43 +0,0 @@ -#![allow(dead_code)] -#![doc(html_no_source)] -#![allow(clippy::needless_doctest_main)] - -mod resource { - include!("src/resource.rs"); -} -use resource::generate_resources_mapping; -mod resource_dir { - include!("src/resource_dir.rs"); -} -use resource_dir::resource_dir; -mod sets { - include!("src/sets.rs"); -} -use sets::{generate_resources_sets, SplitByCount}; - -use std::{env, path::Path}; - -fn main() -> std::io::Result<()> { - resource_dir("./tests").build_test()?; - - let out_dir = env::var("OUT_DIR").unwrap(); - - generate_resources_mapping( - "./tests", - None, - Path::new(&out_dir).join("generated_mapping.rs"), - "pagetop_statics", - )?; - - generate_resources_sets( - "./tests", - None, - Path::new(&out_dir).join("generated_sets.rs"), - "sets", - "generate", - &mut SplitByCount::new(2), - "pagetop_statics", - )?; - - Ok(()) -} diff --git a/helpers/pagetop-statics/src/lib.rs b/helpers/pagetop-statics/src/lib.rs deleted file mode 100644 index dab50d9..0000000 --- a/helpers/pagetop-statics/src/lib.rs +++ /dev/null @@ -1,53 +0,0 @@ -/*! -
- -

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( - 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; -pub use resource::Resource as StaticResource; - -mod resource_dir; -pub use resource_dir::{resource_dir, ResourceDir}; - -mod resource_files; -pub use resource_files::{ResourceFiles, UriSegmentError}; - -/// Support for module based generations. Use it for large data sets (more than 128 Mb). -pub mod sets; diff --git a/helpers/pagetop-statics/src/resource.rs b/helpers/pagetop-statics/src/resource.rs deleted file mode 100644 index 0b81969..0000000 --- a/helpers/pagetop-statics/src/resource.rs +++ /dev/null @@ -1,249 +0,0 @@ -use path_slash::PathExt; -use std::{ - fs::{self, File, Metadata}, - io::{self, Write}, - path::{Path, PathBuf}, - time::SystemTime, -}; - -/// Static files resource. -pub struct Resource { - pub data: &'static [u8], - pub modified: u64, - pub mime_type: &'static str, -} - -/// Used internally in generated functions. -#[inline] -pub fn new_resource(data: &'static [u8], modified: u64, mime_type: &'static str) -> Resource { - Resource { - data, - modified, - mime_type, - } -} - -pub(crate) const DEFAULT_VARIABLE_NAME: &str = "r"; - -/// Generate resources for `project_dir` using `filter`. -/// Result saved in `generated_filename` and function named as `fn_name`. -/// -/// in `build.rs`: -/// ```rust -/// use std::{env, path::Path}; -/// use pagetop_statics::resource::generate_resources; -/// -/// fn main() { -/// let out_dir = env::var("OUT_DIR").unwrap(); -/// let generated_filename = Path::new(&out_dir).join("generated.rs"); -/// generate_resources("./tests", None, generated_filename, "generate", "pagetop_statics").unwrap(); -/// } -/// ``` -/// -/// in `main.rs`: -/// ```rust -/// include!(concat!(env!("OUT_DIR"), "/generated.rs")); -/// -/// fn main() { -/// let generated_file = generate(); -/// -/// assert_eq!(generated_file.len(), 4); -/// } -/// ``` -pub fn generate_resources, G: AsRef>( - project_dir: P, - filter: Option bool>, - generated_filename: G, - fn_name: &str, - crate_name: &str, -) -> io::Result<()> { - let resources = collect_resources(&project_dir, filter)?; - - let mut f = File::create(&generated_filename)?; - - generate_function_header(&mut f, fn_name, crate_name)?; - generate_uses(&mut f, crate_name)?; - - generate_variable_header(&mut f, DEFAULT_VARIABLE_NAME)?; - generate_resource_inserts(&mut f, &project_dir, DEFAULT_VARIABLE_NAME, resources)?; - generate_variable_return(&mut f, DEFAULT_VARIABLE_NAME)?; - - generate_function_end(&mut f)?; - - Ok(()) -} - -/// Generate resource mapping for `project_dir` using `filter`. -/// Result saved in `generated_filename` as anonymous block which returns HashMap<&'static str, Resource>. -/// -/// in `build.rs`: -/// ```rust -/// -/// use std::{env, path::Path}; -/// use pagetop_statics::resource::generate_resources_mapping; -/// -/// fn main() { -/// let out_dir = env::var("OUT_DIR").unwrap(); -/// let generated_filename = Path::new(&out_dir).join("generated_mapping.rs"); -/// generate_resources_mapping("./tests", None, generated_filename, "pagetop_statics").unwrap(); -/// } -/// ``` -/// -/// in `main.rs`: -/// ```rust -/// use std::collections::HashMap; -/// -/// use pagetop_statics::StaticResource; -/// -/// fn generate_mapping() -> HashMap<&'static str, StaticResource> { -/// include!(concat!(env!("OUT_DIR"), "/generated_mapping.rs")) -/// } -/// -/// fn main() { -/// let generated_file = generate_mapping(); -/// -/// assert_eq!(generated_file.len(), 4); -/// -/// } -/// ``` -pub fn generate_resources_mapping, G: AsRef>( - project_dir: P, - filter: Option bool>, - generated_filename: G, - crate_name: &str, -) -> io::Result<()> { - let resources = collect_resources(&project_dir, filter)?; - - let mut f = File::create(&generated_filename)?; - writeln!(f, "{{")?; - - generate_uses(&mut f, crate_name)?; - - generate_variable_header(&mut f, DEFAULT_VARIABLE_NAME)?; - - generate_resource_inserts(&mut f, &project_dir, DEFAULT_VARIABLE_NAME, resources)?; - - generate_variable_return(&mut f, DEFAULT_VARIABLE_NAME)?; - - writeln!(f, "}}")?; - Ok(()) -} - -#[cfg(not(feature = "sort"))] -pub(crate) fn collect_resources>( - path: P, - filter: Option bool>, -) -> io::Result> { - collect_resources_nested(path, filter) -} - -#[cfg(feature = "sort")] -pub(crate) fn collect_resources>( - path: P, - filter: Option bool>, -) -> io::Result> { - let mut resources = collect_resources_nested(path, filter)?; - resources.sort_by(|a, b| a.0.cmp(&b.0)); - Ok(resources) -} - -#[inline] -fn collect_resources_nested>( - path: P, - filter: Option bool>, -) -> io::Result> { - let mut result = vec![]; - - for entry in fs::read_dir(&path)? { - let entry = entry?; - let path = entry.path(); - - if let Some(ref filter) = filter { - if !filter(path.as_ref()) { - continue; - } - } - - if path.is_dir() { - let nested = collect_resources(path, filter)?; - result.extend(nested); - } else { - result.push((path, entry.metadata()?)); - } - } - - Ok(result) -} - -pub(crate) fn generate_resource_inserts, W: Write>( - f: &mut W, - project_dir: &P, - variable_name: &str, - resources: Vec<(PathBuf, Metadata)>, -) -> io::Result<()> { - for resource in &resources { - generate_resource_insert(f, project_dir, variable_name, resource)?; - } - Ok(()) -} - -#[allow(clippy::unnecessary_debug_formatting)] -pub(crate) fn generate_resource_insert, W: Write>( - f: &mut W, - project_dir: &P, - variable_name: &str, - resource: &(PathBuf, Metadata), -) -> io::Result<()> { - let (path, metadata) = resource; - let abs_path = path.canonicalize()?; - let key_path = path.strip_prefix(project_dir).unwrap().to_slash().unwrap(); - - let modified = if let Ok(Ok(modified)) = metadata - .modified() - .map(|x| x.duration_since(SystemTime::UNIX_EPOCH)) - { - modified.as_secs() - } else { - 0 - }; - let mime_type = mime_guess::MimeGuess::from_path(path).first_or_octet_stream(); - writeln!( - f, - "{}.insert({:?},n(i!({:?}),{:?},{:?}));", - variable_name, &key_path, &abs_path, modified, &mime_type, - ) -} - -pub(crate) fn generate_function_header( - f: &mut F, - fn_name: &str, - crate_name: &str, -) -> io::Result<()> { - writeln!( - f, - "#[allow(clippy::unreadable_literal)] pub fn {fn_name}() -> ::std::collections::HashMap<&'static str, ::{crate_name}::StaticResource> {{", - ) -} - -pub(crate) fn generate_function_end(f: &mut F) -> io::Result<()> { - writeln!(f, "}}") -} - -pub(crate) fn generate_uses(f: &mut F, crate_name: &str) -> io::Result<()> { - writeln!( - f, - "use ::{crate_name}::resource::new_resource as n; -use ::std::include_bytes as i;", - ) -} - -pub(crate) fn generate_variable_header(f: &mut F, variable_name: &str) -> io::Result<()> { - writeln!( - f, - "let mut {variable_name} = ::std::collections::HashMap::new();", - ) -} - -pub(crate) fn generate_variable_return(f: &mut F, variable_name: &str) -> io::Result<()> { - writeln!(f, "{variable_name}") -} diff --git a/helpers/pagetop-statics/src/resource_dir.rs b/helpers/pagetop-statics/src/resource_dir.rs deleted file mode 100644 index 805e1ed..0000000 --- a/helpers/pagetop-statics/src/resource_dir.rs +++ /dev/null @@ -1,118 +0,0 @@ -use super::sets::{generate_resources_sets, SplitByCount}; -use std::{ - env, io, - path::{Path, PathBuf}, -}; - -/// Generate resources for `resource_dir`. -/// -/// ```rust,no_run -/// // Generate resources for ./tests dir with file name generated.rs -/// // stored in path defined by OUT_DIR environment variable. -/// // Function name is 'generate' -/// use pagetop_statics::resource_dir; -/// -/// resource_dir("./tests").build().unwrap(); -/// ``` -pub fn resource_dir>(resource_dir: P) -> ResourceDir { - ResourceDir { - resource_dir: resource_dir.as_ref().into(), - ..Default::default() - } -} - -/// Resource dir. -/// -/// A builder structure allows to change default settings for: -/// - file filter -/// - generated file name -/// - generated function name -#[derive(Default)] -pub struct ResourceDir { - pub(crate) resource_dir: PathBuf, - pub(crate) filter: Option bool>, - pub(crate) generated_filename: Option, - pub(crate) generated_fn: Option, - pub(crate) module_name: Option, - pub(crate) count_per_module: Option, -} - -pub const DEFAULT_MODULE_NAME: &str = "sets"; -pub const DEFAULT_COUNT_PER_MODULE: usize = 256; - -impl ResourceDir { - /// Generates resources for current configuration. - pub fn build(self) -> io::Result<()> { - self.internal_build("pagetop") - } - - /// Generates resources for testing current configuration. - #[allow(dead_code)] - pub(crate) fn build_test(self) -> io::Result<()> { - self.internal_build("pagetop_statics") - } - - fn internal_build(self, crate_name: &str) -> io::Result<()> { - let generated_filename = self.generated_filename.unwrap_or_else(|| { - let out_dir = env::var("OUT_DIR").unwrap(); - - Path::new(&out_dir).join("generated.rs") - }); - let generated_fn = self.generated_fn.unwrap_or_else(|| "generate".into()); - - let module_name = self - .module_name - .unwrap_or_else(|| format!("{}_{}", &generated_fn, DEFAULT_MODULE_NAME)); - - let count_per_module = self.count_per_module.unwrap_or(DEFAULT_COUNT_PER_MODULE); - - generate_resources_sets( - &self.resource_dir, - self.filter, - &generated_filename, - module_name.as_str(), - &generated_fn, - &mut SplitByCount::new(count_per_module), - crate_name, - ) - } - - /// Sets the file filter. - pub fn with_filter(&mut self, filter: fn(p: &Path) -> bool) -> &mut Self { - self.filter = Some(filter); - self - } - - /// Sets the generated filename. - pub fn with_generated_filename>(&mut self, generated_filename: P) -> &mut Self { - self.generated_filename = Some(generated_filename.as_ref().into()); - self - } - - /// Sets the generated function name. - pub fn with_generated_fn(&mut self, generated_fn: S) -> &mut Self - where - S: Into, - { - self.generated_fn = Some(generated_fn.into()); - self - } - - /// Sets the generated module name. - /// - /// Default value is based on generated function name and the suffix "sets". - /// Generated module would be overriden by each call. - pub fn with_module_name(&mut self, module_name: S) -> &mut Self - where - S: Into, - { - self.module_name = Some(module_name.into()); - self - } - - /// Sets maximal count of files per module. - pub fn with_count_per_module(&mut self, count_per_module: usize) -> &mut Self { - self.count_per_module = Some(count_per_module); - self - } -} diff --git a/helpers/pagetop-statics/src/resource_files.rs b/helpers/pagetop-statics/src/resource_files.rs deleted file mode 100644 index b487bca..0000000 --- a/helpers/pagetop-statics/src/resource_files.rs +++ /dev/null @@ -1,396 +0,0 @@ -use super::resource::Resource; -use actix_web::{ - dev::{ - always_ready, AppService, HttpServiceFactory, ResourceDef, Service, ServiceFactory, - ServiceRequest, ServiceResponse, - }, - error::Error, - guard::{Guard, GuardContext}, - http::{ - header::{self, ContentType}, - Method, StatusCode, - }, - HttpMessage, HttpRequest, HttpResponse, ResponseError, -}; -use derive_more::{Deref, Display, Error}; -use futures_util::future::{ok, FutureExt, LocalBoxFuture, Ready}; -use std::{collections::HashMap, ops::Deref, rc::Rc}; - -/// Static resource files handling -/// -/// `ResourceFiles` service must be registered with `App::service` method. -/// -/// ```rust -/// use std::collections::HashMap; -/// -/// use actix_web::App; -/// -/// fn main() { -/// // serve root directory with default options: -/// // - resolve index.html -/// let files: HashMap<&'static str, pagetop_statics::StaticResource> = HashMap::new(); -/// let app = App::new() -/// .service(pagetop_statics::ResourceFiles::new("/", files)); -/// // or subpath with additional option to not resolve index.html -/// let files: HashMap<&'static str, pagetop_statics::StaticResource> = HashMap::new(); -/// let app = App::new() -/// .service(pagetop_statics::ResourceFiles::new("/imgs", files) -/// .do_not_resolve_defaults()); -/// } -/// ``` -#[allow(clippy::needless_doctest_main)] -pub struct ResourceFiles { - not_resolve_defaults: bool, - use_guard: bool, - not_found_resolves_to: Option, - inner: Rc, -} - -pub struct ResourceFilesInner { - path: String, - files: HashMap<&'static str, Resource>, -} - -const INDEX_HTML: &str = "index.html"; - -impl ResourceFiles { - pub fn new(path: &str, files: HashMap<&'static str, Resource>) -> Self { - let inner = ResourceFilesInner { - path: path.into(), - files, - }; - Self { - inner: Rc::new(inner), - not_resolve_defaults: false, - not_found_resolves_to: None, - use_guard: false, - } - } - - /// By default trying to resolve '.../' to '.../index.html' if it exists. - /// Turn off this resolution by calling this function. - pub fn do_not_resolve_defaults(mut self) -> Self { - self.not_resolve_defaults = true; - self - } - - /// Resolves not found references to this path. - /// - /// This can be useful for angular-like applications. - pub fn resolve_not_found_to(mut self, path: S) -> Self { - self.not_found_resolves_to = Some(path.to_string()); - self - } - - /// Resolves not found references to root path. - /// - /// This can be useful for angular-like applications. - pub fn resolve_not_found_to_root(self) -> Self { - self.resolve_not_found_to(INDEX_HTML) - } - - /// If this is called, we will use an [actix_web::guard::Guard] to check if this request should be handled. - /// If set to true, we skip using the handler for files that haven't been found, instead of sending 404s. - /// Would be ignored, if `resolve_not_found_to` or `resolve_not_found_to_root` is used. - /// - /// Can be useful if you want to share files on a (sub)path that's also used by a different route handler. - pub fn skip_handler_when_not_found(mut self) -> Self { - self.use_guard = true; - self - } - - fn select_guard(&self) -> Box { - if self.not_resolve_defaults { - Box::new(NotResolveDefaultsGuard::from(self)) - } else { - Box::new(ResolveDefaultsGuard::from(self)) - } - } -} - -impl Deref for ResourceFiles { - type Target = ResourceFilesInner; - - fn deref(&self) -> &Self::Target { - &self.inner - } -} - -struct NotResolveDefaultsGuard { - inner: Rc, -} - -impl Guard for NotResolveDefaultsGuard { - fn check(&self, ctx: &GuardContext<'_>) -> bool { - self.inner - .files - .contains_key(ctx.head().uri.path().trim_start_matches('/')) - } -} - -impl From<&ResourceFiles> for NotResolveDefaultsGuard { - fn from(files: &ResourceFiles) -> Self { - Self { - inner: files.inner.clone(), - } - } -} - -struct ResolveDefaultsGuard { - inner: Rc, -} - -impl Guard for ResolveDefaultsGuard { - fn check(&self, ctx: &GuardContext<'_>) -> bool { - let path = ctx.head().uri.path().trim_start_matches('/'); - self.inner.files.contains_key(path) - || ((path.is_empty() || path.ends_with('/')) - && self - .inner - .files - .contains_key((path.to_string() + INDEX_HTML).as_str())) - } -} - -impl From<&ResourceFiles> for ResolveDefaultsGuard { - fn from(files: &ResourceFiles) -> Self { - Self { - inner: files.inner.clone(), - } - } -} - -impl HttpServiceFactory for ResourceFiles { - fn register(self, config: &mut AppService) { - let prefix = self.path.trim_start_matches('/'); - let rdef = if config.is_root() { - ResourceDef::root_prefix(prefix) - } else { - ResourceDef::prefix(prefix) - }; - let guards = if self.use_guard && self.not_found_resolves_to.is_none() { - Some(vec![self.select_guard()]) - } else { - None - }; - config.register_service(rdef, guards, self, None); - } -} - -impl ServiceFactory for ResourceFiles { - type Config = (); - type Response = ServiceResponse; - type Error = Error; - type Service = ResourceFilesService; - type InitError = (); - type Future = LocalBoxFuture<'static, Result>; - - fn new_service(&self, _: ()) -> Self::Future { - ok(ResourceFilesService { - resolve_defaults: !self.not_resolve_defaults, - not_found_resolves_to: self.not_found_resolves_to.clone(), - inner: self.inner.clone(), - }) - .boxed_local() - } -} - -#[derive(Deref)] -pub struct ResourceFilesService { - resolve_defaults: bool, - not_found_resolves_to: Option, - #[deref] - inner: Rc, -} - -impl Service for ResourceFilesService { - type Response = ServiceResponse; - type Error = Error; - type Future = Ready>; - - always_ready!(); - - fn call(&self, req: ServiceRequest) -> Self::Future { - match *req.method() { - Method::HEAD | Method::GET => (), - _ => { - return ok(ServiceResponse::new( - req.into_parts().0, - HttpResponse::MethodNotAllowed() - .insert_header(ContentType::plaintext()) - .insert_header((header::ALLOW, "GET, HEAD")) - .body("This resource only supports GET and HEAD."), - )); - } - } - - let req_path = req.match_info().unprocessed(); - let mut item = self.files.get(req_path); - - if item.is_none() - && self.resolve_defaults - && (req_path.is_empty() || req_path.ends_with('/')) - { - let index_req_path = req_path.to_string() + INDEX_HTML; - item = self.files.get(index_req_path.trim_start_matches('/')); - } - - let (req, response) = if item.is_some() { - let (req, _) = req.into_parts(); - let response = respond_to(&req, item); - (req, response) - } else { - let real_path = match get_pathbuf(req_path) { - Ok(item) => item, - Err(e) => return ok(req.error_response(e)), - }; - - let (req, _) = req.into_parts(); - - let mut item = self.files.get(real_path.as_str()); - - if item.is_none() && self.not_found_resolves_to.is_some() { - let not_found_path = self.not_found_resolves_to.as_ref().unwrap(); - item = self.files.get(not_found_path.as_str()); - } - - let response = respond_to(&req, item); - (req, response) - }; - - ok(ServiceResponse::new(req, response)) - } -} - -fn respond_to(req: &HttpRequest, item: Option<&Resource>) -> HttpResponse { - if let Some(file) = item { - let etag = Some(header::EntityTag::new_strong(format!( - "{:x}:{:x}", - file.data.len(), - file.modified - ))); - - let precondition_failed = !any_match(etag.as_ref(), req); - - let not_modified = !none_match(etag.as_ref(), req); - - let mut resp = HttpResponse::build(StatusCode::OK); - resp.insert_header((header::CONTENT_TYPE, file.mime_type)); - - if let Some(etag) = etag { - resp.insert_header(header::ETag(etag)); - } - - if precondition_failed { - return resp.status(StatusCode::PRECONDITION_FAILED).finish(); - } else if not_modified { - return resp.status(StatusCode::NOT_MODIFIED).finish(); - } - - resp.body(file.data) - } else { - HttpResponse::NotFound().body("Not found") - } -} - -/// Returns true if `req` has no `If-Match` header or one which matches `etag`. -fn any_match(etag: Option<&header::EntityTag>, req: &HttpRequest) -> bool { - match req.get_header::() { - None | Some(header::IfMatch::Any) => true, - Some(header::IfMatch::Items(ref items)) => { - if let Some(some_etag) = etag { - for item in items { - if item.strong_eq(some_etag) { - return true; - } - } - } - false - } - } -} - -/// Returns true if `req` doesn't have an `If-None-Match` header matching `req`. -fn none_match(etag: Option<&header::EntityTag>, req: &HttpRequest) -> bool { - match req.get_header::() { - Some(header::IfNoneMatch::Any) => false, - Some(header::IfNoneMatch::Items(ref items)) => { - if let Some(some_etag) = etag { - for item in items { - if item.weak_eq(some_etag) { - return false; - } - } - } - true - } - None => true, - } -} - -/// Error type representing invalid characters in a URI path segment. -/// -/// This enum is used to report specific formatting errors in individual segments of a URI path, -/// such as starting, ending, or containing disallowed characters. Each variant wraps the offending -/// character that caused the error. -#[derive(Debug, PartialEq, Display, Error)] -pub enum UriSegmentError { - /// The segment started with the wrapped invalid character. - #[display(fmt = "The segment started with the wrapped invalid character")] - BadStart(#[error(not(source))] char), - - /// The segment contained the wrapped invalid character. - #[display(fmt = "The segment contained the wrapped invalid character")] - BadChar(#[error(not(source))] char), - - /// The segment ended with the wrapped invalid character. - #[display(fmt = "The segment ended with the wrapped invalid character")] - BadEnd(#[error(not(source))] char), -} - -#[cfg(test)] -mod tests_error_impl { - use super::*; - - fn assert_send_and_sync() {} - - #[test] - fn test_error_impl() { - // ensure backwards compatibility when migrating away from failure - assert_send_and_sync::(); - } -} - -/// Return `BadRequest` for `UriSegmentError` -impl ResponseError for UriSegmentError { - fn error_response(&self) -> HttpResponse { - HttpResponse::new(StatusCode::BAD_REQUEST) - } -} - -fn get_pathbuf(path: &str) -> Result { - let mut buf = Vec::new(); - for segment in path.split('/') { - if segment == ".." { - buf.pop(); - } else if segment.starts_with('.') { - return Err(UriSegmentError::BadStart('.')); - } else if segment.starts_with('*') { - return Err(UriSegmentError::BadStart('*')); - } else if segment.ends_with(':') { - return Err(UriSegmentError::BadEnd(':')); - } else if segment.ends_with('>') { - return Err(UriSegmentError::BadEnd('>')); - } else if segment.ends_with('<') { - return Err(UriSegmentError::BadEnd('<')); - } else if segment.is_empty() { - continue; - } else if cfg!(windows) && segment.contains('\\') { - return Err(UriSegmentError::BadChar('\\')); - } else { - buf.push(segment) - } - } - - Ok(buf.join("/")) -} diff --git a/helpers/pagetop-statics/src/sets.rs b/helpers/pagetop-statics/src/sets.rs deleted file mode 100644 index 1d9299d..0000000 --- a/helpers/pagetop-statics/src/sets.rs +++ /dev/null @@ -1,184 +0,0 @@ -use std::{ - fs::{self, File, Metadata}, - io::{self, Write}, - path::{Path, PathBuf}, -}; - -use super::resource::{ - collect_resources, generate_function_end, generate_function_header, generate_resource_insert, - generate_uses, generate_variable_header, generate_variable_return, DEFAULT_VARIABLE_NAME, -}; - -/// Defines the split strategie. -pub trait SetSplitStrategie { - /// Register next file from resources. - fn register(&mut self, path: &Path, metadata: &Metadata); - /// Determine, should we split modules now. - fn should_split(&self) -> bool; - /// Resets internal counters after split. - fn reset(&mut self); -} - -/// Split modules by files count. -pub struct SplitByCount { - current: usize, - max: usize, -} - -impl SplitByCount { - pub fn new(max: usize) -> Self { - Self { current: 0, max } - } -} - -impl SetSplitStrategie for SplitByCount { - fn register(&mut self, _path: &Path, _metadata: &Metadata) { - self.current += 1; - } - - fn should_split(&self) -> bool { - self.current >= self.max - } - - fn reset(&mut self) { - self.current = 0; - } -} - -/// Generate resources for `project_dir` using `filter` -/// breaking them into separate modules using `set_split_strategie` (recommended for large > 128 Mb setups). -/// -/// Result saved in module named `module_name`. It exports -/// only one function named `fn_name`. It is then exported from -/// `generated_filename`. `generated_filename` is also used to determine -/// the parent directory for the module. -/// -/// in `build.rs`: -/// ```rust -/// -/// use std::{env, path::Path}; -/// use pagetop_statics::sets::{generate_resources_sets, SplitByCount}; -/// -/// fn main() { -/// let out_dir = env::var("OUT_DIR").unwrap(); -/// let generated_filename = Path::new(&out_dir).join("generated_sets.rs"); -/// generate_resources_sets( -/// "./tests", -/// None, -/// generated_filename, -/// "sets", -/// "generate", -/// &mut SplitByCount::new(2), -/// "pagetop_statics", -/// ) -/// .unwrap(); -/// } -/// ``` -/// -/// in `main.rs`: -/// ```rust -/// include!(concat!(env!("OUT_DIR"), "/generated_sets.rs")); -/// -/// fn main() { -/// let generated_file = generate(); -/// -/// assert_eq!(generated_file.len(), 4); -/// -/// } -/// ``` -pub fn generate_resources_sets( - project_dir: P, - filter: Option bool>, - generated_filename: G, - module_name: &str, - fn_name: &str, - set_split_strategie: &mut S, - crate_name: &str, -) -> io::Result<()> -where - P: AsRef, - G: AsRef, - S: SetSplitStrategie, -{ - let resources = collect_resources(&project_dir, filter)?; - - let mut generated_file = File::create(&generated_filename)?; - - let module_dir = generated_filename.as_ref().parent().map_or_else( - || PathBuf::from(module_name), - |parent| parent.join(module_name), - ); - fs::create_dir_all(&module_dir)?; - - let mut module_file = File::create(module_dir.join("mod.rs"))?; - - generate_uses(&mut module_file, crate_name)?; - writeln!( - module_file, - " -use ::{crate_name}::StaticResource; -use ::std::collections::HashMap;" - )?; - - let mut modules_count = 1; - - let mut set_file = create_set_module_file(&module_dir, modules_count)?; - let mut should_split = set_split_strategie.should_split(); - - for resource in &resources { - let (path, metadata) = &resource; - if should_split { - set_split_strategie.reset(); - modules_count += 1; - generate_function_end(&mut set_file)?; - set_file = create_set_module_file(&module_dir, modules_count)?; - } - set_split_strategie.register(path, metadata); - should_split = set_split_strategie.should_split(); - - generate_resource_insert(&mut set_file, &project_dir, DEFAULT_VARIABLE_NAME, resource)?; - } - - generate_function_end(&mut set_file)?; - - for module_index in 1..=modules_count { - writeln!(module_file, "mod set_{module_index};")?; - } - - generate_function_header(&mut module_file, fn_name, crate_name)?; - - generate_variable_header(&mut module_file, DEFAULT_VARIABLE_NAME)?; - - for module_index in 1..=modules_count { - writeln!( - module_file, - "set_{module_index}::generate(&mut {DEFAULT_VARIABLE_NAME});", - )?; - } - - generate_variable_return(&mut module_file, DEFAULT_VARIABLE_NAME)?; - - generate_function_end(&mut module_file)?; - - writeln!( - generated_file, - "mod {module_name}; -pub use {module_name}::{fn_name};", - )?; - - Ok(()) -} - -fn create_set_module_file(module_dir: &Path, module_index: usize) -> io::Result { - let mut set_module = File::create(module_dir.join(format!("set_{module_index}.rs")))?; - - writeln!( - set_module, - "#[allow(clippy::wildcard_imports)] -use super::*; -#[allow(clippy::unreadable_literal)] -pub(crate) fn generate({DEFAULT_VARIABLE_NAME}: &mut HashMap<&'static str, StaticResource>) {{", - )?; - - Ok(set_module) -} diff --git a/helpers/pagetop-statics/tests/file1.txt b/helpers/pagetop-statics/tests/file1.txt deleted file mode 100644 index e69de29..0000000 diff --git a/helpers/pagetop-statics/tests/file2.txt b/helpers/pagetop-statics/tests/file2.txt deleted file mode 100644 index e69de29..0000000 diff --git a/helpers/pagetop-statics/tests/file3.info b/helpers/pagetop-statics/tests/file3.info deleted file mode 100644 index e69de29..0000000 diff --git a/helpers/pagetop-statics/tests/index.html b/helpers/pagetop-statics/tests/index.html deleted file mode 100644 index 36f505f..0000000 --- a/helpers/pagetop-statics/tests/index.html +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - Document - - - - diff --git a/src/config.rs b/src/config.rs index 27cf630..08067fe 100644 --- a/src/config.rs +++ b/src/config.rs @@ -110,13 +110,11 @@ //! } //! ``` -use crate::util; - use config::builder::DefaultState; use config::{Config, ConfigBuilder, File}; use std::env; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; use std::sync::LazyLock; // Nombre del directorio de configuración por defecto. @@ -127,12 +125,25 @@ 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(|| { - // 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 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() + }; - // 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). + // 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). 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 a243778..93b5c4b 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, service, static_files_service, trace}; +use crate::{global, include_files, include_files_service, service, trace}; use parking_lot::RwLock; @@ -125,6 +125,8 @@ 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)] @@ -138,5 +140,7 @@ pub fn configure_services(scfg: &mut service::web::ServiceConfig) { extension.configure_service(scfg); } - static_files_service!(scfg, [&global::SETTINGS.dev.pagetop_static_dir, assets] => "/"); + include_files_service!( + scfg, assets => "/", [&global::SETTINGS.dev.pagetop_project_dir, "static"] + ); } diff --git a/src/global.rs b/src/global.rs index 8a03589..ea659b8 100644 --- a/src/global.rs +++ b/src/global.rs @@ -13,7 +13,7 @@ include_config!(SETTINGS: Settings => [ "app.startup_banner" => "Slant", // [dev] - "dev.pagetop_static_dir" => "", + "dev.pagetop_project_dir" => "", // [log] "log.enabled" => true, @@ -68,15 +68,11 @@ pub struct App { #[derive(Debug, Deserialize)] /// Sección `[Dev]` de la configuración. Forma parte de [`Settings`]. pub struct Dev { - /// 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, + /// 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, } #[derive(Debug, Deserialize)] diff --git a/src/html/assets/favicon.rs b/src/html/assets/favicon.rs index 1a8b29e..2af0173 100644 --- a/src/html/assets/favicon.rs +++ b/src/html/assets/favicon.rs @@ -12,7 +12,8 @@ use crate::AutoDefault; /// /// > **Nota** /// > Los archivos de los iconos deben estar disponibles en el servidor web de la aplicación. Pueden -/// > servirse usando [`static_files_service!`](crate::static_files_service). +/// > incluirse en el proyecto utilizando [`include_files!`](crate::include_files) y servirse con +/// > [`include_files_service!`](crate::include_files_service). /// /// # Ejemplo /// diff --git a/src/html/assets/javascript.rs b/src/html/assets/javascript.rs index db5754e..604e85a 100644 --- a/src/html/assets/javascript.rs +++ b/src/html/assets/javascript.rs @@ -30,7 +30,8 @@ enum Source { /// /// > **Nota** /// > Los archivos de los *scripts* deben estar disponibles en el servidor web de la aplicación. -/// > Pueden servirse usando [`static_files_service!`](crate::static_files_service). +/// > Pueden incluirse en el proyecto utilizando [`include_files!`](crate::include_files) y servirse +/// > con [`include_files_service!`](crate::include_files_service). /// /// # Ejemplo /// diff --git a/src/html/assets/stylesheet.rs b/src/html/assets/stylesheet.rs index bb60b01..72a79a1 100644 --- a/src/html/assets/stylesheet.rs +++ b/src/html/assets/stylesheet.rs @@ -55,7 +55,8 @@ impl TargetMedia { /// /// > **Nota** /// > Las hojas de estilo CSS deben estar disponibles en el servidor web de la aplicación. Pueden -/// > servirse usando [`static_files_service!`](crate::static_files_service). +/// > incluirse en el proyecto utilizando [`include_files!`](crate::include_files) y servirse con +/// > [`include_files_service!`](crate::include_files_service). /// /// # Ejemplo /// diff --git a/src/lib.rs b/src/lib.rs index 90ea462..baa57a6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,89 +1,86 @@ -/*! -
- - - -

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. -*/ +//!
+//! +//! +//! +//!

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)](#-license) +//! [![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. #![cfg_attr(docsrs, feature(doc_cfg))] #![doc( @@ -101,24 +98,21 @@ use std::ops::Deref; pub use pagetop_macros::{builder_fn, html, main, test, AutoDefault}; -pub use pagetop_statics::{resource, StaticResource}; - -/// Contenedor para un conjunto de recursos embebidos. -#[derive(AutoDefault)] +/// Conjunto de recursos asociados a `$STATIC` en [`include_files!`](crate::include_files). pub struct StaticResources { - bundle: HashMap<&'static str, StaticResource>, + bundle: HashMap<&'static str, static_files::Resource>, } impl StaticResources { /// Crea un contenedor para un conjunto de recursos generado por `build.rs` (consultar /// [`pagetop_build`](https://docs.rs/pagetop-build)). - pub fn new(bundle: HashMap<&'static str, StaticResource>) -> Self { + pub fn new(bundle: HashMap<&'static str, static_files::Resource>) -> Self { Self { bundle } } } impl Deref for StaticResources { - type Target = HashMap<&'static str, StaticResource>; + type Target = HashMap<&'static str, static_files::Resource>; fn deref(&self) -> &Self::Target { &self.bundle diff --git a/src/prelude.rs b/src/prelude.rs index 9072dec..6bacaf3 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -15,8 +15,7 @@ pub use crate::include_config; // crate::locale pub use crate::include_locales; // crate::service -#[allow(deprecated)] -pub use crate::{include_files, include_files_service, static_files_service}; +pub use crate::{include_files, include_files_service}; // crate::core::action pub use crate::actions_boxed; diff --git a/src/service.rs b/src/service.rs index 288e1eb..89ba496 100644 --- a/src/service.rs +++ b/src/service.rs @@ -8,16 +8,13 @@ pub use actix_web::dev::ServiceRequest as Request; pub use actix_web::dev::ServiceResponse as Response; pub use actix_web::{cookie, http, rt, web}; pub use actix_web::{App, Error, HttpMessage, HttpRequest, HttpResponse, HttpServer}; -pub use actix_web_files::Files as ActixFiles; -pub use pagetop_statics::ResourceFiles; +pub use actix_web_files::Files as ActixFiles; +pub use actix_web_static_files::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 @@ -42,7 +39,6 @@ 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. @@ -67,9 +63,6 @@ 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 @@ -111,7 +104,6 @@ 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])? ) => {{ @@ -145,114 +137,3 @@ 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 21537c5..b605125 100644 --- a/src/util.rs +++ b/src/util.rs @@ -2,44 +2,35 @@ use crate::trace; -use std::env; use std::io; use std::path::{Path, PathBuf}; // FUNCIONES ÚTILES ******************************************************************************** -/// Resuelve y valida la ruta de un directorio existente, devolviendo una ruta absoluta. +/// Devuelve la ruta absoluta a un directorio existente. /// -/// - 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. +/// * 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. /// -/// # Ejemplos +/// # Ejemplo /// /// ```rust,no_run /// use pagetop::prelude::*; /// -/// // 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")); +/// let root = "/home/user"; +/// let rel = "documents"; +/// println!("{:#?}", util::absolute_dir(root, rel)); /// ``` -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) - }; +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()); // Resuelve `.`/`..`, enlaces simbólicos y obtiene la ruta absoluta en un único paso. let absolute_dir = candidate.canonicalize()?; @@ -56,16 +47,6 @@ pub fn resolve_absolute_dir>(path: P) -> io::Result { } } -/// 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 70699a7..6e0896f 100644 --- a/tests/util.rs +++ b/tests/util.rs @@ -1,6 +1,6 @@ use pagetop::prelude::*; -use std::{env, fs, io}; +use std::{fs, io}; use tempfile::TempDir; #[cfg(unix)] @@ -13,36 +13,15 @@ mod unix { // /tmp//sub let td = TempDir::new()?; - let sub = td.path().join("sub"); + let root = td.path(); + let sub = root.join("sub"); fs::create_dir(&sub)?; - let abs = util::resolve_absolute_dir(&sub)?; + let abs = util::absolute_dir(root, "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; @@ -51,7 +30,7 @@ mod unix { let file = td.path().join("foo.txt"); fs::write(&file, b"data")?; - let err = util::resolve_absolute_dir(&file).unwrap_err(); + let err = util::absolute_dir(td.path(), "foo.txt").unwrap_err(); assert_eq!(err.kind(), io::ErrorKind::InvalidInput); Ok(()) } @@ -67,36 +46,15 @@ mod windows { // C:\Users\...\Temp\... let td = TempDir::new()?; - let sub = td.path().join("sub"); + let root = td.path(); + let sub = root.join("sub"); fs::create_dir(&sub)?; - let abs = util::resolve_absolute_dir(&sub)?; + let abs = util::absolute_dir(root, sub.as_path())?; 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; @@ -105,7 +63,7 @@ mod windows { let file = td.path().join("foo.txt"); fs::write(&file, b"data")?; - let err = util::resolve_absolute_dir(&file).unwrap_err(); + let err = util::absolute_dir(td.path(), "foo.txt").unwrap_err(); assert_eq!(err.kind(), io::ErrorKind::InvalidInput); Ok(()) } diff --git a/tools/changelog.sh b/tools/changelog.sh index 2668ec6..19cbed9 100755 --- a/tools/changelog.sh +++ b/tools/changelog.sh @@ -35,25 +35,20 @@ cd "$(dirname "$0")/.." || exit 1 # Determina ruta del archivo y ámbito de los archivos afectados para el crate # ------------------------------------------------------------------------------ case "$CRATE" in - pagetop-statics) - CHANGELOG_FILE="helpers/pagetop-statics/CHANGELOG.md" - PATH_FLAGS=(--include-path "helpers/pagetop-statics/**/*") - ;; - pagetop-build) - CHANGELOG_FILE="helpers/pagetop-build/CHANGELOG.md" - PATH_FLAGS=(--include-path "helpers/pagetop-build/**/*") + pagetop) + CHANGELOG_FILE="CHANGELOG.md" + PATH_FLAGS=( + --exclude-path "helpers/pagetop-macros/**/*" + --exclude-path "helpers/pagetop-build/**/*" + ) ;; pagetop-macros) CHANGELOG_FILE="helpers/pagetop-macros/CHANGELOG.md" PATH_FLAGS=(--include-path "helpers/pagetop-macros/**/*") ;; - pagetop) - CHANGELOG_FILE="CHANGELOG.md" - PATH_FLAGS=( - --exclude-path "helpers/pagetop-statics/**/*" - --exclude-path "helpers/pagetop-build/**/*" - --exclude-path "helpers/pagetop-macros/**/*" - ) + pagetop-build) + CHANGELOG_FILE="helpers/pagetop-build/CHANGELOG.md" + PATH_FLAGS=(--include-path "helpers/pagetop-build/**/*") ;; *) echo "Error: unsupported crate '$CRATE'" >&2 @@ -62,29 +57,22 @@ case "$CRATE" in esac # ------------------------------------------------------------------------------ -# Genera el CHANGELOG para el crate correspondiente +# Obtiene la última etiqueta del crate # ------------------------------------------------------------------------------ -if [[ -f "$CHANGELOG_FILE" ]]; then - # Archivo existe: inserta la nueva sección arriba - OUTPUT_FLAG=(--prepend "$CHANGELOG_FILE") -else - # Primera vez: crea el fichero desde cero - OUTPUT_FLAG=(-o "$CHANGELOG_FILE") -fi -COMMON_ARGS=( - --config "$CLIFF_CONFIG" - "${PATH_FLAGS[@]}" - --tag-pattern "^${CRATE}-v" - --tag "$VERSION" - "${OUTPUT_FLAG[@]}" -) LAST_TAG="$(git tag --list "${CRATE}-v*" --sort=-v:refname | head -n 1)" + if [[ -n "$LAST_TAG" ]]; then - echo "Generating CHANGELOG for '$CRATE' from tag '$LAST_TAG'" + echo "Generating CHANGELOG for '$CRATE' from last tag '$LAST_TAG'" + CLIFF_ARGS=(--unreleased --tag "$VERSION") else echo "Generating initial CHANGELOG for '$CRATE'" + CLIFF_ARGS=(--tag "$VERSION") fi -git-cliff --unreleased "${COMMON_ARGS[@]}" + +# ------------------------------------------------------------------------------ +# Genera el CHANGELOG para el crate correspondiente +# ------------------------------------------------------------------------------ +git-cliff --config "$CLIFF_CONFIG" "${PATH_FLAGS[@]}" "${CLIFF_ARGS[@]}" -o "$CHANGELOG_FILE" -u echo "CHANGELOG generated at '$CHANGELOG_FILE'" # Pregunta por la revisión del archivo de cambios generado @@ -101,7 +89,7 @@ if [[ ! "$REPLY" =~ ^[Yy]$ ]]; then fi # Si hay cambios y procede, añade al stage (cargo-release hará el commit) -if [[ -n $(git status --porcelain -- "$CHANGELOG_FILE") ]]; then +if ! git diff --quiet -- "$CHANGELOG_FILE"; then if [[ "$STAGE" == "--stage" ]]; then git add "$CHANGELOG_FILE" echo "Staged $CHANGELOG_FILE for commit" diff --git a/tools/release.sh b/tools/release.sh index bb09241..082869f 100755 --- a/tools/release.sh +++ b/tools/release.sh @@ -40,9 +40,9 @@ cd "$(dirname "$0")/.." || exit 1 # ------------------------------------------------------------------------------ if [[ "$EXECUTE" != "--execute" ]]; then echo "Running dry-run (default mode). Add --execute to publish" - cargo release --config "$CONFIG" --package "$CRATE" "$LEVEL" + CARGO_RELEASE_CONFIG="$CONFIG" cargo release --package "$CRATE" "$LEVEL" else echo "Releasing $CRATE ($LEVEL)…" - cargo release --config "$CONFIG" --package "$CRATE" "$LEVEL" --execute + CARGO_RELEASE_CONFIG="$CONFIG" cargo release --package "$CRATE" "$LEVEL" --execute echo "Release completed." fi