Compare commits
No commits in common. "main" and "pagetop-macros-v0.1.0" have entirely different histories.
main
...
pagetop-ma
40 changed files with 358 additions and 2096 deletions
|
@ -21,9 +21,9 @@ body = """
|
||||||
{% else %}
|
{% else %}
|
||||||
## Pendiente de publicación
|
## Pendiente de publicación
|
||||||
{% endif %}\
|
{% endif %}\
|
||||||
|
{% set base = "https://git.cillero.es/manuelcillero/pagetop" %}\
|
||||||
{% for group, commits in commits | group_by(attribute="group") %}
|
{% for group, commits in commits | group_by(attribute="group") %}
|
||||||
### {{ group | upper_first }}
|
### {{ group | upper_first }}
|
||||||
|
|
||||||
{% for commit in commits %}
|
{% for commit in commits %}
|
||||||
{%- set msg = commit.message
|
{%- set msg = commit.message
|
||||||
| split(pat="\n")
|
| split(pat="\n")
|
||||||
|
@ -41,8 +41,8 @@ body = """
|
||||||
| replace(from="📝 ", to="")
|
| replace(from="📝 ", to="")
|
||||||
| replace(from="💡 ", to="")
|
| replace(from="💡 ", to="")
|
||||||
-%}
|
-%}
|
||||||
|
{% set sha7 = commit.id | truncate(length=7, end="") %}
|
||||||
- {{ msg | trim }} {% if commit.author.name != "Manuel Cillero" %} - {{ commit.author.name }}{% endif %}
|
- {{ msg | trim }} ([{{ sha7 }}]({{ base }}/commit/{{ commit.id }}){% if commit.author.name != "Manuel Cillero" %} - {{ commit.author.name }}{% endif %})
|
||||||
{% endfor %}{% endfor %}
|
{% endfor %}{% endfor %}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
39
CHANGELOG.md
39
CHANGELOG.md
|
@ -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
|
|
54
Cargo.lock
generated
54
Cargo.lock
generated
|
@ -225,6 +225,18 @@ dependencies = [
|
||||||
"syn",
|
"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]]
|
[[package]]
|
||||||
name = "addr2line"
|
name = "addr2line"
|
||||||
version = "0.24.2"
|
version = "0.24.2"
|
||||||
|
@ -646,9 +658,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crc32fast"
|
name = "crc32fast"
|
||||||
version = "1.5.0"
|
version = "1.4.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511"
|
checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
]
|
]
|
||||||
|
@ -1064,9 +1076,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "h2"
|
name = "h2"
|
||||||
version = "0.3.27"
|
version = "0.3.26"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0beca50380b1fc32983fc1cb4587bfa4bb9e78fc259aad4a0032d2080309222d"
|
checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
"fnv",
|
"fnv",
|
||||||
|
@ -1557,11 +1569,12 @@ checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pagetop"
|
name = "pagetop"
|
||||||
version = "0.3.0"
|
version = "0.0.61"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"actix-files",
|
"actix-files",
|
||||||
"actix-session",
|
"actix-session",
|
||||||
"actix-web",
|
"actix-web",
|
||||||
|
"actix-web-static-files",
|
||||||
"chrono",
|
"chrono",
|
||||||
"colored",
|
"colored",
|
||||||
"concat-string",
|
"concat-string",
|
||||||
|
@ -1571,10 +1584,10 @@ dependencies = [
|
||||||
"itoa",
|
"itoa",
|
||||||
"pagetop-build",
|
"pagetop-build",
|
||||||
"pagetop-macros",
|
"pagetop-macros",
|
||||||
"pagetop-statics",
|
|
||||||
"parking_lot",
|
"parking_lot",
|
||||||
"pastey",
|
"pastey",
|
||||||
"serde",
|
"serde",
|
||||||
|
"static-files",
|
||||||
"substring",
|
"substring",
|
||||||
"tempfile",
|
"tempfile",
|
||||||
"terminal_size",
|
"terminal_size",
|
||||||
|
@ -1587,15 +1600,15 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pagetop-build"
|
name = "pagetop-build"
|
||||||
version = "0.3.0"
|
version = "0.1.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"grass",
|
"grass",
|
||||||
"pagetop-statics",
|
"static-files",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[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",
|
||||||
|
@ -1603,18 +1616,6 @@ dependencies = [
|
||||||
"syn",
|
"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]]
|
[[package]]
|
||||||
name = "parking_lot"
|
name = "parking_lot"
|
||||||
version = "0.12.4"
|
version = "0.12.4"
|
||||||
|
@ -2177,6 +2178,17 @@ version = "1.2.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
|
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]]
|
[[package]]
|
||||||
name = "strsim"
|
name = "strsim"
|
||||||
version = "0.11.1"
|
version = "0.11.1"
|
||||||
|
|
15
Cargo.toml
15
Cargo.toml
|
@ -1,12 +1,12 @@
|
||||||
[package]
|
[package]
|
||||||
name = "pagetop"
|
name = "pagetop"
|
||||||
version = "0.3.0"
|
version = "0.0.61"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
description = """
|
description = """
|
||||||
Un entorno de desarrollo para crear soluciones web modulares, extensibles y configurables.
|
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"]
|
keywords = ["pagetop", "web", "framework", "frontend", "ssr"]
|
||||||
|
|
||||||
repository.workspace = true
|
repository.workspace = true
|
||||||
|
@ -34,14 +34,15 @@ tracing-actix-web = "0.7.19"
|
||||||
fluent-templates = "0.13.0"
|
fluent-templates = "0.13.0"
|
||||||
unic-langid = { version = "0.9.6", features = ["macros"] }
|
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-session = { version = "0.10.1", features = ["cookie-session"] }
|
||||||
actix-web-files = { package = "actix-files", version = "0.6.6" }
|
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"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
|
|
||||||
pagetop-macros.workspace = true
|
pagetop-macros.workspace = true
|
||||||
pagetop-statics.workspace = true
|
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = []
|
default = []
|
||||||
|
@ -59,7 +60,6 @@ resolver = "2"
|
||||||
members = [
|
members = [
|
||||||
"helpers/pagetop-build",
|
"helpers/pagetop-build",
|
||||||
"helpers/pagetop-macros",
|
"helpers/pagetop-macros",
|
||||||
"helpers/pagetop-statics",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[workspace.package]
|
[workspace.package]
|
||||||
|
@ -69,8 +69,7 @@ license = "MIT OR Apache-2.0"
|
||||||
authors = ["Manuel Cillero <manuel@cillero.es>"]
|
authors = ["Manuel Cillero <manuel@cillero.es>"]
|
||||||
|
|
||||||
[workspace.dependencies]
|
[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-build = { version = "0.1", 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" }
|
|
||||||
|
|
16
README.md
16
README.md
|
@ -6,12 +6,11 @@
|
||||||
|
|
||||||
<p>Un entorno para el desarrollo de soluciones web modulares, extensibles y configurables.</p>
|
<p>Un entorno para el desarrollo de soluciones web modulares, extensibles y configurables.</p>
|
||||||
|
|
||||||
[](#-licencia)
|
[](#-license)
|
||||||
[](https://docs.rs/pagetop)
|
[](https://docs.rs/pagetop)
|
||||||
[](https://crates.io/crates/pagetop)
|
[](https://crates.io/crates/pagetop)
|
||||||
[](https://crates.io/crates/pagetop)
|
[](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`.
|
||||||
|
|
|
@ -8,28 +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)
|
|
||||||
|
|
||||||
### 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)
|
## 0.1.1 (2025-08-05)
|
||||||
|
|
||||||
- Depura la edición de CHANGELOGs y publicación de nuevas versiones
|
- Depura la edición de CHANGELOGs y publicación de nuevas versiones
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
[package]
|
[package]
|
||||||
name = "pagetop-build"
|
name = "pagetop-build"
|
||||||
version = "0.3.0"
|
version = "0.1.1"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
description = """
|
description = """
|
||||||
Prepara un conjunto de archivos estáticos o archivos SCSS compilados para ser incluidos en el
|
Prepara un conjunto de archivos estáticos o archivos SCSS compilados para ser incluidos en el
|
||||||
binario de un proyecto PageTop.
|
binario de un proyecto PageTop.
|
||||||
"""
|
"""
|
||||||
categories = ["development-tools::build-utils"]
|
categories = ["development-tools::build-utils", "web-programming"]
|
||||||
keywords = ["pagetop", "build", "assets", "resources", "static"]
|
keywords = ["pagetop", "build", "assets", "resources", "static"]
|
||||||
|
|
||||||
repository.workspace = true
|
repository.workspace = true
|
||||||
|
@ -17,4 +17,4 @@ authors.workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
grass = "0.13.4"
|
grass = "0.13.4"
|
||||||
pagetop-statics.workspace = true
|
static-files.workspace = true
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
<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)
|
[](#-license)
|
||||||
[](https://docs.rs/pagetop-build)
|
[](https://docs.rs/pagetop-build)
|
||||||
[](https://crates.io/crates/pagetop-build)
|
[](https://crates.io/crates/pagetop-build)
|
||||||
[](https://crates.io/crates/pagetop-build)
|
[](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.
|
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
|
||||||
|
|
|
@ -1,129 +1,136 @@
|
||||||
/*!
|
//! <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>
|
//!
|
||||||
|
//! [](#-license)
|
||||||
[](#-licencia)
|
//! [](https://docs.rs/pagetop-build)
|
||||||
[](https://docs.rs/pagetop-build)
|
//! [](https://crates.io/crates/pagetop-build)
|
||||||
[](https://crates.io/crates/pagetop-build)
|
//! [](https://crates.io/crates/pagetop-build)
|
||||||
[](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"
|
||||||
)]
|
)]
|
||||||
|
|
||||||
use grass::{from_path, Options, OutputStyle};
|
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::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 {
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
[package]
|
[package]
|
||||||
name = "pagetop-macros"
|
name = "pagetop-macros"
|
||||||
version = "0.1.1"
|
version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
description = """
|
description = """
|
||||||
Una colección de macros que mejoran la experiencia de desarrollo con PageTop.
|
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"]
|
keywords = ["pagetop", "macros", "proc-macros", "codegen"]
|
||||||
|
|
||||||
repository.workspace = true
|
repository.workspace = true
|
||||||
|
|
|
@ -4,23 +4,16 @@
|
||||||
|
|
||||||
<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)
|
[](#-license)
|
||||||
[](https://docs.rs/pagetop-macros)
|
[](https://docs.rs/pagetop-macros)
|
||||||
[](https://crates.io/crates/pagetop-macros)
|
[](https://crates.io/crates/pagetop-macros)
|
||||||
[](https://crates.io/crates/pagetop-macros)
|
[](https://crates.io/crates/pagetop-macros)
|
||||||
|
|
||||||
</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
|
||||||
|
|
||||||
|
|
|
@ -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>
|
//!
|
||||||
|
//! [](#-license)
|
||||||
[](#-licencia)
|
//! [](https://docs.rs/pagetop-macros)
|
||||||
[](https://docs.rs/pagetop-macros)
|
//! [](https://crates.io/crates/pagetop-macros)
|
||||||
[](https://crates.io/crates/pagetop-macros)
|
//! [](https://crates.io/crates/pagetop-macros)
|
||||||
[](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`.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#, 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
|
|
|
@ -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"
|
|
|
@ -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.
|
|
|
@ -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.
|
|
|
@ -1,53 +0,0 @@
|
||||||
<div align="center">
|
|
||||||
|
|
||||||
<h1>PageTop Statics</h1>
|
|
||||||
|
|
||||||
<p>Librería para automatizar la recopilación de recursos estáticos en <strong>PageTop</strong>.</p>
|
|
||||||
|
|
||||||
[](#-licencia)
|
|
||||||
|
|
||||||
</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
|
|
||||||
|
|
||||||
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.
|
|
|
@ -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(())
|
|
||||||
}
|
|
|
@ -1,53 +0,0 @@
|
||||||
/*!
|
|
||||||
<div align="center">
|
|
||||||
|
|
||||||
<h1>PageTop Statics</h1>
|
|
||||||
|
|
||||||
<p>Librería para automatizar la recopilación de recursos estáticos en <strong>PageTop</strong>.</p>
|
|
||||||
|
|
||||||
[](#-licencia)
|
|
||||||
|
|
||||||
</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
|
|
||||||
|
|
||||||
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;
|
|
|
@ -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<P: AsRef<Path>, G: AsRef<Path>>(
|
|
||||||
project_dir: P,
|
|
||||||
filter: Option<fn(p: &Path) -> 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<P: AsRef<Path>, G: AsRef<Path>>(
|
|
||||||
project_dir: P,
|
|
||||||
filter: Option<fn(p: &Path) -> 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<P: AsRef<Path>>(
|
|
||||||
path: P,
|
|
||||||
filter: Option<fn(p: &Path) -> bool>,
|
|
||||||
) -> io::Result<Vec<(PathBuf, Metadata)>> {
|
|
||||||
collect_resources_nested(path, filter)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "sort")]
|
|
||||||
pub(crate) fn collect_resources<P: AsRef<Path>>(
|
|
||||||
path: P,
|
|
||||||
filter: Option<fn(p: &Path) -> bool>,
|
|
||||||
) -> io::Result<Vec<(PathBuf, Metadata)>> {
|
|
||||||
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<P: AsRef<Path>>(
|
|
||||||
path: P,
|
|
||||||
filter: Option<fn(p: &Path) -> bool>,
|
|
||||||
) -> io::Result<Vec<(PathBuf, Metadata)>> {
|
|
||||||
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<P: AsRef<Path>, 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<P: AsRef<Path>, 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: Write>(
|
|
||||||
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: Write>(f: &mut F) -> io::Result<()> {
|
|
||||||
writeln!(f, "}}")
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn generate_uses<F: Write>(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: Write>(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: Write>(f: &mut F, variable_name: &str) -> io::Result<()> {
|
|
||||||
writeln!(f, "{variable_name}")
|
|
||||||
}
|
|
|
@ -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<P: AsRef<Path>>(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<fn(p: &Path) -> bool>,
|
|
||||||
pub(crate) generated_filename: Option<PathBuf>,
|
|
||||||
pub(crate) generated_fn: Option<String>,
|
|
||||||
pub(crate) module_name: Option<String>,
|
|
||||||
pub(crate) count_per_module: Option<usize>,
|
|
||||||
}
|
|
||||||
|
|
||||||
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<P: AsRef<Path>>(&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<S>(&mut self, generated_fn: S) -> &mut Self
|
|
||||||
where
|
|
||||||
S: Into<String>,
|
|
||||||
{
|
|
||||||
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<S>(&mut self, module_name: S) -> &mut Self
|
|
||||||
where
|
|
||||||
S: Into<String>,
|
|
||||||
{
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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<String>,
|
|
||||||
inner: Rc<ResourceFilesInner>,
|
|
||||||
}
|
|
||||||
|
|
||||||
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<S: ToString>(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<dyn Guard> {
|
|
||||||
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<ResourceFilesInner>,
|
|
||||||
}
|
|
||||||
|
|
||||||
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<ResourceFilesInner>,
|
|
||||||
}
|
|
||||||
|
|
||||||
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<ServiceRequest> for ResourceFiles {
|
|
||||||
type Config = ();
|
|
||||||
type Response = ServiceResponse;
|
|
||||||
type Error = Error;
|
|
||||||
type Service = ResourceFilesService;
|
|
||||||
type InitError = ();
|
|
||||||
type Future = LocalBoxFuture<'static, Result<Self::Service, Self::InitError>>;
|
|
||||||
|
|
||||||
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<String>,
|
|
||||||
#[deref]
|
|
||||||
inner: Rc<ResourceFilesInner>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Service<ServiceRequest> for ResourceFilesService {
|
|
||||||
type Response = ServiceResponse;
|
|
||||||
type Error = Error;
|
|
||||||
type Future = Ready<Result<Self::Response, Self::Error>>;
|
|
||||||
|
|
||||||
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::<header::IfMatch>() {
|
|
||||||
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::<header::IfNoneMatch>() {
|
|
||||||
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<T: Send + Sync + 'static>() {}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_error_impl() {
|
|
||||||
// ensure backwards compatibility when migrating away from failure
|
|
||||||
assert_send_and_sync::<UriSegmentError>();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Return `BadRequest` for `UriSegmentError`
|
|
||||||
impl ResponseError for UriSegmentError {
|
|
||||||
fn error_response(&self) -> HttpResponse {
|
|
||||||
HttpResponse::new(StatusCode::BAD_REQUEST)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_pathbuf(path: &str) -> Result<String, UriSegmentError> {
|
|
||||||
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("/"))
|
|
||||||
}
|
|
|
@ -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<P, G, S>(
|
|
||||||
project_dir: P,
|
|
||||||
filter: Option<fn(p: &Path) -> bool>,
|
|
||||||
generated_filename: G,
|
|
||||||
module_name: &str,
|
|
||||||
fn_name: &str,
|
|
||||||
set_split_strategie: &mut S,
|
|
||||||
crate_name: &str,
|
|
||||||
) -> io::Result<()>
|
|
||||||
where
|
|
||||||
P: AsRef<Path>,
|
|
||||||
G: AsRef<Path>,
|
|
||||||
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<File> {
|
|
||||||
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)
|
|
||||||
}
|
|
|
@ -1,10 +0,0 @@
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>Document</title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
|
@ -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()
|
||||||
|
|
|
@ -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"]
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)]
|
||||||
|
|
|
@ -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
|
||||||
///
|
///
|
||||||
|
|
|
@ -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
|
||||||
///
|
///
|
||||||
|
|
|
@ -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
|
||||||
///
|
///
|
||||||
|
|
180
src/lib.rs
180
src/lib.rs
|
@ -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>
|
//!
|
||||||
|
//! [](#-license)
|
||||||
[](#-licencia)
|
//! [](https://docs.rs/pagetop)
|
||||||
[](https://docs.rs/pagetop)
|
//! [](https://crates.io/crates/pagetop)
|
||||||
[](https://crates.io/crates/pagetop)
|
//! [](https://crates.io/crates/pagetop)
|
||||||
[](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))]
|
||||||
#.
|
||||||
|
|
||||||
/// Contenedor para un conjunto de recursos embebidos.
|
|
||||||
#[derive(AutoDefault)]
|
|
||||||
pub struct StaticResources {
|
pub struct StaticResources {
|
||||||
bundle: HashMap<&'static str, StaticResource>,
|
bundle: HashMap<&'static str, static_files::Resource>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl StaticResources {
|
impl StaticResources {
|
||||||
/// Crea un contenedor para un conjunto de recursos generado por `build.rs` (consultar
|
/// Crea un contenedor para un conjunto de recursos generado por `build.rs` (consultar
|
||||||
/// [`pagetop_build`](https://docs.rs/pagetop-build)).
|
/// [`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 }
|
Self { bundle }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Deref for StaticResources {
|
impl Deref for StaticResources {
|
||||||
type Target = HashMap<&'static str, StaticResource>;
|
type Target = HashMap<&'static str, static_files::Resource>;
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
fn deref(&self) -> &Self::Target {
|
||||||
&self.bundle
|
&self.bundle
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
123
src/service.rs
123
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::dev::ServiceResponse as Response;
|
||||||
pub use actix_web::{cookie, http, rt, web};
|
pub use actix_web::{cookie, http, rt, web};
|
||||||
pub use actix_web::{App, Error, HttpMessage, HttpRequest, HttpResponse, HttpServer};
|
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)]
|
#[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,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}};
|
|
||||||
}
|
|
||||||
|
|
53
src/util.rs
53
src/util.rs
|
@ -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)]
|
||||||
|
|
|
@ -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(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,10 +35,6 @@ cd "$(dirname "$0")/.." || exit 1
|
||||||
# Determina ruta del archivo y ámbito de los archivos afectados para el crate
|
# Determina ruta del archivo y ámbito de los archivos afectados para el crate
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
case "$CRATE" in
|
case "$CRATE" in
|
||||||
pagetop-statics)
|
|
||||||
CHANGELOG_FILE="helpers/pagetop-statics/CHANGELOG.md"
|
|
||||||
PATH_FLAGS=(--include-path "helpers/pagetop-statics/**/*")
|
|
||||||
;;
|
|
||||||
pagetop-build)
|
pagetop-build)
|
||||||
CHANGELOG_FILE="helpers/pagetop-build/CHANGELOG.md"
|
CHANGELOG_FILE="helpers/pagetop-build/CHANGELOG.md"
|
||||||
PATH_FLAGS=(--include-path "helpers/pagetop-build/**/*")
|
PATH_FLAGS=(--include-path "helpers/pagetop-build/**/*")
|
||||||
|
@ -50,7 +46,6 @@ case "$CRATE" in
|
||||||
pagetop)
|
pagetop)
|
||||||
CHANGELOG_FILE="CHANGELOG.md"
|
CHANGELOG_FILE="CHANGELOG.md"
|
||||||
PATH_FLAGS=(
|
PATH_FLAGS=(
|
||||||
--exclude-path "helpers/pagetop-statics/**/*"
|
|
||||||
--exclude-path "helpers/pagetop-build/**/*"
|
--exclude-path "helpers/pagetop-build/**/*"
|
||||||
--exclude-path "helpers/pagetop-macros/**/*"
|
--exclude-path "helpers/pagetop-macros/**/*"
|
||||||
)
|
)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue