use std::collections::HashMap;
use std::io::prelude::*;
use std::fs::File;
use std::path::Path;
#[cfg(all(feature = "rustc_ser_type", not(feature = "serde_type")))]
use serialize::json::ToJson;
#[cfg(feature = "serde_type")]
use serde_json::value::ToJson;
use regex::{Regex, Captures};
use template::Template;
use render::{Renderable, RenderError, RenderContext};
use context::Context;
use helpers::{self, HelperDef};
use directives::{self, DirectiveDef};
use support::str::StringWriter;
use error::{TemplateError, TemplateFileError, TemplateRenderError};
lazy_static!{
static ref DEFAULT_REPLACE: Regex = Regex::new(">|<|\"|&").unwrap();
}
pub type EscapeFn = Box<Fn(&str) -> String + Send + Sync>;
pub fn html_escape(data: &str) -> String {
DEFAULT_REPLACE.replace_all(data, |cap: &Captures| {
match cap.get(0).map(|m| m.as_str()) {
Some("<") => "<",
Some(">") => ">",
Some("\"") => """,
Some("&") => "&",
_ => unreachable!(),
}
.to_owned()
})
.into_owned()
}
pub fn no_escape(data: &str) -> String {
data.to_owned()
}
pub struct Registry {
templates: HashMap<String, Template>,
helpers: HashMap<String, Box<HelperDef + 'static>>,
directives: HashMap<String, Box<DirectiveDef + 'static>>,
escape_fn: EscapeFn,
source_map: bool,
}
impl Registry {
pub fn new() -> Registry {
let r = Registry {
templates: HashMap::new(),
helpers: HashMap::new(),
directives: HashMap::new(),
escape_fn: Box::new(html_escape),
source_map: true,
};
r.setup_builtins()
}
#[cfg(feature="partial_legacy")]
fn setup_builtins(mut self) -> Registry {
self.register_helper("if", Box::new(helpers::IF_HELPER));
self.register_helper("unless", Box::new(helpers::UNLESS_HELPER));
self.register_helper("each", Box::new(helpers::EACH_HELPER));
self.register_helper("with", Box::new(helpers::WITH_HELPER));
self.register_helper("lookup", Box::new(helpers::LOOKUP_HELPER));
self.register_helper("raw", Box::new(helpers::RAW_HELPER));
self.register_helper(">", Box::new(helpers::INCLUDE_HELPER));
self.register_helper("block", Box::new(helpers::BLOCK_HELPER));
self.register_helper("partial", Box::new(helpers::PARTIAL_HELPER));
self.register_helper("log", Box::new(helpers::LOG_HELPER));
self.register_decorator("inline", Box::new(directives::INLINE_DIRECTIVE));
self
}
#[cfg(not(feature = "partial_legacy"))]
fn setup_builtins(mut self) -> Registry {
self.register_helper("if", Box::new(helpers::IF_HELPER));
self.register_helper("unless", Box::new(helpers::UNLESS_HELPER));
self.register_helper("each", Box::new(helpers::EACH_HELPER));
self.register_helper("with", Box::new(helpers::WITH_HELPER));
self.register_helper("lookup", Box::new(helpers::LOOKUP_HELPER));
self.register_helper("raw", Box::new(helpers::RAW_HELPER));
self.register_helper("log", Box::new(helpers::LOG_HELPER));
self.register_decorator("inline", Box::new(directives::INLINE_DIRECTIVE));
self
}
pub fn source_map_enable(&mut self, enable: bool) {
self.source_map = enable;
}
pub fn register_template_string<S>(&mut self,
name: &str,
tpl_str: S)
-> Result<(), TemplateError>
where S: AsRef<str>
{
try!(Template::compile_with_name(tpl_str, name.to_owned(), self.source_map)
.and_then(|t| Ok(self.templates.insert(name.to_string(), t))));
Ok(())
}
pub fn register_partial<S>(&mut self, name: &str, partial_str: S) -> Result<(), TemplateError>
where S: AsRef<str>
{
self.register_template_string(name, partial_str)
}
pub fn register_template_file<P>(&mut self,
name: &str,
tpl_path: P)
-> Result<(), TemplateFileError>
where P: AsRef<Path>
{
let mut file = try!(File::open(tpl_path)
.map_err(|e| TemplateFileError::IOError(e, name.to_owned())));
self.register_template_source(name, &mut file)
}
pub fn register_template_source(&mut self,
name: &str,
tpl_source: &mut Read)
-> Result<(), TemplateFileError> {
let mut buf = String::new();
try!(tpl_source.read_to_string(&mut buf)
.map_err(|e| TemplateFileError::IOError(e, name.to_owned())));
try!(self.register_template_string(name, buf));
Ok(())
}
pub fn unregister_template(&mut self, name: &str) {
self.templates.remove(name);
}
pub fn register_helper(&mut self,
name: &str,
def: Box<HelperDef + 'static>)
-> Option<Box<HelperDef + 'static>> {
self.helpers.insert(name.to_string(), def)
}
pub fn register_decorator(&mut self,
name: &str,
def: Box<DirectiveDef + 'static>)
-> Option<Box<DirectiveDef + 'static>> {
self.directives.insert(name.to_string(), def)
}
pub fn register_escape_fn<F: 'static + Fn(&str) -> String + Send + Sync>(&mut self,
escape_fn: F) {
self.escape_fn = Box::new(escape_fn);
}
pub fn unregister_escape_fn(&mut self) {
self.escape_fn = Box::new(html_escape);
}
pub fn get_escape_fn(&self) -> &Fn(&str) -> String {
&*self.escape_fn
}
pub fn get_template(&self, name: &str) -> Option<&Template> {
self.templates.get(name)
}
pub fn get_helper(&self, name: &str) -> Option<&Box<HelperDef + 'static>> {
self.helpers.get(name)
}
pub fn get_decorator(&self, name: &str) -> Option<&Box<DirectiveDef + 'static>> {
self.directives.get(name)
}
pub fn get_templates(&self) -> &HashMap<String, Template> {
&self.templates
}
pub fn clear_templates(&mut self) {
self.templates.clear();
}
pub fn render<T>(&self, name: &str, data: &T) -> Result<String, RenderError>
where T: ToJson
{
let mut writer = StringWriter::new();
{
try!(self.renderw(name, data, &mut writer));
}
Ok(writer.to_string())
}
pub fn renderw<T>(&self, name: &str, data: &T, writer: &mut Write) -> Result<(), RenderError>
where T: ToJson
{
self.get_template(&name.to_string())
.ok_or(RenderError::new(format!("Template not found: {}", name)))
.and_then(|t| {
let mut ctx = Context::wraps(data);
let mut local_helpers = HashMap::new();
let mut render_context = RenderContext::new(&mut ctx, &mut local_helpers, writer);
render_context.root_template = t.name.clone();
t.render(self, &mut render_context)
})
}
pub fn template_render<T>(&self,
template_string: &str,
data: &T)
-> Result<String, TemplateRenderError>
where T: ToJson
{
let mut writer = StringWriter::new();
{
try!(self.template_renderw(template_string, data, &mut writer));
}
Ok(writer.to_string())
}
pub fn template_renderw<T>(&self,
template_string: &str,
data: &T,
writer: &mut Write)
-> Result<(), TemplateRenderError>
where T: ToJson
{
let tpl = try!(Template::compile(template_string));
let mut ctx = Context::wraps(data);
let mut local_helpers = HashMap::new();
let mut render_context = RenderContext::new(&mut ctx, &mut local_helpers, writer);
tpl.render(self, &mut render_context).map_err(TemplateRenderError::from)
}
pub fn template_renderw2<T>(&self,
template_source: &mut Read,
data: &T,
writer: &mut Write)
-> Result<(), TemplateRenderError>
where T: ToJson
{
let mut tpl_str = String::new();
try!(template_source.read_to_string(&mut tpl_str).map_err(|e| {
TemplateRenderError::IOError(e, "Unamed template source".to_owned())
}));
self.template_renderw(&tpl_str, data, writer)
}
}
#[cfg(test)]
mod test {
use registry::Registry;
use render::{RenderContext, Renderable, RenderError, Helper};
use helpers::HelperDef;
use support::str::StringWriter;
#[cfg(feature = "partial_legacy")]
use error::TemplateRenderError;
#[derive(Clone, Copy)]
struct DummyHelper;
impl HelperDef for DummyHelper {
fn call(&self,
h: &Helper,
r: &Registry,
rc: &mut RenderContext)
-> Result<(), RenderError> {
try!(h.template().unwrap().render(r, rc));
Ok(())
}
}
static DUMMY_HELPER: DummyHelper = DummyHelper;
#[test]
fn test_registry_operations() {
let mut r = Registry::new();
assert!(r.register_template_string("index", "<h1></h1>").is_ok());
assert!(r.register_template_string("index2", "<h2></h2>").is_ok());
assert_eq!(r.templates.len(), 2);
r.unregister_template("index");
assert_eq!(r.templates.len(), 1);
r.clear_templates();
assert_eq!(r.templates.len(), 0);
r.register_helper("dummy", Box::new(DUMMY_HELPER));
#[cfg(feature = "partial_legacy")]
assert_eq!(r.helpers.len(), 10 + 1);
#[cfg(not(feature = "partial_legacy"))]
assert_eq!(r.helpers.len(), 7 + 1);
}
#[test]
fn test_renderw() {
let mut r = Registry::new();
assert!(r.register_template_string("index", "<h1></h1>").is_ok());
let mut sw = StringWriter::new();
{
r.renderw("index", &(), &mut sw).ok().unwrap();
}
assert_eq!("<h1></h1>".to_string(), sw.to_string());
}
#[test]
fn test_escape_fn() {
let mut r = Registry::new();
let input = String::from("\"<>&");
r.register_template_string("test", String::from("{{this}}")).unwrap();
assert_eq!(""<>&", r.render("test", &input).unwrap());
r.register_escape_fn(|s| s.into());
assert_eq!("\"<>&", r.render("test", &input).unwrap());
r.unregister_escape_fn();
assert_eq!(""<>&", r.render("test", &input).unwrap());
}
#[test]
#[cfg(feature="partial_legacy")]
fn test_template_render() {
let mut r = Registry::new();
assert!(r.register_template_string("index", "<h1></h1>").is_ok());
assert_eq!("<h1></h1>".to_string(),
r.template_render("{{> index}}", &{}).unwrap());
assert_eq!("hello world".to_string(),
r.template_render("hello {{this}}", &"world".to_string()).unwrap());
let mut sw = StringWriter::new();
{
r.template_renderw("{{> index}}", &{}, &mut sw).unwrap();
}
assert_eq!("<h1></h1>".to_string(), sw.to_string());
match r.template_render("{{ hello", &{}).unwrap_err() {
TemplateRenderError::TemplateError(_) => {}
_ => {
panic!();
}
}
match r.template_render("{{> notfound}}", &{}).unwrap_err() {
TemplateRenderError::RenderError(_) => {}
_ => {
panic!();
}
}
}
}