tera/renderer/
for_loop.rs

1use std::borrow::Cow;
2
3use serde_json::Value;
4use unic_segment::Graphemes;
5
6use crate::renderer::stack_frame::Val;
7
8/// Enumerates the two types of for loops
9#[derive(Debug, PartialEq)]
10pub enum ForLoopKind {
11    /// Loop over values, eg an `Array`
12    Value,
13    /// Loop over key value pairs, eg a `HashMap` or `Object` style iteration
14    KeyValue,
15}
16
17/// Enumerates the states of a for loop
18#[derive(Clone, Copy, Debug, PartialEq)]
19pub enum ForLoopState {
20    /// State during iteration
21    Normal,
22    /// State on encountering *break* statement
23    Break,
24    /// State on encountering *continue* statement
25    Continue,
26}
27
28/// Enumerates on the types of values to be iterated, scalars and pairs
29#[derive(Debug)]
30pub enum ForLoopValues<'a> {
31    /// Values for an array style iteration
32    Array(Val<'a>),
33    /// Values for a per-character iteration on a string
34    String(Val<'a>),
35    /// Values for an object style iteration
36    Object(Vec<(String, Val<'a>)>),
37}
38
39impl<'a> ForLoopValues<'a> {
40    pub fn current_key(&self, i: usize) -> String {
41        match *self {
42            ForLoopValues::Array(_) | ForLoopValues::String(_) => {
43                unreachable!("No key in array list or string")
44            }
45            ForLoopValues::Object(ref values) => {
46                values.get(i).expect("Failed getting current key").0.clone()
47            }
48        }
49    }
50    pub fn current_value(&self, i: usize) -> Val<'a> {
51        match *self {
52            ForLoopValues::Array(ref values) => match *values {
53                Cow::Borrowed(v) => {
54                    Cow::Borrowed(v.as_array().expect("Is array").get(i).expect("Value"))
55                }
56                Cow::Owned(_) => {
57                    Cow::Owned(values.as_array().expect("Is array").get(i).expect("Value").clone())
58                }
59            },
60            ForLoopValues::String(ref values) => {
61                let mut graphemes = Graphemes::new(values.as_str().expect("Is string"));
62                Cow::Owned(Value::String(graphemes.nth(i).expect("Value").to_string()))
63            }
64            ForLoopValues::Object(ref values) => values.get(i).expect("Value").1.clone(),
65        }
66    }
67}
68
69// We need to have some data in the renderer for when we are in a ForLoop
70// For example, accessing the local variable would fail when
71// looking it up in the global context
72#[derive(Debug)]
73pub struct ForLoop<'a> {
74    /// The key name when iterate as a Key-Value, ie in `{% for i, person in people %}` it would be `i`
75    pub key_name: Option<String>,
76    /// The value name, ie in `{% for person in people %}` it would be `person`
77    pub value_name: String,
78    /// What's the current loop index (0-indexed)
79    pub current: usize,
80    /// A list of (key, value) for the forloop. The key is `None` for `ForLoopKind::Value`
81    pub values: ForLoopValues<'a>,
82    /// Value or KeyValue?
83    pub kind: ForLoopKind,
84    /// Has the for loop encountered break or continue?
85    pub state: ForLoopState,
86}
87
88impl<'a> ForLoop<'a> {
89    pub fn from_array(value_name: &str, values: Val<'a>) -> Self {
90        ForLoop {
91            key_name: None,
92            value_name: value_name.to_string(),
93            current: 0,
94            values: ForLoopValues::Array(values),
95            kind: ForLoopKind::Value,
96            state: ForLoopState::Normal,
97        }
98    }
99
100    pub fn from_string(value_name: &str, values: Val<'a>) -> Self {
101        ForLoop {
102            key_name: None,
103            value_name: value_name.to_string(),
104            current: 0,
105            values: ForLoopValues::String(values),
106            kind: ForLoopKind::Value,
107            state: ForLoopState::Normal,
108        }
109    }
110
111    pub fn from_object(key_name: &str, value_name: &str, object: &'a Value) -> Self {
112        let object_values = object.as_object().unwrap();
113        let mut values = Vec::with_capacity(object_values.len());
114        for (k, v) in object_values {
115            values.push((k.to_string(), Cow::Borrowed(v)));
116        }
117
118        ForLoop {
119            key_name: Some(key_name.to_string()),
120            value_name: value_name.to_string(),
121            current: 0,
122            values: ForLoopValues::Object(values),
123            kind: ForLoopKind::KeyValue,
124            state: ForLoopState::Normal,
125        }
126    }
127
128    pub fn from_object_owned(key_name: &str, value_name: &str, object: Value) -> Self {
129        let object_values = match object {
130            Value::Object(c) => c,
131            _ => unreachable!(
132                "Tried to create a Forloop from an object owned but it wasn't an object"
133            ),
134        };
135        let mut values = Vec::with_capacity(object_values.len());
136        for (k, v) in object_values {
137            values.push((k.to_string(), Cow::Owned(v)));
138        }
139
140        ForLoop {
141            key_name: Some(key_name.to_string()),
142            value_name: value_name.to_string(),
143            current: 0,
144            values: ForLoopValues::Object(values),
145            kind: ForLoopKind::KeyValue,
146            state: ForLoopState::Normal,
147        }
148    }
149
150    #[inline]
151    pub fn increment(&mut self) {
152        self.current += 1;
153        self.state = ForLoopState::Normal;
154    }
155
156    pub fn is_key_value(&self) -> bool {
157        self.kind == ForLoopKind::KeyValue
158    }
159
160    #[inline]
161    pub fn break_loop(&mut self) {
162        self.state = ForLoopState::Break;
163    }
164
165    #[inline]
166    pub fn continue_loop(&mut self) {
167        self.state = ForLoopState::Continue;
168    }
169
170    #[inline]
171    pub fn get_current_value(&self) -> Val<'a> {
172        self.values.current_value(self.current)
173    }
174
175    /// Only called in `ForLoopKind::KeyValue`
176    #[inline]
177    pub fn get_current_key(&self) -> String {
178        self.values.current_key(self.current)
179    }
180
181    /// Checks whether the key string given is the variable used as key for
182    /// the current forloop
183    pub fn is_key(&self, name: &str) -> bool {
184        if self.kind == ForLoopKind::Value {
185            return false;
186        }
187
188        if let Some(ref key_name) = self.key_name {
189            return key_name == name;
190        }
191
192        false
193    }
194
195    pub fn len(&self) -> usize {
196        match self.values {
197            ForLoopValues::Array(ref values) => values.as_array().expect("Value is array").len(),
198            ForLoopValues::String(ref values) => {
199                values.as_str().expect("Value is string").chars().count()
200            }
201            ForLoopValues::Object(ref values) => values.len(),
202        }
203    }
204}
205
206#[cfg(test)]
207mod tests {
208    use std::borrow::Cow;
209
210    use serde_json::Value;
211
212    use super::ForLoop;
213
214    #[test]
215    fn test_that_iterating_on_string_yields_grapheme_clusters() {
216        let text = "a\u{310}e\u{301}o\u{308}\u{332}".to_string();
217        let string = Value::String(text.clone());
218        let mut string_loop = ForLoop::from_string("whatever", Cow::Borrowed(&string));
219        assert_eq!(*string_loop.get_current_value(), text[0..3]);
220        string_loop.increment();
221        assert_eq!(*string_loop.get_current_value(), text[3..6]);
222        string_loop.increment();
223        assert_eq!(*string_loop.get_current_value(), text[6..]);
224    }
225}