use std::marker::PhantomData;
use std::path::Path;
use crate::file_loader::{build_embedded_registry, walk_dir};
use crate::style::{StylesheetRegistry, STYLESHEET_EXTENSIONS};
use crate::template::{walk_template_dir, TemplateRegistry};
use crate::theme::Theme;
#[derive(Debug, Clone, Copy)]
pub struct TemplateResource;
#[derive(Debug, Clone, Copy)]
pub struct StylesheetResource;
#[derive(Debug, Clone)]
pub struct EmbeddedSource<R> {
pub entries: &'static [(&'static str, &'static str)],
pub source_path: &'static str,
_marker: PhantomData<R>,
}
impl<R> EmbeddedSource<R> {
#[doc(hidden)]
pub const fn new(
entries: &'static [(&'static str, &'static str)],
source_path: &'static str,
) -> Self {
Self {
entries,
source_path,
_marker: PhantomData,
}
}
pub fn entries(&self) -> &'static [(&'static str, &'static str)] {
self.entries
}
pub fn source_path(&self) -> &'static str {
self.source_path
}
pub fn should_hot_reload(&self) -> bool {
cfg!(debug_assertions) && std::path::Path::new(self.source_path).exists()
}
}
pub type EmbeddedTemplates = EmbeddedSource<TemplateResource>;
pub type EmbeddedStyles = EmbeddedSource<StylesheetResource>;
impl From<EmbeddedTemplates> for TemplateRegistry {
fn from(source: EmbeddedTemplates) -> Self {
if source.should_hot_reload() {
let files = match walk_template_dir(source.source_path) {
Ok(files) => files,
Err(e) => {
eprintln!(
"Warning: Failed to walk templates directory '{}', using embedded: {}",
source.source_path, e
);
return TemplateRegistry::from_embedded_entries(source.entries);
}
};
let mut registry = TemplateRegistry::new();
if let Err(e) = registry.add_from_files(files) {
eprintln!(
"Warning: Failed to register templates from '{}', using embedded: {}",
source.source_path, e
);
return TemplateRegistry::from_embedded_entries(source.entries);
}
registry
} else {
TemplateRegistry::from_embedded_entries(source.entries)
}
}
}
impl From<EmbeddedStyles> for StylesheetRegistry {
fn from(source: EmbeddedStyles) -> Self {
if source.should_hot_reload() {
let files = match walk_dir(Path::new(source.source_path), STYLESHEET_EXTENSIONS) {
Ok(files) => files,
Err(e) => {
eprintln!(
"Warning: Failed to walk styles directory '{}', using embedded: {}",
source.source_path, e
);
return StylesheetRegistry::from_embedded_entries(source.entries)
.expect("embedded stylesheets should parse");
}
};
let entries: Vec<(String, String)> = files
.into_iter()
.filter_map(|file| match std::fs::read_to_string(&file.path) {
Ok(content) => Some((file.name_with_ext, content)),
Err(e) => {
eprintln!(
"Warning: Failed to read stylesheet '{}': {}",
file.path.display(),
e
);
None
}
})
.collect();
let entries_refs: Vec<(&str, &str)> = entries
.iter()
.map(|(n, c)| (n.as_str(), c.as_str()))
.collect();
let inline =
match build_embedded_registry(&entries_refs, STYLESHEET_EXTENSIONS, |yaml| {
Theme::from_yaml(yaml)
}) {
Ok(map) => map,
Err(e) => {
eprintln!(
"Warning: Failed to parse stylesheets from '{}', using embedded: {}",
source.source_path, e
);
return StylesheetRegistry::from_embedded_entries(source.entries)
.expect("embedded stylesheets should parse");
}
};
let mut registry = StylesheetRegistry::new();
registry.add_embedded(inline);
registry
} else {
StylesheetRegistry::from_embedded_entries(source.entries)
.expect("embedded stylesheets should parse")
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_embedded_source_new() {
static ENTRIES: &[(&str, &str)] = &[("test.jinja", "content")];
let source: EmbeddedTemplates = EmbeddedSource::new(ENTRIES, "src/templates");
assert_eq!(source.entries().len(), 1);
assert_eq!(source.source_path(), "src/templates");
}
#[test]
fn test_should_hot_reload_nonexistent_path() {
static ENTRIES: &[(&str, &str)] = &[];
let source: EmbeddedTemplates = EmbeddedSource::new(ENTRIES, "/nonexistent/path");
assert!(!source.should_hot_reload());
}
}