use core::fmt;
use std::{
collections::BTreeMap,
error::Error as StdError,
fs, io,
path::PathBuf,
sync::{Arc, RwLock},
time::{Duration, Instant},
};
use femstache_parser::ParseError;
use crate::template::Template;
#[derive(Debug)]
pub enum LoaderError {
Io(io::Error),
NoLoader,
ParseError(ParseError),
}
impl fmt::Display for LoaderError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
LoaderError::Io(err) => write!(f, "io error: {err}"),
LoaderError::NoLoader => write!(f, "no loader set"),
LoaderError::ParseError(err) => write!(f, "parse error: {err}"),
}
}
}
impl StdError for LoaderError {}
impl From<io::Error> for LoaderError {
fn from(err: io::Error) -> Self {
Self::Io(err)
}
}
impl From<ParseError> for LoaderError {
fn from(err: ParseError) -> Self {
Self::ParseError(err)
}
}
#[derive(Debug, Clone)]
pub struct TemplateLoaderInner {
root: PathBuf,
cache: BTreeMap<String, (Instant, Arc<Template>)>,
}
#[derive(Debug, Clone)]
pub struct TemplateLoader {
inner: Arc<RwLock<TemplateLoaderInner>>,
}
impl TemplateLoader {
pub fn new(root: impl Into<PathBuf>) -> Self {
Self {
inner: Arc::new(RwLock::new(TemplateLoaderInner {
root: root.into(),
cache: BTreeMap::new(),
})),
}
}
pub(crate) fn load(&self, path: &str) -> Result<Arc<Template>, LoaderError> {
let full_path = {
let inner = self.inner.read().unwrap();
if let Some((created, template)) = inner.cache.get(path)
&& created.elapsed() < Duration::from_secs(1)
{
return Ok(template.clone());
}
inner.root.join(path)
};
let source = fs::read_to_string(&full_path)?;
let template = Arc::new(Template::parse(&source)?);
{
let mut inner = self.inner.write().unwrap();
inner
.cache
.insert(path.to_owned(), (Instant::now(), template.clone()));
}
Ok(template)
}
}