1 use std::borrow::Cow;
2 
3 use serde_json::Value;
4 use unic_segment::Graphemes;
5 
6 use crate::renderer::stack_frame::Val;
7 
8 /// Enumerates the two types of for loops
9 #[derive(Debug, PartialEq)]
10 pub 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)]
19 pub 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)]
30 pub 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 
39 impl<'a> ForLoopValues<'a> {
current_key(&self, i: usize) -> String40     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     }
current_value(&self, i: usize) -> Val<'a>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)]
73 pub 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 
88 impl<'a> ForLoop<'a> {
from_array(value_name: &str, values: Val<'a>) -> Self89     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 
from_string(value_name: &str, values: Val<'a>) -> Self100     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 
from_object(key_name: &str, value_name: &str, object: &'a Value) -> Self111     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 
from_object_owned(key_name: &str, value_name: &str, object: Value) -> Self128     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]
increment(&mut self)151     pub fn increment(&mut self) {
152         self.current += 1;
153         self.state = ForLoopState::Normal;
154     }
155 
is_key_value(&self) -> bool156     pub fn is_key_value(&self) -> bool {
157         self.kind == ForLoopKind::KeyValue
158     }
159 
160     #[inline]
break_loop(&mut self)161     pub fn break_loop(&mut self) {
162         self.state = ForLoopState::Break;
163     }
164 
165     #[inline]
continue_loop(&mut self)166     pub fn continue_loop(&mut self) {
167         self.state = ForLoopState::Continue;
168     }
169 
170     #[inline]
get_current_value(&self) -> Val<'a>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]
get_current_key(&self) -> String177     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
is_key(&self, name: &str) -> bool183     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 
len(&self) -> usize195     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)]
207 mod tests {
208     use std::borrow::Cow;
209 
210     use serde_json::Value;
211 
212     use super::ForLoop;
213 
214     #[test]
test_that_iterating_on_string_yields_grapheme_clusters()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 }
226