sato/
builtins.rs

1use crate::renderer::{Attributes, Renderer, RenderValue, RenderError, basic_html_tag};
2use crate::context::{ContextValue, RenderContext};
3use crate::template::{TemplateExprNode, TemplateTag};
4
5
6pub(crate) fn do_html(attrs: Attributes, expr: &[TemplateExprNode], renderer: &Renderer, context: &RenderContext) -> Result<RenderValue, RenderError> {
7    let mut v: Vec<RenderValue> = vec!["<!doctype html5>".into()];
8    v.push(basic_html_tag("html".into(), &attrs, &expr, renderer, context)?.into());
9    Ok(v.into())
10}
11
12pub(crate) fn do_is_set(_: Attributes, expr: &[TemplateExprNode], _render: &Renderer, context: &RenderContext) -> Result<RenderValue, RenderError> {
13    match expr.get(0) {
14        Some(TemplateExprNode::Identifier(ident)) => {
15            Ok(match context.get(ident) {
16                Some(_) => RenderValue::Boolean(true),
17                None => RenderValue::Boolean(false)
18            })
19        },
20        _ => Err(RenderError::IsSet("expected identifier".into(), expr.to_vec()))
21    }
22}
23
24pub(crate) fn do_cmp_op<F>(_: Attributes, expr: &[TemplateExprNode], renderer: &Renderer, context: &RenderContext, op: F) -> Result<RenderValue, RenderError>
25where
26    F: FnOnce(ContextValue, ContextValue) -> bool,
27{
28    let exp1 = expr.get(0)
29        .and_then(|e| {
30            match e {
31                TemplateExprNode::Identifier(ident) => Some(context.get(ident).cloned().unwrap_or(ContextValue::String(ident.clone()))),
32                TemplateExprNode::Integer(i) => Some(ContextValue::Integer(*i)),
33                TemplateExprNode::Tag(_tag) => Some(renderer.evaluate(e, context).unwrap().into()),
34            }
35        })
36        .ok_or_else(|| RenderError::Cmp("missing expr 1".into(), expr.to_vec()))?;
37    let exp2 = expr.get(1)
38        .and_then(|e| {
39            match e {
40                TemplateExprNode::Identifier(ident) => Some(context.get(ident).cloned().unwrap_or(ContextValue::String(ident.clone()))),
41                TemplateExprNode::Integer(i) => Some(ContextValue::Integer(*i)),
42                TemplateExprNode::Tag(_tag) => Some(renderer.evaluate(e, context).ok()?.into()),
43            }
44        })
45        .ok_or_else(|| RenderError::Cmp("missing expr 2".into(), expr.to_vec()))?;
46
47    Ok(op(exp1, exp2).into())
48}
49
50
51pub(crate) fn do_math_op<F>(_: Attributes, expr: &[TemplateExprNode], renderer: &Renderer, context: &RenderContext, op: F) -> Result<RenderValue, RenderError>
52where
53    F: FnOnce(i64, i64) -> i64
54{
55    let exp1 = expr.get(0)
56        .and_then(|e| renderer.evaluate(e, context).ok())
57        .and_then(|rv| rv.as_int())
58        .ok_or_else(|| RenderError::Math("missing expr 1".into(), expr.to_vec()))?;
59
60    let exp2 = expr.get(1)
61        .and_then(|e| renderer.evaluate(e, context).ok())
62        .and_then(|rv| rv.as_int())
63        .ok_or_else(|| RenderError::Math("missing expr 2".into(), expr.to_vec()))?;
64
65    Ok(op(exp1, exp2).into())
66}
67
68
69pub(crate) fn do_if(_: Attributes, expr: &[TemplateExprNode], renderer: &Renderer, context: &RenderContext) -> Result<RenderValue, RenderError> {
70    let conditional = expr.get(0)
71        .ok_or_else(|| RenderError::If("condition not found".into(), expr.to_vec()))?;
72
73    let result = renderer.evaluate(conditional, context)?;
74
75    let is_true = match result {
76        RenderValue::Boolean(b) => b,
77        RenderValue::String(s) if s != "" => true,
78        RenderValue::Integer(i) if i != 0 => true,
79        _ => false
80    };
81
82    Ok(
83        if is_true {
84            renderer.evaluate(expr
85                              .get(1)
86                              .ok_or_else(|| RenderError::If("code block not found".into(), expr.to_vec()))?,
87                              context)?
88        }
89        else {
90            match expr.get(2) {
91                Some(e) => renderer.evaluate(e, context)?,
92                None => RenderValue::Empty,
93            }
94        })
95}
96
97pub(crate) fn do_case(_: Attributes, expr: &[TemplateExprNode], renderer: &Renderer, context: &RenderContext) -> Result<RenderValue, RenderError> {
98    let condition = expr.get(0)
99        .ok_or_else(|| RenderError::Case("variant not found".into(), expr.to_vec()))?;
100
101    let switch_value = context.get("__switch")
102        .ok_or_else(|| RenderError::Case("builtin switch variable not found?".into(), expr.to_vec()))?;
103
104    let body = expr.get(1..)
105        .unwrap_or_default();
106
107    match (condition, switch_value) {
108        (TemplateExprNode::Identifier(condition_str), ContextValue::String(switch_str)) if condition_str == switch_str => {
109            renderer.evaluate_multiple(body, context)
110        },
111        _ => return Ok(RenderValue::Empty)
112    }
113}
114
115pub(crate) fn do_switch(_: Attributes, expr: &[TemplateExprNode], renderer: &Renderer, context: &RenderContext) -> Result<RenderValue, RenderError> {
116    let variable = renderer.evaluate(expr.get(0)
117                                     .ok_or_else(|| RenderError::Switch("variable not found".into(), expr.to_vec()))?,
118                                     context)?;
119    let cases = expr.get(1..);
120    let mut context = context.clone();
121    context.insert("__switch", &variable);
122    Ok(cases.iter()
123        .map(|case| {
124            renderer.evaluate_multiple(*case, &context)
125        })
126        .collect::<Result<Vec<_>, _>>()?
127        .into())
128}
129
130
131fn parse_range(tag: &TemplateTag, renderer: &Renderer, context: &RenderContext) -> Option<ContextValue> {
132    let min = tag.children.get(0)
133        .and_then(|e| renderer.evaluate(e, context).ok())
134        .and_then(|e| e.as_int())?;
135    let max = tag.children.get(1)
136        .and_then(|e| renderer.evaluate(e, context).ok())
137        .and_then(|e| e.as_int())?;
138    let step = tag.children.get(2)
139        .and_then(|e| renderer.evaluate(e, context).ok())
140        .and_then(|e| e.as_int())
141        .unwrap_or(1) as usize;
142
143    let range = (min..max)
144        .step_by(step)
145        .map(Into::into)
146        .collect();
147
148    Some(ContextValue::Vec(range))
149}
150
151pub(crate) fn do_for(attrs: Attributes, expr: &[TemplateExprNode], renderer: &Renderer, context: &RenderContext) -> Result<RenderValue, RenderError> {
152    let in_position = expr.iter()
153        .position(|b| {
154            match b {
155                TemplateExprNode::Identifier(ident) if ident == "in" => true,
156                _ => false,
157            }
158        });
159
160    if let Some(in_position) = in_position {
161        let iterable = expr.get(in_position+1)
162            .map(|e| {
163                match e {
164                    TemplateExprNode::Identifier(ident) => {
165                        crate::renderer::expand_variable(ident, renderer, context)
166                            .map(|k| k.into())
167                    },
168                    TemplateExprNode::Tag(tag) if tag.tag == "range" => {
169                        parse_range(tag, renderer, context)
170                            .ok_or_else(|| RenderError::For("invalid range".into(), attrs.clone(), expr.to_vec()))
171                    },
172                    _ => Err(RenderError::For("iteration variable is not a valid type".into(), attrs.clone(), expr.to_vec()))
173                }
174            })
175            .ok_or_else(|| RenderError::For("no iteration variable specified".into(), attrs.clone(), expr.to_vec()))??;
176        let body = expr.get(in_position+2..)
177            .unwrap_or_default();
178
179        match iterable {
180            ContextValue::Vec(v) => {
181                enum IterType {
182                    Normal(String),
183                    Enum(String, String),
184                }
185
186                let val = expr.get(in_position-1)
187                    .and_then(|e| {
188                        match e {
189                            TemplateExprNode::Identifier(_) => renderer.evaluate(e, context).map(|e| IterType::Normal(e.finalize())).ok(),
190                            TemplateExprNode::Tag(tag) if tag.tag == "enumerate" => {
191                                let index = tag.children.get(0)
192                                    .and_then(TemplateExprNode::as_identifier)?;
193                                let iter = tag.children.get(1)
194                                    .and_then(TemplateExprNode::as_identifier)?;
195                                Some(IterType::Enum(iter.clone(), index.clone()))
196                            },
197                            _ => None
198                        }
199                    })
200                    .ok_or_else(|| RenderError::For("missing variable to iterate over".into(), attrs, expr.to_vec()))?;
201
202                let mut second_context = context.clone();
203                Ok(v.iter()
204                   .enumerate()
205                   .map(|(i, value)| {
206                       match &val {
207                           IterType::Normal(val) => {
208                               second_context.insert(val.clone(), value.clone());
209                           }
210                           IterType::Enum(iter, index) => {
211                               second_context.insert(iter.clone(), value.clone());
212                               second_context.insert(index.clone(), i);
213                           }
214                       }
215                       renderer.evaluate_multiple(body, &second_context)
216                   })
217                   .collect::<Result<Vec<_>, RenderError>>()?
218                   .into())
219            },
220            ContextValue::Object(o) => {
221                let key_var = expr.get(in_position-2)
222                    .and_then(|a| renderer.evaluate(a, context).ok())
223                    .map(|e| e.finalize())
224                    .ok_or_else(|| RenderError::For("missing key variable to iterate over".into(), attrs.clone(), expr.to_vec()))?;
225                let value_var = expr.get(in_position-1)
226                    .and_then(|a| renderer.evaluate(a, context).ok())
227                    .map(|e| e.finalize())
228                    .ok_or_else(|| RenderError::For("missing value variable to iterate over".into(), attrs.clone(), expr.to_vec()))?;
229                let mut second_context = context.clone();
230                Ok(o.0.iter()
231                   .map(|(key, value)| {
232                       second_context.insert(key_var.clone(), ContextValue::String(key.clone()));
233                       second_context.insert(value_var.clone(), value.clone());
234                       renderer.evaluate_multiple(body, &second_context)
235                   })
236                   .collect::<Result<Vec<_>, RenderError>>()?
237                   .into())
238            },
239            _ => Err(RenderError::For("element is not iterable".into(), attrs, expr.to_vec()))
240        }
241    }
242    else {
243        Err(RenderError::For("invalid syntax".into(), attrs, expr.to_vec()))
244    }
245}
246
247pub(crate) fn do_get(_: Attributes, expr: &[TemplateExprNode], renderer: &Renderer, context: &RenderContext) -> Result<RenderValue, RenderError> {
248    let indexable = expr.get(0)
249        .and_then(|e| renderer.evaluate(e, context).ok())
250        .unwrap();
251
252    let index = expr.get(1)
253        .and_then(|e| renderer.evaluate(e, context).ok())
254        .unwrap();
255
256    match (indexable, index){
257        (RenderValue::Vec(v), RenderValue::Integer(i)) => {
258            Ok(v.get(i as usize)
259                .ok_or_else(|| RenderError::Get("array out of bounds".into(), expr.to_vec()))?
260                .clone())
261
262        },
263        (RenderValue::Object(o), RenderValue::String(s)) => {
264            Ok(o.get(&s).ok_or_else(|| RenderError::Get("array out of bounds".into(), expr.to_vec()))?.clone())
265        },
266        _ => Err(RenderError::Get("invalid index/indexable".into(), expr.to_vec()))
267    }
268}