femstache 0.1.1

Embrace the feminine mustache to unlock your templating dreams! Or something, I don't know. :3
Documentation
use std::{borrow::Borrow, collections::BTreeMap};

use crate::parse::{self, Token};

/// Trait used to get strings to interpolate into the templates
pub trait TemplateInput {
    /// Get the item with key `key`, return `Some` when it exists and `None`
    /// when it does not
    fn get(&self, key: &str) -> Option<String>;
}

impl<K: Borrow<str> + Ord, V: ToString> TemplateInput for BTreeMap<K, V> {
    fn get(&self, key: &str) -> Option<String> {
        self.get(key).map(|v| v.to_string())
    }
}

#[derive(Debug, Clone)]
enum Command {
    String(String),
    Stache(String),
}

/// A compiled template
#[derive(Debug, Clone)]
pub struct Template {
    commands: Vec<Command>,
}

impl Template {
    /// Parse a template from a string
    ///
    /// ```rust
    /// # use femstache::Template;
    /// Template::parse("hello {{place}}! :3");
    /// ```
    pub fn parse(source: impl AsRef<str>) -> Template {
        let parsed = parse::parse(source.as_ref());
        Template {
            commands: parsed
                .into_iter()
                .map(|t| match t {
                    Token::Str(s) => Command::String(s.to_owned()),
                    Token::Stache(s) => Command::Stache(s.to_owned()),
                })
                .collect(),
        }
    }

    /// Render a template using the specified input
    ///
    /// ```rust
    /// # use std::collections::BTreeMap;
    /// # use femstache::Template;
    /// let tpl = Template::parse("hello, {{place}}! :3c");
    ///
    /// let mut map = BTreeMap::new();
    /// map.insert("place", "delicious world");
    ///
    /// assert_eq!(tpl.render(&map), "hello, delicious world! :3c");
    /// ```
    pub fn render(&self, inputs: &impl TemplateInput) -> String {
        let mut output = String::new();
        for command in &self.commands {
            match command {
                Command::String(s) => {
                    output.push_str(s);
                }
                Command::Stache(key) => {
                    if let Some(value) = inputs.get(key) {
                        output.push_str(&value);
                    } else {
                        // TODO(lumi): error?
                        output.push_str("{{");
                        output.push_str(key);
                        output.push_str("}}");
                    }
                }
            }
        }
        output
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn template_works() {
        let tpl = Template::parse("hewo! you are {{meow}}");
        struct Input;
        impl TemplateInput for Input {
            fn get(&self, _key: &str) -> Option<String> {
                Some("gay".to_owned())
            }
        }
        assert_eq!(tpl.render(&Input), "hewo! you are gay");
    }

    #[test]
    fn template_missing_works() {
        let tpl = Template::parse("hewo! you are {{meow}}");
        struct Input;
        impl TemplateInput for Input {
            fn get(&self, _key: &str) -> Option<String> {
                None
            }
        }
        assert_eq!(tpl.render(&Input), "hewo! you are {{meow}}");
    }
}