Añade librería para gestionar recursos estáticos

This commit is contained in:
Manuel Cillero 2025-08-08 18:49:02 +02:00
parent 8ed0e6621a
commit 4e277a2b03
20 changed files with 1370 additions and 41 deletions

View 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}")
}