✨ Añade gestión de recursos en binario o de disco
- Añade dosnuevas macros `include_files` y `include_files_service` para incluir los recursos preparados con `pagetop-build`. - Añade nueva función útil `absolute_dir` y sus tests correspondientes.
This commit is contained in:
parent
500f69fa4f
commit
34c4ac262f
7 changed files with 340 additions and 13 deletions
97
Cargo.lock
generated
97
Cargo.lock
generated
|
@ -19,6 +19,29 @@ dependencies = [
|
|||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "actix-files"
|
||||
version = "0.6.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0773d59061dedb49a8aed04c67291b9d8cf2fe0b60130a381aab53c6dd86e9be"
|
||||
dependencies = [
|
||||
"actix-http",
|
||||
"actix-service",
|
||||
"actix-utils",
|
||||
"actix-web",
|
||||
"bitflags",
|
||||
"bytes",
|
||||
"derive_more 0.99.20",
|
||||
"futures-core",
|
||||
"http-range",
|
||||
"log",
|
||||
"mime",
|
||||
"mime_guess",
|
||||
"percent-encoding",
|
||||
"pin-project-lite",
|
||||
"v_htmlescape",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "actix-http"
|
||||
version = "3.11.0"
|
||||
|
@ -34,7 +57,7 @@ dependencies = [
|
|||
"brotli",
|
||||
"bytes",
|
||||
"bytestring",
|
||||
"derive_more",
|
||||
"derive_more 2.0.1",
|
||||
"encoding_rs",
|
||||
"flate2",
|
||||
"foldhash",
|
||||
|
@ -149,7 +172,7 @@ dependencies = [
|
|||
"bytestring",
|
||||
"cfg-if",
|
||||
"cookie",
|
||||
"derive_more",
|
||||
"derive_more 2.0.1",
|
||||
"encoding_rs",
|
||||
"foldhash",
|
||||
"futures-core",
|
||||
|
@ -185,6 +208,18 @@ dependencies = [
|
|||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "actix-web-static-files"
|
||||
version = "4.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "adf6d1ef6d7a60e084f9e0595e2a5234abda14e76c105ecf8e2d0e8800c41a1f"
|
||||
dependencies = [
|
||||
"actix-web",
|
||||
"derive_more 0.99.20",
|
||||
"futures-util",
|
||||
"static-files",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "addr2line"
|
||||
version = "0.24.2"
|
||||
|
@ -502,6 +537,12 @@ dependencies = [
|
|||
"winnow",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "convert_case"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e"
|
||||
|
||||
[[package]]
|
||||
name = "cookie"
|
||||
version = "0.16.2"
|
||||
|
@ -590,6 +631,19 @@ dependencies = [
|
|||
"powerfmt",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive_more"
|
||||
version = "0.99.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6edb4b64a43d977b8e99788fe3a04d483834fba1215a7e02caa415b626497f7f"
|
||||
dependencies = [
|
||||
"convert_case",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"rustc_version",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive_more"
|
||||
version = "2.0.1"
|
||||
|
@ -632,12 +686,6 @@ dependencies = [
|
|||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dunce"
|
||||
version = "1.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813"
|
||||
|
||||
[[package]]
|
||||
name = "encoding_rs"
|
||||
version = "0.8.35"
|
||||
|
@ -809,6 +857,7 @@ dependencies = [
|
|||
"futures-task",
|
||||
"pin-project-lite",
|
||||
"pin-utils",
|
||||
"slab",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -942,6 +991,12 @@ dependencies = [
|
|||
"itoa",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "http-range"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "21dec9db110f5f872ed9699c3ecf50cf16f423502706ba5c72462e28d3157573"
|
||||
|
||||
[[package]]
|
||||
name = "httparse"
|
||||
version = "1.10.1"
|
||||
|
@ -1352,13 +1407,14 @@ checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
|
|||
|
||||
[[package]]
|
||||
name = "pagetop"
|
||||
version = "0.0.8"
|
||||
version = "0.0.9"
|
||||
dependencies = [
|
||||
"actix-files",
|
||||
"actix-web",
|
||||
"actix-web-static-files",
|
||||
"chrono",
|
||||
"colored",
|
||||
"config",
|
||||
"dunce",
|
||||
"figlet-rs",
|
||||
"fluent-templates",
|
||||
"itoa",
|
||||
|
@ -1742,6 +1798,15 @@ version = "2.1.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d"
|
||||
|
||||
[[package]]
|
||||
name = "rustc_version"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92"
|
||||
dependencies = [
|
||||
"semver",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "1.0.7"
|
||||
|
@ -1797,6 +1862,12 @@ version = "1.2.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0f7d95a54511e0c7be3f51e8867aa8cf35148d7b9445d44de2f943e2b206e749"
|
||||
|
||||
[[package]]
|
||||
name = "semver"
|
||||
version = "1.0.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.219"
|
||||
|
@ -2357,6 +2428,12 @@ dependencies = [
|
|||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "v_htmlescape"
|
||||
version = "0.15.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4e8257fbc510f0a46eb602c10215901938b5c2a7d5e70fc11483b1d3c9b5b18c"
|
||||
|
||||
[[package]]
|
||||
name = "valuable"
|
||||
version = "0.1.1"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "pagetop"
|
||||
version = "0.0.8"
|
||||
version = "0.0.9"
|
||||
edition = "2021"
|
||||
|
||||
description = """\
|
||||
|
@ -33,6 +33,8 @@ fluent-templates = "0.13.0"
|
|||
unic-langid = { version = "0.9.6", features = ["macros"] }
|
||||
|
||||
actix-web = "4.11.0"
|
||||
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"] }
|
||||
|
|
|
@ -34,6 +34,9 @@
|
|||
|
||||
pub use pagetop_macros::{html, main, test, AutoDefault};
|
||||
|
||||
/// Representa un conjunto de recursos asociados a `$STATIC` en [`include_files!`].
|
||||
pub type StaticResources = std::collections::HashMap<&'static str, static_files::Resource>;
|
||||
|
||||
// API *********************************************************************************************
|
||||
|
||||
// Funciones y macros útiles.
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
pub use crate::{html, main, test};
|
||||
|
||||
pub use crate::AutoDefault;
|
||||
pub use crate::{AutoDefault, StaticResources};
|
||||
|
||||
// MACROS.
|
||||
|
||||
|
@ -14,6 +14,8 @@ pub use crate::hm;
|
|||
pub use crate::include_config;
|
||||
// crate::locale
|
||||
pub use crate::include_locales;
|
||||
// crate::service
|
||||
pub use crate::{include_files, include_files_service};
|
||||
|
||||
// API.
|
||||
|
||||
|
|
126
src/service.rs
126
src/service.rs
|
@ -5,5 +5,129 @@ pub use actix_web::dev::Server;
|
|||
pub use actix_web::dev::ServiceFactory as Factory;
|
||||
pub use actix_web::dev::ServiceRequest as Request;
|
||||
pub use actix_web::dev::ServiceResponse as Response;
|
||||
pub use actix_web::{http, rt, test};
|
||||
pub use actix_web::{http, rt, web};
|
||||
pub use actix_web::{App, Error, HttpServer};
|
||||
|
||||
#[doc(hidden)]
|
||||
pub use actix_web::test;
|
||||
|
||||
/// Incluye en código un conjunto de recursos previamente preparado con `build.rs`.
|
||||
///
|
||||
/// # Formas de uso
|
||||
///
|
||||
/// * `include_files!(media)` - Incluye el conjunto de recursos llamado `media`. Normalmente se
|
||||
/// usará esta forma.
|
||||
///
|
||||
/// * `include_files!(BLOG_HM => blog)` - Asigna a la variable estática `BLOG_HM` un conjunto de
|
||||
/// recursos llamado `blog`.
|
||||
///
|
||||
/// # Argumentos
|
||||
///
|
||||
/// * `$bundle` – Nombre del conjunto de recursos generado por `build.rs` (consultar
|
||||
/// [`pagetop_build`](https://docs.rs/pagetop-build)).
|
||||
/// * `$STATIC` – Identificador para la variable estática de tipo
|
||||
/// [`StaticResources`](`crate::StaticResources`).
|
||||
///
|
||||
/// # Ejemplos
|
||||
///
|
||||
/// ```rust,ignore
|
||||
/// include_files!(assets); // Uso habitual.
|
||||
///
|
||||
/// include_files!(STATIC_ASSETS => assets);
|
||||
/// ```
|
||||
#[macro_export]
|
||||
macro_rules! include_files {
|
||||
// Forma 1: incluye un conjunto de recursos por nombre.
|
||||
( $bundle:ident ) => {
|
||||
$crate::util::paste! {
|
||||
mod [<static_files_ $bundle>] {
|
||||
include!(concat!(env!("OUT_DIR"), "/", stringify!($bundle), ".rs"));
|
||||
}
|
||||
}
|
||||
};
|
||||
// Forma 2: asigna a una variable estática $STATIC un conjunto de recursos.
|
||||
( $STATIC:ident => $bundle:ident ) => {
|
||||
$crate::util::paste! {
|
||||
mod [<static_files_ $bundle>] {
|
||||
include!(concat!(env!("OUT_DIR"), "/", stringify!($bundle), ".rs"));
|
||||
}
|
||||
pub static $STATIC: std::sync::LazyLock<StaticResources> = std::sync::LazyLock::new(
|
||||
[<static_files_ $bundle>]::$bundle
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// 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
|
||||
/// archivos de los recursos embebidos o directamente desde el sistema de ficheros, si se ha
|
||||
/// indicado una ruta válida a un directorio de recursos.
|
||||
///
|
||||
/// # Argumentos
|
||||
///
|
||||
/// * `$scfg` – Instancia de [`ServiceConfig`](crate::service::web::ServiceConfig) donde aplicar la
|
||||
/// configuración del servicio web.
|
||||
/// * `$bundle` – Nombre del conjunto de recursos incluido con [`include_files!`].
|
||||
/// * `$route` – Ruta URL de origen desde la que se servirán los archivos.
|
||||
/// * `[ $root, $relative ]` *(opcional)* – Directorio raíz y ruta relativa para construir la ruta
|
||||
/// absoluta donde buscar los archivos en el sistema de ficheros (ver
|
||||
/// [`absolute_dir`](crate::util::absolute_dir)). Si no existe, se usarán los recursos embebidos.
|
||||
///
|
||||
/// # Ejemplos
|
||||
///
|
||||
/// ```rust,ignore
|
||||
/// use pagetop::prelude::*;
|
||||
///
|
||||
/// include_files!(assets);
|
||||
///
|
||||
/// pub struct MyExtension;
|
||||
///
|
||||
/// impl ExtensionTrait for MyExtension {
|
||||
/// fn configure_service(&self, scfg: &mut service::web::ServiceConfig) {
|
||||
/// include_files_service!(scfg, assets => "/public");
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// Y para buscar los recursos en el sistema de ficheros (si existe la ruta absoluta):
|
||||
///
|
||||
/// ```rust,ignore
|
||||
/// include_files_service!(cfg, assets => "/public", ["/var/www", "assets"]);
|
||||
///
|
||||
/// // También desde el directorio actual de ejecución.
|
||||
/// include_files_service!(cfg, assets => "/public", ["", "static"]);
|
||||
/// ```
|
||||
#[macro_export]
|
||||
macro_rules! include_files_service {
|
||||
( $scfg:ident, $bundle:ident => $route:expr $(, [$root:expr, $relative:expr])? ) => {{
|
||||
$crate::util::paste! {
|
||||
let span = $crate::trace::debug_span!("Configuring static files ", path = $route);
|
||||
let _ = span.in_scope(|| {
|
||||
// Determina si se sirven recursos embebidos (`true`) o desde disco (`false`).
|
||||
#[allow(unused_mut)]
|
||||
let mut serve_embedded:bool = true;
|
||||
$(
|
||||
// Si `$root` y `$relative` no están vacíos, se comprueba la ruta absoluta.
|
||||
if !$root.is_empty() && !$relative.is_empty() {
|
||||
if let Ok(absolute) = $crate::util::absolute_dir($root, $relative) {
|
||||
// Servimos directamente desde el sistema de ficheros.
|
||||
$scfg.service($crate::service::ActixFiles::new(
|
||||
$route,
|
||||
absolute,
|
||||
).show_files_listing());
|
||||
serve_embedded = false
|
||||
}
|
||||
}
|
||||
)?
|
||||
// Si no se localiza el directorio, se exponen entonces los recursos embebidos.
|
||||
if serve_embedded {
|
||||
$scfg.service($crate::service::ResourceFiles::new(
|
||||
$route,
|
||||
[<static_files_ $bundle>]::$bundle(),
|
||||
));
|
||||
}
|
||||
});
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
|
49
src/util.rs
49
src/util.rs
|
@ -1,7 +1,56 @@
|
|||
//! Funciones y macros útiles.
|
||||
|
||||
use crate::trace;
|
||||
|
||||
use std::io;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
|
||||
/// Devuelve la ruta absoluta a un directorio existente.
|
||||
///
|
||||
/// * Si `relative_path` es una ruta absoluta, entonces se ignora `root_path`.
|
||||
/// * Si la ruta final es relativa, se convierte en absoluta respecto al directorio actual.
|
||||
/// * Devuelve error si la ruta no existe o no es un directorio.
|
||||
///
|
||||
/// # Ejemplo
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// use pagetop::prelude::*;
|
||||
///
|
||||
/// let root = "/home/user";
|
||||
/// let rel = "documents";
|
||||
/// println!("{:#?}", util::absolute_dir(root, rel));
|
||||
/// ```
|
||||
pub fn absolute_dir<P, Q>(root_path: P, relative_path: Q) -> io::Result<PathBuf>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
Q: AsRef<Path>,
|
||||
{
|
||||
// Une ambas rutas:
|
||||
// - Si `relative_path` es absoluta, el `join` la devuelve tal cual, descartando `root_path`.
|
||||
// - Si el resultado es aún relativo, lo será respecto al directorio actual.
|
||||
let candidate = root_path.as_ref().join(relative_path.as_ref());
|
||||
|
||||
// Resuelve `.`/`..`, enlaces simbólicos y obtiene la ruta absoluta en un único paso.
|
||||
let absolute_dir = candidate.canonicalize()?;
|
||||
|
||||
// Asegura que realmente es un directorio existente.
|
||||
if absolute_dir.is_dir() {
|
||||
Ok(absolute_dir)
|
||||
} else {
|
||||
Err({
|
||||
let msg = format!("Path \"{}\" is not a directory", absolute_dir.display());
|
||||
trace::warn!(msg);
|
||||
io::Error::new(io::ErrorKind::InvalidInput, msg)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// MACROS ÚTILES ***********************************************************************************
|
||||
|
||||
#[doc(hidden)]
|
||||
pub use paste::paste;
|
||||
|
||||
#[macro_export]
|
||||
/// Macro para construir una colección de pares clave-valor.
|
||||
///
|
||||
|
|
70
tests/util.rs
Normal file
70
tests/util.rs
Normal file
|
@ -0,0 +1,70 @@
|
|||
use pagetop::prelude::*;
|
||||
|
||||
use std::{fs, io};
|
||||
use tempfile::TempDir;
|
||||
|
||||
#[cfg(unix)]
|
||||
mod unix {
|
||||
use super::*;
|
||||
|
||||
#[pagetop::test]
|
||||
async fn ok_absolute_dir() -> io::Result<()> {
|
||||
let _app = service::test::init_service(Application::new().test()).await;
|
||||
|
||||
// /tmp/<rand>/sub
|
||||
let td = TempDir::new()?;
|
||||
let root = td.path();
|
||||
let sub = root.join("sub");
|
||||
fs::create_dir(&sub)?;
|
||||
|
||||
let abs = util::absolute_dir(root, "sub")?;
|
||||
assert_eq!(abs, std::fs::canonicalize(&sub)?);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[pagetop::test]
|
||||
async fn error_not_a_directory() -> io::Result<()> {
|
||||
let _app = service::test::init_service(Application::new().test()).await;
|
||||
|
||||
let td = TempDir::new()?;
|
||||
let file = td.path().join("foo.txt");
|
||||
fs::write(&file, b"data")?;
|
||||
|
||||
let err = util::absolute_dir(td.path(), "foo.txt").unwrap_err();
|
||||
assert_eq!(err.kind(), io::ErrorKind::InvalidInput);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
mod windows {
|
||||
use super::*;
|
||||
|
||||
#[pagetop::test]
|
||||
async fn ok_absolute_dir() -> io::Result<()> {
|
||||
let _app = service::test::init_service(Application::new().test()).await;
|
||||
|
||||
// C:\Users\…\Temp\…
|
||||
let td = TempDir::new()?;
|
||||
let root = td.path();
|
||||
let sub = root.join("sub");
|
||||
fs::create_dir(&sub)?;
|
||||
|
||||
let abs = util::absolute_dir(root, sub.as_path())?;
|
||||
assert_eq!(abs, std::fs::canonicalize(&sub)?);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[pagetop::test]
|
||||
async fn error_not_a_directory() -> io::Result<()> {
|
||||
let _app = service::test::init_service(Application::new().test()).await;
|
||||
|
||||
let td = TempDir::new()?;
|
||||
let file = td.path().join("foo.txt");
|
||||
fs::write(&file, b"data")?;
|
||||
|
||||
let err = util::absolute_dir(td.path(), "foo.txt").unwrap_err();
|
||||
assert_eq!(err.kind(), io::ErrorKind::InvalidInput);
|
||||
Ok(())
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue