use std::collections::HashMap;
use std::convert::TryFrom;
#[cfg(feature = "fs")]
use std::ffi::OsStr;
#[cfg(feature = "fs")]
use std::path::Path;
use serde::Serialize;
use std::fmt;
use crate::{
error::Error,
escape::EscapeFn,
helper::HelperRegistry,
output::Output,
parser::{ast::Node, Parser, ParserOptions},
render::Render,
RenderResult, Result, SyntaxResult,
};
#[derive(Default)]
pub struct Loader {
sources: HashMap<String, String>,
}
impl Loader {
pub fn new() -> Self {
Self {
sources: HashMap::new(),
}
}
pub fn sources(&self) -> &HashMap<String, String> {
&self.sources
}
pub fn insert<N, S>(&mut self, name: N, content: S)
where
N: AsRef<str>,
S: AsRef<str>,
{
self.sources
.insert(name.as_ref().to_owned(), content.as_ref().to_owned());
}
#[cfg(feature = "fs")]
pub fn add<N, P>(&mut self, name: N, file: P) -> std::io::Result<()>
where
N: AsRef<str>,
P: AsRef<Path>,
{
let (_, content) = self.read(file)?;
self.insert(name, &content);
Ok(())
}
#[cfg(feature = "fs")]
pub fn load<P: AsRef<Path>>(&mut self, file: P) -> std::io::Result<()> {
let (name, content) = self.read(file)?;
self.insert(name, &content);
Ok(())
}
#[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.insert(name, &content);
}
}
}
}
Ok(())
}
#[cfg(feature = "fs")]
fn read<P: AsRef<Path>>(
&mut 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))
}
}
#[derive(Default)]
pub struct Templates<'source> {
templates: HashMap<&'source str, Template<'source>>,
}
impl<'source> Templates<'source> {
pub fn new() -> Self {
Self {
templates: HashMap::new(),
}
}
pub fn insert(&mut self, name: &'source str, template: Template<'source>) {
self.templates.insert(name, template);
}
pub fn remove(&mut self, name: &'source str) -> Option<Template<'source>> {
self.templates.remove(name)
}
pub fn get(&self, name: &str) -> Option<&Template<'source>> {
self.templates.get(name)
}
pub fn compile(s: &str, options: ParserOptions) -> Result<Template<'_>> {
Ok(Template::compile(s, options).map_err(Error::from)?)
}
}
impl<'source> TryFrom<&'source Loader> for Templates<'source> {
type Error = crate::error::Error;
fn try_from(
loader: &'source Loader,
) -> std::result::Result<Self, Self::Error> {
let mut tpl = Templates::new();
for (k, v) in loader.sources() {
let template =
Templates::compile(v, ParserOptions::new(k.to_string(), 0, 0))?;
tpl.insert(k.as_str(), template);
}
Ok(tpl)
}
}
#[derive(Debug)]
pub struct Template<'source> {
node: Node<'source>,
}
impl<'source> Template<'source> {
pub(crate) fn new(node: Node<'source>) -> Self {
Self { node }
}
pub fn node(&self) -> &'source Node {
&self.node
}
}
impl fmt::Display for Template<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.node.fmt(f)
}
}
impl<'reg, 'source> Template<'source> {
pub fn compile(
source: &'source str,
options: ParserOptions,
) -> SyntaxResult<Template> {
let mut parser = Parser::new(source, options);
let node = parser.parse()?;
Ok(Template::new(node))
}
pub(crate) fn render<'a, T>(
&self,
strict: bool,
escape: &EscapeFn,
helpers: &'reg HelperRegistry<'reg>,
templates: &'source Templates<'source>,
name: &str,
data: &T,
writer: &'a mut impl Output,
) -> RenderResult<()>
where
T: Serialize,
{
let mut rc = Render::new(
strict,
escape,
helpers,
templates,
name,
data,
Box::new(writer),
)?;
rc.render(&self.node)
}
}