Skip to content

Commit

Permalink
Add the ability to extend the template collection from another folder
Browse files Browse the repository at this point in the history
Adds functions `extend_from_folder` and `extend_from_folder_with_extension`
to include templates from another folder in the collection.
If there is a file with the same name as a previously loaded template or partial,
it will not be loaded.
Also, this should fix the issue maciejhirsz#53 (the name of the partial which was not found
should always be returned).
  • Loading branch information
grego committed Dec 4, 2021
1 parent f9d8ad8 commit e6bc018
Show file tree
Hide file tree
Showing 4 changed files with 91 additions and 38 deletions.
100 changes: 62 additions & 38 deletions ramhorns/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -115,11 +115,7 @@ impl Ramhorns {
/// let rendered = tpls.get("hello.html").unwrap().render(&content);
/// ```
pub fn from_folder<P: AsRef<Path>>(dir: P) -> Result<Self, Error> {
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
Expand All @@ -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<P: AsRef<Path>>(
dir: P,
extension: &str,
) -> Result<Self, Error> {
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<P: AsRef<Path>>(&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<P: AsRef<Path>>(
&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()))?;
}
}
}
Expand Down Expand Up @@ -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(())
}
}

Expand All @@ -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])
}
}
5 changes: 5 additions & 0 deletions tests/more_templates/basic2.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{{>head.html}}
<body>
<div>More {{body}}</div>
{{>footer.html}}
</body>
7 changes: 7 additions & 0 deletions tests/more_templates/basic2.result
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<head>
<title>Hello, Ramhorns!</title>
</head>
<body>
<div>More This is a really simple test of the rendering!</div>
<footer>Sup?</footer>
</body>
17 changes: 17 additions & 0 deletions tests/tests/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down

0 comments on commit e6bc018

Please sign in to comment.