diff --git a/ramhorns/src/lib.rs b/ramhorns/src/lib.rs index f898ef2..2b708ce 100644 --- a/ramhorns/src/lib.rs +++ b/ramhorns/src/lib.rs @@ -115,11 +115,7 @@ impl Ramhorns { /// let rendered = tpls.get("hello.html").unwrap().render(&content); /// ``` pub fn from_folder>(dir: P) -> Result { - let mut templates = Ramhorns::lazy(dir.as_ref())?; - - Self::load_folder(&templates.dir.clone(), "html", &mut templates)?; - - Ok(templates) + Self::from_folder_with_extension(dir, "html") } /// Loads all files with the extension given in the `extension` parameter as templates @@ -131,33 +127,56 @@ impl Ramhorns { /// let content = "I am the content"; /// let rendered = tpls.get("hello.mustache").unwrap().render(&content); /// ``` + #[inline] pub fn from_folder_with_extension>( dir: P, extension: &str, ) -> Result { let mut templates = Ramhorns::lazy(dir)?; - - Self::load_folder(&templates.dir.clone(), extension, &mut templates)?; + templates.load_folder(&templates.dir.clone(), extension)?; Ok(templates) } - fn load_folder(path: &Path, extension: &str, templates: &mut Ramhorns) -> Result<(), Error> { - for entry in std::fs::read_dir(path)? { + /// Extends the template collection with files with `.html` extension + /// from the given folder, making them accessible via their path, joining partials as + /// required. + /// If there is a file with the same name as a previously loaded template or partial, + /// it will not be loaded. + pub fn extend_from_folder>(&mut self, dir: P) -> Result<(), Error> { + self.extend_from_folder_with_extension(dir, "html") + } + + /// Extends the template collection with files with `extension` + /// from the given folder, making them accessible via their path, joining partials as + /// required. + /// If there is a file with the same name as a previously loaded template or partial, + /// it will not be loaded. + #[inline] + pub fn extend_from_folder_with_extension>( + &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, templates)?; - } else if path.extension().unwrap_or_else(|| "".as_ref()) == extension { + self.load_folder(&path, extension)?; + } else if path.extension().map(|e| e == extension).unwrap_or(false) { let name = path - .strip_prefix(&templates.dir) + .strip_prefix(&self.dir) .unwrap_or(&path) .to_string_lossy(); - - if !templates.partials.contains_key(&*name) { - let template = Template::load(std::fs::read_to_string(&path)?, templates)?; - templates - .partials - .insert(name.into_owned().into(), template); + if !self.partials.contains_key(name.as_ref()) { + self.load_internal(&path, Cow::owned(name.to_string()))?; } } } @@ -193,28 +212,26 @@ impl Ramhorns { /// /// Use this method in tandem with [`lazy`](#method.lazy). pub fn from_file(&mut self, name: &str) -> Result<&Template<'static>, Error> { - self.load_internal(name.to_owned().into()) + 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]) } - fn load_internal(&mut self, name: Cow<'static, str>) -> Result<&Template<'static>, Error> { - let n: &str = &name; - if !self.partials.contains_key(n) { - let path = self.dir.join(n).canonicalize()?; - - if !path.starts_with(&self.dir) { - return Err(Error::IllegalPartial(n.into())); + // Unsafe to expose as it loads the template from arbitrary path. + #[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())) } - let file = match std::fs::read_to_string(&path) { - Ok(file) => Ok(file), - Err(e) if e.kind() == ErrorKind::NotFound => { - Err(Error::NotFound(name.as_ref().into())) - } - Err(e) => Err(Error::Io(e)), - }?; - let template = Template::load(file, self)?; - self.partials.insert(name.clone(), template); - }; - Ok(&self.partials[n]) + Err(e) => Err(Error::Io(e)), + }?; + let template = Template::load(file, self)?; + self.partials.insert(name, template); + Ok(()) } } @@ -224,6 +241,13 @@ pub(crate) trait Partials<'tpl> { impl Partials<'static> for Ramhorns { fn get_partial(&mut self, name: &'static str) -> Result<&Template<'static>, Error> { - self.load_internal(Cow::borrowed(name)) + 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]) } } diff --git a/tests/more_templates/basic2.html b/tests/more_templates/basic2.html new file mode 100644 index 0000000..b2be793 --- /dev/null +++ b/tests/more_templates/basic2.html @@ -0,0 +1,5 @@ +{{>head.html}} + +
More {{body}}
+ {{>footer.html}} + diff --git a/tests/more_templates/basic2.result b/tests/more_templates/basic2.result new file mode 100644 index 0000000..fad77d1 --- /dev/null +++ b/tests/more_templates/basic2.result @@ -0,0 +1,7 @@ + + Hello, Ramhorns! + + +
More This is a really simple test of the rendering!
+ + diff --git a/tests/tests/main.rs b/tests/tests/main.rs index 3e8b915..a6c211b 100644 --- a/tests/tests/main.rs +++ b/tests/tests/main.rs @@ -720,6 +720,23 @@ fn simple_partials_folder() { ); } +#[test] +fn simple_partials_extend() { + use std::fs::read_to_string; + + let mut tpls = Ramhorns::from_folder("templates").unwrap(); + tpls.extend_from_folder("more_templates").unwrap(); + let post = Post { + title: "Hello, Ramhorns!", + body: "This is a really simple test of the rendering!", + }; + + assert_eq!( + tpls.get("basic2.html").unwrap().render(&post), + read_to_string("more_templates/basic2.result").unwrap().trim_end() + ); +} + #[test] fn illegal_partials() { use ramhorns::Error;