tera/renderer/
stack_frame.rs

1use std::borrow::Cow;
2use std::collections::HashMap;
3
4use serde_json::Value;
5
6use crate::context::dotted_pointer;
7use crate::renderer::for_loop::ForLoop;
8use crate::template::Template;
9
10pub type Val<'a> = Cow<'a, Value>;
11pub type FrameContext<'a> = HashMap<&'a str, Val<'a>>;
12
13/// Gets a value within a value by pointer, keeping lifetime
14#[inline]
15pub fn value_by_pointer<'a>(pointer: &str, val: &Val<'a>) -> Option<Val<'a>> {
16    match *val {
17        Cow::Borrowed(r) => dotted_pointer(r, pointer).map(Cow::Borrowed),
18        Cow::Owned(ref r) => dotted_pointer(r, pointer).map(|found| Cow::Owned(found.clone())),
19    }
20}
21
22/// Enumerates the types of stack frames
23#[derive(Clone, Copy, Debug, PartialEq)]
24pub enum FrameType {
25    /// Original frame
26    Origin,
27    /// New frame for macro call
28    Macro,
29    /// New frame for for loop
30    ForLoop,
31    /// Include template
32    Include,
33}
34
35/// Entry in the stack frame
36#[derive(Debug)]
37pub struct StackFrame<'a> {
38    /// Type of stack frame
39    pub kind: FrameType,
40    /// Frame name for context/debugging
41    pub name: &'a str,
42    /// Assigned value (via {% set ... %}, {% for ... %}, {% namespace::macro(a=a, b=b) %})
43    ///
44    /// - {% set ... %} adds to current frame_context
45    /// - {% for ... %} builds frame_context before iteration
46    /// - {% namespace::macro(a=a, b=b)} builds frame_context before invocation
47    context: FrameContext<'a>,
48    /// Active template for frame
49    pub active_template: &'a Template,
50    /// `ForLoop` if frame is for a for loop
51    pub for_loop: Option<ForLoop<'a>>,
52    /// Macro namespace if MacroFrame
53    pub macro_namespace: Option<&'a str>,
54}
55
56impl<'a> StackFrame<'a> {
57    pub fn new(kind: FrameType, name: &'a str, tpl: &'a Template) -> Self {
58        StackFrame {
59            kind,
60            name,
61            context: FrameContext::new(),
62            active_template: tpl,
63            for_loop: None,
64            macro_namespace: None,
65        }
66    }
67
68    pub fn new_for_loop(name: &'a str, tpl: &'a Template, for_loop: ForLoop<'a>) -> Self {
69        StackFrame {
70            kind: FrameType::ForLoop,
71            name,
72            context: FrameContext::new(),
73            active_template: tpl,
74            for_loop: Some(for_loop),
75            macro_namespace: None,
76        }
77    }
78
79    pub fn new_macro(
80        name: &'a str,
81        tpl: &'a Template,
82        macro_namespace: &'a str,
83        context: FrameContext<'a>,
84    ) -> Self {
85        StackFrame {
86            kind: FrameType::Macro,
87            name,
88            context,
89            active_template: tpl,
90            for_loop: None,
91            macro_namespace: Some(macro_namespace),
92        }
93    }
94
95    pub fn new_include(name: &'a str, tpl: &'a Template) -> Self {
96        StackFrame {
97            kind: FrameType::Include,
98            name,
99            context: FrameContext::new(),
100            active_template: tpl,
101            for_loop: None,
102            macro_namespace: None,
103        }
104    }
105
106    /// Finds a value in the stack frame.
107    /// Looks first in `frame_context`, then compares to for_loop key_name and value_name.
108    pub fn find_value(&self, key: &str) -> Option<Val<'a>> {
109        self.find_value_in_frame(key).or_else(|| self.find_value_in_for_loop(key))
110    }
111
112    /// Finds a value in `frame_context`.
113    pub fn find_value_in_frame(&self, key: &str) -> Option<Val<'a>> {
114        if let Some(dot) = key.find('.') {
115            if dot < key.len() + 1 {
116                if let Some(found_value) =
117                    self.context.get(&key[0..dot]).map(|v| value_by_pointer(&key[dot + 1..], v))
118                {
119                    return found_value;
120                }
121            }
122        } else if let Some(found) = self.context.get(key) {
123            return Some(found.clone());
124        }
125
126        None
127    }
128    /// Finds a value in the `for_loop` if there is one
129    pub fn find_value_in_for_loop(&self, key: &str) -> Option<Val<'a>> {
130        if let Some(ref for_loop) = self.for_loop {
131            // 1st case: the variable is the key of a KeyValue for loop
132            if for_loop.is_key(key) {
133                return Some(Cow::Owned(Value::String(for_loop.get_current_key())));
134            }
135
136            let (real_key, tail) = if let Some(tail_pos) = key.find('.') {
137                (&key[..tail_pos], &key[tail_pos + 1..])
138            } else {
139                (key, "")
140            };
141
142            // 2nd case: one of Tera loop built-in variable
143            if real_key == "loop" {
144                match tail {
145                    "index" => {
146                        return Some(Cow::Owned(Value::Number((for_loop.current + 1).into())));
147                    }
148                    "index0" => {
149                        return Some(Cow::Owned(Value::Number(for_loop.current.into())));
150                    }
151                    "first" => {
152                        return Some(Cow::Owned(Value::Bool(for_loop.current == 0)));
153                    }
154                    "last" => {
155                        return Some(Cow::Owned(Value::Bool(
156                            for_loop.current == for_loop.len() - 1,
157                        )));
158                    }
159                    _ => return None,
160                };
161            }
162
163            // Last case: the variable is/starts with the value name of the for loop
164            // The `set` case will have been taken into account before
165
166            // Exact match to the loop value and no tail
167            if key == for_loop.value_name {
168                return Some(for_loop.get_current_value());
169            }
170
171            if real_key == for_loop.value_name && !tail.is_empty() {
172                return value_by_pointer(tail, &for_loop.get_current_value());
173            }
174        }
175
176        None
177    }
178
179    /// Insert a value in the context
180    pub fn insert(&mut self, key: &'a str, value: Val<'a>) {
181        self.context.insert(key, value);
182    }
183
184    /// Context is cleared on each loop
185    pub fn clear_context(&mut self) {
186        if self.for_loop.is_some() {
187            self.context.clear();
188        }
189    }
190
191    pub fn context_owned(&self) -> HashMap<String, Value> {
192        let mut context = HashMap::new();
193
194        for (key, val) in &self.context {
195            context.insert((*key).to_string(), val.clone().into_owned());
196        }
197
198        context
199    }
200}