Skip to content

Commit

Permalink
✨ Use a generic method for handling partials
Browse files Browse the repository at this point in the history
This fixes a few issues:
- nickel-org#67
- nickel-org#62 (by implementing
  a custom PartialLoader without the set_extension)

But it also adds a lot of complexity and breaks previous code.
  • Loading branch information
adri326 committed Jul 27, 2022
1 parent 9d1ca3f commit 3d2b68b
Show file tree
Hide file tree
Showing 7 changed files with 147 additions and 94 deletions.
5 changes: 3 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@ name = "mustache"
description = "Rust implementation of Mustache"
repository = "https://github.com/nickel-org/rust-mustache"
documentation = "http://nickel-org.github.io/rust-mustache"
version = "0.9.0"
authors = ["[email protected]"]
version = "0.10.0"
authors = ["[email protected]", "Shad Amethyst <[email protected]>"]
license = "MIT/Apache-2.0"
autotests = false

[features]
unstable = []
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
Mustache [![Ohloh statistics](http://www.ohloh.net/p/rust-mustache/widgets/project_thin_badge.gif)](https://www.ohloh.net/p/rust-mustache) [![Build Status](http://travis-ci.org/nickel-org/rust-mustache.png?branch=master)](https://travis-ci.org/nickel-org/rust-mustache) [![](http://meritbadge.herokuapp.com/mustache)](https://crates.io/crates/mustache)
Mustache (customization fork)
========

Inspired by [ctemplate][1] and [et][2], [Mustache][3] is a framework-agnostic way
Expand Down
67 changes: 28 additions & 39 deletions src/compiler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,24 @@ use std::io::Read;
use std::fs::File;

use parser::{Parser, Token};
use super::Context;
use super::{Context, PartialLoader};

use Result;

pub type PartialsMap = HashMap<String, Vec<Token>>;

/// `Compiler` is a object that compiles a string into a `Vec<Token>`.
pub struct Compiler<T> {
ctx: Context,
pub struct Compiler<T, P: PartialLoader> {
ctx: Context<P>,
reader: T,
partials: PartialsMap,
otag: String,
ctag: String,
}

impl<T: Iterator<Item = char>> Compiler<T> {
impl<T: Iterator<Item = char>, P: PartialLoader> Compiler<T, P> {
/// Construct a default compiler.
pub fn new(ctx: Context, reader: T) -> Compiler<T> {
pub fn new(ctx: Context<P>, reader: T) -> Compiler<T, P> {
Compiler {
ctx: ctx,
reader: reader,
Expand All @@ -32,12 +32,13 @@ impl<T: Iterator<Item = char>> Compiler<T> {
}

/// Construct a default compiler.
pub fn new_with(ctx: Context,
reader: T,
partials: PartialsMap,
otag: String,
ctag: String)
-> Compiler<T> {
pub fn new_with(
ctx: Context<P>,
reader: T,
partials: PartialsMap,
otag: String,
ctag: String
) -> Compiler<T, P> {
Compiler {
ctx: ctx,
reader: reader,
Expand All @@ -56,38 +57,26 @@ impl<T: Iterator<Item = char>> Compiler<T> {

// Compile the partials if we haven't done so already.
for name in partials.into_iter() {
let path =
self.ctx.template_path.join(&(name.clone() + "." + &self.ctx.template_extension));

if !self.partials.contains_key(&name) {
// Insert a placeholder so we don't recurse off to infinity.
self.partials.insert(name.to_string(), Vec::new());

match File::open(&path) {
Ok(mut file) => {
let mut string = String::new();
file.read_to_string(&mut string)?;

let compiler = Compiler {
ctx: self.ctx.clone(),
reader: string.chars(),
partials: self.partials.clone(),
otag: "{{".to_string(),
ctag: "}}".to_string(),
};

let (tokens, subpartials) = compiler.compile()?;

// Include subpartials
self.partials.extend(subpartials.into_iter());

// Set final compiled tokens for *this* partial
self.partials.insert(name, tokens);
}
// Ignore missing files.
Err(ref e) if e.kind() == NotFound => {},
Err(e) => return Err(e.into()),
}
let string = self.ctx.partial_loader.load(&name)?;
let compiler = Compiler {
ctx: self.ctx.clone(),
reader: string.chars(),
partials: self.partials.clone(),
otag: "{{".to_string(),
ctag: "}}".to_string(),
};

let (tokens, subpartials) = compiler.compile()?;

// Include subpartials
self.partials.extend(subpartials.into_iter());

// Set final compiled tokens for *this* partial
self.partials.insert(name, tokens);
}
}

Expand Down
128 changes: 97 additions & 31 deletions src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,56 +10,122 @@ use std::path::{Path, PathBuf};

/// Represents the shared metadata needed to compile and render a mustache
/// template.
#[derive(Clone)]
pub struct Context {
pub template_path: PathBuf,
pub template_extension: String,
#[derive(Debug, Clone)]
pub struct Context<P: PartialLoader> {
pub partial_loader: P,
}

impl fmt::Debug for Context {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f,
"Context {{ template_path: {:?}, template_extension: {} }}",
&*self.template_path,
self.template_extension)
impl Context<DefaultLoader> {
/// Configures a mustache context the specified path to the templates.
pub fn new(path: PathBuf) -> Self {
Context {
// template_path: path.clone(),
// template_extension: "mustache".to_string(),
partial_loader: DefaultLoader::new(path, "mustache".to_string()),
}
}
}

impl Context {
/// Configures a mustache context the specified path to the templates.
pub fn new(path: PathBuf) -> Context {
/// Configures a mustache context the specified path and extension to the templates.
pub fn with_extension(path: PathBuf, extension: String) -> Self {
Context {
template_path: path,
template_extension: "mustache".to_string(),
partial_loader: DefaultLoader::new(path, extension),
}
}
}

impl<P: PartialLoader> Context<P> {
/// Configures a mustache context to use a custom loader
pub fn with_loader(loader: P) -> Self {
Self {
partial_loader: loader
}
}

/// Compiles a template from a string
pub fn compile<IT: Iterator<Item = char>>(&self, reader: IT) -> Result<Template> {
pub fn compile<IT: Iterator<Item = char>>(&self, reader: IT) -> Result<Template<P>> {
let compiler = compiler::Compiler::new(self.clone(), reader);
let (tokens, partials) = compiler.compile()?;

Ok(template::new(self.clone(), tokens, partials))
}

/// Compiles a template from a path.
pub fn compile_path<U: AsRef<Path>>(&self, path: U) -> Result<Template> {
pub fn compile_path(&self, path: impl AsRef<Path>) -> Result<Template<P>> {
let template = self.partial_loader.load(path)?;

self.compile(template.chars())
}
}

/// A trait that defines how partials should be loaded.
/// Types implementing this trait must also implement [`Clone`],
/// and must provide the [`PartialLoader::load`] method.
///
/// Its default implementation, [`DefaultLoader`], simply loads the corresponding file from the disk.
///
/// # Example
///
/// ```
/// use mustache::{PartialLoader, Error};
/// use std::path::Path;
///
/// // A simple loader, that returns the name of the partial as the partial's body
/// #[derive(Clone, Debug)]
/// pub struct MyLoader {}
///
/// impl PartialLoader for MyLoader {
/// fn load(&self, name: impl AsRef<Path>) -> Result<String, Error> {
/// let name = name.as_ref().to_str().ok_or(Error::InvalidStr)?;
/// Ok(name.to_string())
/// }
/// }
/// ```
pub trait PartialLoader: Clone {
fn load(&self, name: impl AsRef<Path>) -> Result<String>;
}

/// Default [`PartialLoader`].
///
/// For a given partial with `name`, loads `{template_path}/{name}.{template_extension}`.
/// Uses `set_extension` to set the extension.
#[derive(Clone, Debug, PartialEq)]
pub struct DefaultLoader {
pub template_path: PathBuf,
pub template_extension: String,
}

impl DefaultLoader {
pub fn new(
template_path: PathBuf,
template_extension: String
) -> Self {
Self {
template_path,
template_extension,
}
}
}

impl PartialLoader for DefaultLoader {
fn load(&self, name: impl AsRef<Path>) -> Result<String> {
let mut path = self.template_path.join(name.as_ref());
path.set_extension(&self.template_extension);

// FIXME(#6164): This should use the file decoding tools when they are
// written. For now we'll just read the file and treat it as UTF-8file.
let mut path = self.template_path.join(path.as_ref());
path.set_extension(&self.template_extension);
let mut s = vec![];
let mut file = File::open(&path)?;
file.read_to_end(&mut s)?;

// TODO: maybe allow UTF-16 as well?
let template = match str::from_utf8(&*s) {
Ok(string) => string,
_ => {
return Err(Error::InvalidStr);

match File::open(path) {
Ok(mut file) => {
let mut string = String::new();
file.read_to_string(&mut string)?;

Ok(string)
}
};

self.compile(template.chars())
Err(ref e) if e.kind() == std::io::ErrorKind::NotFound => {
Ok(String::new())
},
Err(e) => return Err(e.into()),
}
}
}
13 changes: 5 additions & 8 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ mod parser;
mod template;

pub use builder::{MapBuilder, VecBuilder};
pub use context::Context;
pub use context::{Context, PartialLoader, DefaultLoader};
pub use data::Data;
pub use encoder::Encoder;
pub use encoder::Error as EncoderError;
Expand All @@ -38,13 +38,13 @@ where
}

/// Compiles a template from an `Iterator<char>`.
pub fn compile_iter<T: Iterator<Item = char>>(iter: T) -> Result<Template> {
pub fn compile_iter<T: Iterator<Item = char>>(iter: T) -> Result<Template<DefaultLoader>> {
Context::new(PathBuf::from(".")).compile(iter)
}

/// Compiles a template from a path.
/// returns None if the file cannot be read OR the file is not UTF-8 encoded
pub fn compile_path<U: AsRef<Path>>(path: U) -> Result<Template> {
pub fn compile_path<U: AsRef<Path>>(path: U) -> Result<Template<DefaultLoader>> {
let path = path.as_ref();

match path.file_name() {
Expand All @@ -54,17 +54,14 @@ pub fn compile_path<U: AsRef<Path>>(path: U) -> Result<Template> {
// the extension is not utf8 :(
let extension = path.extension().and_then(|ext| ext.to_str()).unwrap_or("mustache");

let context = Context {
template_path: template_dir.to_path_buf(),
template_extension: extension.to_string(),
};
let context = Context::with_extension(template_dir.to_path_buf(), extension.to_string());
context.compile_path(filename)
}
None => Err(Error::NoFilename),
}
}

/// Compiles a template from a string.
pub fn compile_str(template: &str) -> Result<Template> {
pub fn compile_str(template: &str) -> Result<Template<DefaultLoader>> {
compile_iter(template.chars())
}
18 changes: 9 additions & 9 deletions src/template.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,27 +9,27 @@ use log::{log, error};
use parser::Token;
use serde::Serialize;

use super::{Context, Data, Error, Result, to_data};
use super::{Context, PartialLoader, Data, Error, Result, to_data};

/// `Template` represents a compiled mustache file.
#[derive(Debug, Clone)]
pub struct Template {
ctx: Context,
pub struct Template<P: PartialLoader> {
ctx: Context<P>,
tokens: Vec<Token>,
partials: HashMap<String, Vec<Token>>,
}

/// Construct a `Template`. This is not part of the impl of Template so it is
/// not exported outside of mustache.
pub fn new(ctx: Context, tokens: Vec<Token>, partials: HashMap<String, Vec<Token>>) -> Template {
pub fn new<P: PartialLoader>(ctx: Context<P>, tokens: Vec<Token>, partials: HashMap<String, Vec<Token>>) -> Template<P> {
Template {
ctx: ctx,
tokens: tokens,
partials: partials,
}
}

impl Template {
impl<P: PartialLoader> Template<P> {
/// Renders the template with the `Encodable` data.
pub fn render<W, T>(&self, wr: &mut W, data: &T) -> Result<()>
where W: Write,
Expand Down Expand Up @@ -62,14 +62,14 @@ impl Template {
}
}

struct RenderContext<'a> {
template: &'a Template,
struct RenderContext<'a, P: PartialLoader> {
template: &'a Template<P>,
indent: String,
line_start: bool,
}

impl<'a> RenderContext<'a> {
fn new(template: &'a Template) -> RenderContext<'a> {
impl<'a, P: PartialLoader> RenderContext<'a, P> {
fn new(template: &'a Template<P>) -> RenderContext<'a, P> {
RenderContext {
template: template,
indent: "".to_string(),
Expand Down
Loading

0 comments on commit 3d2b68b

Please sign in to comment.