+use std::io::{Error, ErrorKind::Other, Result};
+use std::collections::HashMap;
use std::fs::File;
use std::path::PathBuf;
use std::io::Read;
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)?;
}
}
}
+#[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>
}
-pub fn parse(template: &str) -> ParsedTemplate {
+impl ParsedTemplate {
+ pub fn render(&self, context: &TemplateContext) -> Result<String> {
+ ParsedTemplate::render_tokens(&self.tokens, context)
+ }
+
+ pub fn render_tokens(tokens: &Vec<Token>, context: &TemplateContext) -> Result<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 } => {
+ let value = TemplateContextGetter::get(context, &content)
+ .ok_or_else(|| Error::new(Other, format!("{} is not a valid key", content)))?;
+ rendered_template.push_str(&value.render());
+ },
+ 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();
+ }
+
+ let value = TemplateContextGetter::get(context, &condition)
+ .ok_or_else(|| Error::new(Other, format!("{} is not a valid key", condition)))?;
+
+ match value {
+ TemplateValue::Bool(value) => {
+ if negator ^ value {
+ rendered_template.push_str(&ParsedTemplate::render_tokens(children, context)?)
+ }
+ Ok(())
+ },
+ _ => Err(Error::new(Other, format!("{} is not a boolean value", condition))),
+ }?;
+ },
+ Token::IteratorDirective { collection, member_label, children } => {
+ let value = TemplateContextGetter::get(context, &collection)
+ .ok_or_else(|| Error::new(Other, format!("{} is not a valid key", collection)))?;
+
+ match value {
+ 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)?)
+ }
+ Ok(())
+ },
+ _ => Err(Error::new(Other, format!("{} is not a collection", collection))),
+ }?;
+ }
+ }
+ }
+ Ok(rendered_template)
+ }
+}
+
+pub fn parse(template: &str) -> Option<ParsedTemplate> {
let mut tokens = Vec::new();
- tokenize(template, &mut tokens);
- ParsedTemplate {
+ tokenize(template, &mut tokens).ok()?;
+ Some(ParsedTemplate {
tokens
- }
+ })
}
-fn tokenize(template: &str, tokens: &mut Vec<Token>) {
+fn tokenize(template: &str, tokens: &mut Vec<Token>) -> Result<()> {
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");
+ .ok_or_else(|| Error::new(Other, "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;
+ .ok_or_else(|| Error::new(Other, "Was expecting }} after {{"))? + 2;
let directive = &remaining_template[..directive_end_index];
remaining_template = &remaining_template[directive_end_index..];
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);
+ tokenize(directive_block, &mut children)?;
tokens.push(Token::ConditionalDirective{
condition: content.to_string(),
children
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);
+ tokenize(directive_block, &mut children)?;
if parts.len() == 2 {
tokens.push(Token::IteratorDirective {
collection: parts[0].trim().to_string(),
}
}
tokens.push(Token::Text(remaining_template.to_string()));
+ Ok(())
}
// File helpers.