]> git.r.bdr.sh - rbdr/blog/blobdiff - src/template.rs
Add generator
[rbdr/blog] / src / template.rs
index 37906ede58842f9fb321747090a8bd5501c3af71..03325063a94e45f5fe291f7396e17ac32c1879c0 100644 (file)
@@ -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<TemplateContext>),
+    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<String, TemplateValue>;
+
+struct TemplateContextGetter {}
+impl TemplateContextGetter {
+    fn get(context: &TemplateContext, path: &str) -> Option<TemplateValue> {
+        let path_parts: Vec<&str> = path.split('.').collect();
+        TemplateContextGetter::recursively_get_value(context, &path_parts)
+    }
+
+    fn recursively_get_value(context: &TemplateContext, path: &[&str]) -> Option<TemplateValue> {
+        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<Token>
 }
 
+impl ParsedTemplate {
+    pub fn render(&self, context: &TemplateContext) -> String {
+        ParsedTemplate::render_tokens(&self.tokens, context)
+    }
+
+    pub fn render_tokens(tokens: &Vec<Token>, 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);