tera_v1/
context.rs

1use std::borrow::Cow;
2use std::collections::BTreeMap;
3
4use serde::ser::Serialize;
5use serde_json::value::{to_value, Map, Value};
6
7/// The struct that holds the context of a template rendering.
8///
9/// Light wrapper around a `BTreeMap` for easier insertions of Serializable
10/// values
11#[derive(Debug, Clone, PartialEq)]
12pub struct Context {
13    data: BTreeMap<String, Value>,
14}
15
16impl Context {
17    /// Initializes an empty context
18    pub fn new() -> Context {
19        Context { data: BTreeMap::new() }
20    }
21
22    /// Converts the `val` parameter to `Value` and insert it into the context
23    ///
24    /// ```rust,ignore
25    /// let mut context = Context::new();
26    /// // user is an instance of a struct implementing `Serialize`
27    /// context.insert("number_users", 42);
28    /// ```
29    pub fn insert<T: Serialize + ?Sized>(&mut self, key: &str, val: &T) {
30        self.data.insert(key.to_owned(), to_value(val).unwrap());
31    }
32
33    /// Appends the data of the `source` parameter to `self`, overwriting existing keys.
34    /// The source context will be dropped.
35    ///
36    /// ```rust,ignore
37    /// let mut target = Context::new();
38    /// target.insert("a", 1);
39    /// target.insert("b", 2);
40    /// let mut source = Context::new();
41    /// source.insert("b", 3);
42    /// source.insert("d", 4);
43    /// target.extend(source);
44    /// ```
45    pub fn extend(&mut self, mut source: Context) {
46        self.data.append(&mut source.data);
47    }
48
49    /// Converts the context to a `serde_json::Value` consuming the context
50    pub fn into_json(self) -> Value {
51        let mut m = Map::new();
52        for (key, value) in self.data {
53            m.insert(key, value);
54        }
55        Value::Object(m)
56    }
57}
58
59impl Default for Context {
60    fn default() -> Context {
61        Context::new()
62    }
63}
64
65pub trait ValueRender {
66    fn render(&self) -> Cow<str>;
67}
68
69// Convert serde Value to String
70impl ValueRender for Value {
71    fn render(&self) -> Cow<str> {
72        match *self {
73            Value::String(ref s) => Cow::Borrowed(s),
74            Value::Number(ref i) => Cow::Owned(i.to_string()),
75            Value::Bool(i) => Cow::Owned(i.to_string()),
76            Value::Null => Cow::Owned(String::new()),
77            Value::Array(ref a) => {
78                let mut buf = String::new();
79                buf.push('[');
80                for i in a.iter() {
81                    if buf.len() > 1 {
82                        buf.push_str(", ");
83                    }
84                    buf.push_str(i.render().as_ref());
85                }
86                buf.push(']');
87                Cow::Owned(buf)
88            }
89            Value::Object(_) => Cow::Owned("[object]".to_owned()),
90        }
91    }
92}
93
94pub trait ValueNumber {
95    fn to_number(&self) -> Result<f64, ()>;
96}
97// Needed for all the maths
98// Convert everything to f64, seems like a terrible idea
99impl ValueNumber for Value {
100    fn to_number(&self) -> Result<f64, ()> {
101        match *self {
102            Value::Number(ref i) => Ok(i.as_f64().unwrap()),
103            _ => Err(()),
104        }
105    }
106}
107
108// From handlebars-rust
109pub trait ValueTruthy {
110    fn is_truthy(&self) -> bool;
111}
112
113impl ValueTruthy for Value {
114    fn is_truthy(&self) -> bool {
115        match *self {
116            Value::Number(ref i) => {
117                if i.is_i64() {
118                    return i.as_i64().unwrap() != 0;
119                }
120                if i.is_u64() {
121                    return i.as_u64().unwrap() != 0;
122                }
123                let f = i.as_f64().unwrap();
124                f != 0.0 && !f.is_nan()
125            }
126            Value::Bool(ref i) => *i,
127            Value::Null => false,
128            Value::String(ref i) => !i.is_empty(),
129            Value::Array(ref i) => !i.is_empty(),
130            Value::Object(ref i) => !i.is_empty(),
131        }
132    }
133}
134
135/// Converts a dotted path to a json pointer one
136#[inline]
137pub fn get_json_pointer(key: &str) -> String {
138    ["/", &key.replace(".", "/")].join("")
139}
140
141#[cfg(test)]
142mod tests {
143    use super::*;
144
145    #[test]
146    fn test_extend() {
147        let mut target = Context::new();
148        target.insert("a", &1);
149        target.insert("b", &2);
150        let mut source = Context::new();
151        source.insert("b", &3);
152        source.insert("c", &4);
153        target.extend(source);
154        assert_eq!(*target.data.get("a").unwrap(), to_value(1).unwrap());
155        assert_eq!(*target.data.get("b").unwrap(), to_value(3).unwrap());
156        assert_eq!(*target.data.get("c").unwrap(), to_value(4).unwrap());
157    }
158}