tera/renderer/
stack_frame.rs1use 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#[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#[derive(Clone, Copy, Debug, PartialEq)]
24pub enum FrameType {
25 Origin,
27 Macro,
29 ForLoop,
31 Include,
33}
34
35#[derive(Debug)]
37pub struct StackFrame<'a> {
38 pub kind: FrameType,
40 pub name: &'a str,
42 context: FrameContext<'a>,
48 pub active_template: &'a Template,
50 pub for_loop: Option<ForLoop<'a>>,
52 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 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 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 pub fn find_value_in_for_loop(&self, key: &str) -> Option<Val<'a>> {
130 if let Some(ref for_loop) = self.for_loop {
131 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 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 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 pub fn insert(&mut self, key: &'a str, value: Val<'a>) {
181 self.context.insert(key, value);
182 }
183
184 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}