duster/src/renderer/renderer.rs

190 lines
5.1 KiB
Rust

use crate::parser::template;
use crate::parser::DustTag;
use crate::parser::Template;
use crate::parser::TemplateElement;
use crate::renderer::errors::CompileError;
use crate::renderer::errors::RenderError;
use crate::renderer::renderable::Renderable;
use crate::renderer::walkable::Walkable;
use std::collections::HashMap;
use std::ops::Index;
#[derive(Clone, Debug)]
pub struct CompiledTemplate<'a> {
template: Template<'a>,
pub name: String,
}
#[derive(Clone, Debug)]
pub struct DustRenderer<'a> {
templates: HashMap<String, &'a Template<'a>>,
}
pub fn compile_template<'a>(
source: &'a str,
name: String,
) -> Result<CompiledTemplate<'a>, CompileError> {
// TODO: Make this all consuming
// TODO: This could use better error management
let (_remaining, parsed_template) = template(source).expect("Failed to compile template");
Ok(CompiledTemplate {
template: parsed_template,
name: name,
})
}
impl<'a> DustRenderer<'a> {
pub fn new() -> DustRenderer<'a> {
DustRenderer {
templates: HashMap::new(),
}
}
pub fn load_source(&mut self, template: &'a CompiledTemplate) {
self.templates
.insert(template.name.clone(), &template.template);
}
pub fn render<C>(&self, name: &str, context: &C) -> Result<String, RenderError>
where
C: Index<&'a str>,
<C as std::ops::Index<&'a str>>::Output: Renderable,
{
let main_template = match self.templates.get(name) {
Some(tmpl) => tmpl,
None => {
return Err(RenderError {
message: format!("No template named {} in context", name),
});
}
};
self.render_template(main_template, context)
}
fn render_template<C>(&self, template: &Template, context: &C) -> Result<String, RenderError>
where
C: Index<&'a str>,
<C as std::ops::Index<&'a str>>::Output: Renderable,
{
let mut output = String::new();
for elem in &template.contents.elements {
match elem {
TemplateElement::TESpan(span) => output.push_str(span.contents),
TemplateElement::TETag(dt) => {
output.push_str(&self.render_tag(dt, context)?);
}
}
}
Ok(output)
}
fn render_tag<C>(&self, tag: &DustTag, context: &C) -> Result<String, RenderError>
where
C: Index<&'a str>,
<C as std::ops::Index<&'a str>>::Output: Renderable,
{
match tag {
DustTag::DTComment(comment) => (),
DustTag::DTReference(reference) => {
let val = context.index("name");
return Ok(val.render());
}
_ => (), // TODO: Implement the rest
}
Ok("".to_owned())
}
}
fn walk_path<'a, C>(context: &'a C, path: &Vec<&str>) -> &'a C
where
C: Walkable<Output = C>,
{
let mut output: &C = context;
for elem in path.iter() {
output = context.walk(elem);
}
output
}
#[cfg(test)]
mod tests {
use super::*;
impl<'a, I: Walkable> Walkable for HashMap<&str, I> {
type Output = I;
fn walk(&self, segment: &str) -> &I {
self.get(segment).unwrap()
}
fn val(&self) -> String {
"val".to_owned()
}
}
impl<'a> Walkable for &str {
type Output = u32;
fn walk(&self, segment: &str) -> &u32 {
panic!("Tried to walk down a str");
}
fn val(&self) -> String {
"val".to_owned()
}
}
impl<'a> Walkable for u32 {
type Output = u32;
fn walk(&self, segment: &str) -> &u32 {
panic!("Tried to walk down a str");
}
fn val(&self) -> String {
"val".to_owned()
}
}
fn do_the_walk<'a>(context: &'a impl Walkable, path: &Vec<&str>) -> &'a impl Walkable {
let mut output = context;
context.walk(path.first().unwrap())
// for elem in path.iter() {
// output = context.walk(elem);
// }
// output
}
#[test]
fn test_walk_path() {
let context: HashMap<&str, &str> =
[("cat", "kitty"), ("dog", "doggy"), ("tiger", "murderkitty")]
.iter()
.cloned()
.collect();
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();
assert_eq!(do_the_walk(&context, &vec!["cat"]).val(), "kitty");
// assert_eq!(do_the_walk(&number_context, &vec!["tiger"]), &3);
// assert_eq!(
// do_the_walk(&deep_context, &vec!["tiger"]),
// &[("food", "people")].iter().cloned().collect()
// );
}
}