liquid_interpreter/
context.rs

1use std::sync;
2
3use anymap;
4use liquid_error::Error;
5use liquid_error::Result;
6
7use super::PartialStore;
8use super::Renderable;
9use super::Stack;
10use super::ValueStore;
11
12/// Block processing interrupt state.
13#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14pub enum Interrupt {
15    /// Restart processing the current block.
16    Continue,
17    /// Stop processing the current block.
18    Break,
19}
20
21/// The current interrupt state. The interrupt state is used by
22/// the `break` and `continue` tags to halt template rendering
23/// at a given point and unwind the `render` call stack until
24/// it reaches an enclosing `for_loop`. At that point the interrupt
25/// is cleared, and the `for_loop` carries on processing as directed.
26#[derive(Debug, Clone, PartialEq, Eq, Default)]
27pub struct InterruptState {
28    interrupt: Option<Interrupt>,
29}
30
31impl InterruptState {
32    /// An interrupt state is active.
33    pub fn interrupted(&self) -> bool {
34        self.interrupt.is_some()
35    }
36
37    /// Sets the interrupt state. Any previous state is obliterated.
38    pub fn set_interrupt(&mut self, interrupt: Interrupt) {
39        self.interrupt = Some(interrupt);
40    }
41
42    /// Fetches and clears the interrupt state.
43    pub fn pop_interrupt(&mut self) -> Option<Interrupt> {
44        let rval = self.interrupt;
45        self.interrupt = None;
46        rval
47    }
48}
49
50#[derive(Copy, Clone, Debug)]
51struct NullPartials;
52
53impl PartialStore for NullPartials {
54    fn contains(&self, _name: &str) -> bool {
55        false
56    }
57
58    fn names(&self) -> Vec<&str> {
59        Vec::new()
60    }
61
62    fn try_get(&self, _name: &str) -> Option<sync::Arc<Renderable>> {
63        None
64    }
65
66    fn get(&self, name: &str) -> Result<sync::Arc<Renderable>> {
67        Err(Error::with_msg("Partial does not exist").context("name", name.to_owned()))
68    }
69}
70
71/// Create processing context for a template.
72pub struct ContextBuilder<'g> {
73    globals: Option<&'g ValueStore>,
74    partials: Option<&'g PartialStore>,
75}
76
77impl<'g> ContextBuilder<'g> {
78    /// Creates a new, empty rendering context.
79    pub fn new() -> Self {
80        Self {
81            globals: None,
82            partials: None,
83        }
84    }
85
86    /// Initialize the stack with the given globals.
87    pub fn set_globals(mut self, values: &'g ValueStore) -> Self {
88        self.globals = Some(values);
89        self
90    }
91
92    /// Initialize partial-templates availible for including.
93    pub fn set_partials(mut self, values: &'g PartialStore) -> Self {
94        self.partials = Some(values);
95        self
96    }
97
98    /// Create the `Context`.
99    pub fn build(self) -> Context<'g> {
100        let stack = match self.globals {
101            Some(globals) => Stack::with_globals(globals),
102            None => Stack::empty(),
103        };
104        let partials = self.partials.unwrap_or(&NullPartials);
105        Context {
106            stack,
107            partials,
108            registers: anymap::AnyMap::new(),
109            interrupt: InterruptState::default(),
110        }
111    }
112}
113
114impl<'g> Default for ContextBuilder<'g> {
115    fn default() -> Self {
116        Self::new()
117    }
118}
119
120/// Processing context for a template.
121pub struct Context<'g> {
122    stack: Stack<'g>,
123    partials: &'g PartialStore,
124
125    registers: anymap::AnyMap,
126    interrupt: InterruptState,
127}
128
129impl<'g> Context<'g> {
130    /// Create a default `Context`.
131    ///
132    /// See `ContextBuilder` for more control.
133    pub fn new() -> Self {
134        Context::default()
135    }
136
137    /// Access the block's `InterruptState`.
138    pub fn interrupt(&self) -> &InterruptState {
139        &self.interrupt
140    }
141
142    /// Access the block's `InterruptState`.
143    pub fn interrupt_mut(&mut self) -> &mut InterruptState {
144        &mut self.interrupt
145    }
146
147    /// Partial templates for inclusion.
148    pub fn partials(&self) -> &PartialStore {
149        self.partials
150    }
151
152    /// Data store for stateful tags/blocks.
153    ///
154    /// If a plugin needs state, it creates a `struct State : Default` and accesses it via
155    /// `get_register_mut`.
156    pub fn get_register_mut<T: anymap::any::IntoBox<anymap::any::Any> + Default>(
157        &mut self,
158    ) -> &mut T {
159        self.registers.entry::<T>().or_insert_with(Default::default)
160    }
161
162    /// Access the current `Stack`.
163    pub fn stack(&self) -> &Stack {
164        &self.stack
165    }
166
167    /// Access the current `Stack`.
168    pub fn stack_mut<'a>(&'a mut self) -> &'a mut Stack<'g>
169    where
170        'g: 'a,
171    {
172        &mut self.stack
173    }
174
175    /// Sets up a new stack frame, executes the supplied function and then
176    /// tears the stack frame down before returning the function's result
177    /// to the caller.
178    pub fn run_in_scope<RvalT, FnT>(&mut self, f: FnT) -> RvalT
179    where
180        FnT: FnOnce(&mut Context) -> RvalT,
181    {
182        self.stack.push_frame();
183        let result = f(self);
184        self.stack.pop_frame();
185        result
186    }
187
188    /// Sets up a new stack frame, executes the supplied function and then
189    /// tears the stack frame down before returning the function's result
190    /// to the caller.
191    pub fn run_in_named_scope<RvalT, S: Into<String>, FnT>(&mut self, name: S, f: FnT) -> RvalT
192    where
193        FnT: FnOnce(&mut Context) -> RvalT,
194    {
195        self.stack.push_named_frame(name);
196        let result = f(self);
197        self.stack.pop_frame();
198        result
199    }
200}
201
202impl<'g> Default for Context<'g> {
203    fn default() -> Self {
204        Self {
205            stack: Stack::empty(),
206            partials: &NullPartials,
207            registers: anymap::AnyMap::new(),
208            interrupt: InterruptState::default(),
209        }
210    }
211}
212
213#[cfg(test)]
214mod test {
215    use super::*;
216
217    use liquid_value::Scalar;
218    use liquid_value::Value;
219
220    #[test]
221    fn scoped_variables() {
222        let test_path = [Scalar::new("test")];
223        let global_path = [Scalar::new("global")];
224
225        let mut ctx = Context::new();
226        ctx.stack_mut().set_global("test", Value::scalar(42f64));
227        assert_eq!(ctx.stack().get(&test_path).unwrap(), &Value::scalar(42f64));
228
229        ctx.run_in_scope(|new_scope| {
230            // assert that values are chained to the parent scope
231            assert_eq!(
232                new_scope.stack().get(&test_path).unwrap(),
233                &Value::scalar(42f64)
234            );
235
236            // set a new local value, and assert that it overrides the previous value
237            new_scope.stack_mut().set("test", Value::scalar(3.14f64));
238            assert_eq!(
239                new_scope.stack().get(&test_path).unwrap(),
240                &Value::scalar(3.14f64)
241            );
242
243            // sat a new val that we will pick up outside the scope
244            new_scope
245                .stack_mut()
246                .set_global("global", Value::scalar("some value"));
247        });
248
249        // assert that the value has reverted to the old one
250        assert_eq!(ctx.stack().get(&test_path).unwrap(), &Value::scalar(42f64));
251        assert_eq!(
252            ctx.stack().get(&global_path).unwrap(),
253            &Value::scalar("some value")
254        );
255    }
256}