use serde::Serialize;
use std::collections::HashMap;
use std::sync::{Arc, RwLock};
#[cfg(feature = "fs")]
use std::ffi::OsStr;
#[cfg(feature = "fs")]
use std::path::Path;
use crate::{
escape::{self, EscapeFn},
helper::HelperRegistry,
output::{Output, StringOutput},
parser::{Parser, ParserOptions},
template::{Template, Templates},
Error, Result,
};
pub struct Registry<'reg, 'source> {
sources: HashMap<String, String>,
helpers: HelperRegistry<'reg>,
templates: Arc<RwLock<Templates<'source>>>,
escape: EscapeFn,
strict: bool,
}
impl<'reg, 'source> Registry<'reg, 'source> {
pub fn new() -> Self {
Self {
sources: HashMap::new(),
helpers: HelperRegistry::new(),
templates: Arc::new(RwLock::new(Default::default())),
escape: Box::new(escape::html),
strict: false,
}
}
pub fn set_strict(&mut self, strict: bool) {
self.strict = strict
}
pub fn strict(&self) -> bool {
self.strict
}
pub fn set_escape(&mut self, escape: EscapeFn) {
self.escape = escape;
}
pub fn escape(&self) -> &EscapeFn {
&self.escape
}
pub fn helpers(&self) -> &HelperRegistry<'reg> {
&self.helpers
}
pub fn helpers_mut(&mut self) -> &mut HelperRegistry<'reg> {
&mut self.helpers
}
fn templates(&self) -> &Arc<RwLock<Templates<'source>>> {
&self.templates
}
pub fn insert<N>(&mut self, name: N, content: String)
where
N: AsRef<str>,
{
self.sources.insert(name.as_ref().to_owned(), content);
}
#[cfg(feature = "fs")]
pub fn add<P>(&mut self, name: String, file: P) -> std::io::Result<()>
where
P: AsRef<Path>,
{
let (_, content) = self.read(file)?;
self.sources.insert(name, content);
Ok(())
}
#[cfg(feature = "fs")]
pub fn load<P: AsRef<Path>>(&mut self, file: P) -> Result<String> {
let (name, content) = self.read(file)?;
self.sources.insert(name.clone(), content);
Ok(name)
}
#[cfg(feature = "fs")]
pub fn read_dir<P: AsRef<Path>>(
&mut self,
file: P,
extension: &str,
) -> std::io::Result<()> {
let ext = OsStr::new(extension);
for entry in std::fs::read_dir(file.as_ref())? {
let entry = entry?;
let path = entry.path();
if path.is_file() {
if let Some(extension) = path.extension() {
if extension == ext {
let name = path
.file_stem()
.unwrap()
.to_string_lossy()
.to_owned()
.to_string();
let (_, content) = self.read(path)?;
self.sources.insert(name, content);
}
}
}
}
Ok(())
}
#[cfg(feature = "fs")]
fn read<P: AsRef<Path>>(
&self,
file: P,
) -> std::io::Result<(String, String)> {
let path = file.as_ref();
let name = path.to_string_lossy().to_owned().to_string();
let content = std::fs::read_to_string(path)?;
Ok((name, content))
}
pub fn build<'a: 'source>(&'a self) -> Result<()> {
let mut templates = self.templates.write().unwrap();
for (k, v) in &self.sources {
let template = Template::compile(v, ParserOptions::new(k.to_string(), 0, 0))?;
templates.insert(k, template);
}
Ok(())
}
pub fn compile(
&self,
template: &'source str,
options: ParserOptions,
) -> Result<Template<'source>> {
Templates::compile(template, options)
}
pub fn parse(
&self,
name: &str,
template: &'source str,
) -> Result<Template<'source>> {
self.compile(template, ParserOptions::new(name.to_string(), 0, 0))
}
pub fn lint(
&self,
name: &str,
template: &'source str,
) -> Result<Vec<Error>> {
let mut errors: Vec<Error> = Vec::new();
let mut parser =
Parser::new(template, ParserOptions::new(name.to_string(), 0, 0));
parser.set_errors(&mut errors);
for _ in parser {}
Ok(errors)
}
pub fn once<T>(&self, name: &str, source: &'source str, data: &T) -> Result<String>
where
T: Serialize,
{
let templates = self.templates().read().unwrap();
let mut writer = StringOutput::new();
let template =
self.compile(source, ParserOptions::new(name.to_string(), 0, 0))?;
template.render(
self.strict(),
self.escape(),
self.helpers(),
&*templates,
name,
data,
&mut writer,
)?;
Ok(writer.into())
}
pub fn render<T>(&self, name: &str, data: &T) -> Result<String>
where
T: Serialize,
{
let mut writer = StringOutput::new();
self.render_to_write(name, data, &mut writer)?;
Ok(writer.into())
}
pub fn render_template<T>(
&self,
name: &str,
template: &Template<'source>,
data: &T,
) -> Result<String>
where
T: Serialize,
{
let templates = self.templates().read().unwrap();
let mut writer = StringOutput::new();
template.render(
self.strict(),
self.escape(),
self.helpers(),
&*templates,
name,
data,
&mut writer,
)?;
Ok(writer.into())
}
pub fn render_to_write<T>(
&self,
name: &str,
data: &T,
writer: &mut impl Output,
) -> Result<()>
where
T: Serialize,
{
let templates = self.templates().read().unwrap();
let tpl = templates
.get(name)
.ok_or_else(|| Error::TemplateNotFound(name.to_string()))?;
tpl.render(
self.strict(),
self.escape(),
self.helpers(),
&*templates,
name,
data,
writer,
)?;
Ok(())
}
}