Compare commits

..

No commits in common. "main" and "pagetop-build-v0.2.0" have entirely different histories.

27 changed files with 334 additions and 698 deletions

View file

@ -8,32 +8,6 @@ Resume la evolución del proyecto para usuarios y colaboradores, destacando nuev
correcciones, mejoras durante el desarrollo o cambios en la documentación. Cambios menores o correcciones, mejoras durante el desarrollo o cambios en la documentación. Cambios menores o
internos pueden omitirse si no afectan al uso del proyecto. 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) ## 0.1.0 (2025-08-06)
- Versión inicial - Versión inicial

8
Cargo.lock generated
View file

@ -1557,7 +1557,7 @@ checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
[[package]] [[package]]
name = "pagetop" name = "pagetop"
version = "0.3.0" version = "0.1.0"
dependencies = [ dependencies = [
"actix-files", "actix-files",
"actix-session", "actix-session",
@ -1587,7 +1587,7 @@ dependencies = [
[[package]] [[package]]
name = "pagetop-build" name = "pagetop-build"
version = "0.3.0" version = "0.2.0"
dependencies = [ dependencies = [
"grass", "grass",
"pagetop-statics", "pagetop-statics",
@ -1595,7 +1595,7 @@ dependencies = [
[[package]] [[package]]
name = "pagetop-macros" name = "pagetop-macros"
version = "0.1.1" version = "0.1.0"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"proc-macro2-diagnostics", "proc-macro2-diagnostics",
@ -1605,7 +1605,7 @@ dependencies = [
[[package]] [[package]]
name = "pagetop-statics" name = "pagetop-statics"
version = "0.1.1" version = "0.1.0"
dependencies = [ dependencies = [
"actix-web", "actix-web",
"change-detection", "change-detection",

View file

@ -1,6 +1,6 @@
[package] [package]
name = "pagetop" name = "pagetop"
version = "0.3.0" version = "0.1.0"
edition = "2021" edition = "2021"
description = """ description = """
@ -71,6 +71,6 @@ authors = ["Manuel Cillero <manuel@cillero.es>"]
[workspace.dependencies] [workspace.dependencies]
actix-web = { version = "4.11.0", default-features = false } actix-web = { version = "4.11.0", default-features = false }
pagetop-build = { version = "0.3", path = "helpers/pagetop-build" } pagetop-build = { version = "0.2", path = "helpers/pagetop-build" }
pagetop-macros = { version = "0.1", path = "helpers/pagetop-macros" } pagetop-macros = { version = "0.1", path = "helpers/pagetop-macros" }
pagetop-statics = { version = "0.1", path = "helpers/pagetop-statics" } pagetop-statics = { version = "0.1", path = "helpers/pagetop-statics" }

View file

@ -11,7 +11,6 @@
[![Crates.io](https://img.shields.io/crates/v/pagetop.svg?style=for-the-badge&logo=ipfs)](https://crates.io/crates/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) [![Descargas](https://img.shields.io/crates/d/pagetop.svg?label=Descargas&style=for-the-badge&logo=transmission)](https://crates.io/crates/pagetop)
<br>
</div> </div>
`PageTop` reivindica la esencia de la web clásica usando [Rust](https://www.rust-lang.org/es) para `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í: La aplicación más sencilla de `PageTop` se ve así:
```rust,no_run ```rust
use pagetop::prelude::*; use pagetop::prelude::*;
#[pagetop::main] #[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: Para personalizar el servicio, se puede crear una extensión de `PageTop` de la siguiente manera:
```rust,no_run ```rust
use pagetop::prelude::*; use pagetop::prelude::*;
struct HelloWorld; struct HelloWorld;
@ -84,14 +83,9 @@ El código se organiza en un *workspace* donde actualmente se incluyen los sigui
## Auxiliares ## 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)**, * **[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 permite incluir fácilmente archivos estáticos o archivos SCSS compilados directamente en el
aplicaciones `PageTop` durante la compilación de los ejecutables. binario de las aplicaciones `PageTop`.
* **[pagetop-macros](https://git.cillero.es/manuelcillero/pagetop/src/branch/main/helpers/pagetop-macros)**, * **[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`. proporciona una colección de macros que mejoran la experiencia de desarrollo con `PageTop`.

View file

@ -8,17 +8,6 @@ Resume la evolución del proyecto para usuarios y colaboradores, destacando nuev
correcciones, mejoras durante el desarrollo o cambios en la documentación. Cambios menores o correcciones, mejoras durante el desarrollo o cambios en la documentación. Cambios menores o
internos pueden omitirse si no afectan al uso del proyecto. 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) ## 0.2.0 (2025-08-09)
### Añadido ### Añadido

View file

@ -1,6 +1,6 @@
[package] [package]
name = "pagetop-build" name = "pagetop-build"
version = "0.3.0" version = "0.2.0"
edition = "2021" edition = "2021"
description = """ description = """

View file

@ -18,99 +18,6 @@ clásica para crear soluciones web SSR (*renderizadas en el servidor*) modulares
configurables, basadas en HTML, CSS y JavaScript. 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 # 🚧 Advertencia
`PageTop` es un proyecto personal para aprender [Rust](https://www.rust-lang.org/es) y conocer su `PageTop` es un proyecto personal para aprender [Rust](https://www.rust-lang.org/es) y conocer su

View file

@ -1,116 +1,122 @@
/*! //! <div align="center">
<div align="center"> //!
//! <h1>PageTop Build</h1>
<h1>PageTop Build</h1> //!
//! <p>Prepara un conjunto de archivos estáticos o archivos SCSS compilados para ser incluidos en el binario de un proyecto <strong>PageTop</strong>.</p>
<p>Prepara un conjunto de archivos estáticos o archivos SCSS compilados para ser incluidos en el binario de un proyecto <strong>PageTop</strong>.</p> //!
//! [![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)](#-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)
[![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)
[![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)
[![Descargas](https://img.shields.io/crates/d/pagetop-build.svg?label=Descargas&style=for-the-badge&logo=transmission)](https://crates.io/crates/pagetop-build) //!
//! </div>
</div> //!
//! ## Sobre PageTop
## Sobre PageTop //!
//! [PageTop](https://docs.rs/pagetop) es un entorno de desarrollo que reivindica la esencia de la
[PageTop](https://docs.rs/pagetop) es un entorno de desarrollo que reivindica la esencia de la web //! web clásica para crear soluciones web SSR (*renderizadas en el servidor*) modulares, extensibles
clásica para crear soluciones web SSR (*renderizadas en el servidor*) modulares, extensibles y //! y configurables, basadas en HTML, CSS y JavaScript.
configurables, basadas en HTML, CSS y JavaScript. //!
//!
//! # ⚡️ Guía rápida
# Guía rápida //!
//! Añadir en el archivo `Cargo.toml` del proyecto:
Añadir en el archivo `Cargo.toml` del proyecto: //!
//! ```toml
```toml //! [build-dependencies]
[build-dependencies] //! pagetop-build = { ... }
pagetop-build = { ... } //! ```
``` //!
//! Y crear un archivo `build.rs` a la altura de `Cargo.toml` para indicar cómo se van a incluir los
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:
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
## Incluir archivos estáticos desde un directorio //!
//! Hay que preparar una carpeta en el proyecto con todos los archivos que se quieren incluir, por
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:
ejemplo `static`, y añadir el siguiente código en `build.rs` para crear el conjunto de recursos: //!
//! ```rust,no_run
```rust,no_run //! use pagetop_build::StaticFilesBundle;
use pagetop_build::StaticFilesBundle; //!
//! fn main() -> std::io::Result<()> {
fn main() -> std::io::Result<()> { //! StaticFilesBundle::from_dir("./static", None)
StaticFilesBundle::from_dir("./static", None) //! .with_name("guides")
.with_name("guides") //! .build()
.build() //! }
} //! ```
``` //!
//! Si es necesario, se puede añadir un filtro para seleccionar archivos específicos de la carpeta,
Si es necesario, se puede añadir un filtro para seleccionar archivos específicos de la carpeta, por //! por ejemplo:
ejemplo: //!
//! ```rust,no_run
```rust,no_run //! use pagetop_build::StaticFilesBundle;
use pagetop_build::StaticFilesBundle; //! use std::path::Path;
use std::path::Path; //!
//! fn main() -> std::io::Result<()> {
fn main() -> std::io::Result<()> { //! fn only_pdf_files(path: &Path) -> bool {
fn only_pdf_files(path: &Path) -> bool { //! // Selecciona únicamente los archivos con extensión `.pdf`.
// Selecciona únicamente los archivos con extensión `.pdf`. //! path.extension().map_or(false, |ext| ext == "pdf")
path.extension().map_or(false, |ext| ext == "pdf") //! }
} //!
//! StaticFilesBundle::from_dir("./static", Some(only_pdf_files))
StaticFilesBundle::from_dir("./static", Some(only_pdf_files)) //! .with_name("guides")
.with_name("guides") //! .build()
.build() //! }
} //! ```
``` //!
//! ## Compilar archivos SCSS a CSS
## Compilar archivos SCSS a CSS //!
//! Se puede compilar un archivo SCSS, que podría importar otros a su vez, para preparar un recurso
Se puede compilar un archivo SCSS, que podría importar otros a su vez, para preparar un recurso con //! con el archivo CSS minificado obtenido. Por ejemplo:
el archivo CSS minificado obtenido. Por ejemplo: //!
//! ```rust,no_run
```rust,no_run //! use pagetop_build::StaticFilesBundle;
use pagetop_build::StaticFilesBundle; //!
//! fn main() -> std::io::Result<()> {
fn main() -> std::io::Result<()> { //! StaticFilesBundle::from_scss("./styles/main.scss", "styles.min.css")
StaticFilesBundle::from_scss("./styles/main.scss", "styles.min.css") //! .with_name("main_styles")
.with_name("main_styles") //! .build()
.build() //! }
} //! ```
``` //!
//! Este código compila el archivo `main.scss` de la carpeta `static` del proyecto, y prepara un
Este código compila el archivo `main.scss` de la carpeta `static` del proyecto, y prepara un recurso //! recurso llamado `main_styles` que contiene el archivo `styles.min.css` obtenido.
llamado `main_styles` que contiene el archivo `styles.min.css` obtenido. //!
//!
//! # 📦 Módulos generados
# 📦 Archivos generados //!
//! Cada conjunto de recursos [`StaticFilesBundle`] genera un archivo en el directorio estándar
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)
[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
donde se incluye el código necesario para compilar el proyecto. Por ejemplo, para //! `with_name("guides")` se crea un archivo llamado `guides.rs`.
`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.
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; bastará con incluirlos en el proyecto con
//! [`include_files!`](https://docs.rs/pagetop/latest/pagetop/macro.include_files.html), y luego con
Normalmente no habrá que acceder a estos módulos; sólo declarar el nombre del conjunto de recursos //! [`include_files_service!`](https://docs.rs/pagetop/latest/pagetop/macro.include_files_service.html)
en [`static_files_service!`](https://docs.rs/pagetop/latest/pagetop/macro.static_files_service.html) //! configurar un servicio web para servir los recursos desde la ruta indicada:
para configurar un servicio web que sirva los archivos desde la ruta indicada. Por ejemplo: //!
//! ```rust,ignore
```rust,ignore //! use pagetop::prelude::*;
use pagetop::prelude::*; //!
//! include_files!(guides);
pub struct MyExtension; //!
//! pub struct MyExtension;
impl Extension for MyExtension { //!
// Servicio web que publica los recursos de `guides` en `/ruta/a/guides`. //! impl Extension for MyExtension {
fn configure_service(&self, scfg: &mut service::web::ServiceConfig) { //! // Servicio web que publica los recursos de `guides` en `/ruta/a/guides`.
static_files_service!(scfg, guides => "/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( #![doc(
html_favicon_url = "https://git.cillero.es/manuelcillero/pagetop/raw/branch/main/static/favicon.ico" html_favicon_url = "https://git.cillero.es/manuelcillero/pagetop/raw/branch/main/static/favicon.ico"
@ -123,7 +129,8 @@ use std::fs::{create_dir_all, remove_dir_all, File};
use std::io::Write; use std::io::Write;
use std::path::Path; 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 { pub struct StaticFilesBundle {
resource_dir: ResourceDir, resource_dir: ResourceDir,
} }
@ -156,19 +163,8 @@ impl StaticFilesBundle {
/// .build() /// .build()
/// } /// }
/// ``` /// ```
pub fn from_dir<P>(dir: P, filter: Option<fn(&Path) -> bool>) -> Self pub fn from_dir(dir: impl AsRef<str>, filter: Option<fn(p: &Path) -> bool>) -> Self {
where let mut resource_dir = resource_dir(dir.as_ref());
P: AsRef<Path>,
{
let dir_path = dir.as_ref();
let dir_str = dir_path.to_str().unwrap_or_else(|| {
panic!(
"Resource directory path is not valid UTF-8: {}",
dir_path.display()
);
});
let mut resource_dir = resource_dir(dir_str);
// Aplica el filtro si está definido. // Aplica el filtro si está definido.
if let Some(f) = filter { if let Some(f) = filter {

View file

@ -8,17 +8,6 @@ Resume la evolución del proyecto para usuarios y colaboradores, destacando nuev
correcciones, mejoras durante el desarrollo o cambios en la documentación. Cambios menores o correcciones, mejoras durante el desarrollo o cambios en la documentación. Cambios menores o
internos pueden omitirse si no afectan al uso del proyecto. 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) ## 0.1.0 (2025-08-06)
- Versión inicial - Versión inicial

View file

@ -1,6 +1,6 @@
[package] [package]
name = "pagetop-macros" name = "pagetop-macros"
version = "0.1.1" version = "0.1.0"
edition = "2021" edition = "2021"
description = """ description = """

View file

@ -11,16 +11,9 @@
</div> </div>
## Sobre PageTop ## Descripción general
[PageTop](https://docs.rs/pagetop) es un entorno de desarrollo que reivindica la esencia de la web Entre sus macros se incluye una adaptación de [maud-macros](https://crates.io/crates/maud_macros)
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 ([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 [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 [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 necesidad de referenciar `maud` o `smart_default` en las dependencias del archivo `Cargo.toml` de
cada proyecto `PageTop`. 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 # 🚧 Advertencia

View file

@ -1,34 +1,21 @@
/*! //! <div align="center">
<div align="center"> //!
//! <h1>PageTop Macros</h1>
<h1>PageTop Macros</h1> //!
//! <p>Una colección de macros que mejoran la experiencia de desarrollo con <strong>PageTop</strong>.</p>
<p>Una colección de macros que mejoran la experiencia de desarrollo con <strong>PageTop</strong>.</p> //!
//! [![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)](#-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)
[![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)
[![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)
[![Descargas](https://img.shields.io/crates/d/pagetop-macros.svg?label=Descargas&style=for-the-badge&logo=transmission)](https://crates.io/crates/pagetop-macros) //!
//! </div>
</div> //!
//! ## Sobre PageTop
## Sobre PageTop //!
//! [PageTop](https://docs.rs/pagetop) es un entorno de desarrollo que reivindica la esencia de la
[PageTop](https://docs.rs/pagetop) es un entorno de desarrollo que reivindica la esencia de la web //! web clásica para crear soluciones web SSR (*renderizadas en el servidor*) modulares, extensibles
clásica para crear soluciones web SSR (*renderizadas en el servidor*) modulares, extensibles y //! y configurables, basadas en HTML, CSS y JavaScript.
configurables, basadas en HTML, CSS y JavaScript.
## Créditos
Esta librería incluye entre sus macros una adaptación de
[maud-macros](https://crates.io/crates/maud_macros)
([0.27.0](https://github.com/lambda-fairy/maud/tree/v0.27.0/maud_macros)) de
[Chris Wong](https://crates.io/users/lambda-fairy) y una versión renombrada de
[SmartDefault](https://crates.io/crates/smart_default) (0.7.1) de
[Jane Doe](https://crates.io/users/jane-doe), llamada `AutoDefault`. Estas macros eliminan la
necesidad de referenciar `maud` o `smart_default` en las dependencias del archivo `Cargo.toml` de
cada proyecto `PageTop`.
*/
#![doc( #![doc(
html_favicon_url = "https://git.cillero.es/manuelcillero/pagetop/raw/branch/main/static/favicon.ico" html_favicon_url = "https://git.cillero.es/manuelcillero/pagetop/raw/branch/main/static/favicon.ico"

View file

@ -8,12 +8,6 @@ Resume la evolución del proyecto para usuarios y colaboradores, destacando nuev
correcciones, mejoras durante el desarrollo o cambios en la documentación. Cambios menores o correcciones, mejoras durante el desarrollo o cambios en la documentación. Cambios menores o
internos pueden omitirse si no afectan al uso del proyecto. 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) ## 0.1.0 (2025-08-09)
### Añadido ### Añadido

View file

@ -1,6 +1,6 @@
[package] [package]
name = "pagetop-statics" name = "pagetop-statics"
version = "0.1.1" version = "0.1.0"
edition = "2021" edition = "2021"
description = """ description = """

View file

@ -8,21 +8,12 @@
</div> </div>
## Sobre PageTop
[PageTop](https://docs.rs/pagetop) es un entorno de desarrollo que reivindica la esencia de la web
clásica para crear soluciones web SSR (*renderizadas en el servidor*) modulares, extensibles y
configurables, basadas en HTML, CSS y JavaScript.
## Descripción general ## Descripción general
Esta librería permite incluir archivos estáticos en el ejecutable de las aplicaciones `PageTop` para Permite a `PageTop` incluir archivos estáticos en el ejecutable de la aplicación para servirlos de
servirlos de forma eficiente vía web, con detección de cambios que optimizan el tiempo de forma eficiente vía web, con detección de cambios que optimiza el tiempo de compilación.
compilación.
## Créditos Para ello, reúne el código de los *crates* [static-files](https://crates.io/crates/static_files)
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 (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 [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 [4.0.1](https://github.com/kilork/actix-web-static-files/tree/v4.0.1)), desarrollados ambos por
@ -31,6 +22,12 @@ Para ello, adapta el código de los *crates* [static-files](https://crates.io/cr
Estas implementaciones se integran en `PageTop` para evitar que cada proyecto tenga que declarar Estas implementaciones se integran en `PageTop` para evitar que cada proyecto tenga que declarar
`static-files` manualmente como dependencia en su `Cargo.toml`. `static-files` manualmente como dependencia en su `Cargo.toml`.
## Sobre PageTop
[PageTop](https://docs.rs/pagetop) es un entorno de desarrollo que reivindica la esencia de la web
clásica para crear soluciones web SSR (*renderizadas en el servidor*) modulares, extensibles y
configurables, basadas en HTML, CSS y JavaScript.
# 🚧 Advertencia # 🚧 Advertencia

View file

@ -1,37 +1,18 @@
/*! //! <div align="center">
<div align="center"> //!
//! <h1>PageTop Statics</h1>
<h1>PageTop Statics</h1> //!
//! <p>Librería para automatizar la recopilación de recursos estáticos en <strong>PageTop</strong>.</p>
<p>Librería para automatizar la recopilación de recursos estáticos en <strong>PageTop</strong>.</p> //!
//! [![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)](#-licencia) //!
//! </div>
</div> //!
//! ## Sobre PageTop
## Sobre PageTop //!
//! [PageTop](https://docs.rs/pagetop) es un entorno de desarrollo que reivindica la esencia de la
[PageTop](https://docs.rs/pagetop) es un entorno de desarrollo que reivindica la esencia de la web //! web clásica para crear soluciones web SSR (*renderizadas en el servidor*) modulares, extensibles
clásica para crear soluciones web SSR (*renderizadas en el servidor*) modulares, extensibles y //! y configurables, basadas en HTML, CSS y JavaScript.
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(test(no_crate_inject))]
#![doc( #![doc(

View file

@ -110,13 +110,11 @@
//! } //! }
//! ``` //! ```
use crate::util;
use config::builder::DefaultState; use config::builder::DefaultState;
use config::{Config, ConfigBuilder, File}; use config::{Config, ConfigBuilder, File};
use std::env; use std::env;
use std::path::PathBuf; use std::path::{Path, PathBuf};
use std::sync::LazyLock; use std::sync::LazyLock;
// Nombre del directorio de configuración por defecto. // 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`. /// Valores originales cargados desde los archivos de configuración como pares `clave = valor`.
pub static CONFIG_VALUES: LazyLock<ConfigBuilder<DefaultState>> = LazyLock::new(|| { pub static CONFIG_VALUES: LazyLock<ConfigBuilder<DefaultState>> = LazyLock::new(|| {
// CONFIG_DIR (si existe) o DEFAULT_CONFIG_DIR. Si no se puede resolver, se usa tal cual. // Determina el directorio de configuración:
let dir = env::var_os("CONFIG_DIR").unwrap_or_else(|| DEFAULT_CONFIG_DIR.into()); // - Usa CONFIG_DIR si está definido en el entorno (p.ej.: CONFIG_DIR=/etc/myapp ./myapp).
let config_dir = util::resolve_absolute_dir(&dir).unwrap_or_else(|_| PathBuf::from(&dir)); // - 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 // Determina el modo de ejecución según la variable de entorno PAGETOP_RUN_MODE. Por defecto usa
// por defecto, DEFAULT_RUN_MODE (p.ej.: PAGETOP_RUN_MODE=production). // 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()); let rm = env::var("PAGETOP_RUN_MODE").unwrap_or_else(|_| DEFAULT_RUN_MODE.into());
Config::builder() Config::builder()

View file

@ -1,7 +1,7 @@
use crate::core::action::add_action; use crate::core::action::add_action;
use crate::core::extension::ExtensionRef; use crate::core::extension::ExtensionRef;
use crate::core::theme::all::THEMES; 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; use parking_lot::RwLock;
@ -125,6 +125,8 @@ pub fn initialize_extensions() {
// CONFIGURA LOS SERVICIOS ************************************************************************* // CONFIGURA LOS SERVICIOS *************************************************************************
include_files!(assets);
pub fn configure_services(scfg: &mut service::web::ServiceConfig) { pub fn configure_services(scfg: &mut service::web::ServiceConfig) {
// Sólo compila durante el desarrollo, para evitar errores 400 en la traza de eventos. // Sólo compila durante el desarrollo, para evitar errores 400 en la traza de eventos.
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
@ -138,5 +140,7 @@ pub fn configure_services(scfg: &mut service::web::ServiceConfig) {
extension.configure_service(scfg); 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"]
);
} }

View file

@ -13,7 +13,7 @@ include_config!(SETTINGS: Settings => [
"app.startup_banner" => "Slant", "app.startup_banner" => "Slant",
// [dev] // [dev]
"dev.pagetop_static_dir" => "", "dev.pagetop_project_dir" => "",
// [log] // [log]
"log.enabled" => true, "log.enabled" => true,
@ -68,15 +68,11 @@ pub struct App {
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
/// Sección `[Dev]` de la configuración. Forma parte de [`Settings`]. /// Sección `[Dev]` de la configuración. Forma parte de [`Settings`].
pub struct Dev { pub struct Dev {
/// Directorio desde el que servir los archivos estáticos de `PageTop`. /// 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
/// Por defecto, los archivos se integran en el binario de la aplicación. Si aquí se indica una /// directorio para evitar recompilar cada vez que se modifican. En ese caso, este ajuste debe
/// ruta válida, ya sea absoluta o relativa al directorio del proyecto o del binario en /// indicar la ruta absoluta al directorio raíz del proyecto.
/// ejecución, se servirán desde el sistema de ficheros en su lugar. Esto es especialmente útil pub pagetop_project_dir: String,
/// en desarrollo, ya que evita recompilar el proyecto por cambios en estos archivos.
///
/// Si la cadena está vacía, se ignora este ajuste.
pub pagetop_static_dir: String,
} }
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]

View file

@ -12,7 +12,8 @@ use crate::AutoDefault;
/// ///
/// > **Nota** /// > **Nota**
/// > Los archivos de los iconos deben estar disponibles en el servidor web de la aplicación. Pueden /// > 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 /// # Ejemplo
/// ///

View file

@ -30,7 +30,8 @@ enum Source {
/// ///
/// > **Nota** /// > **Nota**
/// > Los archivos de los *scripts* deben estar disponibles en el servidor web de la aplicación. /// > 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 /// # Ejemplo
/// ///

View file

@ -55,7 +55,8 @@ impl TargetMedia {
/// ///
/// > **Nota** /// > **Nota**
/// > Las hojas de estilo CSS deben estar disponibles en el servidor web de la aplicación. Pueden /// > 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 /// # Ejemplo
/// ///

View file

@ -1,89 +1,86 @@
/*! //! <div align="center">
<div align="center"> //!
//! <img src="https://git.cillero.es/manuelcillero/pagetop/raw/branch/main/static/banner.png" />
<img src="https://git.cillero.es/manuelcillero/pagetop/raw/branch/main/static/banner.png" /> //!
//! <h1>PageTop</h1>
<h1>PageTop</h1> //!
//! <p>Un entorno de desarrollo para crear soluciones web modulares, extensibles y configurables.</p>
<p>Un entorno para el desarrollo de soluciones web modulares, extensibles y configurables.</p> //!
//! [![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)](#-licencia) //! [![Doc API](https://img.shields.io/docsrs/pagetop?label=Doc%20API&style=for-the-badge&logo=Docs.rs)](https://docs.rs/pagetop)
[![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)
[![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)
[![Descargas](https://img.shields.io/crates/d/pagetop.svg?label=Descargas&style=for-the-badge&logo=transmission)](https://crates.io/crates/pagetop) //!
//! <br>
<br> //! </div>
</div> //!
//! `PageTop` reivindica la esencia de la web clásica usando [Rust](https://www.rust-lang.org/es)
`PageTop` reivindica la esencia de la web clásica usando [Rust](https://www.rust-lang.org/es) para //! para la creación de soluciones web SSR (*renderizadas en el servidor*) basadas en HTML, CSS y
la creación de soluciones web SSR (*renderizadas en el servidor*) basadas en HTML, CSS y JavaScript. //! JavaScript. Ofrece un conjunto de herramientas que los desarrolladores pueden implementar,
Ofrece un conjunto de herramientas que los desarrolladores pueden implementar, extender o adaptar //! extender o adaptar según las necesidades de cada proyecto, incluyendo:
según las necesidades de cada proyecto, incluyendo: //!
//! * **Acciones** (*actions*): alteran la lógica interna de una funcionalidad interceptando su
* **Acciones** (*actions*): alteran la lógica interna de una funcionalidad interceptando su flujo //! flujo de ejecución.
de ejecución. //! * **Componentes** (*components*): encapsulan HTML, CSS y JavaScript en unidades funcionales,
* **Componentes** (*components*): encapsulan HTML, CSS y JavaScript en unidades funcionales, //! configurables y reutilizables.
configurables y reutilizables. //! * **Extensiones** (*extensions*): añaden, extienden o personalizan funcionalidades usando las
* **Extensiones** (*extensions*): añaden, extienden o personalizan funcionalidades usando las APIs //! APIs de `PageTop` o de terceros.
de `PageTop` o de terceros. //! * **Temas** (*themes*): son extensiones que permiten modificar la apariencia de páginas y
* **Temas** (*themes*): son extensiones que permiten modificar la apariencia de páginas y //! componentes sin comprometer su funcionalidad.
componentes sin comprometer su funcionalidad. //!
//! # ⚡️ Guía rápida
//!
# Guía rápida //! La aplicación más sencilla de `PageTop` se ve así:
//!
La aplicación más sencilla de `PageTop` se ve así: //! ```rust,no_run
//! use pagetop::prelude::*;
```rust,no_run //!
use pagetop::prelude::*; //! #[pagetop::main]
//! async fn main() -> std::io::Result<()> {
#[pagetop::main] //! Application::new().run()?.await
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
Este código arranca el servidor de `PageTop`. Con la configuración por defecto, muestra una página //! desde un navegador local en la dirección `http://localhost:8080`.
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:
Para personalizar el servicio, se puede crear una extensión de `PageTop` de la siguiente manera: //!
//! ```rust,no_run
```rust,no_run //! use pagetop::prelude::*;
use pagetop::prelude::*; //!
//! struct HelloWorld;
struct HelloWorld; //!
//! impl Extension for HelloWorld {
impl Extension for HelloWorld { //! fn configure_service(&self, scfg: &mut service::web::ServiceConfig) {
fn configure_service(&self, scfg: &mut service::web::ServiceConfig) { //! scfg.route("/", service::web::get().to(hello_world));
scfg.route("/", service::web::get().to(hello_world)); //! }
} //! }
} //!
//! async fn hello_world(request: HttpRequest) -> ResultPage<Markup, ErrorPage> {
async fn hello_world(request: HttpRequest) -> ResultPage<Markup, ErrorPage> { //! Page::new(Some(request))
Page::new(Some(request)) //! .with_component(Html::with(move |_| html! { h1 { "Hello world!" } }))
.with_component(Html::with(move |_| html! { h1 { "Hello World!" } })) //! .render()
.render() //! }
} //!
//! #[pagetop::main]
#[pagetop::main] //! async fn main() -> std::io::Result<()> {
async fn main() -> std::io::Result<()> { //! Application::prepare(&HelloWorld).run()?.await
Application::prepare(&HelloWorld).run()?.await //! }
} //! ```
``` //!
//! Este programa implementa una extensión llamada `HelloWorld` que sirve una página web en la ruta
Este programa implementa una extensión llamada `HelloWorld` que sirve una página web en la ruta raíz //! raíz (`/`) mostrando el texto "Hello world!" dentro de un elemento HTML `<h1>`.
(`/`) mostrando el texto "Hello world!" dentro de un elemento HTML `<h1>`. //!
//! # 🧩 Gestión de Dependencias
//!
# 🧩 Gestión de Dependencias //! Los proyectos que utilizan `PageTop` gestionan las dependencias con `cargo`, como cualquier otro
//! proyecto en Rust.
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
Sin embargo, es fundamental que cada extensión declare explícitamente sus //! estructurar e inicializar la aplicación de forma modular.
[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))] #![cfg_attr(docsrs, feature(doc_cfg))]
#![doc( #![doc(
@ -103,8 +100,7 @@ pub use pagetop_macros::{builder_fn, html, main, test, AutoDefault};
pub use pagetop_statics::{resource, StaticResource}; pub use pagetop_statics::{resource, StaticResource};
/// Contenedor para un conjunto de recursos embebidos. /// Conjunto de recursos asociados a `$STATIC` en [`include_files!`](crate::include_files).
#[derive(AutoDefault)]
pub struct StaticResources { pub struct StaticResources {
bundle: HashMap<&'static str, StaticResource>, bundle: HashMap<&'static str, StaticResource>,
} }

View file

@ -15,8 +15,7 @@ pub use crate::include_config;
// crate::locale // crate::locale
pub use crate::include_locales; pub use crate::include_locales;
// crate::service // crate::service
#[allow(deprecated)] pub use crate::{include_files, include_files_service};
pub use crate::{include_files, include_files_service, static_files_service};
// crate::core::action // crate::core::action
pub use crate::actions_boxed; pub use crate::actions_boxed;

View file

@ -15,9 +15,6 @@ pub use pagetop_statics::ResourceFiles;
#[doc(hidden)] #[doc(hidden)]
pub use actix_web::test; 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`. /// Incluye en código un conjunto de recursos previamente preparado con `build.rs`.
/// ///
/// # Formas de uso /// # Formas de uso
@ -42,7 +39,6 @@ pub use actix_web::test;
/// ///
/// include_files!(STATIC_ASSETS => assets); /// include_files!(STATIC_ASSETS => assets);
/// ``` /// ```
#[deprecated(since = "0.3.0", note = "Use `static_files_service!` instead")]
#[macro_export] #[macro_export]
macro_rules! include_files { macro_rules! include_files {
// Forma 1: incluye un conjunto de recursos por nombre. // 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!`]. /// 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 /// 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. /// // También desde el directorio actual de ejecución.
/// include_files_service!(cfg, assets => "/public", ["", "static"]); /// include_files_service!(cfg, assets => "/public", ["", "static"]);
/// ``` /// ```
#[deprecated(since = "0.3.0", note = "Use `static_files_service!` instead")]
#[macro_export] #[macro_export]
macro_rules! include_files_service { macro_rules! include_files_service {
( $scfg:ident, $bundle:ident => $route:expr $(, [$root:expr, $relative:expr])? ) => {{ ( $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 [<static_files_ $bundle>] {
include!(concat!(env!("OUT_DIR"), "/", stringify!($bundle), ".rs"));
}
$scfg.service($crate::service::ResourceFiles::new(
$route,
[<static_files_ $bundle>]::$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 [<static_files_ $bundle>] {
include!(concat!(env!("OUT_DIR"), "/", stringify!($bundle), ".rs"));
}
$scfg.service($crate::service::ResourceFiles::new(
$route,
[<static_files_ $bundle>]::$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,
);
}
});
}};
}

View file

@ -2,44 +2,35 @@
use crate::trace; use crate::trace;
use std::env;
use std::io; use std::io;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
// FUNCIONES ÚTILES ******************************************************************************** // 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 /// * Si `relative_path` es una ruta absoluta, entonces se ignora `root_path`.
/// entorno `CARGO_MANIFEST_DIR` (si existe) o, en su defecto, respecto al directorio actual de /// * Si la ruta final es relativa, se convierte en absoluta respecto al directorio actual.
/// trabajo. /// * Devuelve error si la ruta no existe o no es un directorio.
/// - Normaliza y valida la ruta final (resuelve `.`/`..` y enlaces simbólicos).
/// - Devuelve error si la ruta no existe o no es un directorio.
/// ///
/// # Ejemplos /// # Ejemplo
/// ///
/// ```rust,no_run /// ```rust,no_run
/// use pagetop::prelude::*; /// use pagetop::prelude::*;
/// ///
/// // Ruta relativa, se resuelve respecto a CARGO_MANIFEST_DIR o al directorio actual (`cwd`). /// let root = "/home/user";
/// println!("{:#?}", util::resolve_absolute_dir("documents")); /// let rel = "documents";
/// /// println!("{:#?}", util::absolute_dir(root, rel));
/// // Ruta absoluta, se normaliza y valida tal cual.
/// println!("{:#?}", util::resolve_absolute_dir("/var/www"));
/// ``` /// ```
pub fn resolve_absolute_dir<P: AsRef<Path>>(path: P) -> io::Result<PathBuf> { pub fn absolute_dir<P, Q>(root_path: P, relative_path: Q) -> io::Result<PathBuf>
let path = path.as_ref(); where
P: AsRef<Path>,
let candidate = if path.is_absolute() { Q: AsRef<Path>,
path.to_path_buf() {
} else { // Une ambas rutas:
// Directorio base CARGO_MANIFEST_DIR si está disponible; o current_dir() en su defecto. // - Si `relative_path` es absoluta, el `join` la devuelve tal cual, descartando `root_path`.
env::var_os("CARGO_MANIFEST_DIR") // - Si el resultado es aún relativo, lo será respecto al directorio actual.
.map(PathBuf::from) let candidate = root_path.as_ref().join(relative_path.as_ref());
.or_else(|| env::current_dir().ok())
.unwrap_or_else(|| PathBuf::from("."))
.join(path)
};
// Resuelve `.`/`..`, enlaces simbólicos y obtiene la ruta absoluta en un único paso. // Resuelve `.`/`..`, enlaces simbólicos y obtiene la ruta absoluta en un único paso.
let absolute_dir = candidate.canonicalize()?; let absolute_dir = candidate.canonicalize()?;
@ -56,16 +47,6 @@ pub fn resolve_absolute_dir<P: AsRef<Path>>(path: P) -> io::Result<PathBuf> {
} }
} }
/// Devuelve la ruta absoluta a un directorio existente.
#[deprecated(since = "0.3.0", note = "Use [`resolve_absolute_dir`] instead")]
pub fn absolute_dir<P, Q>(root_path: P, relative_path: Q) -> io::Result<PathBuf>
where
P: AsRef<Path>,
Q: AsRef<Path>,
{
resolve_absolute_dir(root_path.as_ref().join(relative_path.as_ref()))
}
// MACROS ÚTILES *********************************************************************************** // MACROS ÚTILES ***********************************************************************************
#[doc(hidden)] #[doc(hidden)]

View file

@ -1,6 +1,6 @@
use pagetop::prelude::*; use pagetop::prelude::*;
use std::{env, fs, io}; use std::{fs, io};
use tempfile::TempDir; use tempfile::TempDir;
#[cfg(unix)] #[cfg(unix)]
@ -13,36 +13,15 @@ mod unix {
// /tmp/<rand>/sub // /tmp/<rand>/sub
let td = TempDir::new()?; let td = TempDir::new()?;
let sub = td.path().join("sub"); let root = td.path();
let sub = root.join("sub");
fs::create_dir(&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)?); assert_eq!(abs, std::fs::canonicalize(&sub)?);
Ok(()) 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] #[pagetop::test]
async fn error_not_a_directory() -> io::Result<()> { async fn error_not_a_directory() -> io::Result<()> {
let _app = service::test::init_service(Application::new().test()).await; let _app = service::test::init_service(Application::new().test()).await;
@ -51,7 +30,7 @@ mod unix {
let file = td.path().join("foo.txt"); let file = td.path().join("foo.txt");
fs::write(&file, b"data")?; 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); assert_eq!(err.kind(), io::ErrorKind::InvalidInput);
Ok(()) Ok(())
} }
@ -67,36 +46,15 @@ mod windows {
// C:\Users\...\Temp\... // C:\Users\...\Temp\...
let td = TempDir::new()?; let td = TempDir::new()?;
let sub = td.path().join("sub"); let root = td.path();
let sub = root.join("sub");
fs::create_dir(&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)?); assert_eq!(abs, std::fs::canonicalize(&sub)?);
Ok(()) 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] #[pagetop::test]
async fn error_not_a_directory() -> io::Result<()> { async fn error_not_a_directory() -> io::Result<()> {
let _app = service::test::init_service(Application::new().test()).await; let _app = service::test::init_service(Application::new().test()).await;
@ -105,7 +63,7 @@ mod windows {
let file = td.path().join("foo.txt"); let file = td.path().join("foo.txt");
fs::write(&file, b"data")?; 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); assert_eq!(err.kind(), io::ErrorKind::InvalidInput);
Ok(()) Ok(())
} }