liquid_core/runtime/
runtime.rs

1use std::sync;
2
3use crate::error::Error;
4use crate::error::Result;
5use crate::model::{Object, ObjectView, Scalar, ScalarCow, Value, ValueCow, ValueView};
6
7use super::PartialStore;
8use super::Renderable;
9
10/// State for rendering a template
11pub trait Runtime {
12    /// Partial templates for inclusion.
13    fn partials(&self) -> &dyn PartialStore;
14
15    /// The name of the currently active template.
16    fn name(&self) -> Option<crate::model::KStringRef<'_>>;
17
18    /// All available values
19    fn roots(&self) -> std::collections::BTreeSet<crate::model::KStringCow<'_>>;
20    /// Recursively index into the stack.
21    fn try_get(&self, path: &[ScalarCow<'_>]) -> Option<ValueCow<'_>>;
22    /// Recursively index into the stack.
23    fn get(&self, path: &[ScalarCow<'_>]) -> Result<ValueCow<'_>>;
24
25    /// Sets a value in the global runtime.
26    fn set_global(
27        &self,
28        name: crate::model::KString,
29        val: crate::model::Value,
30    ) -> Option<crate::model::Value>;
31
32    /// Used by increment and decrement tags
33    fn set_index(&self, name: crate::model::KString, val: Value) -> Option<Value>;
34    /// Used by increment and decrement tags
35    fn get_index<'a>(&'a self, name: &str) -> Option<ValueCow<'a>>;
36
37    /// Unnamed state for plugins during rendering
38    fn registers(&self) -> &Registers;
39}
40
41impl<R: Runtime + ?Sized> Runtime for &R {
42    fn partials(&self) -> &dyn super::PartialStore {
43        <R as Runtime>::partials(self)
44    }
45
46    fn name(&self) -> Option<crate::model::KStringRef<'_>> {
47        <R as Runtime>::name(self)
48    }
49
50    fn roots(&self) -> std::collections::BTreeSet<crate::model::KStringCow<'_>> {
51        <R as Runtime>::roots(self)
52    }
53
54    fn try_get(&self, path: &[ScalarCow<'_>]) -> Option<ValueCow<'_>> {
55        <R as Runtime>::try_get(self, path)
56    }
57
58    fn get(&self, path: &[ScalarCow<'_>]) -> Result<ValueCow<'_>> {
59        <R as Runtime>::get(self, path)
60    }
61
62    fn set_global(
63        &self,
64        name: crate::model::KString,
65        val: crate::model::Value,
66    ) -> Option<crate::model::Value> {
67        <R as Runtime>::set_global(self, name, val)
68    }
69
70    fn set_index(&self, name: crate::model::KString, val: Value) -> Option<Value> {
71        <R as Runtime>::set_index(self, name, val)
72    }
73
74    fn get_index<'a>(&'a self, name: &str) -> Option<ValueCow<'a>> {
75        <R as Runtime>::get_index(self, name)
76    }
77
78    fn registers(&self) -> &super::Registers {
79        <R as Runtime>::registers(self)
80    }
81}
82
83/// Create processing runtime for a template.
84pub struct RuntimeBuilder<'g, 'p> {
85    globals: Option<&'g dyn ObjectView>,
86    partials: Option<&'p dyn PartialStore>,
87}
88
89impl<'c, 'g: 'c, 'p: 'c> RuntimeBuilder<'g, 'p> {
90    /// Creates a new, empty rendering runtime.
91    pub fn new() -> Self {
92        Self {
93            globals: None,
94            partials: None,
95        }
96    }
97
98    /// Initialize the stack with the given globals.
99    pub fn set_globals<'n>(self, values: &'n dyn ObjectView) -> RuntimeBuilder<'n, 'p> {
100        RuntimeBuilder {
101            globals: Some(values),
102            partials: self.partials,
103        }
104    }
105
106    /// Initialize partial-templates available for including.
107    pub fn set_partials<'n>(self, values: &'n dyn PartialStore) -> RuntimeBuilder<'g, 'n> {
108        RuntimeBuilder {
109            globals: self.globals,
110            partials: Some(values),
111        }
112    }
113
114    /// Create the `Runtime`.
115    pub fn build(self) -> impl Runtime + 'c {
116        let partials = self.partials.unwrap_or(&NullPartials);
117        let runtime = RuntimeCore {
118            partials,
119            ..Default::default()
120        };
121        let runtime = super::IndexFrame::new(runtime);
122        let runtime = super::StackFrame::new(runtime, self.globals.unwrap_or(&NullObject));
123        super::GlobalFrame::new(runtime)
124    }
125}
126
127#[derive(Copy, Clone, Debug)]
128struct NullObject;
129
130impl ValueView for NullObject {
131    fn as_debug(&self) -> &dyn std::fmt::Debug {
132        self
133    }
134
135    fn render(&self) -> crate::model::DisplayCow<'_> {
136        Value::Nil.render()
137    }
138    fn source(&self) -> crate::model::DisplayCow<'_> {
139        Value::Nil.source()
140    }
141    fn type_name(&self) -> &'static str {
142        "object"
143    }
144    fn query_state(&self, state: crate::model::State) -> bool {
145        match state {
146            crate::model::State::Truthy => true,
147            crate::model::State::DefaultValue
148            | crate::model::State::Empty
149            | crate::model::State::Blank => false,
150        }
151    }
152
153    fn to_kstr(&self) -> crate::model::KStringCow<'_> {
154        crate::model::KStringCow::from_static("")
155    }
156    fn to_value(&self) -> Value {
157        Value::Object(Object::new())
158    }
159
160    fn as_object(&self) -> Option<&dyn ObjectView> {
161        Some(self)
162    }
163}
164
165impl ObjectView for NullObject {
166    fn as_value(&self) -> &dyn ValueView {
167        self
168    }
169
170    fn size(&self) -> i64 {
171        0
172    }
173
174    fn keys<'k>(&'k self) -> Box<dyn Iterator<Item = crate::model::KStringCow<'k>> + 'k> {
175        let keys = Vec::new().into_iter();
176        Box::new(keys)
177    }
178
179    fn values<'k>(&'k self) -> Box<dyn Iterator<Item = &'k dyn ValueView> + 'k> {
180        let i = Vec::new().into_iter();
181        Box::new(i)
182    }
183
184    fn iter<'k>(
185        &'k self,
186    ) -> Box<dyn Iterator<Item = (crate::model::KStringCow<'k>, &'k dyn ValueView)> + 'k> {
187        let i = Vec::new().into_iter();
188        Box::new(i)
189    }
190
191    fn contains_key(&self, _index: &str) -> bool {
192        false
193    }
194
195    fn get<'s>(&'s self, _index: &str) -> Option<&'s dyn ValueView> {
196        None
197    }
198}
199
200impl Default for RuntimeBuilder<'static, 'static> {
201    fn default() -> Self {
202        Self::new()
203    }
204}
205
206/// Processing runtime for a template.
207pub struct RuntimeCore<'g> {
208    partials: &'g dyn PartialStore,
209
210    registers: Registers,
211}
212
213impl RuntimeCore<'_> {
214    /// Create a default `RuntimeCore`.
215    ///
216    /// See `RuntimeBuilder` for more control.
217    pub fn new() -> Self {
218        RuntimeCore::default()
219    }
220
221    /// Partial templates for inclusion.
222    pub fn partials(&self) -> &dyn PartialStore {
223        self.partials
224    }
225}
226
227impl Runtime for RuntimeCore<'_> {
228    fn partials(&self) -> &dyn PartialStore {
229        self.partials
230    }
231
232    fn name(&self) -> Option<crate::model::KStringRef<'_>> {
233        None
234    }
235
236    fn roots(&self) -> std::collections::BTreeSet<crate::model::KStringCow<'_>> {
237        // Indexes don't count
238        std::collections::BTreeSet::new()
239    }
240
241    fn try_get(&self, _path: &[ScalarCow<'_>]) -> Option<ValueCow<'_>> {
242        None
243    }
244
245    fn get(&self, path: &[ScalarCow<'_>]) -> Result<ValueCow<'_>> {
246        let key = path.first().cloned().unwrap_or_else(|| Scalar::new("nil"));
247        Error::with_msg("Unknown variable")
248            .context("requested variable", key.to_kstr())
249            .into_err()
250    }
251
252    fn set_global(
253        &self,
254        _name: crate::model::KString,
255        _val: crate::model::Value,
256    ) -> Option<crate::model::Value> {
257        unreachable!("Must be masked by a global frame");
258    }
259
260    fn set_index(&self, _name: crate::model::KString, _val: Value) -> Option<Value> {
261        unreachable!("Must be masked by a global frame");
262    }
263
264    fn get_index<'a>(&'a self, _name: &str) -> Option<ValueCow<'a>> {
265        None
266    }
267
268    fn registers(&self) -> &Registers {
269        &self.registers
270    }
271}
272
273impl Default for RuntimeCore<'_> {
274    fn default() -> Self {
275        Self {
276            partials: &NullPartials,
277            registers: Default::default(),
278        }
279    }
280}
281
282/// Unnamed state for plugins during rendering
283pub struct Registers {
284    registers: std::cell::RefCell<anymap2::AnyMap>,
285}
286
287impl Registers {
288    /// Data store for stateful tags/blocks.
289    ///
290    /// If a plugin needs state, it creates a `struct Register : Default` and accesses it via
291    /// `get_mut`.
292    pub fn get_mut<T: std::any::Any + Default>(&self) -> std::cell::RefMut<'_, T> {
293        std::cell::RefMut::map(self.registers.borrow_mut(), |registers| {
294            registers.entry::<T>().or_default()
295        })
296    }
297}
298
299impl Default for Registers {
300    fn default() -> Self {
301        Self {
302            registers: std::cell::RefCell::new(anymap2::AnyMap::new()),
303        }
304    }
305}
306
307/// The current interrupt state. The interrupt state is used by
308/// the `break` and `continue` tags to halt template rendering
309/// at a given point and unwind the `render` call stack until
310/// it reaches an enclosing `for_loop`. At that point the interrupt
311/// is cleared, and the `for_loop` carries on processing as directed.
312#[derive(Debug, Clone, PartialEq, Eq, Default)]
313pub struct InterruptRegister {
314    interrupt: Option<Interrupt>,
315}
316
317impl InterruptRegister {
318    /// An interrupt state is active.
319    pub fn interrupted(&self) -> bool {
320        self.interrupt.is_some()
321    }
322
323    /// Sets the interrupt state. Any previous state is obliterated.
324    pub fn set(&mut self, interrupt: Interrupt) {
325        self.interrupt.replace(interrupt);
326    }
327
328    /// Fetches and clears the interrupt state.
329    pub fn reset(&mut self) -> Option<Interrupt> {
330        self.interrupt.take()
331    }
332}
333
334/// Block processing interrupt state.
335#[derive(Debug, Clone, Copy, PartialEq, Eq)]
336pub enum Interrupt {
337    /// Restart processing the current block.
338    Continue,
339    /// Stop processing the current block.
340    Break,
341}
342
343#[derive(Copy, Clone, Debug)]
344struct NullPartials;
345
346impl PartialStore for NullPartials {
347    fn contains(&self, _name: &str) -> bool {
348        false
349    }
350
351    fn names(&self) -> Vec<&str> {
352        Vec::new()
353    }
354
355    fn try_get(&self, _name: &str) -> Option<sync::Arc<dyn Renderable>> {
356        None
357    }
358
359    fn get(&self, name: &str) -> Result<sync::Arc<dyn Renderable>> {
360        Err(Error::with_msg("Partial does not exist").context("name", name.to_owned()))
361    }
362}
363
364#[cfg(test)]
365mod test {
366    use super::*;
367
368    use crate::model::Scalar;
369    use crate::model::Value;
370    use crate::model::ValueViewCmp;
371
372    #[test]
373    fn mask_variables() {
374        let test_path = [Scalar::new("test")];
375
376        let rt = RuntimeBuilder::new().build();
377        rt.set_global("test".into(), Value::scalar(42f64));
378        assert_eq!(&rt.get(&test_path).unwrap(), &ValueViewCmp::new(&42f64));
379
380        {
381            let data = crate::object!({"test": 3});
382            let new_scope = super::super::StackFrame::new(&rt, &data);
383
384            // assert that values are chained to the parent scope
385            assert_eq!(&new_scope.get(&test_path).unwrap(), &ValueViewCmp::new(&3));
386        }
387
388        // assert that the value has reverted to the old one
389        assert_eq!(&rt.get(&test_path).unwrap(), &ValueViewCmp::new(&42));
390    }
391
392    #[test]
393    fn global_variables() {
394        let global_path = [Scalar::new("global")];
395
396        let rt = RuntimeBuilder::new().build();
397
398        {
399            let data = crate::object!({"test": 3});
400            let new_scope = super::super::StackFrame::new(&rt, &data);
401
402            // sat a new val that we will pick up outside the scope
403            new_scope.set_global("global".into(), Value::scalar("some value"));
404        }
405        assert_eq!(
406            &rt.get(&global_path).unwrap(),
407            &ValueViewCmp::new(&"some value")
408        );
409    }
410}