1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
extern crate regex;

use regex::Regex;
use std::collections::{HashMap, HashSet};

#[derive(Debug, PartialEq)]
pub struct Template {
    pub src: String,
    pub matches: Vec<(usize, usize)>,
    pub names: HashSet<String>,
}

impl Template {
    pub fn new(template: &str) -> Self {
        let regex = Regex::new(r"\{\{\s*([^}\s]*)\s*\}\}").unwrap();

        Template {
            src: template.to_owned(),
            matches: regex
                .find_iter(template)
                .map(|m| (m.start(), m.end()))
                .collect(),
            names: regex
                .captures_iter(template)
                .map(|cap| cap[1].to_string())
                // .into_iter()
                .collect(),
        }
    }

    /// # Examples
    /// ```
    /// use std::collections::HashMap;
    /// use requestr_core::Template;
    ///
    /// let template = Template::new("Hi, my name is {{ name }} and I'm a {{ lang }} developer.");
    ///
    /// let mut args: HashMap<String, String> = HashMap::new();
    /// args.insert("name".to_string(), "Stefan".to_string());
    /// args.insert("lang".to_string(), "Scala".to_string());
    /// let s = template.render(&args);
    ///
    /// assert_eq!(s, "Hi, my name is Stefan and I'm a Scala developer.");
    /// ```
    ///
    /// ```
    /// use std::collections::HashMap;
    /// use requestr_core::Template;
    ///
    /// let template = Template::new("Hi, my name is {{   name   }} and I'm a {{lang}} developer.");

    /// let mut args: HashMap<String, String> = HashMap::new();
    /// args.insert("name".to_string(), "undefined".to_string());
    /// args.insert("lang".to_string(), "JavaScript".to_string());
    /// let s = template.render(&args);
    ///
    /// assert_eq!(s, "Hi, my name is undefined and I'm a JavaScript developer.");
    /// ```
    pub fn render(&self, vals: &HashMap<String, String>) -> String {
        let mut parts: Vec<&str> = vec![];
        let template_str = &self.src;

        // get index of first arg match or return a copy of the template if no args matched
        let first = match self.matches.first() {
            Some((start, _)) => *start,
            _ => return template_str.clone(),
        };

        // copy from template start to first arg
        if first > 0 {
            parts.push(&template_str[0..first])
        }

        // keeps the index of the previous argument end
        let mut prev_end: Option<usize> = None;

        for (start, end) in self.matches.iter() {
            // copy from previous argument end till current argument start
            if let Some(last_end) = prev_end {
                parts.push(&template_str[last_end..*start])
            }

            // argument name with braces
            let arg = &template_str[*start..*end];
            // just the argument name
            let arg_name = arg[2..arg.len() - 2].trim();

            // if value passed for argument then append it, otherwise append original argument
            // name with braces
            match vals.get(arg_name) {
                Some(s) => parts.push(s),
                _ => parts.push(arg),
            }

            prev_end = Some(*end);
        }

        let template_len = template_str.len();
        // if last arg end index isn't the end of the string then copy
        // from last arg end till end of template string
        if let Some(last_pos) = prev_end {
            if last_pos < template_len {
                parts.push(&template_str[last_pos..template_len])
            }
        }

        parts.join("")
    }
}