use std::borrow::Cow;
use std::collections::hash_map::Entry;
use std::collections::{BTreeMap, HashMap};
use std::fmt;
use std::fs;
use std::io;
use std::path::Path;
use std::path::PathBuf;
use std::sync::{Arc, Mutex};
use std::time::SystemTime;
use crate::vendor::self_cell::self_cell;
use crate::compiler::instructions::Instructions;
use crate::error::{Error, ErrorKind};
use crate::template::CompiledTemplate;
use crate::template::TemplateConfig;
type LoadFunc = dyn for<'a> Fn(&'a str) -> Result<Option<TemplateSource>, Error> + Send + Sync;
type UptodateFunc = dyn Fn() -> bool + Send + Sync;
pub struct TemplateSource {
source: String,
uptodate: Option<Box<UptodateFunc>>,
}
impl TemplateSource {
pub fn new<S: Into<String>>(source: S) -> TemplateSource {
TemplateSource {
source: source.into(),
uptodate: None,
}
}
pub fn with_uptodate_check<F>(mut self, f: F) -> TemplateSource
where
F: Fn() -> bool + Send + Sync + 'static,
{
self.uptodate = Some(Box::new(f));
self
}
pub fn source(&self) -> &str {
&self.source
}
}
impl fmt::Debug for TemplateSource {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("TemplateSource")
.field("source", &self.source)
.field("has_uptodate_check", &self.uptodate.is_some())
.finish()
}
}
impl From<String> for TemplateSource {
fn from(source: String) -> TemplateSource {
TemplateSource::new(source)
}
}
impl From<&str> for TemplateSource {
fn from(source: &str) -> TemplateSource {
TemplateSource::new(source)
}
}
impl From<Box<str>> for TemplateSource {
fn from(source: Box<str>) -> TemplateSource {
TemplateSource::new(source)
}
}
impl From<Cow<'_, str>> for TemplateSource {
fn from(source: Cow<'_, str>) -> TemplateSource {
TemplateSource::new(source)
}
}
#[derive(Clone)]
pub(crate) struct LoaderStore<'source> {
pub template_config: TemplateConfig,
pub auto_reload: bool,
loader: Option<Arc<LoadFunc>>,
owned_templates: OwnedTemplates,
borrowed_templates: BTreeMap<&'source str, Arc<CompiledTemplate<'source>>>,
}
impl fmt::Debug for LoaderStore<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut l = f.debug_list();
let owned = self.owned_templates.current.lock().unwrap();
for key in owned.keys() {
l.entry(key);
}
for key in self.borrowed_templates.keys() {
if !owned.contains_key(*key) {
l.entry(key);
}
}
l.finish()
}
}
self_cell! {
struct LoadedTemplate {
owner: (Arc<str>, Box<str>),
#[covariant]
dependent: CompiledTemplate,
}
}
self_cell! {
pub(crate) struct OwnedInstructions {
owner: Box<str>,
#[covariant]
dependent: Instructions,
}
}
impl fmt::Debug for LoadedTemplate {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Debug::fmt(&self.borrow_dependent(), f)
}
}
struct CachedTemplate {
template: LoadedTemplate,
uptodate: Option<Box<UptodateFunc>>,
}
impl CachedTemplate {
fn is_uptodate(&self) -> bool {
match self.uptodate {
Some(ref f) => f(),
None => true,
}
}
unsafe fn compiled_unbounded<'a>(this: &Arc<CachedTemplate>) -> &'a CompiledTemplate<'a> {
let ptr: *const CachedTemplate = Arc::as_ptr(this);
(*ptr).template.borrow_dependent()
}
}
struct OwnedTemplates {
current: Mutex<HashMap<Arc<str>, Arc<CachedTemplate>>>,
stale: Mutex<Vec<Arc<CachedTemplate>>>,
}
impl Default for OwnedTemplates {
fn default() -> OwnedTemplates {
OwnedTemplates {
current: Mutex::new(HashMap::new()),
stale: Mutex::new(Vec::new()),
}
}
}
impl Clone for OwnedTemplates {
fn clone(&self) -> OwnedTemplates {
OwnedTemplates {
current: Mutex::new(self.current.lock().unwrap().clone()),
stale: Mutex::new(Vec::new()),
}
}
}
impl OwnedTemplates {
fn current_mut(&mut self) -> &mut HashMap<Arc<str>, Arc<CachedTemplate>> {
self.stale.get_mut().unwrap().clear();
self.current.get_mut().unwrap()
}
fn get(&self, name: &str) -> Option<Arc<CachedTemplate>> {
self.current.lock().unwrap().get(name).cloned()
}
fn retire(&self, name: &str, expected: &Arc<CachedTemplate>) {
let mut current = self.current.lock().unwrap();
if current.get(name).is_some_and(|x| Arc::ptr_eq(x, expected)) {
let old = current.remove(name).unwrap();
self.stale.lock().unwrap().push(old);
}
}
fn install(
&self,
name: Arc<str>,
expected: Option<&Arc<CachedTemplate>>,
new: Arc<CachedTemplate>,
) -> Arc<CachedTemplate> {
let mut current = self.current.lock().unwrap();
match current.entry(name) {
Entry::Vacant(entry) => entry.insert(new).clone(),
Entry::Occupied(mut entry) => {
if expected.is_some_and(|x| Arc::ptr_eq(x, entry.get())) {
let old = entry.insert(new.clone());
self.stale.lock().unwrap().push(old);
new
} else {
entry.get().clone()
}
}
}
}
}
impl<'source> LoaderStore<'source> {
pub fn new(template_config: TemplateConfig) -> LoaderStore<'source> {
LoaderStore {
template_config,
auto_reload: true,
loader: None,
owned_templates: OwnedTemplates::default(),
borrowed_templates: BTreeMap::default(),
}
}
pub fn insert(&mut self, name: &'source str, source: &'source str) -> Result<(), Error> {
self.insert_cow(Cow::Borrowed(name), Cow::Borrowed(source))
}
pub fn insert_cow(
&mut self,
name: Cow<'source, str>,
source: Cow<'source, str>,
) -> Result<(), Error> {
match (source, name) {
(Cow::Borrowed(source), Cow::Borrowed(name)) => {
let compiled = ok!(CompiledTemplate::new(name, source, &self.template_config));
self.owned_templates.current_mut().remove(name);
self.borrowed_templates.insert(name, Arc::new(compiled));
}
(source, name) => {
let name: Arc<str> = name.into();
let template = ok!(self
.make_owned_template(name.clone(), TemplateSource::new(source.into_owned())));
self.borrowed_templates.remove(&name as &str);
self.owned_templates
.current_mut()
.insert(name, Arc::new(template));
}
}
Ok(())
}
pub fn remove(&mut self, name: &str) {
self.borrowed_templates.remove(name);
self.owned_templates.current_mut().remove(name);
}
pub fn clear(&mut self) {
self.borrowed_templates.clear();
self.owned_templates.current_mut().clear();
}
pub fn get(&self, name: &str) -> Result<&CompiledTemplate<'_>, Error> {
if let Some(rv) = self.borrowed_templates.get(name) {
return Ok(&**rv);
}
let existing = self.owned_templates.get(name);
if let Some(ref existing) = existing {
if !self.auto_reload || existing.is_uptodate() {
return Ok(unsafe { CachedTemplate::compiled_unbounded(existing) });
}
}
let source = match self.loader {
Some(ref loader) => ok!(loader(name)),
None => None,
};
let Some(source) = source else {
if let Some(ref existing) = existing {
self.owned_templates.retire(name, existing);
}
return Err(Error::new_not_found(name));
};
let name: Arc<str> = name.into();
let template = ok!(self.make_owned_template(name.clone(), source));
let rv = self
.owned_templates
.install(name, existing.as_ref(), Arc::new(template));
Ok(unsafe { CachedTemplate::compiled_unbounded(&rv) })
}
pub fn set_loader<F, R>(&mut self, f: F)
where
F: Fn(&str) -> Result<Option<R>, Error> + Send + Sync + 'static,
R: Into<TemplateSource>,
{
self.loader = Some(Arc::new(move |name| f(name).map(|x| x.map(Into::into))));
}
fn make_owned_template(
&self,
name: Arc<str>,
source: TemplateSource,
) -> Result<CachedTemplate, Error> {
let template = ok!(LoadedTemplate::try_new(
(name, source.source.into_boxed_str()),
|(name, source)| -> Result<_, Error> {
CompiledTemplate::new(name, source, &self.template_config)
},
));
Ok(CachedTemplate {
template,
uptodate: source.uptodate,
})
}
pub fn iter(&self) -> impl Iterator<Item = (&str, &CompiledTemplate<'_>)> {
let borrowed = self
.borrowed_templates
.iter()
.map(|(name, template)| (*name, &**template));
let owned = self
.owned_templates
.current
.lock()
.unwrap()
.values()
.map(|template| {
let compiled = unsafe { CachedTemplate::compiled_unbounded(template) };
(compiled.instructions.name(), compiled)
})
.collect::<Vec<_>>();
borrowed.chain(owned)
}
}
pub fn safe_join(base: &Path, template: &str) -> Option<PathBuf> {
let mut rv = base.to_path_buf();
for segment in template.split('/') {
if segment.starts_with('.') || segment.contains('\\') {
return None;
}
rv.push(segment);
}
Some(rv)
}
pub fn path_loader<'x, P: AsRef<Path> + 'x>(
dir: P,
) -> impl for<'a> Fn(&'a str) -> Result<Option<TemplateSource>, Error> + Send + Sync + 'static {
let dir = dir.as_ref().to_path_buf();
move |name| {
let Some(path) = safe_join(&dir, name) else {
return Ok(None);
};
let stamp = file_stamp(&path);
match fs::read_to_string(&path) {
Ok(result) => {
let mut rv = TemplateSource::new(result);
if let Some(stamp) = stamp {
rv = rv.with_uptodate_check(move || file_stamp(&path) == Some(stamp));
}
Ok(Some(rv))
}
Err(err) if err.kind() == io::ErrorKind::NotFound => Ok(None),
Err(err) => Err(
Error::new(ErrorKind::InvalidOperation, "could not read template").with_source(err),
),
}
}
}
fn file_stamp(path: &Path) -> Option<(SystemTime, u64)> {
let metadata = fs::metadata(path).ok()?;
Some((metadata.modified().ok()?, metadata.len()))
}
#[cfg(test)]
mod tests {
use super::*;
use similar_asserts::assert_eq;
#[test]
fn test_safe_join() {
assert_eq!(
safe_join(Path::new("foo"), "bar/baz"),
Some(PathBuf::from("foo").join("bar").join("baz"))
);
assert_eq!(safe_join(Path::new("foo"), ".bar/baz"), None);
assert_eq!(safe_join(Path::new("foo"), "bar/.baz"), None);
assert_eq!(safe_join(Path::new("foo"), "bar/../baz"), None);
}
}