2020-04-11 00:27:27 +00:00
|
|
|
use crate::parser::template;
|
2020-04-12 03:03:07 +00:00
|
|
|
use crate::parser::Body;
|
2020-04-11 22:40:36 +00:00
|
|
|
use crate::parser::DustTag;
|
2020-04-11 00:27:27 +00:00
|
|
|
use crate::parser::Template;
|
2020-04-11 22:25:48 +00:00
|
|
|
use crate::parser::TemplateElement;
|
2020-04-28 23:09:02 +00:00
|
|
|
use crate::renderer::context_element::ContextElement;
|
2020-04-11 22:25:48 +00:00
|
|
|
use crate::renderer::errors::CompileError;
|
|
|
|
use crate::renderer::errors::RenderError;
|
|
|
|
use std::collections::HashMap;
|
2020-04-11 00:27:27 +00:00
|
|
|
|
|
|
|
#[derive(Clone, Debug)]
|
|
|
|
pub struct CompiledTemplate<'a> {
|
|
|
|
template: Template<'a>,
|
2020-04-11 22:25:48 +00:00
|
|
|
pub name: String,
|
2020-04-11 00:27:27 +00:00
|
|
|
}
|
|
|
|
|
2020-04-11 00:55:44 +00:00
|
|
|
#[derive(Clone, Debug)]
|
2020-04-11 00:58:55 +00:00
|
|
|
pub struct DustRenderer<'a> {
|
2020-04-11 22:25:48 +00:00
|
|
|
templates: HashMap<String, &'a Template<'a>>,
|
2020-04-11 00:55:44 +00:00
|
|
|
}
|
|
|
|
|
2020-05-02 19:55:38 +00:00
|
|
|
pub trait RenderWrapper {
|
|
|
|
fn render_body<'a>(
|
|
|
|
&'a self,
|
|
|
|
renderer: &'a DustRenderer,
|
|
|
|
body: &Body,
|
|
|
|
) -> Result<String, RenderError>;
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<C: ContextElement> RenderWrapper for C {
|
|
|
|
fn render_body<'a>(
|
|
|
|
&'a self,
|
|
|
|
renderer: &'a DustRenderer,
|
|
|
|
body: &Body,
|
|
|
|
) -> Result<String, RenderError<'a>> {
|
|
|
|
renderer.render_body(body, self)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-11 22:25:48 +00:00
|
|
|
pub fn compile_template<'a>(
|
|
|
|
source: &'a str,
|
|
|
|
name: String,
|
|
|
|
) -> Result<CompiledTemplate<'a>, CompileError> {
|
2020-04-11 00:27:27 +00:00
|
|
|
// TODO: Make this all consuming
|
2020-04-11 22:25:48 +00:00
|
|
|
// TODO: This could use better error management
|
2020-04-11 00:27:27 +00:00
|
|
|
let (_remaining, parsed_template) = template(source).expect("Failed to compile template");
|
2020-04-11 22:25:48 +00:00
|
|
|
Ok(CompiledTemplate {
|
2020-04-11 00:27:27 +00:00
|
|
|
template: parsed_template,
|
|
|
|
name: name,
|
2020-04-11 22:25:48 +00:00
|
|
|
})
|
2020-04-11 00:27:27 +00:00
|
|
|
}
|
2020-04-11 00:55:44 +00:00
|
|
|
|
2020-04-11 00:58:55 +00:00
|
|
|
impl<'a> DustRenderer<'a> {
|
|
|
|
pub fn new() -> DustRenderer<'a> {
|
|
|
|
DustRenderer {
|
2020-04-11 22:25:48 +00:00
|
|
|
templates: HashMap::new(),
|
2020-04-11 00:55:44 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn load_source(&mut self, template: &'a CompiledTemplate) {
|
2020-04-11 22:25:48 +00:00
|
|
|
self.templates
|
|
|
|
.insert(template.name.clone(), &template.template);
|
2020-04-11 00:55:44 +00:00
|
|
|
}
|
2020-04-11 00:58:55 +00:00
|
|
|
|
2020-04-12 22:29:40 +00:00
|
|
|
pub fn render<C>(&self, name: &str, context: &'a C) -> Result<String, RenderError<'a>>
|
2020-04-11 22:25:48 +00:00
|
|
|
where
|
2020-04-12 02:52:20 +00:00
|
|
|
C: ContextElement,
|
2020-04-11 22:25:48 +00:00
|
|
|
{
|
|
|
|
let main_template = match self.templates.get(name) {
|
|
|
|
Some(tmpl) => tmpl,
|
|
|
|
None => {
|
2020-04-12 22:29:40 +00:00
|
|
|
return Err(RenderError::Generic(format!(
|
|
|
|
"No template named {} in context",
|
|
|
|
name
|
|
|
|
)));
|
2020-04-11 22:25:48 +00:00
|
|
|
}
|
|
|
|
};
|
2020-04-12 03:03:07 +00:00
|
|
|
self.render_body(&main_template.contents, context)
|
2020-04-11 22:25:48 +00:00
|
|
|
}
|
2020-04-11 00:58:55 +00:00
|
|
|
|
2020-04-12 22:29:40 +00:00
|
|
|
fn render_body<C>(&self, body: &Body, context: &'a C) -> Result<String, RenderError<'a>>
|
2020-04-11 22:25:48 +00:00
|
|
|
where
|
2020-04-12 02:52:20 +00:00
|
|
|
C: ContextElement,
|
2020-04-11 22:25:48 +00:00
|
|
|
{
|
|
|
|
let mut output = String::new();
|
2020-04-12 03:03:07 +00:00
|
|
|
for elem in &body.elements {
|
2020-04-11 22:25:48 +00:00
|
|
|
match elem {
|
|
|
|
TemplateElement::TESpan(span) => output.push_str(span.contents),
|
2020-04-11 22:40:36 +00:00
|
|
|
TemplateElement::TETag(dt) => {
|
|
|
|
output.push_str(&self.render_tag(dt, context)?);
|
|
|
|
}
|
2020-04-11 22:25:48 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
Ok(output)
|
|
|
|
}
|
2020-04-11 22:40:36 +00:00
|
|
|
|
2020-04-12 22:29:40 +00:00
|
|
|
fn render_tag<C>(&self, tag: &DustTag, context: &'a C) -> Result<String, RenderError<'a>>
|
2020-04-11 22:40:36 +00:00
|
|
|
where
|
2020-04-12 02:52:20 +00:00
|
|
|
C: ContextElement,
|
2020-04-11 22:40:36 +00:00
|
|
|
{
|
|
|
|
match tag {
|
2020-04-13 01:03:55 +00:00
|
|
|
DustTag::DTComment(_comment) => (),
|
2020-04-11 23:03:41 +00:00
|
|
|
DustTag::DTReference(reference) => {
|
2020-04-13 01:26:23 +00:00
|
|
|
let val = walk_path(context, &reference.path.keys);
|
|
|
|
if let Err(RenderError::WontWalk { .. }) = val {
|
|
|
|
// If reference does not exist in the context, it becomes an empty string
|
|
|
|
return Ok("".to_owned());
|
|
|
|
} else {
|
2020-04-13 01:54:15 +00:00
|
|
|
return val?.render(&reference.filters);
|
2020-04-13 01:26:23 +00:00
|
|
|
}
|
2020-04-11 23:03:41 +00:00
|
|
|
}
|
2020-04-29 00:46:29 +00:00
|
|
|
DustTag::DTSection(container) => {
|
|
|
|
let val = walk_path(context, &container.path.keys);
|
|
|
|
if let Err(RenderError::WontWalk { .. }) = val {
|
|
|
|
// If reference does not exist in the context, it becomes an empty string
|
|
|
|
return Ok("".to_owned());
|
|
|
|
} else {
|
|
|
|
let loop_elements: Vec<&dyn ContextElement> = val?.get_loop_elements()?;
|
|
|
|
if loop_elements.is_empty() {
|
|
|
|
// Oddly enough if the value is falsey (like
|
|
|
|
// an empty array or null), Dust uses the
|
|
|
|
// original context before walking the path as
|
|
|
|
// the context for rendering the else block
|
|
|
|
//
|
|
|
|
// TODO: do filters apply? I don't think so
|
|
|
|
// but I should test
|
|
|
|
return match &container.else_contents {
|
|
|
|
Some(body) => self.render_body(&body, context),
|
|
|
|
None => Ok("".to_owned()),
|
|
|
|
};
|
|
|
|
} else {
|
|
|
|
match container.contents {
|
2020-05-02 20:04:10 +00:00
|
|
|
None => return Ok("".to_owned()),
|
2020-04-29 00:46:29 +00:00
|
|
|
Some(body) => {
|
|
|
|
let rendered_results: Result<Vec<String>, RenderError> =
|
|
|
|
loop_elements
|
|
|
|
.into_iter()
|
2020-05-02 19:55:38 +00:00
|
|
|
.map(|array_elem| array_elem.render_body(self, &body))
|
2020-04-29 00:46:29 +00:00
|
|
|
.collect();
|
|
|
|
let rendered_slice: &[String] = &rendered_results?;
|
|
|
|
return Ok(rendered_slice.join(""));
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-04-11 22:40:36 +00:00
|
|
|
_ => (), // TODO: Implement the rest
|
|
|
|
}
|
|
|
|
Ok("".to_owned())
|
|
|
|
}
|
2020-04-11 00:55:44 +00:00
|
|
|
}
|
2020-04-12 00:31:44 +00:00
|
|
|
|
2020-04-12 22:29:40 +00:00
|
|
|
fn walk_path<'a>(
|
|
|
|
context: &'a dyn ContextElement,
|
|
|
|
path: &Vec<&str>,
|
|
|
|
) -> Result<&'a dyn ContextElement, RenderError<'a>> {
|
2020-04-12 02:36:22 +00:00
|
|
|
let mut output = context;
|
2020-04-12 00:31:44 +00:00
|
|
|
|
2020-04-12 02:36:22 +00:00
|
|
|
for elem in path.iter() {
|
2020-04-12 22:29:40 +00:00
|
|
|
output = output.walk(elem)?;
|
2020-04-12 02:36:22 +00:00
|
|
|
}
|
2020-04-12 01:57:24 +00:00
|
|
|
|
2020-04-12 22:29:40 +00:00
|
|
|
Ok(output)
|
2020-04-12 02:36:22 +00:00
|
|
|
}
|
2020-04-12 00:31:44 +00:00
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
use super::*;
|
2020-04-13 01:54:15 +00:00
|
|
|
use crate::parser::Filter;
|
2020-04-28 23:34:52 +00:00
|
|
|
use crate::renderer::context_element::Loopable;
|
2020-04-28 23:09:02 +00:00
|
|
|
use crate::renderer::context_element::Renderable;
|
|
|
|
use crate::renderer::context_element::Walkable;
|
2020-04-12 00:31:44 +00:00
|
|
|
|
2020-04-12 02:54:48 +00:00
|
|
|
#[test]
|
|
|
|
fn test_walk_path() {
|
|
|
|
impl ContextElement for u32 {}
|
|
|
|
impl ContextElement for &str {}
|
|
|
|
impl<I: ContextElement> ContextElement for HashMap<&str, I> {}
|
2020-04-12 02:19:54 +00:00
|
|
|
|
2020-04-12 02:54:48 +00:00
|
|
|
impl Renderable for u32 {
|
2020-04-13 01:57:42 +00:00
|
|
|
fn render(&self, _filters: &Vec<Filter>) -> Result<String, RenderError> {
|
2020-04-13 01:54:15 +00:00
|
|
|
// TODO: handle the filters
|
2020-04-13 01:03:55 +00:00
|
|
|
Ok(self.to_string())
|
2020-04-12 02:54:48 +00:00
|
|
|
}
|
2020-04-12 02:19:54 +00:00
|
|
|
}
|
|
|
|
|
2020-04-12 02:54:48 +00:00
|
|
|
impl Renderable for &str {
|
2020-04-13 01:57:42 +00:00
|
|
|
fn render(&self, _filters: &Vec<Filter>) -> Result<String, RenderError> {
|
2020-04-13 01:54:15 +00:00
|
|
|
// TODO: handle the filters
|
2020-04-13 01:03:55 +00:00
|
|
|
Ok(self.to_string())
|
2020-04-12 02:54:48 +00:00
|
|
|
}
|
2020-04-12 02:19:54 +00:00
|
|
|
}
|
|
|
|
|
2020-04-12 02:54:48 +00:00
|
|
|
impl<I: ContextElement> Renderable for HashMap<&str, I> {
|
2020-04-13 01:57:42 +00:00
|
|
|
fn render(&self, _filters: &Vec<Filter>) -> Result<String, RenderError> {
|
2020-04-13 01:54:15 +00:00
|
|
|
// TODO: handle the filters
|
2020-04-13 01:03:55 +00:00
|
|
|
Err(RenderError::CantRender { elem: self })
|
2020-04-12 02:54:48 +00:00
|
|
|
}
|
2020-04-12 02:19:54 +00:00
|
|
|
}
|
|
|
|
|
2020-04-12 02:54:48 +00:00
|
|
|
impl<I: ContextElement> Walkable for HashMap<&str, I> {
|
2020-04-12 22:29:40 +00:00
|
|
|
fn walk(&self, segment: &str) -> Result<&dyn ContextElement, RenderError> {
|
2020-04-13 00:52:30 +00:00
|
|
|
let child = self.get(segment).ok_or(RenderError::WontWalk {
|
|
|
|
segment: segment.to_string(),
|
|
|
|
elem: self,
|
|
|
|
})?;
|
|
|
|
Ok(child)
|
2020-04-12 02:54:48 +00:00
|
|
|
}
|
2020-04-12 00:31:44 +00:00
|
|
|
}
|
|
|
|
|
2020-04-12 02:54:48 +00:00
|
|
|
impl Walkable for &str {
|
2020-04-13 00:52:30 +00:00
|
|
|
fn walk(&self, segment: &str) -> Result<&dyn ContextElement, RenderError> {
|
|
|
|
Err(RenderError::CantWalk {
|
|
|
|
segment: segment.to_string(),
|
|
|
|
elem: self,
|
|
|
|
})
|
2020-04-12 02:54:48 +00:00
|
|
|
}
|
2020-04-12 01:07:12 +00:00
|
|
|
}
|
|
|
|
|
2020-04-12 02:54:48 +00:00
|
|
|
impl Walkable for u32 {
|
2020-04-13 00:52:30 +00:00
|
|
|
fn walk(&self, segment: &str) -> Result<&dyn ContextElement, RenderError> {
|
|
|
|
Err(RenderError::CantWalk {
|
|
|
|
segment: segment.to_string(),
|
|
|
|
elem: self,
|
|
|
|
})
|
2020-04-12 02:54:48 +00:00
|
|
|
}
|
2020-04-12 00:34:16 +00:00
|
|
|
}
|
|
|
|
|
2020-04-28 23:34:52 +00:00
|
|
|
impl Loopable for &str {
|
|
|
|
fn get_loop_elements(&self) -> Result<Vec<&dyn ContextElement>, RenderError> {
|
|
|
|
if self.is_empty() {
|
|
|
|
Ok(Vec::new())
|
|
|
|
} else {
|
|
|
|
Ok(vec![self])
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Loopable for u32 {
|
|
|
|
fn get_loop_elements(&self) -> Result<Vec<&dyn ContextElement>, RenderError> {
|
|
|
|
Ok(vec![self])
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<I: ContextElement> Loopable for HashMap<&str, I> {
|
|
|
|
fn get_loop_elements(&self) -> Result<Vec<&dyn ContextElement>, RenderError> {
|
|
|
|
Ok(vec![self])
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-12 01:07:12 +00:00
|
|
|
let context: HashMap<&str, &str> =
|
|
|
|
[("cat", "kitty"), ("dog", "doggy"), ("tiger", "murderkitty")]
|
|
|
|
.iter()
|
|
|
|
.cloned()
|
|
|
|
.collect();
|
2020-04-12 01:12:42 +00:00
|
|
|
let number_context: HashMap<&str, u32> = [("cat", 1), ("dog", 2), ("tiger", 3)]
|
|
|
|
.iter()
|
|
|
|
.cloned()
|
|
|
|
.collect();
|
2020-04-12 01:20:45 +00:00
|
|
|
let deep_context: HashMap<&str, HashMap<&str, &str>> = [
|
|
|
|
("cat", [("food", "meat")].iter().cloned().collect()),
|
|
|
|
("dog", [("food", "meat")].iter().cloned().collect()),
|
|
|
|
("tiger", [("food", "people")].iter().cloned().collect()),
|
|
|
|
]
|
|
|
|
.iter()
|
|
|
|
.cloned()
|
|
|
|
.collect();
|
2020-04-12 02:23:59 +00:00
|
|
|
assert_eq!(
|
2020-04-13 01:54:15 +00:00
|
|
|
walk_path(&context, &vec!["cat"])
|
|
|
|
.unwrap()
|
|
|
|
.render(&Vec::new())
|
|
|
|
.unwrap(),
|
2020-04-13 01:03:55 +00:00
|
|
|
"kitty".to_owned()
|
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
walk_path(&number_context, &vec!["tiger"])
|
|
|
|
.unwrap()
|
2020-04-13 01:54:15 +00:00
|
|
|
.render(&Vec::new())
|
2020-04-13 01:03:55 +00:00
|
|
|
.unwrap(),
|
|
|
|
"3".to_owned()
|
2020-04-12 22:31:27 +00:00
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
walk_path(&deep_context, &vec!["tiger", "food"])
|
|
|
|
.unwrap()
|
2020-04-13 01:54:15 +00:00
|
|
|
.render(&Vec::new())
|
2020-04-13 01:03:55 +00:00
|
|
|
.unwrap(),
|
|
|
|
"people".to_owned()
|
2020-04-12 02:23:59 +00:00
|
|
|
);
|
2020-04-12 00:31:44 +00:00
|
|
|
}
|
|
|
|
}
|