X-Git-Url: https://git.r.bdr.sh/rbdr/blog/blobdiff_plain/2998247083406f914b3647cedd19abf5507bf2c6..refs/heads/rust:/src/template.rs diff --git a/src/template.rs b/src/template.rs index 37906ed..0332506 100644 --- a/src/template.rs +++ b/src/template.rs @@ -1,3 +1,4 @@ +use std::collections::HashMap; use std::fs::File; use std::path::PathBuf; use std::io::Read; @@ -29,7 +30,7 @@ impl std::fmt::Display for Token { write!(f, "\n]]]") }, Token::IteratorDirective{collection, member_label, children} => { - write!(f, "IteratorDirective {}: {} [[[\n", collection, member_label)?; + write!(f, "{} in {}\n", collection, member_label)?; for child in children { write!(f, "\t{}\n", child)?; } @@ -39,10 +40,101 @@ impl std::fmt::Display for Token { } } +#[derive(Clone)] +pub enum TemplateValue { + String(String), + Unsigned(u64), + Bool(bool), + Collection(Vec), + Context(TemplateContext) +} + +impl TemplateValue { + fn render(&self) -> String { + match self { + TemplateValue::String(string) => string.to_string(), + TemplateValue::Unsigned(number) => format!("{}", number), + TemplateValue::Bool(bool) => format!("{}", bool), + _ => "".to_string() + } + } +} + +pub type TemplateContext = HashMap; + +struct TemplateContextGetter {} +impl TemplateContextGetter { + fn get(context: &TemplateContext, path: &str) -> Option { + let path_parts: Vec<&str> = path.split('.').collect(); + TemplateContextGetter::recursively_get_value(context, &path_parts) + } + + fn recursively_get_value(context: &TemplateContext, path: &[&str]) -> Option { + match context.get(path[0]) { + Some(TemplateValue::Context(next)) if path.len() > 1 => TemplateContextGetter::recursively_get_value(next, &path[1..]), + Some(value) if path.len() == 1 => Some(value.clone()), + _ => None + } + } +} + pub struct ParsedTemplate { pub tokens: Vec } +impl ParsedTemplate { + pub fn render(&self, context: &TemplateContext) -> String { + ParsedTemplate::render_tokens(&self.tokens, context) + } + + pub fn render_tokens(tokens: &Vec, context: &TemplateContext) -> String { + let mut rendered_template: String = String::new(); + for token in tokens { + match token { + Token::Text(contents) => rendered_template.push_str(&contents), + Token::DisplayDirective { content } => { + match TemplateContextGetter::get(context, &content) { + Some(value) => rendered_template.push_str(&value.render()), + None => panic!("{} is not a valid key", content) + } + }, + Token::ConditionalDirective { condition, children} => { + let mut negator = false; + let mut condition = condition.to_string(); + if condition.starts_with('!') { + negator = true; + condition = condition[1..].to_string(); + } + match TemplateContextGetter::get(context, &condition) { + Some(TemplateValue::Bool(value)) => { + if negator ^ value { + rendered_template.push_str(&ParsedTemplate::render_tokens(children, context)) + } + }, + _ => panic!("{} is not a boolean value", condition) + } + }, + Token::IteratorDirective { collection, member_label, children } => { + match TemplateContextGetter::get(context, &collection) { + Some(TemplateValue::Collection(collection)) => { + for member in collection { + let mut child_context = context.clone(); + child_context.insert( + member_label.to_string(), + TemplateValue::Context(member) + ); + rendered_template.push_str(&ParsedTemplate::render_tokens(&children, &child_context)) + } + }, + _ => panic!("{} is not a collection", collection) + } + } + } + } + rendered_template + } +} + pub fn parse(template: &str) -> ParsedTemplate { let mut tokens = Vec::new(); tokenize(template, &mut tokens);