mint_template_engine/
lib.rs

1use std::collections::HashMap;
2use std::fs::File;
3use std::io;
4use std::io::prelude::*;
5
6pub fn take2<I, T>(x: &mut I) -> Option<(T, T)>
7where
8    I: Iterator<Item = T>,
9{
10    let x1 = x.next()?;
11    let x2 = x.next()?;
12    Some((x1, x2))
13}
14
15fn str_find_at(s: &str, start: usize, pat: &str) -> Option<usize> {
16    s[start..].find(pat).and_then(|i| Some(start + i))
17}
18
19struct StrFindAll<'a, 'b> {
20    s: &'a str,
21    pat: &'b str,
22    start: usize,
23}
24
25impl<'a, 'b> Iterator for StrFindAll<'a, 'b> {
26    type Item = usize;
27
28    fn next(&mut self) -> Option<usize> {
29        str_find_at(self.s, self.start, self.pat)
30            .and_then(|pos| {
31                self.start = pos + self.pat.len();
32                Some(pos)
33            })
34    }
35}
36
37fn str_find_all<'a, 'b>(s: &'a str, pat: &'b str) -> StrFindAll<'a, 'b> {
38    StrFindAll { s, pat, start: 0 }
39}
40
41pub trait SliceAsStrs {
42    fn as_strs(&self) -> Vec<&str>;
43}
44
45// TODO: Is this actually useful?
46impl SliceAsStrs for [String] {
47    fn as_strs(&self) -> Vec<&str> {
48        self.iter().map(|s| &s[..]).collect()
49    }
50}
51
52pub fn do_file(tmpl_name: &str, environ: &HashMap<&str, &str>)
53    -> Result<Vec<String>, String>
54{
55    let tf = File::open(tmpl_name).map_err(|e| e.to_string())?;
56    let tb = io::BufReader::new(tf);
57    let lines: Vec<String> = tb.lines()
58        .collect::<Result<_, _>>() // TODO: understand this magic
59        .map_err(|e| e.to_string())?; // TODO: make this friendlier
60    do_lines(&lines, environ)
61}
62
63pub fn do_lines(lines: &Vec<String>, environ: &HashMap<&str, &str>)
64    -> Result<Vec<String>, String>
65{
66    static OPEN_PAT: &str = "{{";
67    static OPEN_ESC: &str = "{{!";
68    static CLOSE_PAT: &str = "}}";
69
70    let mut lines2 = Vec::new();
71    for (row, line) in lines.iter().enumerate() {
72        // Some((to, from, val)) or None (end)
73        let mut replace = vec![Some((0, 0, ""))];
74        for open_pos in str_find_all(line, OPEN_PAT) {
75            let name_start = line[open_pos + OPEN_PAT.len() ..].chars().next();
76            if name_start == Some('!') {
77                replace.push(
78                    Some((open_pos, open_pos + OPEN_ESC.len(), "{{"))
79                );
80            } else {
81                let close_pos = str_find_at(line, open_pos, CLOSE_PAT)
82                    .ok_or(
83                        format!("{}: Missing \"{}\"", row, CLOSE_PAT)
84                    )?;
85                let name_from = open_pos + OPEN_PAT.len();
86                let name_to = close_pos;
87                let name = &line[name_from..name_to];
88                let val = environ.get(name)
89                    .ok_or_else(||
90                        format!(
91                            "{},{}: {} is not defined", row, open_pos, name
92                        )
93                    )?;
94                replace.push(
95                    Some((open_pos, close_pos + CLOSE_PAT.len(), val))
96                );
97            }
98        }
99        replace.push(None);
100
101        let mut line2 = "".to_string();
102        for window in replace.windows(2) {
103            let (prev, this) = (window[0], window[1]);
104            let (_, prev_to, _) = prev.unwrap();
105            if let Some((from, _, val)) = this {
106                line2.push_str(&line[prev_to..from]);
107                line2.push_str(val);
108            } else {
109                line2.push_str(&line[prev_to..]);
110            }
111        }
112        lines2.push(line2);
113    }
114    Ok(lines2)
115}
116
117trait InvertOption<T: Default> {
118    fn invert(self) -> Option<T>;
119}
120
121// TODO: Is this useful?
122impl<T: Default> InvertOption<T> for Option<T> {
123    fn invert(self) -> Option<T> {
124        match self {
125            Some(_) => None,
126            None => Some(T::default()),
127        }
128    }
129}
130
131fn args_to_environ<'a>(args: &[&'a str]) -> Option<HashMap<&'a str, &'a str>> {
132    let mut environ = HashMap::<&str, &str>::new();
133    for pair in args {
134        let (name, val) = take2(&mut pair.splitn(2, '='))?;
135        if name.starts_with("!") || name.contains("}}") {
136            return None;
137        }
138        environ.insert(name, val).invert()?; // TODO: need an error message
139    }
140    Some(environ)
141}
142
143#[derive(Debug, PartialEq)]
144pub struct Mint<'a> {
145    pub tmpl_name: &'a str,
146    pub environ: HashMap<&'a str, &'a str>,
147}
148
149impl<'a> Mint<'a> {
150    pub fn with_args(args: &[&'a str]) -> Option<Mint<'a>> {
151        let tmpl_name = args.get(0)?;
152        let environ_args: Vec<&str> = args[1..].to_vec();
153        let environ = args_to_environ(&environ_args)?;
154        Some(Mint { tmpl_name, environ })
155    }
156}