]> git.r.bdr.sh - rbdr/blog/blobdiff - src/template.rs
Add tokenizer
[rbdr/blog] / src / template.rs
diff --git a/src/template.rs b/src/template.rs
new file mode 100644 (file)
index 0000000..37906ed
--- /dev/null
@@ -0,0 +1,140 @@
+use std::fs::File;
+use std::path::PathBuf;
+use std::io::Read;
+
+const TXT_TEMPLATE: &'static str = include_str!("../templates/index.txt");
+const HTML_TEMPLATE: &'static str = include_str!("../templates/index.html");
+const GMI_TEMPLATE: &'static str = include_str!("../templates/index.gmi");
+const RSS_TEMPLATE: &'static str = include_str!("../templates/feed.xml");
+
+// Parse and Render
+
+pub enum Token {
+    Text(String),
+    DisplayDirective { content: String },
+    ConditionalDirective { condition: String, children: Vec<Token>},
+    IteratorDirective { collection: String, member_label: String, children: Vec<Token> }
+}
+
+impl std::fmt::Display for Token {
+    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
+        match self {
+            Token::Text(label) => write!(f, "Text {}", label),
+            Token::DisplayDirective{content} => write!(f, "DisplayDirective {}", content),
+            Token::ConditionalDirective{condition, children} => {
+                write!(f, "ConditionalDirective {} [[[\n", condition)?;
+                for child in children {
+                    write!(f, "\t{}\n", child)?;
+                }
+                write!(f, "\n]]]")
+            },
+            Token::IteratorDirective{collection, member_label, children} => {
+                write!(f, "IteratorDirective {}: {} [[[\n", collection, member_label)?;
+                for child in children {
+                    write!(f, "\t{}\n", child)?;
+                }
+                write!(f, "\n]]]")
+            },
+        }
+    }
+}
+
+pub struct ParsedTemplate {
+    pub tokens: Vec<Token>
+}
+
+pub fn parse(template: &str) -> ParsedTemplate {
+    let mut tokens = Vec::new();
+    tokenize(template, &mut tokens);
+    ParsedTemplate {
+        tokens
+    }
+}
+
+fn tokenize(template: &str, tokens: &mut Vec<Token>) {
+    let mut remaining_template = template;
+
+    while !remaining_template.is_empty() && remaining_template.contains("{{") {
+        let directive_start_index = remaining_template.find("{{")
+            .expect("Was expecting at least one tag opener");
+        if directive_start_index > 0 {
+            let text = remaining_template[..directive_start_index].to_string();
+            tokens.push(Token::Text(text.to_string()));
+        }
+        remaining_template = &remaining_template[directive_start_index..];
+
+        let directive_end_index = remaining_template.find("}}")
+            .expect("Was expecting }} after {{") + 2;
+        let directive = &remaining_template[..directive_end_index];
+        remaining_template = &remaining_template[directive_end_index..];
+
+        let directive_type = directive.chars().nth(2).unwrap();
+        match directive_type {
+            // Simple Directives
+            '=' => {
+                let content = directive[3..directive.len() - 2].trim();
+                tokens.push(Token::DisplayDirective{
+                    content: content.to_string()
+                });
+            },
+            // Block Directives
+            '?' | '~' => {
+                let content = directive[3..directive.len() - 2].trim();
+                let mut children = Vec::new();
+
+                match directive_type {
+                    '?' => {
+                        let closing_block = remaining_template.find("{{?}}").unwrap();
+                        let directive_block = &remaining_template[..closing_block];
+                        remaining_template = &remaining_template[closing_block + 5..];
+                        tokenize(directive_block, &mut children);
+                        tokens.push(Token::ConditionalDirective{
+                            condition: content.to_string(),
+                            children
+                        });
+                    },
+                    '~' => {
+                        let parts: Vec<_> = content.splitn(2, ':').collect();
+                        let closing_block = remaining_template.find("{{~}}").unwrap();
+                        let directive_block = &remaining_template[..closing_block];
+                        remaining_template = &remaining_template[closing_block + 5..];
+                        tokenize(directive_block, &mut children);
+                        if parts.len() == 2 {
+                            tokens.push(Token::IteratorDirective {
+                                collection: parts[0].trim().to_string(),
+                                member_label: parts[1].trim().to_string(),
+                                children
+                            });
+                        }
+                    },
+                    _ => unreachable!()
+                }
+            },
+            _ => unreachable!()
+        }
+    }
+    tokens.push(Token::Text(remaining_template.to_string()));
+}
+
+// File helpers.
+
+pub fn find(template_directory: &PathBuf, filename: &str) -> Option<String> {
+    let template_path = template_directory.join(filename);
+    if template_path.exists() {
+        let mut contents = String::new();
+        if File::open(template_path).ok()?.read_to_string(&mut contents).is_ok() {
+            return Some(contents);
+        }
+    }
+    find_default(filename)
+}
+
+fn find_default(filename: &str) -> Option<String> {
+    match filename {
+        "index.txt" => Some(TXT_TEMPLATE.to_string()),
+        "index.html" => Some(HTML_TEMPLATE.to_string()),
+        "index.gmi" => Some(GMI_TEMPLATE.to_string()),
+        "index.rss" => Some(RSS_TEMPLATE.to_string()),
+        &_ => None
+    }
+}