Compare commits
15 commits
pagetop-v0
...
main
Author | SHA1 | Date | |
---|---|---|---|
bf2c298d18 | |||
8ac25a7938 | |||
946ad161f4 | |||
242f49ef52 | |||
0c987f2923 | |||
d810117fa6 | |||
b2420af278 | |||
5cb14d290a | |||
489dd8dfe2 | |||
298135b518 | |||
a0f92e9487 | |||
c6075fd312 | |||
47ea9d9f7d | |||
8ed0e6621a | |||
74f92a29f3 |
40 changed files with 2083 additions and 358 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 }} ([{{ sha7 }}]({{ base }}/commit/{{ commit.id }}){% if commit.author.name != "Manuel Cillero" %} - {{ commit.author.name }}{% endif %})
|
- {{ msg | trim }} {% if commit.author.name != "Manuel Cillero" %} - {{ commit.author.name }}{% endif %}
|
||||||
{% endfor %}{% endfor %}
|
{% endfor %}{% endfor %}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
26
CHANGELOG.md
26
CHANGELOG.md
|
@ -8,6 +8,32 @@ Resume la evolución del proyecto para usuarios y colaboradores, destacando nuev
|
||||||
correcciones, mejoras durante el desarrollo o cambios en la documentación. Cambios menores o
|
correcciones, mejoras durante el desarrollo o cambios en la documentación. Cambios menores o
|
||||||
internos pueden omitirse si no afectan al uso del proyecto.
|
internos pueden omitirse si no afectan al uso del proyecto.
|
||||||
|
|
||||||
|
## 0.3.0 (2025-08-16)
|
||||||
|
|
||||||
|
### Cambiado
|
||||||
|
|
||||||
|
- Redefine función para directorios absolutos
|
||||||
|
- Mejora la integración de archivos estáticos
|
||||||
|
|
||||||
|
### Documentado
|
||||||
|
|
||||||
|
- Cambia el formato para la documentación (#4)
|
||||||
|
|
||||||
|
## 0.2.0 (2025-08-09)
|
||||||
|
|
||||||
|
### Añadido
|
||||||
|
|
||||||
|
- Añade librería para gestionar recursos estáticos (#1)
|
||||||
|
- Añade soporte a changelog de `pagetop-statics` (#2)
|
||||||
|
|
||||||
|
### Documentado
|
||||||
|
|
||||||
|
- Corrige enlace del botón de licencia en la documentación
|
||||||
|
|
||||||
|
### Otros cambios
|
||||||
|
|
||||||
|
- Afina Cargo.toml para buscar la mejor categoría
|
||||||
|
|
||||||
## 0.1.0 (2025-08-06)
|
## 0.1.0 (2025-08-06)
|
||||||
|
|
||||||
- Versión inicial
|
- Versión inicial
|
||||||
|
|
54
Cargo.lock
generated
54
Cargo.lock
generated
|
@ -225,18 +225,6 @@ 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"
|
||||||
|
@ -658,9 +646,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crc32fast"
|
name = "crc32fast"
|
||||||
version = "1.4.2"
|
version = "1.5.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3"
|
checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
]
|
]
|
||||||
|
@ -1076,9 +1064,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "h2"
|
name = "h2"
|
||||||
version = "0.3.26"
|
version = "0.3.27"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8"
|
checksum = "0beca50380b1fc32983fc1cb4587bfa4bb9e78fc259aad4a0032d2080309222d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
"fnv",
|
"fnv",
|
||||||
|
@ -1569,12 +1557,11 @@ checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pagetop"
|
name = "pagetop"
|
||||||
version = "0.1.0"
|
version = "0.3.0"
|
||||||
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",
|
||||||
|
@ -1584,10 +1571,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",
|
||||||
|
@ -1600,15 +1587,15 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pagetop-build"
|
name = "pagetop-build"
|
||||||
version = "0.1.1"
|
version = "0.3.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"grass",
|
"grass",
|
||||||
"static-files",
|
"pagetop-statics",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pagetop-macros"
|
name = "pagetop-macros"
|
||||||
version = "0.1.0"
|
version = "0.1.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"proc-macro2-diagnostics",
|
"proc-macro2-diagnostics",
|
||||||
|
@ -1616,6 +1603,18 @@ 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"
|
||||||
|
@ -2178,17 +2177,6 @@ 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.1.0"
|
version = "0.3.0"
|
||||||
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", "gui", "development-tools", "asynchronous"]
|
categories = ["web-programming::http-server"]
|
||||||
keywords = ["pagetop", "web", "framework", "frontend", "ssr"]
|
keywords = ["pagetop", "web", "framework", "frontend", "ssr"]
|
||||||
|
|
||||||
repository.workspace = true
|
repository.workspace = true
|
||||||
|
@ -34,15 +34,14 @@ 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 = "4.11.0"
|
actix-web = { workspace = true, default-features = true }
|
||||||
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 = []
|
||||||
|
@ -60,6 +59,7 @@ resolver = "2"
|
||||||
members = [
|
members = [
|
||||||
"helpers/pagetop-build",
|
"helpers/pagetop-build",
|
||||||
"helpers/pagetop-macros",
|
"helpers/pagetop-macros",
|
||||||
|
"helpers/pagetop-statics",
|
||||||
]
|
]
|
||||||
|
|
||||||
[workspace.package]
|
[workspace.package]
|
||||||
|
@ -69,7 +69,8 @@ license = "MIT OR Apache-2.0"
|
||||||
authors = ["Manuel Cillero <manuel@cillero.es>"]
|
authors = ["Manuel Cillero <manuel@cillero.es>"]
|
||||||
|
|
||||||
[workspace.dependencies]
|
[workspace.dependencies]
|
||||||
static-files = "0.2.5"
|
actix-web = { version = "4.11.0", default-features = false }
|
||||||
|
|
||||||
pagetop-build = { version = "0.1", path = "helpers/pagetop-build" }
|
pagetop-build = { version = "0.3", path = "helpers/pagetop-build" }
|
||||||
pagetop-macros = { version = "0.1", path = "helpers/pagetop-macros" }
|
pagetop-macros = { version = "0.1", path = "helpers/pagetop-macros" }
|
||||||
|
pagetop-statics = { version = "0.1", path = "helpers/pagetop-statics" }
|
||||||
|
|
16
README.md
16
README.md
|
@ -6,11 +6,12 @@
|
||||||
|
|
||||||
<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>
|
||||||
|
|
||||||
[](#-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>
|
||||||
</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
|
||||||
|
@ -32,7 +33,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
|
```rust,no_run
|
||||||
use pagetop::prelude::*;
|
use pagetop::prelude::*;
|
||||||
|
|
||||||
#[pagetop::main]
|
#[pagetop::main]
|
||||||
|
@ -46,7 +47,7 @@ de bienvenida accesible desde un navegador local en la dirección `http://localh
|
||||||
|
|
||||||
Para personalizar el servicio, se puede crear una extensión de `PageTop` de la siguiente manera:
|
Para personalizar el servicio, se puede crear una extensión de `PageTop` de la siguiente manera:
|
||||||
|
|
||||||
```rust
|
```rust,no_run
|
||||||
use pagetop::prelude::*;
|
use pagetop::prelude::*;
|
||||||
|
|
||||||
struct HelloWorld;
|
struct HelloWorld;
|
||||||
|
@ -83,9 +84,14 @@ 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)**,
|
||||||
permite incluir fácilmente archivos estáticos o archivos SCSS compilados directamente en el
|
prepara los archivos estáticos o archivos SCSS compilados para incluirlos en el binario de las
|
||||||
binario de las aplicaciones `PageTop`.
|
aplicaciones `PageTop` durante la compilación de los ejecutables.
|
||||||
|
|
||||||
* **[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,6 +8,28 @@ Resume la evolución del proyecto para usuarios y colaboradores, destacando nuev
|
||||||
correcciones, mejoras durante el desarrollo o cambios en la documentación. Cambios menores o
|
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.1.1"
|
version = "0.3.0"
|
||||||
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", "web-programming"]
|
categories = ["development-tools::build-utils"]
|
||||||
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"
|
||||||
static-files.workspace = true
|
pagetop-statics.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>
|
||||||
|
|
||||||
[](#-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)
|
||||||
|
@ -18,6 +18,99 @@ 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,136 +1,129 @@
|
||||||
//! <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)
|
|
||||||
//! [](https://docs.rs/pagetop-build)
|
[](#-licencia)
|
||||||
//! [](https://crates.io/crates/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)
|
||||||
//! </div>
|
|
||||||
//!
|
</div>
|
||||||
//! ## Sobre PageTop
|
|
||||||
//!
|
## Sobre PageTop
|
||||||
//! [PageTop](https://docs.rs/pagetop) es un entorno de desarrollo que reivindica la esencia de la
|
|
||||||
//! web clásica para crear soluciones web SSR (*renderizadas en el servidor*) modulares, extensibles
|
[PageTop](https://docs.rs/pagetop) es un entorno de desarrollo que reivindica la esencia de la web
|
||||||
//! y configurables, basadas en HTML, CSS y JavaScript.
|
clásica para crear soluciones web SSR (*renderizadas en el servidor*) modulares, extensibles y
|
||||||
//!
|
configurables, basadas en HTML, CSS y JavaScript.
|
||||||
//!
|
|
||||||
//! # ⚡️ Guía rápida
|
|
||||||
//!
|
# ⚡️ Guía rápida
|
||||||
//! Añadir en el archivo `Cargo.toml` del proyecto:
|
|
||||||
//!
|
Añadir en el archivo `Cargo.toml` del proyecto:
|
||||||
//! ```toml
|
|
||||||
//! [build-dependencies]
|
```toml
|
||||||
//! pagetop-build = { ... }
|
[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:
|
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
|
|
||||||
//!
|
## 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:
|
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;
|
```rust,no_run
|
||||||
//!
|
use pagetop_build::StaticFilesBundle;
|
||||||
//! fn main() -> std::io::Result<()> {
|
|
||||||
//! StaticFilesBundle::from_dir("./static", None)
|
fn main() -> std::io::Result<()> {
|
||||||
//! .with_name("guides")
|
StaticFilesBundle::from_dir("./static", None)
|
||||||
//! .build()
|
.with_name("guides")
|
||||||
//! }
|
.build()
|
||||||
//! ```
|
}
|
||||||
//!
|
```
|
||||||
//! Si es necesario, se puede añadir un filtro para seleccionar archivos específicos de la carpeta,
|
|
||||||
//! por ejemplo:
|
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;
|
```rust,no_run
|
||||||
//! use std::path::Path;
|
use pagetop_build::StaticFilesBundle;
|
||||||
//!
|
use std::path::Path;
|
||||||
//! fn main() -> std::io::Result<()> {
|
|
||||||
//! fn only_pdf_files(path: &Path) -> bool {
|
fn main() -> std::io::Result<()> {
|
||||||
//! // Selecciona únicamente los archivos con extensión `.pdf`.
|
fn only_pdf_files(path: &Path) -> bool {
|
||||||
//! path.extension().map_or(false, |ext| ext == "pdf")
|
// 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")
|
StaticFilesBundle::from_dir("./static", Some(only_pdf_files))
|
||||||
//! .build()
|
.with_name("guides")
|
||||||
//! }
|
.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
|
|
||||||
//! con el archivo CSS minificado obtenido. Por ejemplo:
|
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;
|
```rust,no_run
|
||||||
//!
|
use pagetop_build::StaticFilesBundle;
|
||||||
//! fn main() -> std::io::Result<()> {
|
|
||||||
//! StaticFilesBundle::from_scss("./styles/main.scss", "styles.min.css")
|
fn main() -> std::io::Result<()> {
|
||||||
//! .with_name("main_styles")
|
StaticFilesBundle::from_scss("./styles/main.scss", "styles.min.css")
|
||||||
//! .build()
|
.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.
|
Este código compila el archivo `main.scss` de la carpeta `static` del proyecto, y prepara un recurso
|
||||||
//!
|
llamado `main_styles` que contiene el archivo `styles.min.css` obtenido.
|
||||||
//!
|
|
||||||
//! # 📦 Módulos generados
|
|
||||||
//!
|
# 📦 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)
|
Cada conjunto de recursos [`StaticFilesBundle`] genera un archivo en el directorio estándar
|
||||||
//! donde se incluyen los recursos necesarios para compilar el proyecto. Por ejemplo, para
|
[OUT_DIR](https://doc.rust-lang.org/cargo/reference/environment-variables.html#environment-variables-cargo-sets-for-build-scripts)
|
||||||
//! `with_name("guides")` se crea un archivo llamado `guides.rs`.
|
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.
|
|
||||||
//!
|
No hay ningún problema en generar más de un conjunto de recursos para cada proyecto siempre que se
|
||||||
//! Normalmente no habrá que acceder a estos módulos; bastará con incluirlos en el proyecto con
|
usen nombres diferentes.
|
||||||
//! [`include_files!`](https://docs.rs/pagetop/latest/pagetop/macro.include_files.html), y luego con
|
|
||||||
//! [`include_files_service!`](https://docs.rs/pagetop/latest/pagetop/macro.include_files_service.html)
|
Normalmente no habrá que acceder a estos módulos; sólo declarar el nombre del conjunto de recursos
|
||||||
//! configurar un servicio web para servir los recursos desde la ruta indicada:
|
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::*;
|
```rust,ignore
|
||||||
//!
|
use pagetop::prelude::*;
|
||||||
//! include_files!(guides);
|
|
||||||
//!
|
pub struct MyExtension;
|
||||||
//! pub struct MyExtension;
|
|
||||||
//!
|
impl Extension for MyExtension {
|
||||||
//! impl Extension for MyExtension {
|
// Servicio web que publica los recursos de `guides` en `/ruta/a/guides`.
|
||||||
//! // Servicio web que publica los recursos de `guides` en `/ruta/a/guides`.
|
fn configure_service(&self, scfg: &mut service::web::ServiceConfig) {
|
||||||
//! fn configure_service(&self, scfg: &mut service::web::ServiceConfig) {
|
static_files_service!(scfg, guides => "/ruta/a/guides");
|
||||||
//! 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 static_files::{resource_dir, ResourceDir};
|
use pagetop_statics::{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 utilizando
|
/// Prepara un conjunto de recursos para ser incluidos en el binario del proyecto.
|
||||||
/// [static_files](https://docs.rs/static-files/).
|
|
||||||
pub struct StaticFilesBundle {
|
pub struct StaticFilesBundle {
|
||||||
resource_dir: ResourceDir,
|
resource_dir: ResourceDir,
|
||||||
}
|
}
|
||||||
|
@ -163,8 +156,19 @@ impl StaticFilesBundle {
|
||||||
/// .build()
|
/// .build()
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
pub fn from_dir(dir: impl AsRef<str>, filter: Option<fn(p: &Path) -> bool>) -> Self {
|
pub fn from_dir<P>(dir: P, filter: Option<fn(&Path) -> bool>) -> Self
|
||||||
let mut resource_dir = resource_dir(dir.as_ref());
|
where
|
||||||
|
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,6 +8,17 @@ Resume la evolución del proyecto para usuarios y colaboradores, destacando nuev
|
||||||
correcciones, mejoras durante el desarrollo o cambios en la documentación. Cambios menores o
|
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.0"
|
version = "0.1.1"
|
||||||
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", "web-programming"]
|
categories = ["development-tools::procedural-macro-helpers"]
|
||||||
keywords = ["pagetop", "macros", "proc-macros", "codegen"]
|
keywords = ["pagetop", "macros", "proc-macros", "codegen"]
|
||||||
|
|
||||||
repository.workspace = true
|
repository.workspace = true
|
||||||
|
|
|
@ -4,16 +4,23 @@
|
||||||
|
|
||||||
<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>
|
||||||
|
|
||||||
## Descripción general
|
## Sobre PageTop
|
||||||
|
|
||||||
Entre sus macros se incluye una adaptación de [maud-macros](https://crates.io/crates/maud_macros)
|
[PageTop](https://docs.rs/pagetop) es un entorno de desarrollo que reivindica la esencia de la web
|
||||||
|
clásica para crear soluciones web SSR (*renderizadas en el servidor*) modulares, extensibles y
|
||||||
|
configurables, basadas en HTML, CSS y JavaScript.
|
||||||
|
|
||||||
|
## Créditos
|
||||||
|
|
||||||
|
Esta librería incluye entre sus macros una adaptación de
|
||||||
|
[maud-macros](https://crates.io/crates/maud_macros)
|
||||||
([0.27.0](https://github.com/lambda-fairy/maud/tree/v0.27.0/maud_macros)) de
|
([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
|
||||||
|
@ -21,12 +28,6 @@ Entre sus macros se incluye una adaptación de [maud-macros](https://crates.io/c
|
||||||
necesidad de referenciar `maud` o `smart_default` en las dependencias del archivo `Cargo.toml` de
|
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,21 +1,34 @@
|
||||||
//! <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)
|
|
||||||
//! [](https://docs.rs/pagetop-macros)
|
[](#-licencia)
|
||||||
//! [](https://crates.io/crates/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)
|
||||||
//! </div>
|
|
||||||
//!
|
</div>
|
||||||
//! ## Sobre PageTop
|
|
||||||
//!
|
## Sobre PageTop
|
||||||
//! [PageTop](https://docs.rs/pagetop) es un entorno de desarrollo que reivindica la esencia de la
|
|
||||||
//! web clásica para crear soluciones web SSR (*renderizadas en el servidor*) modulares, extensibles
|
[PageTop](https://docs.rs/pagetop) es un entorno de desarrollo que reivindica la esencia de la web
|
||||||
//! y configurables, basadas en HTML, CSS y JavaScript.
|
clásica para crear soluciones web SSR (*renderizadas en el servidor*) modulares, extensibles y
|
||||||
|
configurables, basadas en HTML, CSS y JavaScript.
|
||||||
|
|
||||||
|
## Créditos
|
||||||
|
|
||||||
|
Esta librería incluye entre sus macros una adaptación de
|
||||||
|
[maud-macros](https://crates.io/crates/maud_macros)
|
||||||
|
([0.27.0](https://github.com/lambda-fairy/maud/tree/v0.27.0/maud_macros)) de
|
||||||
|
[Chris Wong](https://crates.io/users/lambda-fairy) y una versión renombrada de
|
||||||
|
[SmartDefault](https://crates.io/crates/smart_default) (0.7.1) de
|
||||||
|
[Jane Doe](https://crates.io/users/jane-doe), llamada `AutoDefault`. Estas macros eliminan la
|
||||||
|
necesidad de referenciar `maud` o `smart_default` en las dependencias del archivo `Cargo.toml` de
|
||||||
|
cada proyecto `PageTop`.
|
||||||
|
*/
|
||||||
|
|
||||||
#, 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
|
33
helpers/pagetop-statics/Cargo.toml
Normal file
33
helpers/pagetop-statics/Cargo.toml
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
[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"
|
201
helpers/pagetop-statics/LICENSE-APACHE
Normal file
201
helpers/pagetop-statics/LICENSE-APACHE
Normal file
|
@ -0,0 +1,201 @@
|
||||||
|
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.
|
21
helpers/pagetop-statics/LICENSE-MIT
Normal file
21
helpers/pagetop-statics/LICENSE-MIT
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
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.
|
53
helpers/pagetop-statics/README.md
Normal file
53
helpers/pagetop-statics/README.md
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
<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.
|
43
helpers/pagetop-statics/build.rs
Normal file
43
helpers/pagetop-statics/build.rs
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
#![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(())
|
||||||
|
}
|
53
helpers/pagetop-statics/src/lib.rs
Normal file
53
helpers/pagetop-statics/src/lib.rs
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
/*!
|
||||||
|
<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;
|
249
helpers/pagetop-statics/src/resource.rs
Normal file
249
helpers/pagetop-statics/src/resource.rs
Normal file
|
@ -0,0 +1,249 @@
|
||||||
|
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}")
|
||||||
|
}
|
118
helpers/pagetop-statics/src/resource_dir.rs
Normal file
118
helpers/pagetop-statics/src/resource_dir.rs
Normal file
|
@ -0,0 +1,118 @@
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
396
helpers/pagetop-statics/src/resource_files.rs
Normal file
396
helpers/pagetop-statics/src/resource_files.rs
Normal file
|
@ -0,0 +1,396 @@
|
||||||
|
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("/"))
|
||||||
|
}
|
184
helpers/pagetop-statics/src/sets.rs
Normal file
184
helpers/pagetop-statics/src/sets.rs
Normal file
|
@ -0,0 +1,184 @@
|
||||||
|
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)
|
||||||
|
}
|
0
helpers/pagetop-statics/tests/file1.txt
Normal file
0
helpers/pagetop-statics/tests/file1.txt
Normal file
0
helpers/pagetop-statics/tests/file2.txt
Normal file
0
helpers/pagetop-statics/tests/file2.txt
Normal file
0
helpers/pagetop-statics/tests/file3.info
Normal file
0
helpers/pagetop-statics/tests/file3.info
Normal file
10
helpers/pagetop-statics/tests/index.html
Normal file
10
helpers/pagetop-statics/tests/index.html
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
<!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,11 +110,13 @@
|
||||||
//! }
|
//! }
|
||||||
//! ```
|
//! ```
|
||||||
|
|
||||||
|
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::{Path, PathBuf};
|
use std::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.
|
||||||
|
@ -125,25 +127,12 @@ 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(|| {
|
||||||
// Determina el directorio de configuración:
|
// CONFIG_DIR (si existe) o DEFAULT_CONFIG_DIR. Si no se puede resolver, se usa tal cual.
|
||||||
// - Usa CONFIG_DIR si está definido en el entorno (p.ej.: CONFIG_DIR=/etc/myapp ./myapp).
|
let dir = env::var_os("CONFIG_DIR").unwrap_or_else(|| DEFAULT_CONFIG_DIR.into());
|
||||||
// - Si no, intenta DEFAULT_CONFIG_DIR dentro del proyecto (en CARGO_MANIFEST_DIR).
|
let config_dir = util::resolve_absolute_dir(&dir).unwrap_or_else(|_| PathBuf::from(&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()
|
|
||||||
};
|
|
||||||
|
|
||||||
// Determina el modo de ejecución según la variable de entorno PAGETOP_RUN_MODE. Por defecto usa
|
// Modo de ejecución según la variable de entorno PAGETOP_RUN_MODE. Si no está definida, se usa
|
||||||
// DEFAULT_RUN_MODE si no está definida (p.ej.: PAGETOP_RUN_MODE=production ./myapp).
|
// por defecto, DEFAULT_RUN_MODE (p.ej.: PAGETOP_RUN_MODE=production).
|
||||||
let rm = env::var("PAGETOP_RUN_MODE").unwrap_or_else(|_| DEFAULT_RUN_MODE.into());
|
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, include_files, include_files_service, service, trace};
|
use crate::{global, service, static_files_service, trace};
|
||||||
|
|
||||||
use parking_lot::RwLock;
|
use parking_lot::RwLock;
|
||||||
|
|
||||||
|
@ -125,8 +125,6 @@ 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)]
|
||||||
|
@ -140,7 +138,5 @@ pub fn configure_services(scfg: &mut service::web::ServiceConfig) {
|
||||||
extension.configure_service(scfg);
|
extension.configure_service(scfg);
|
||||||
}
|
}
|
||||||
|
|
||||||
include_files_service!(
|
static_files_service!(scfg, [&global::SETTINGS.dev.pagetop_static_dir, assets] => "/");
|
||||||
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_project_dir" => "",
|
"dev.pagetop_static_dir" => "",
|
||||||
|
|
||||||
// [log]
|
// [log]
|
||||||
"log.enabled" => true,
|
"log.enabled" => true,
|
||||||
|
@ -68,11 +68,15 @@ 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 {
|
||||||
/// Los archivos estáticos requeridos por `PageTop` se integran por defecto en el binario
|
/// Directorio desde el que servir los archivos estáticos de `PageTop`.
|
||||||
/// ejecutable. Sin embargo, durante el desarrollo puede resultar útil servirlos desde su propio
|
///
|
||||||
/// directorio para evitar recompilar cada vez que se modifican. En ese caso, este ajuste debe
|
/// Por defecto, los archivos se integran en el binario de la aplicación. Si aquí se indica una
|
||||||
/// indicar la ruta absoluta al directorio raíz del proyecto.
|
/// ruta válida, ya sea absoluta o relativa al directorio del proyecto o del binario en
|
||||||
pub pagetop_project_dir: String,
|
/// ejecución, se servirán desde el sistema de ficheros en su lugar. Esto es especialmente útil
|
||||||
|
/// en desarrollo, ya que evita recompilar el proyecto por cambios en estos archivos.
|
||||||
|
///
|
||||||
|
/// Si la cadena está vacía, se ignora este ajuste.
|
||||||
|
pub pagetop_static_dir: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
|
|
|
@ -12,8 +12,7 @@ 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
|
||||||
/// > incluirse en el proyecto utilizando [`include_files!`](crate::include_files) y servirse con
|
/// > servirse usando [`static_files_service!`](crate::static_files_service).
|
||||||
/// > [`include_files_service!`](crate::include_files_service).
|
|
||||||
///
|
///
|
||||||
/// # Ejemplo
|
/// # Ejemplo
|
||||||
///
|
///
|
||||||
|
|
|
@ -30,8 +30,7 @@ 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 incluirse en el proyecto utilizando [`include_files!`](crate::include_files) y servirse
|
/// > Pueden servirse usando [`static_files_service!`](crate::static_files_service).
|
||||||
/// > con [`include_files_service!`](crate::include_files_service).
|
|
||||||
///
|
///
|
||||||
/// # Ejemplo
|
/// # Ejemplo
|
||||||
///
|
///
|
||||||
|
|
|
@ -55,8 +55,7 @@ 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
|
||||||
/// > incluirse en el proyecto utilizando [`include_files!`](crate::include_files) y servirse con
|
/// > servirse usando [`static_files_service!`](crate::static_files_service).
|
||||||
/// > [`include_files_service!`](crate::include_files_service).
|
|
||||||
///
|
///
|
||||||
/// # Ejemplo
|
/// # Ejemplo
|
||||||
///
|
///
|
||||||
|
|
180
src/lib.rs
180
src/lib.rs
|
@ -1,86 +1,89 @@
|
||||||
//! <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)
|
|
||||||
//! [](https://docs.rs/pagetop)
|
[](#-licencia)
|
||||||
//! [](https://crates.io/crates/pagetop)
|
[](https://docs.rs/pagetop)
|
||||||
//! [](https://crates.io/crates/pagetop)
|
[](https://crates.io/crates/pagetop)
|
||||||
//!
|
[](https://crates.io/crates/pagetop)
|
||||||
//! <br>
|
|
||||||
//! </div>
|
<br>
|
||||||
//!
|
</div>
|
||||||
//! `PageTop` reivindica la esencia de la web clásica usando [Rust](https://www.rust-lang.org/es)
|
|
||||||
//! para la creación de soluciones web SSR (*renderizadas en el servidor*) basadas en HTML, CSS y
|
`PageTop` reivindica la esencia de la web clásica usando [Rust](https://www.rust-lang.org/es) para
|
||||||
//! JavaScript. Ofrece un conjunto de herramientas que los desarrolladores pueden implementar,
|
la creación de soluciones web SSR (*renderizadas en el servidor*) basadas en HTML, CSS y JavaScript.
|
||||||
//! extender o adaptar según las necesidades de cada proyecto, incluyendo:
|
Ofrece un conjunto de herramientas que los desarrolladores pueden implementar, extender o adaptar
|
||||||
//!
|
según las necesidades de cada proyecto, incluyendo:
|
||||||
//! * **Acciones** (*actions*): alteran la lógica interna de una funcionalidad interceptando su
|
|
||||||
//! flujo de ejecución.
|
* **Acciones** (*actions*): alteran la lógica interna de una funcionalidad interceptando su flujo
|
||||||
//! * **Componentes** (*components*): encapsulan HTML, CSS y JavaScript en unidades funcionales,
|
de ejecución.
|
||||||
//! configurables y reutilizables.
|
* **Componentes** (*components*): encapsulan HTML, CSS y JavaScript en unidades funcionales,
|
||||||
//! * **Extensiones** (*extensions*): añaden, extienden o personalizan funcionalidades usando las
|
configurables y reutilizables.
|
||||||
//! APIs de `PageTop` o de terceros.
|
* **Extensiones** (*extensions*): añaden, extienden o personalizan funcionalidades usando las APIs
|
||||||
//! * **Temas** (*themes*): son extensiones que permiten modificar la apariencia de páginas y
|
de `PageTop` o de terceros.
|
||||||
//! componentes sin comprometer su funcionalidad.
|
* **Temas** (*themes*): son extensiones que permiten modificar la apariencia de páginas y
|
||||||
//!
|
componentes sin comprometer su funcionalidad.
|
||||||
//! # ⚡️ Guía rápida
|
|
||||||
//!
|
|
||||||
//! La aplicación más sencilla de `PageTop` se ve así:
|
# ⚡️ Guía rápida
|
||||||
//!
|
|
||||||
//! ```rust,no_run
|
La aplicación más sencilla de `PageTop` se ve así:
|
||||||
//! use pagetop::prelude::*;
|
|
||||||
//!
|
```rust,no_run
|
||||||
//! #[pagetop::main]
|
use pagetop::prelude::*;
|
||||||
//! async fn main() -> std::io::Result<()> {
|
|
||||||
//! Application::new().run()?.await
|
#[pagetop::main]
|
||||||
//! }
|
async fn main() -> std::io::Result<()> {
|
||||||
//! ```
|
Application::new().run()?.await
|
||||||
//!
|
}
|
||||||
//! Este código arranca el servidor de `PageTop`. Con la
|
```
|
||||||
//! [configuración por defecto](crate::global::SETTINGS), muestra una página de bienvenida accesible
|
|
||||||
//! desde un navegador local en la dirección `http://localhost:8080`.
|
Este código arranca el servidor de `PageTop`. Con la configuración por defecto, muestra una página
|
||||||
//!
|
de bienvenida accesible desde un navegador local en la dirección `http://localhost:8080`.
|
||||||
//! Para personalizar el servicio, se puede crear una extensión de `PageTop` de la siguiente manera:
|
|
||||||
//!
|
Para personalizar el servicio, se puede crear una extensión de `PageTop` de la siguiente manera:
|
||||||
//! ```rust,no_run
|
|
||||||
//! use pagetop::prelude::*;
|
```rust,no_run
|
||||||
//!
|
use pagetop::prelude::*;
|
||||||
//! struct HelloWorld;
|
|
||||||
//!
|
struct HelloWorld;
|
||||||
//! impl Extension for HelloWorld {
|
|
||||||
//! fn configure_service(&self, scfg: &mut service::web::ServiceConfig) {
|
impl Extension for HelloWorld {
|
||||||
//! scfg.route("/", service::web::get().to(hello_world));
|
fn configure_service(&self, scfg: &mut service::web::ServiceConfig) {
|
||||||
//! }
|
scfg.route("/", service::web::get().to(hello_world));
|
||||||
//! }
|
}
|
||||||
//!
|
}
|
||||||
//! async fn hello_world(request: HttpRequest) -> ResultPage<Markup, ErrorPage> {
|
|
||||||
//! Page::new(Some(request))
|
async fn hello_world(request: HttpRequest) -> ResultPage<Markup, ErrorPage> {
|
||||||
//! .with_component(Html::with(move |_| html! { h1 { "Hello world!" } }))
|
Page::new(Some(request))
|
||||||
//! .render()
|
.with_component(Html::with(move |_| html! { h1 { "Hello World!" } }))
|
||||||
//! }
|
.render()
|
||||||
//!
|
}
|
||||||
//! #[pagetop::main]
|
|
||||||
//! async fn main() -> std::io::Result<()> {
|
#[pagetop::main]
|
||||||
//! Application::prepare(&HelloWorld).run()?.await
|
async fn main() -> std::io::Result<()> {
|
||||||
//! }
|
Application::prepare(&HelloWorld).run()?.await
|
||||||
//! ```
|
}
|
||||||
//!
|
```
|
||||||
//! Este programa implementa una extensión llamada `HelloWorld` que sirve una página web en la ruta
|
|
||||||
//! raíz (`/`) mostrando el texto "Hello world!" dentro de un elemento HTML `<h1>`.
|
Este programa implementa una extensión llamada `HelloWorld` que sirve una página web en la ruta raíz
|
||||||
//!
|
(`/`) mostrando el texto "Hello world!" dentro de un elemento HTML `<h1>`.
|
||||||
//! # 🧩 Gestión de Dependencias
|
|
||||||
//!
|
|
||||||
//! Los proyectos que utilizan `PageTop` gestionan las dependencias con `cargo`, como cualquier otro
|
# 🧩 Gestión de Dependencias
|
||||||
//! proyecto en Rust.
|
|
||||||
//!
|
Los proyectos que utilizan `PageTop` gestionan las dependencias con `cargo`, como cualquier otro
|
||||||
//! Sin embargo, es fundamental que cada extensión declare explícitamente sus
|
proyecto en Rust.
|
||||||
//! [dependencias](core::extension::Extension::dependencies), si las tiene, para que `PageTop` pueda
|
|
||||||
//! estructurar e inicializar la aplicación de forma modular.
|
Sin embargo, es fundamental que cada extensión declare explícitamente sus
|
||||||
|
[dependencias](core::extension::Extension::dependencies), si las tiene, para que `PageTop` pueda
|
||||||
|
estructurar e inicializar la aplicación de forma modular.
|
||||||
|
*/
|
||||||
|
|
||||||
#![cfg_attr(docsrs, feature(doc_cfg))]
|
#![cfg_attr(docsrs, feature(doc_cfg))]
|
||||||
#.
|
pub use pagetop_statics::{resource, StaticResource};
|
||||||
|
|
||||||
|
/// Contenedor para un conjunto de recursos embebidos.
|
||||||
|
#[derive(AutoDefault)]
|
||||||
pub struct StaticResources {
|
pub struct StaticResources {
|
||||||
bundle: HashMap<&'static str, static_files::Resource>,
|
bundle: HashMap<&'static str, StaticResource>,
|
||||||
}
|
}
|
||||||
|
|
||||||
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, static_files::Resource>) -> Self {
|
pub fn new(bundle: HashMap<&'static str, StaticResource>) -> Self {
|
||||||
Self { bundle }
|
Self { bundle }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Deref for StaticResources {
|
impl Deref for StaticResources {
|
||||||
type Target = HashMap<&'static str, static_files::Resource>;
|
type Target = HashMap<&'static str, StaticResource>;
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
fn deref(&self) -> &Self::Target {
|
||||||
&self.bundle
|
&self.bundle
|
||||||
|
|
|
@ -15,7 +15,8 @@ pub use crate::include_config;
|
||||||
// crate::locale
|
// crate::locale
|
||||||
pub use crate::include_locales;
|
pub use crate::include_locales;
|
||||||
// crate::service
|
// crate::service
|
||||||
pub use crate::{include_files, include_files_service};
|
#[allow(deprecated)]
|
||||||
|
pub use crate::{include_files, include_files_service, static_files_service};
|
||||||
// crate::core::action
|
// crate::core::action
|
||||||
pub use crate::actions_boxed;
|
pub use crate::actions_boxed;
|
||||||
|
|
||||||
|
|
123
src/service.rs
123
src/service.rs
|
@ -8,13 +8,16 @@ 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 actix_web_files::Files as ActixFiles;
|
||||||
pub use actix_web_static_files::ResourceFiles;
|
|
||||||
|
pub use pagetop_statics::ResourceFiles;
|
||||||
|
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
pub use actix_web::test;
|
pub use actix_web::test;
|
||||||
|
|
||||||
|
/// **Obsoleto desde la versión 0.3.0**: usar [`static_files_service!`](crate::static_files_service)
|
||||||
|
/// en su lugar.
|
||||||
|
///
|
||||||
/// Incluye en código un conjunto de recursos previamente preparado con `build.rs`.
|
/// Incluye en código un conjunto de recursos previamente preparado con `build.rs`.
|
||||||
///
|
///
|
||||||
/// # Formas de uso
|
/// # Formas de uso
|
||||||
|
@ -39,6 +42,7 @@ 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.
|
||||||
|
@ -63,6 +67,9 @@ macro_rules! include_files {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// **Obsoleto desde la versión 0.3.0**: usar [`static_files_service!`](crate::static_files_service)
|
||||||
|
/// en su lugar.
|
||||||
|
///
|
||||||
/// Configura un servicio web para publicar los recursos embebidos con [`include_files!`].
|
/// 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
|
||||||
|
@ -104,6 +111,7 @@ 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])? ) => {{
|
||||||
|
@ -137,3 +145,114 @@ macro_rules! include_files_service {
|
||||||
}
|
}
|
||||||
}};
|
}};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Configura un servicio web para publicar archivos estáticos.
|
||||||
|
///
|
||||||
|
/// La macro ofrece tres modos para configurar el servicio:
|
||||||
|
///
|
||||||
|
/// - **Sistema de ficheros o embebido** (`[$path, $bundle]`): trata de servir los archivos desde
|
||||||
|
/// `$path`; y si es una cadena vacía, no existe o no es un directorio, entonces usará el conjunto
|
||||||
|
/// de recursos `$bundle` integrado en el binario.
|
||||||
|
/// - **Sólo embebido** (`[$bundle]`): sirve siempre desde el conjunto de recursos `$bundle`
|
||||||
|
/// integrado en el binario.
|
||||||
|
/// - **Sólo sistema de ficheros** (`$path`): sin usar corchetes, sirve únicamente desde el sistema
|
||||||
|
/// de ficheros si existe; en otro caso no registra el servicio.
|
||||||
|
///
|
||||||
|
/// # Argumentos
|
||||||
|
///
|
||||||
|
/// * `$scfg` – Instancia de [`ServiceConfig`](crate::service::web::ServiceConfig) donde aplicar la
|
||||||
|
/// configuración.
|
||||||
|
/// * `$path` – Ruta al directorio local con los archivos estáticos.
|
||||||
|
/// * `$bundle` – Nombre del conjunto de recursos que esta macro integra en el binario.
|
||||||
|
/// * `$route` – Ruta URL base desde la que se servirán los archivos.
|
||||||
|
///
|
||||||
|
/// # Ejemplos
|
||||||
|
///
|
||||||
|
/// ```rust,ignore
|
||||||
|
/// use pagetop::prelude::*;
|
||||||
|
///
|
||||||
|
/// pub struct MyExtension;
|
||||||
|
///
|
||||||
|
/// impl Extension for MyExtension {
|
||||||
|
/// fn configure_service(&self, scfg: &mut service::web::ServiceConfig) {
|
||||||
|
/// // Forma 1) Sistema de ficheros o embebido.
|
||||||
|
/// static_files_service!(scfg, ["/var/www/static", assets] => "/public");
|
||||||
|
///
|
||||||
|
/// // Forma 2) Siempre embebido.
|
||||||
|
/// static_files_service!(scfg, [assets] => "/public");
|
||||||
|
///
|
||||||
|
/// // Forma 3) Sólo sistema de ficheros (no requiere `assets`).
|
||||||
|
/// static_files_service!(scfg, "/var/www/static" => "/public");
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! static_files_service {
|
||||||
|
// Forma 1: primero intenta servir desde el sistema de ficheros; si falla, sirve embebido.
|
||||||
|
( $scfg:ident, [$path:expr, $bundle:ident] => $route:expr $(,)? ) => {{
|
||||||
|
let span = $crate::trace::debug_span!(
|
||||||
|
"Configuring static files (file system or embedded)",
|
||||||
|
mode = "fs_or_embedded",
|
||||||
|
route = $route,
|
||||||
|
);
|
||||||
|
let _ = span.in_scope(|| {
|
||||||
|
let mut serve_embedded: bool = true;
|
||||||
|
if !::std::path::Path::new(&$path).as_os_str().is_empty() {
|
||||||
|
if let Ok(absolute) = $crate::util::resolve_absolute_dir($path) {
|
||||||
|
$scfg.service($crate::service::ActixFiles::new($route, absolute));
|
||||||
|
serve_embedded = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if serve_embedded {
|
||||||
|
$crate::util::paste! {
|
||||||
|
mod [<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,35 +2,44 @@
|
||||||
|
|
||||||
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 ********************************************************************************
|
||||||
|
|
||||||
/// Devuelve la ruta absoluta a un directorio existente.
|
/// Resuelve y valida la ruta de un directorio existente, devolviendo una ruta absoluta.
|
||||||
///
|
///
|
||||||
/// * Si `relative_path` es una ruta absoluta, entonces se ignora `root_path`.
|
/// - Si la ruta es relativa, se resuelve respecto al directorio del proyecto según la variable de
|
||||||
/// * Si la ruta final es relativa, se convierte en absoluta respecto al directorio actual.
|
/// entorno `CARGO_MANIFEST_DIR` (si existe) o, en su defecto, respecto al directorio actual de
|
||||||
/// * Devuelve error si la ruta no existe o no es un directorio.
|
/// trabajo.
|
||||||
|
/// - Normaliza y valida la ruta final (resuelve `.`/`..` y enlaces simbólicos).
|
||||||
|
/// - Devuelve error si la ruta no existe o no es un directorio.
|
||||||
///
|
///
|
||||||
/// # Ejemplo
|
/// # Ejemplos
|
||||||
///
|
///
|
||||||
/// ```rust,no_run
|
/// ```rust,no_run
|
||||||
/// use pagetop::prelude::*;
|
/// use pagetop::prelude::*;
|
||||||
///
|
///
|
||||||
/// let root = "/home/user";
|
/// // Ruta relativa, se resuelve respecto a CARGO_MANIFEST_DIR o al directorio actual (`cwd`).
|
||||||
/// let rel = "documents";
|
/// println!("{:#?}", util::resolve_absolute_dir("documents"));
|
||||||
/// println!("{:#?}", util::absolute_dir(root, rel));
|
///
|
||||||
|
/// // Ruta absoluta, se normaliza y valida tal cual.
|
||||||
|
/// println!("{:#?}", util::resolve_absolute_dir("/var/www"));
|
||||||
/// ```
|
/// ```
|
||||||
pub fn absolute_dir<P, Q>(root_path: P, relative_path: Q) -> io::Result<PathBuf>
|
pub fn resolve_absolute_dir<P: AsRef<Path>>(path: P) -> io::Result<PathBuf> {
|
||||||
where
|
let path = path.as_ref();
|
||||||
P: AsRef<Path>,
|
|
||||||
Q: AsRef<Path>,
|
let candidate = if path.is_absolute() {
|
||||||
{
|
path.to_path_buf()
|
||||||
// Une ambas rutas:
|
} else {
|
||||||
// - Si `relative_path` es absoluta, el `join` la devuelve tal cual, descartando `root_path`.
|
// Directorio base CARGO_MANIFEST_DIR si está disponible; o current_dir() en su defecto.
|
||||||
// - Si el resultado es aún relativo, lo será respecto al directorio actual.
|
env::var_os("CARGO_MANIFEST_DIR")
|
||||||
let candidate = root_path.as_ref().join(relative_path.as_ref());
|
.map(PathBuf::from)
|
||||||
|
.or_else(|| env::current_dir().ok())
|
||||||
|
.unwrap_or_else(|| PathBuf::from("."))
|
||||||
|
.join(path)
|
||||||
|
};
|
||||||
|
|
||||||
// Resuelve `.`/`..`, enlaces simbólicos y obtiene la ruta absoluta en un único paso.
|
// Resuelve `.`/`..`, enlaces simbólicos y obtiene la ruta absoluta en un único paso.
|
||||||
let absolute_dir = candidate.canonicalize()?;
|
let absolute_dir = candidate.canonicalize()?;
|
||||||
|
@ -47,6 +56,16 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Devuelve la ruta absoluta a un directorio existente.
|
||||||
|
#[deprecated(since = "0.3.0", note = "Use [`resolve_absolute_dir`] instead")]
|
||||||
|
pub fn absolute_dir<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::{fs, io};
|
use std::{env, fs, io};
|
||||||
use tempfile::TempDir;
|
use tempfile::TempDir;
|
||||||
|
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
|
@ -13,15 +13,36 @@ mod unix {
|
||||||
|
|
||||||
// /tmp/<rand>/sub
|
// /tmp/<rand>/sub
|
||||||
let td = TempDir::new()?;
|
let td = TempDir::new()?;
|
||||||
let root = td.path();
|
let sub = td.path().join("sub");
|
||||||
let sub = root.join("sub");
|
|
||||||
fs::create_dir(&sub)?;
|
fs::create_dir(&sub)?;
|
||||||
|
|
||||||
let abs = util::absolute_dir(root, "sub")?;
|
let abs = util::resolve_absolute_dir(&sub)?;
|
||||||
assert_eq!(abs, std::fs::canonicalize(&sub)?);
|
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;
|
||||||
|
@ -30,7 +51,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::absolute_dir(td.path(), "foo.txt").unwrap_err();
|
let err = util::resolve_absolute_dir(&file).unwrap_err();
|
||||||
assert_eq!(err.kind(), io::ErrorKind::InvalidInput);
|
assert_eq!(err.kind(), io::ErrorKind::InvalidInput);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -46,15 +67,36 @@ mod windows {
|
||||||
|
|
||||||
// C:\Users\...\Temp\...
|
// C:\Users\...\Temp\...
|
||||||
let td = TempDir::new()?;
|
let td = TempDir::new()?;
|
||||||
let root = td.path();
|
let sub = td.path().join("sub");
|
||||||
let sub = root.join("sub");
|
|
||||||
fs::create_dir(&sub)?;
|
fs::create_dir(&sub)?;
|
||||||
|
|
||||||
let abs = util::absolute_dir(root, sub.as_path())?;
|
let abs = util::resolve_absolute_dir(&sub)?;
|
||||||
assert_eq!(abs, std::fs::canonicalize(&sub)?);
|
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;
|
||||||
|
@ -63,7 +105,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::absolute_dir(td.path(), "foo.txt").unwrap_err();
|
let err = util::resolve_absolute_dir(&file).unwrap_err();
|
||||||
assert_eq!(err.kind(), io::ErrorKind::InvalidInput);
|
assert_eq!(err.kind(), io::ErrorKind::InvalidInput);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,6 +35,10 @@ 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/**/*")
|
||||||
|
@ -46,6 +50,7 @@ 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