diff --git a/src/lib.rs b/src/lib.rs index 359974356..33a8eb5bf 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -392,6 +392,7 @@ pub use self::json::value::{to_json, JsonRender, PathAndJson, ScopedJson}; pub use self::output::Output; pub use self::registry::{html_escape, no_escape, EscapeFn, Registry as Handlebars}; pub use self::render::{Decorator, Evaluable, Helper, RenderContext, Renderable}; +pub use self::sources::Source; pub use self::template::Template; #[doc(hidden)] diff --git a/src/registry.rs b/src/registry.rs index e7f5f1cfd..f03e74ce3 100644 --- a/src/registry.rs +++ b/src/registry.rs @@ -216,6 +216,15 @@ impl<'reg> Registry<'reg> { Ok(()) } + /// Register a template source + pub fn register_template_source(&mut self, name: &str, tpl_src: S) + where + S: Source + Send + Sync + 'reg, + { + self.template_sources + .insert(name.to_owned(), Arc::new(tpl_src)); + } + /// Register a partial string /// /// A named partial will be added to the registry. It will overwrite template with @@ -242,6 +251,8 @@ impl<'reg> Registry<'reg> { .map_err(|err| TemplateError::from((err, name.to_owned())))?; self.register_template_string(name, template_string)?; + // Register as template source only with dev_mode. + // The default FileSource has no cache so it doesn't perform well if self.dev_mode { self.template_sources .insert(name.to_owned(), Arc::new(source)); @@ -308,6 +319,7 @@ impl<'reg> Registry<'reg> { /// Remove a template from the registry pub fn unregister_template(&mut self, name: &str) { self.templates.remove(name); + self.template_sources.remove(name); } /// Register a helper @@ -394,12 +406,25 @@ impl<'reg> Registry<'reg> { /// Return `true` if a template is registered for the given name pub fn has_template(&self, name: &str) -> bool { - self.get_template(name).is_some() + self.templates.contains_key(name) || self.template_sources.contains_key(name) } /// Return a registered template, - pub fn get_template(&self, name: &str) -> Option<&Template> { - self.templates.get(name) + pub fn get_template( + &'reg self, + name: &str, + ) -> Option, TemplateError>> { + self.templates + .get(name) + .map(|t| Ok(Cow::Borrowed(t))) + .or_else(|| { + self.template_sources.get(name).map(|src| { + src.load() + .map_err(|e| TemplateError::from((e, name.to_owned()))) + .and_then(|tpl_str| Template::compile_with_name(tpl_str, name.to_owned())) + .map(Cow::Owned) + }) + }) } #[inline] @@ -407,7 +432,7 @@ impl<'reg> Registry<'reg> { &'reg self, name: &str, ) -> Option, RenderError>> { - if let (true, Some(source)) = (self.dev_mode, self.template_sources.get(name)) { + if let Some(source) = self.template_sources.get(name) { let r = source .load() .map_err(|e| TemplateError::from((e, name.to_owned()))) @@ -438,7 +463,7 @@ impl<'reg> Registry<'reg> { name: &str, ) -> Result>, RenderError> { #[cfg(feature = "script_helper")] - if let (true, Some(source)) = (self.dev_mode, self.script_sources.get(name)) { + if let Some(source) = self.script_sources.get(name) { return source .load() .map_err(ScriptError::from) @@ -467,14 +492,23 @@ impl<'reg> Registry<'reg> { self.decorators.get(name).map(|v| v.as_ref()) } - /// Return all templates registered - pub fn get_templates(&self) -> &HashMap { - &self.templates + /// Return all registered template names + pub fn get_template_names(&self) -> Vec { + let mut names = self + .templates + .keys() + .chain(self.template_sources.keys()) + .cloned() + .collect::>(); + names.sort(); + names.dedup(); + names } /// Unregister all templates pub fn clear_templates(&mut self) { self.templates.clear(); + self.template_sources.clear(); } #[inline] @@ -583,8 +617,9 @@ mod test { use crate::render::{Helper, RenderContext, Renderable}; use crate::support::str::StringWriter; use crate::template::Template; + use crate::Source; use std::fs::File; - use std::io::Write; + use std::io::{Error as IoError, ErrorKind as IoErrorKind, Write}; use tempfile::tempdir; #[derive(Clone, Copy)] @@ -634,6 +669,31 @@ mod test { ); } + #[test] + fn test_register_template_source() { + let mut r = Registry::new(); + + impl Source for &'static str { + type Item = String; + type Error = IoError; + fn load(&self) -> Result { + Ok(String::from(*self)) + } + } + r.register_template_source("test", "

Hello world!

"); + assert_eq!("

Hello world!

", r.render("test", &()).unwrap()); + + impl Source for () { + type Item = String; + type Error = IoError; + fn load(&self) -> Result { + Err(IoError::from(IoErrorKind::Other)) + } + } + r.register_template_source("test2", ()); + assert!(r.render("test2", &()).is_err()); + } + #[test] #[cfg(feature = "dir_source")] fn test_register_templates_directory() { diff --git a/src/sources.rs b/src/sources.rs index 8c8b2ba57..285685e74 100644 --- a/src/sources.rs +++ b/src/sources.rs @@ -2,7 +2,7 @@ use std::fs::File; use std::io::{BufReader, Error as IOError, Read}; use std::path::PathBuf; -pub(crate) trait Source { +pub trait Source { type Item; type Error;