use core::fmt;
use std::{
collections::BTreeMap,
error::Error as StdError,
fs, io,
path::{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 FilesystemSource {
root: PathBuf,
}
impl FilesystemSource {
fn load(&self, path: impl AsRef<Path>) -> Result<String, LoaderError> {
fs::read_to_string(self.root.join(path)).map_err(Into::into)
}
}
#[derive(Debug, Clone)]
pub struct MapSource {
map: BTreeMap<PathBuf, String>,
}
impl MapSource {
pub fn load(&self, path: impl AsRef<Path>) -> Result<String, LoaderError> {
self.map
.get(path.as_ref())
.map(|s| s.clone())
.ok_or_else(|| {
LoaderError::Io(io::Error::new(
io::ErrorKind::NotFound,
"template not present in map",
))
})
}
}
#[derive(Debug, Clone)]
pub enum TemplateSource {
#[doc(hidden)]
Filesystem(FilesystemSource),
#[doc(hidden)]
Map(MapSource),
}
impl TemplateSource {
pub fn filesystem(root: impl Into<PathBuf>) -> Self {
Self::Filesystem(FilesystemSource { root: root.into() })
}
pub fn map(map: BTreeMap<PathBuf, String>) -> Self {
Self::Map(MapSource { map })
}
fn load(&self, path: impl AsRef<Path>) -> Result<String, LoaderError> {
match self {
TemplateSource::Filesystem(source) => source.load(path),
TemplateSource::Map(source) => source.load(path),
}
}
}
#[derive(Debug, Clone)]
pub struct TemplateLoaderInner {
cache: BTreeMap<String, (Instant, Arc<Template>)>,
}
#[derive(Debug, Clone)]
pub struct TemplateLoader {
source: TemplateSource,
inner: Arc<RwLock<TemplateLoaderInner>>,
}
impl TemplateLoader {
pub fn new(source: TemplateSource) -> Self {
Self {
source,
inner: Arc::new(RwLock::new(TemplateLoaderInner {
cache: BTreeMap::new(),
})),
}
}
pub(crate) fn load(&self, path: &str) -> Result<Arc<Template>, LoaderError> {
{
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());
}
}
let source = self.source.load(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)
}
}
}