mint_template_engine/
lib.rs1use 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
45impl 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<_, _>>() .map_err(|e| e.to_string())?; 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 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
121impl<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()?; }
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}