use std::sync;
use anymap;
use liquid_error::Error;
use liquid_error::Result;
use super::PartialStore;
use super::Renderable;
use super::Stack;
use super::ValueStore;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Interrupt {
Continue,
Break,
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct InterruptState {
interrupt: Option<Interrupt>,
}
impl InterruptState {
pub fn interrupted(&self) -> bool {
self.interrupt.is_some()
}
pub fn set_interrupt(&mut self, interrupt: Interrupt) {
self.interrupt = Some(interrupt);
}
pub fn pop_interrupt(&mut self) -> Option<Interrupt> {
let rval = self.interrupt;
self.interrupt = None;
rval
}
}
#[derive(Copy, Clone, Debug)]
struct NullPartials;
impl PartialStore for NullPartials {
fn contains(&self, _name: &str) -> bool {
false
}
fn names(&self) -> Vec<&str> {
Vec::new()
}
fn try_get(&self, _name: &str) -> Option<sync::Arc<Renderable>> {
None
}
fn get(&self, name: &str) -> Result<sync::Arc<Renderable>> {
Err(Error::with_msg("Partial does not exist").context("name", name.to_owned()))
}
}
pub struct ContextBuilder<'g> {
globals: Option<&'g ValueStore>,
partials: Option<&'g PartialStore>,
}
impl<'g> ContextBuilder<'g> {
pub fn new() -> Self {
Self {
globals: None,
partials: None,
}
}
pub fn set_globals(mut self, values: &'g ValueStore) -> Self {
self.globals = Some(values);
self
}
pub fn set_partials(mut self, values: &'g PartialStore) -> Self {
self.partials = Some(values);
self
}
pub fn build(self) -> Context<'g> {
let stack = match self.globals {
Some(globals) => Stack::with_globals(globals),
None => Stack::empty(),
};
let partials = self.partials.unwrap_or(&NullPartials);
Context {
stack,
partials,
registers: anymap::AnyMap::new(),
interrupt: InterruptState::default(),
}
}
}
impl<'g> Default for ContextBuilder<'g> {
fn default() -> Self {
Self::new()
}
}
pub struct Context<'g> {
stack: Stack<'g>,
partials: &'g PartialStore,
registers: anymap::AnyMap,
interrupt: InterruptState,
}
impl<'g> Context<'g> {
pub fn new() -> Self {
Context::default()
}
pub fn interrupt(&self) -> &InterruptState {
&self.interrupt
}
pub fn interrupt_mut(&mut self) -> &mut InterruptState {
&mut self.interrupt
}
pub fn partials(&self) -> &PartialStore {
self.partials
}
pub fn get_register_mut<T: anymap::any::IntoBox<anymap::any::Any> + Default>(
&mut self,
) -> &mut T {
self.registers.entry::<T>().or_insert_with(Default::default)
}
pub fn stack(&self) -> &Stack {
&self.stack
}
pub fn stack_mut<'a>(&'a mut self) -> &'a mut Stack<'g>
where
'g: 'a,
{
&mut self.stack
}
pub fn run_in_scope<RvalT, FnT>(&mut self, f: FnT) -> RvalT
where
FnT: FnOnce(&mut Context) -> RvalT,
{
self.stack.push_frame();
let result = f(self);
self.stack.pop_frame();
result
}
pub fn run_in_named_scope<RvalT, S: Into<String>, FnT>(&mut self, name: S, f: FnT) -> RvalT
where
FnT: FnOnce(&mut Context) -> RvalT,
{
self.stack.push_named_frame(name);
let result = f(self);
self.stack.pop_frame();
result
}
}
impl<'g> Default for Context<'g> {
fn default() -> Self {
Self {
stack: Stack::empty(),
partials: &NullPartials,
registers: anymap::AnyMap::new(),
interrupt: InterruptState::default(),
}
}
}
#[cfg(test)]
mod test {
use super::*;
use liquid_value::Scalar;
use liquid_value::Value;
#[test]
fn scoped_variables() {
let test_path = [Scalar::new("test")];
let global_path = [Scalar::new("global")];
let mut ctx = Context::new();
ctx.stack_mut().set_global("test", Value::scalar(42f64));
assert_eq!(ctx.stack().get(&test_path).unwrap(), &Value::scalar(42f64));
ctx.run_in_scope(|new_scope| {
assert_eq!(
new_scope.stack().get(&test_path).unwrap(),
&Value::scalar(42f64)
);
new_scope.stack_mut().set("test", Value::scalar(3.14f64));
assert_eq!(
new_scope.stack().get(&test_path).unwrap(),
&Value::scalar(3.14f64)
);
new_scope
.stack_mut()
.set_global("global", Value::scalar("some value"));
});
assert_eq!(ctx.stack().get(&test_path).unwrap(), &Value::scalar(42f64));
assert_eq!(
ctx.stack().get(&global_path).unwrap(),
&Value::scalar("some value")
);
}
}