duster/src/renderer/renderer.rs

208 lines
6.1 KiB
Rust
Raw Normal View History

use crate::parser::template;
use crate::parser::Body;
use crate::parser::DustTag;
use crate::parser::Template;
2020-04-11 22:25:48 +00:00
use crate::parser::TemplateElement;
use crate::renderer::errors::CompileError;
use crate::renderer::errors::RenderError;
use crate::renderer::renderable::Renderable;
2020-04-12 02:36:22 +00:00
use crate::renderer::walkable::ContextElement;
2020-04-12 00:31:44 +00:00
use crate::renderer::walkable::Walkable;
2020-04-11 22:25:48 +00:00
use std::collections::HashMap;
#[derive(Clone, Debug)]
pub struct CompiledTemplate<'a> {
template: Template<'a>,
2020-04-11 22:25:48 +00:00
pub name: String,
}
#[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 22:25:48 +00:00
pub fn compile_template<'a>(
source: &'a str,
name: String,
) -> Result<CompiledTemplate<'a>, CompileError> {
// TODO: Make this all consuming
2020-04-11 22:25:48 +00:00
// TODO: This could use better error management
let (_remaining, parsed_template) = template(source).expect("Failed to compile template");
2020-04-11 22:25:48 +00:00
Ok(CompiledTemplate {
template: parsed_template,
name: name,
2020-04-11 22:25:48 +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(),
}
}
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:58:55 +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
C: ContextElement,
2020-04-11 22:25:48 +00:00
{
let main_template = match self.templates.get(name) {
Some(tmpl) => tmpl,
None => {
return Err(RenderError::Generic(format!(
"No template named {} in context",
name
)));
2020-04-11 22:25:48 +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
fn render_body<C>(&self, body: &Body, context: &'a C) -> Result<String, RenderError<'a>>
2020-04-11 22:25:48 +00:00
where
C: ContextElement,
2020-04-11 22:25:48 +00:00
{
let mut output = String::new();
for elem in &body.elements {
2020-04-11 22:25:48 +00:00
match elem {
TemplateElement::TESpan(span) => output.push_str(span.contents),
TemplateElement::TETag(dt) => {
output.push_str(&self.render_tag(dt, context)?);
}
2020-04-11 22:25:48 +00:00
}
}
Ok(output)
}
fn render_tag<C>(&self, tag: &DustTag, context: &'a C) -> Result<String, RenderError<'a>>
where
C: ContextElement,
{
match tag {
DustTag::DTComment(_comment) => (),
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 {
return val?.render();
}
}
_ => (), // TODO: Implement the rest
}
Ok("".to_owned())
}
}
2020-04-12 00:31:44 +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() {
output = output.walk(elem)?;
2020-04-12 02:36:22 +00:00
}
2020-04-12 01:57:24 +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::*;
#[test]
fn test_walk_path() {
impl ContextElement for u32 {}
impl ContextElement for &str {}
impl<I: ContextElement> ContextElement for HashMap<&str, I> {}
impl Renderable for u32 {
fn render(&self) -> Result<String, RenderError> {
Ok(self.to_string())
}
}
impl Renderable for &str {
fn render(&self) -> Result<String, RenderError> {
Ok(self.to_string())
}
}
impl<I: ContextElement> Renderable for HashMap<&str, I> {
fn render(&self) -> Result<String, RenderError> {
Err(RenderError::CantRender { elem: self })
}
}
impl<I: ContextElement> Walkable for HashMap<&str, I> {
fn walk(&self, segment: &str) -> Result<&dyn ContextElement, RenderError> {
let child = self.get(segment).ok_or(RenderError::WontWalk {
segment: segment.to_string(),
elem: self,
})?;
Ok(child)
}
2020-04-12 00:31:44 +00:00
}
impl Walkable for &str {
fn walk(&self, segment: &str) -> Result<&dyn ContextElement, RenderError> {
Err(RenderError::CantWalk {
segment: segment.to_string(),
elem: self,
})
}
2020-04-12 01:07:12 +00:00
}
impl Walkable for u32 {
fn walk(&self, segment: &str) -> Result<&dyn ContextElement, RenderError> {
Err(RenderError::CantWalk {
segment: segment.to_string(),
elem: self,
})
}
2020-04-12 00:34:16 +00:00
}
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();
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!(
walk_path(&context, &vec!["cat"]).unwrap().render().unwrap(),
"kitty".to_owned()
);
assert_eq!(
walk_path(&number_context, &vec!["tiger"])
.unwrap()
.render()
.unwrap(),
"3".to_owned()
2020-04-12 22:31:27 +00:00
);
assert_eq!(
walk_path(&deep_context, &vec!["tiger", "food"])
.unwrap()
.render()
.unwrap(),
"people".to_owned()
2020-04-12 02:23:59 +00:00
);
2020-04-12 00:31:44 +00:00
}
}