#![warn(missing_docs)]
use std::collections::HashMap;
use std::fmt;
use std::hash::BuildHasher;
use std::path::{Path, PathBuf};
use beef::Cow;
use std::io::ErrorKind;
mod content;
mod error;
mod template;
pub mod traits;
pub mod encoding;
pub use content::Content;
pub use error::Error;
pub use template::{Section, Template};
#[cfg(feature = "export_derive")]
pub use ramhorns_derive::Content;
pub struct Ramhorns<H = fnv::FnvBuildHasher> {
partials: HashMap<Cow<'static, str>, Template<'static>, H>,
dir: PathBuf,
}
impl<H> fmt::Debug for Ramhorns<H> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Ramhorns").field("dir", &self.dir).finish()
}
}
impl<H: BuildHasher + Default> Ramhorns<H> {
pub fn from_folder<P: AsRef<Path>>(dir: P) -> Result<Self, Error> {
Self::from_folder_with_extension(dir, "html")
}
#[inline]
pub fn from_folder_with_extension<P: AsRef<Path>>(
dir: P,
extension: &str,
) -> Result<Self, Error> {
let mut templates = Ramhorns::lazy(dir)?;
templates.load_folder(&templates.dir.clone(), extension)?;
Ok(templates)
}
pub fn extend_from_folder<P: AsRef<Path>>(&mut self, dir: P) -> Result<(), Error> {
self.extend_from_folder_with_extension(dir, "html")
}
#[inline]
pub fn extend_from_folder_with_extension<P: AsRef<Path>>(
&mut self,
dir: P,
extension: &str,
) -> Result<(), Error> {
let dir = std::mem::replace(&mut self.dir, dir.as_ref().canonicalize()?);
self.load_folder(&self.dir.clone(), extension)?;
self.dir = dir;
Ok(())
}
fn load_folder(&mut self, dir: &Path, extension: &str) -> Result<(), Error> {
for entry in std::fs::read_dir(dir)? {
let path = entry?.path();
if path.is_dir() {
self.load_folder(&path, extension)?;
} else if path.extension().map(|e| e == extension).unwrap_or(false) {
let name = path
.strip_prefix(&self.dir)
.unwrap_or(&path)
.to_string_lossy();
if !self.partials.contains_key(name.as_ref()) {
self.load_internal(&path, Cow::owned(name.to_string()))?;
}
}
}
Ok(())
}
pub fn lazy<P: AsRef<Path>>(dir: P) -> Result<Self, Error> {
Ok(Ramhorns {
partials: HashMap::default(),
dir: dir.as_ref().canonicalize()?,
})
}
pub fn get(&self, name: &str) -> Option<&Template<'static>> {
self.partials.get(name)
}
pub fn from_file(&mut self, name: &str) -> Result<&Template<'static>, Error> {
let path = self.dir.join(name);
if !self.partials.contains_key(name) {
self.load_internal(&path, Cow::owned(name.to_string()))?;
}
Ok(&self.partials[name])
}
#[inline]
fn load_internal(&mut self, path: &Path, name: Cow<'static, str>) -> Result<(), Error> {
let file = match std::fs::read_to_string(path) {
Ok(file) => Ok(file),
Err(e) if e.kind() == ErrorKind::NotFound => {
Err(Error::NotFound(name.to_string().into()))
}
Err(e) => Err(Error::Io(e)),
}?;
self.insert(file, name)
}
pub fn insert<S, T>(&mut self, src: S, name: T) -> Result<(), Error>
where
S: Into<Cow<'static, str>>,
T: Into<Cow<'static, str>>,
{
let template = Template::load(src, self)?;
self.partials.insert(name.into(), template);
Ok(())
}
}
pub(crate) trait Partials<'tpl> {
fn get_partial(&mut self, name: &'tpl str) -> Result<&Template<'tpl>, Error>;
}
impl<H: BuildHasher + Default> Partials<'static> for Ramhorns<H> {
fn get_partial(&mut self, name: &'static str) -> Result<&Template<'static>, Error> {
if !self.partials.contains_key(name) {
let path = self.dir.join(name).canonicalize()?;
if !path.starts_with(&self.dir) {
return Err(Error::IllegalPartial(name.into()));
}
self.load_internal(&path, Cow::borrowed(name))?;
}
Ok(&self.partials[name])
}
}