tera/renderer/
call_stack.rs

1use std::borrow::Cow;
2use std::collections::HashMap;
3
4use serde_json::{to_value, Value};
5
6use crate::context::dotted_pointer;
7use crate::errors::{Error, Result};
8use crate::renderer::for_loop::{ForLoop, ForLoopState};
9use crate::renderer::stack_frame::{FrameContext, FrameType, StackFrame, Val};
10use crate::template::Template;
11use crate::Context;
12
13/// Contains the user data and allows no mutation
14#[derive(Debug)]
15pub struct UserContext<'a> {
16    /// Read-only context
17    inner: &'a Context,
18}
19
20impl<'a> UserContext<'a> {
21    /// Create an immutable user context to be used in the call stack
22    pub fn new(context: &'a Context) -> Self {
23        UserContext { inner: context }
24    }
25
26    pub fn find_value(&self, key: &str) -> Option<&'a Value> {
27        self.inner.get(key)
28    }
29
30    pub fn find_value_by_dotted_pointer(&self, pointer: &str) -> Option<&'a Value> {
31        let root = pointer.split('.').next().unwrap().replace("~1", "/").replace("~0", "~");
32        let rest = &pointer[root.len() + 1..];
33        self.inner.get(&root).and_then(|val| dotted_pointer(val, rest))
34    }
35}
36
37/// Contains the stack of frames
38#[derive(Debug)]
39pub struct CallStack<'a> {
40    /// The stack of frames
41    stack: Vec<StackFrame<'a>>,
42    /// User supplied context for the render
43    context: UserContext<'a>,
44}
45
46impl<'a> CallStack<'a> {
47    /// Create the initial call stack
48    pub fn new(context: &'a Context, template: &'a Template) -> CallStack<'a> {
49        CallStack {
50            stack: vec![StackFrame::new(FrameType::Origin, "ORIGIN", template)],
51            context: UserContext::new(context),
52        }
53    }
54
55    pub fn push_for_loop_frame(&mut self, name: &'a str, for_loop: ForLoop<'a>) {
56        let tpl = self.stack.last().expect("Stack frame").active_template;
57        self.stack.push(StackFrame::new_for_loop(name, tpl, for_loop));
58    }
59
60    pub fn push_macro_frame(
61        &mut self,
62        namespace: &'a str,
63        name: &'a str,
64        context: FrameContext<'a>,
65        tpl: &'a Template,
66    ) {
67        self.stack.push(StackFrame::new_macro(name, tpl, namespace, context));
68    }
69
70    pub fn push_include_frame(&mut self, name: &'a str, tpl: &'a Template) {
71        self.stack.push(StackFrame::new_include(name, tpl));
72    }
73
74    /// Returns mutable reference to global `StackFrame`
75    /// i.e gets first stack outside current for loops
76    pub fn global_frame_mut(&mut self) -> &mut StackFrame<'a> {
77        if self.current_frame().kind == FrameType::ForLoop {
78            for stack_frame in self.stack.iter_mut().rev() {
79                // walk up the parent stacks until we meet the current template
80                if stack_frame.kind != FrameType::ForLoop {
81                    return stack_frame;
82                }
83            }
84            unreachable!("Global frame not found when trying to break out of for loop");
85        } else {
86            // Macro, Origin, or Include
87            self.current_frame_mut()
88        }
89    }
90
91    /// Returns mutable reference to current `StackFrame`
92    pub fn current_frame_mut(&mut self) -> &mut StackFrame<'a> {
93        self.stack.last_mut().expect("No current frame exists")
94    }
95
96    /// Returns immutable reference to current `StackFrame`
97    pub fn current_frame(&self) -> &StackFrame<'a> {
98        self.stack.last().expect("No current frame exists")
99    }
100
101    /// Pop the last frame
102    pub fn pop(&mut self) {
103        self.stack.pop().expect("Mistakenly popped Origin frame");
104    }
105
106    pub fn lookup(&self, key: &str) -> Option<Val<'a>> {
107        for stack_frame in self.stack.iter().rev() {
108            let found = stack_frame.find_value(key);
109            if found.is_some() {
110                return found;
111            }
112
113            // If we looked in a macro or origin frame, no point continuing
114            // Origin is the last one and macro frame don't have access to parent frames
115            if stack_frame.kind == FrameType::Macro || stack_frame.kind == FrameType::Origin {
116                break;
117            }
118        }
119
120        // Not in stack frame, look in user supplied context
121        if key.contains('.') {
122            return self.context.find_value_by_dotted_pointer(key).map(Cow::Borrowed);
123        } else if let Some(value) = self.context.find_value(key) {
124            return Some(Cow::Borrowed(value));
125        }
126
127        None
128    }
129
130    /// Add an assignment value (via {% set ... %} and {% set_global ... %} )
131    pub fn add_assignment(&mut self, key: &'a str, global: bool, value: Val<'a>) {
132        if global {
133            self.global_frame_mut().insert(key, value);
134        } else {
135            self.current_frame_mut().insert(key, value);
136        }
137    }
138
139    /// Breaks current for loop
140    pub fn break_for_loop(&mut self) -> Result<()> {
141        match self.current_frame_mut().for_loop {
142            Some(ref mut for_loop) => {
143                for_loop.break_loop();
144                Ok(())
145            }
146            None => Err(Error::msg("Attempted `break` while not in `for loop`")),
147        }
148    }
149
150    /// Continues current for loop
151    pub fn increment_for_loop(&mut self) -> Result<()> {
152        let frame = self.current_frame_mut();
153        frame.clear_context();
154        match frame.for_loop {
155            Some(ref mut for_loop) => {
156                for_loop.increment();
157                Ok(())
158            }
159            None => Err(Error::msg("Attempted `increment` while not in `for loop`")),
160        }
161    }
162
163    /// Continues current for loop
164    pub fn continue_for_loop(&mut self) -> Result<()> {
165        match self.current_frame_mut().for_loop {
166            Some(ref mut for_loop) => {
167                for_loop.continue_loop();
168                Ok(())
169            }
170            None => Err(Error::msg("Attempted `continue` while not in `for loop`")),
171        }
172    }
173
174    /// True if should break body, applicable to `break` and `continue`
175    pub fn should_break_body(&self) -> bool {
176        match self.current_frame().for_loop {
177            Some(ref for_loop) => {
178                for_loop.state == ForLoopState::Break || for_loop.state == ForLoopState::Continue
179            }
180            None => false,
181        }
182    }
183
184    /// True if should break loop, applicable to `break` only
185    pub fn should_break_for_loop(&self) -> bool {
186        match self.current_frame().for_loop {
187            Some(ref for_loop) => for_loop.state == ForLoopState::Break,
188            None => false,
189        }
190    }
191
192    /// Grab the current frame template
193    pub fn active_template(&self) -> &'a Template {
194        self.current_frame().active_template
195    }
196
197    pub fn current_context_cloned(&self) -> Value {
198        let mut context = HashMap::new();
199
200        // Go back the stack in reverse to see what we have access to
201        for frame in self.stack.iter().rev() {
202            context.extend(frame.context_owned());
203            if let Some(ref for_loop) = frame.for_loop {
204                context.insert(
205                    for_loop.value_name.to_string(),
206                    for_loop.get_current_value().into_owned(),
207                );
208                if for_loop.is_key_value() {
209                    context.insert(
210                        for_loop.key_name.clone().unwrap(),
211                        Value::String(for_loop.get_current_key()),
212                    );
213                }
214            }
215            // Macros don't have access to the user context, we're done
216            if frame.kind == FrameType::Macro {
217                return to_value(&context).unwrap();
218            }
219        }
220
221        // If we are here we take the user context
222        // and add the values found in the stack to it.
223        // We do it this way as we can override global variable temporarily in forloops
224        let mut new_ctx = self.context.inner.clone();
225        for (key, val) in context {
226            new_ctx.insert(key, &val)
227        }
228        new_ctx.into_json()
229    }
230}