text_template/
lib.rs

1//! # A Minimal Text Template Engine
2//! 
3//! ## Overview
4//! This library implements templates consisting of text including named placeholders.
5//! Placeholders are special character sequences: their names surrounded by `${` and `}`.
6//! The example Template `Hello␣${name}` consists of the text `Hello␣` and the placeholder `name`.
7//! 
8//! Trailing and pending whitespace inside placeholder elements are significant and conditionally
9//! included in the output if the value to be inserted for the placeholder is not empty. For
10//! example `${title␣}${name}` may evaluate to `Dr.␣X` or `Y` while `${title}␣${name}` may evaluate to
11//! `Dr.␣X` or `␣Y`.
12//! 
13//! Templates are represented by the structure [`Template`].
14//! The templates implementation of `From<&str>` can be used to construct templates from strings.
15//! Templates can be filled in by using [`fill_in`] or [`try_fill_in`], which replace any 
16//! placeholders in the template by the given values. The returned [`Text`] structure is simply
17//! a wrapper type around `Vec<&str>` and dereferences to it.
18//! 
19//! A text representation of the templates and the filled in results can be obtained by using `to_string`.
20//! 
21//! This library only stores references to the given template strings. It allocates a `Vec`
22//! to store a list of text and placeholder elements while parsing a template string. A template,
23//! once created, can be used multiple times to fill in different sets of values.
24//! 
25//! ## Example
26//! 
27//!     use text_template::*;
28//!     use std::collections::HashMap;
29//! 
30//!     let template = Template::from("Hello ${title }${name}");
31//! 
32//!     let mut values = HashMap::new();
33//!     values.insert("name", "Jane");
34//! 
35//!     let text = template.fill_in(&values);
36//!
37//!     assert_eq!(text.to_string(), "Hello Jane");
38//!     assert_eq!(template.to_string(), "Hello ${title }${name}");
39//!
40//! [`Piece`]: enum.Piece.html
41//! [`Piece::Text`]: enum.Piece.html#variant.Text
42//! [`Piece::Placeholder`]: enum.Piece.html#variant.Placeholder
43//! [`Pieces`]: struct.Pieces.html
44//! [`Template`]: struct.Template.html
45//! [`fill_in`]: struct.Template.html#method.fill_in
46//! [`try_fill_in`]: struct.Template.html#method.try_fill_in
47//! [`Text`]: struct.Text.html
48
49use std::collections::HashMap;
50use std::fmt;
51use std::ops::{Deref,DerefMut};
52
53
54/// Main data structure of this crate.
55/// 
56/// Use [`Template::from`] to create a template from a `&str` and [`fill_in`] or [`try_fill_in`] to replace
57/// the placeholders with some actual values.
58/// 
59/// Each template is mainly a vector of `Piece`.
60/// 
61/// [`fill_in`]: struct.Template.html#method.fill_in
62/// [`try_fill_in`]: struct.Template.html#method.try_fill_in
63/// [`Template::from`]: struct.Template.html#method.from
64#[derive(Clone,PartialEq,Debug)]
65pub struct Template<'a>(Pieces<'a>);
66
67impl<'a> Deref for Template<'a> {
68    type Target = Vec<Piece<'a>>;
69    fn deref(&self) -> &Self::Target {
70        &(self.0).0
71    }
72}
73
74impl<'a> DerefMut for Template<'a> {
75    fn deref_mut(&mut self) -> &mut Self::Target {
76        &mut (self.0).0
77    }
78}
79
80impl<'a> fmt::Display for Template<'a> {
81    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
82        self.0.fmt(f)
83    }
84}
85
86impl<'a> Template<'a> {
87    pub fn with_pieces(v: Vec<Piece<'a>>) -> Self {
88        Template(Pieces(v))
89    }
90
91    /// Fills in all placeholder elements. If `lookup_tbl` does not contain the value for a placeholder,
92    /// an empty string is inserted instead.
93    pub fn fill_in(&'a self, lookup_tbl: &HashMap<&'a str, &'a str>) -> Text<'a> {
94        let mut t = Text::new();
95
96        for v in self.0.iter() {
97            match v {
98                Piece::Text(s) => t.push(s),
99                Piece::Placeholder{name, before, after} => {
100                    let entry = lookup_tbl.get(name);
101                    match entry {
102                        Some(value) if value.len() > 0 => {
103                            if before.len() > 0 { t.push(before) };
104                            t.push(value);
105                            if after.len() > 0 { t.push(after) };
106                        }
107                        _ => {}
108                    }
109                }
110            }
111        }
112        
113        t
114    }
115
116    /// Tries to fill in all placeholder elements. If `lookup_tbl` does not contain the value for a placeholder,
117    /// a `TemplateError` is returned.
118    pub fn try_fill_in(&'a self, lookup_tbl: &HashMap<&'a str, &'a str>) -> Result<Text<'a>, TemplateError> {
119        let mut t = Text::new();
120
121        for v in self.0.iter() {
122            match v {
123                Piece::Text(s) => t.push(s),
124                Piece::Placeholder{name, before, after} => {
125                    let entry = lookup_tbl.get(name);
126                    match entry {
127                        Some(value) => {
128                            if before.len() > 0 { t.push(before) };
129                            t.push(value);
130                            if after.len() > 0 { t.push(after) };
131                        },
132                        None => { return Err(TemplateError) }
133                    }
134                }
135            }
136        }
137        
138        Ok(t)
139    }
140
141    /*pub fn pieces(&self) -> &Pieces {
142        &self.0
143    }*/
144}
145
146impl<'a> From<&'a str> for Template<'a> {
147    fn from(s: &'a str) -> Self {
148        enum State{InText, InPlaceholder};
149
150        let mut v: Vec<Piece> = vec![];
151        let mut state = State::InText;
152        let mut rest = s;
153        
154        while rest.len() > 0 {
155            match state {
156                State::InText => {
157                    if let Some(idx) = rest.find("${") {
158                        v.push(Piece::Text(&rest[..idx]));
159                        rest = &rest[idx+2..];
160                        state = State::InPlaceholder;
161                    } else {
162                        v.push(Piece::Text(rest));
163                        rest = &rest[0..0];
164                    }
165                }
166                State::InPlaceholder => {
167                    if let Some(idx) = rest.find("}") {
168                        let (before, name, after) = trim_split(&rest[..idx]);
169                        v.push(Piece::Placeholder{name, before, after});
170
171                        rest = &rest[idx+1..];
172                        state = State::InText;                       
173                    } else {
174                        v.push(Piece::Text(rest));
175                        rest = &rest[0..0];
176                    }
177                }
178            }
179        }
180        Template::with_pieces(v)
181    }    
182}
183
184
185
186/// A vector of pieces.
187#[derive(Clone,PartialEq,Debug)]
188struct Pieces<'a>(Vec<Piece<'a>>);
189
190impl<'a> Pieces<'a> {
191}
192
193impl<'a> Deref for Pieces<'a> {
194    type Target = Vec<Piece<'a>>;
195    fn deref(&self) -> &Self::Target {
196        &self.0
197    }
198}
199
200impl<'a> fmt::Display for Pieces<'a> {
201    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
202        self.0.iter().map(|v| v.fmt(f) ).collect()
203    }
204}
205
206/// A piece of template, either text or a placeholder to be substituted.
207#[derive(Clone,PartialEq,Debug)]
208pub enum Piece<'a> {
209    Text(&'a str),
210    Placeholder{name: &'a str, before: &'a str, after: &'a str}
211}
212
213impl<'a> fmt::Display for Piece<'a> {
214    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
215        match self {
216            Piece::Text(s) => { write!(f, "{}", s) },
217            Piece::Placeholder{name, before, after} => {
218                write!(f, "${{{}{}{}}}", before, name, after)
219            }
220        }
221    }
222}
223
224
225/// Simple wrapper around Vec<&str>, returned from `Template::fill_in`.
226/// 
227/// Implements `Deref<Vec<&str>>` and `DerefMut<Vec<&str>>`.
228#[derive(Debug)]
229pub struct Text<'a>(Vec<&'a str>);
230
231impl<'a> Deref for Text<'a> {
232    type Target = Vec<&'a str>;
233    fn deref(&self) -> &Self::Target {
234        &self.0
235    }
236}
237
238impl<'a> DerefMut for Text<'a> {
239    fn deref_mut(&mut self) -> &mut Self::Target {
240        &mut self.0
241    }
242}
243
244impl<'a> Text<'a> {
245    fn new() -> Self {
246        Text(Vec::new())
247    }
248}
249
250impl<'a> fmt::Display for Text<'a> {
251    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
252        self.0.iter().map(|v| v.fmt(f) ).collect()
253    }
254}
255
256#[derive(Debug)]
257/// Returned by `Template::try_fill_in` in case of an error.
258pub struct TemplateError;
259
260// Paritions s into (whitespace, non-whitespace, whitespace).
261fn trim_split
262(s: &str) -> (&str, &str, &str) {
263    let mut name = s;
264    let before = if let Some((last,_)) = s.chars().enumerate().take_while(|(_,v)| v.is_whitespace()).last() {
265        name = &name[last+1..];
266        &s[..last+1]
267    } else {
268        ""
269    };
270
271    let after = if let Some((first,_)) = name.chars().rev().enumerate().take_while(|(_,v)| v.is_whitespace()).last() {
272        let res = &name[name.len() - first - 1..];
273        name = &name[..name.len() - first - 1];
274        res
275    } else {
276        ""
277    };
278
279    (before, name, after)
280}
281
282
283#[cfg(test)]
284mod tests {
285    use super::*;
286    use std::collections::HashMap;
287
288
289
290    #[test]
291    fn split() {
292        assert_eq!(trim_split("Hallo"), ("", "Hallo", ""));
293        assert_eq!(trim_split(" Hallo"), (" ", "Hallo", ""));
294        assert_eq!(trim_split("Hallo "), ("", "Hallo", " "));
295        assert_eq!(trim_split(" Hallo "), (" ", "Hallo", " "));
296    }
297
298    #[test]
299    fn from() {
300        assert_eq!(*Template::from(""), vec![]);
301        assert_eq!(*Template::from("{"), vec![Piece::Text("{")]);
302        assert_eq!(*Template::from("}"), vec![Piece::Text("}")]);
303    }
304
305    #[test]
306    fn to_string() {
307        assert_eq!(Template::from("").to_string(), "");
308        assert_eq!(Template::from("{").to_string(), "{");
309        assert_eq!(Template::from("}").to_string(), "}");
310        assert_eq!(Template::from("${}").to_string(), "${}");
311        assert_eq!(Template::from("${x}").to_string(), "${x}");
312        assert_eq!(Template::from(" ${x}").to_string(), " ${x}");
313        assert_eq!(Template::from("${x} ").to_string(), "${x} ");
314        assert_eq!(Template::from("${x }").to_string(), "${x }");
315        assert_eq!(Template::from("${ x}").to_string(), "${ x}");
316        assert_eq!(Template::from("${ x }").to_string(), "${ x }");         
317        assert_eq!(Template::from("Hallo ${name}").to_string(), "Hallo ${name}");
318    }
319
320    #[test]
321    fn fill_in() {
322        let mut dict = HashMap::new();
323        dict.insert("k", "v");
324        dict.insert("l", "");
325
326        assert_eq!(Template::from("${}").fill_in(&dict).to_string(), "");
327        assert_eq!(Template::from("${k}").fill_in(&dict).to_string(), "v");
328        assert_eq!(Template::from("${ k }").fill_in(&dict).to_string(), " v ");
329        assert_eq!(Template::from("${l}").fill_in(&dict).to_string(), "");
330        assert_eq!(Template::from("${ l }").fill_in(&dict).to_string(), "");
331    }
332}