use crate::args::{ArgFromValue, Kwargs};
use crate::errors::TeraResult;
use crate::filters::StoredFilter;
use crate::parsing::Chunk;
use crate::vm::for_loop::ForLoop;
use crate::vm::stack::Stack;
use crate::{Context, HashMap, Value};
use std::collections::BTreeMap;
pub(crate) static MAGICAL_DUMP_VAR: &str = "__tera_context";
#[derive(Debug)]
pub struct State<'tera> {
pub(crate) stack: Stack,
pub(crate) chunk: Option<&'tera Chunk>,
pub(crate) for_loops: Vec<ForLoop>,
set_variables: BTreeMap<String, Value>,
pub(crate) context: &'tera Context,
pub(crate) global_context: Option<&'tera Context>,
pub(crate) capture_buffers: Vec<Vec<u8>>,
pub(crate) escape_buffer: Vec<u8>,
pub(crate) include_parent: Option<&'tera State<'tera>>,
pub(crate) blocks: BTreeMap<&'tera str, (Vec<&'tera Chunk>, usize)>,
pub(crate) current_block_name: Option<&'tera str>,
pub(crate) filters: Option<&'tera HashMap<&'static str, StoredFilter>>,
}
impl<'t> State<'t> {
pub(crate) fn new_with_chunk(context: &'t Context, chunk: &'t Chunk) -> Self {
let mut s = Self::new(context);
s.chunk = Some(chunk);
s
}
pub fn new(context: &'t Context) -> Self {
Self {
stack: Stack::new(),
for_loops: Vec::with_capacity(4),
set_variables: BTreeMap::new(),
context,
global_context: None,
chunk: None,
capture_buffers: Vec::with_capacity(4),
escape_buffer: Vec::with_capacity(128),
include_parent: None,
blocks: BTreeMap::new(),
current_block_name: None,
filters: None,
}
}
pub(crate) fn store_local(&mut self, name: &str, value: Value) {
if let Some(forloop) = self.for_loops.last_mut() {
forloop.store(name, value);
} else {
self.store_global(name, value);
}
}
pub(crate) fn store_global(&mut self, name: &str, value: Value) {
self.set_variables.insert(name.to_string(), value);
}
pub(crate) fn get_value(&self, name: &str) -> Value {
for forloop in self.for_loops.iter().rev() {
if let Some(v) = forloop.get(name) {
return v;
}
}
if let Some(val) = self.set_variables.get(name) {
return val.clone();
}
if let Some(val) = self.context.data.get(name) {
return val.clone();
}
if let Some(global) = self.global_context
&& let Some(val) = global.data.get(name)
{
return val.clone();
}
if let Some(parent) = self.include_parent {
parent.get_value(name)
} else {
Value::undefined()
}
}
pub fn get<T>(&self, name: &str) -> TeraResult<Option<T>>
where
for<'a> T: ArgFromValue<'a, Output = T>,
{
let value = self.get_value(name);
if value.is_undefined() {
Ok(None)
} else {
T::from_value(&value).map(Some)
}
}
pub(crate) fn dump_context(&self) -> Value {
let mut context = crate::HashMap::new();
if let Some(global) = self.global_context {
for (k, v) in &global.data {
context.insert(k.to_string(), v.clone());
}
}
for (k, v) in &self.context.data {
context.insert(k.to_string(), v.clone());
}
context.extend(self.set_variables.clone());
for forloop in &self.for_loops {
context.extend(forloop.context.clone());
}
context.into()
}
pub(crate) fn load_name(&mut self, name: &str, span_idx: u32) {
if name == MAGICAL_DUMP_VAR {
self.stack.push(self.dump_context(), None);
} else {
self.stack
.push(self.get_value(name), Some(span_idx..=span_idx));
}
}
pub fn call_filter(&self, name: &str, value: &Value, kwargs: Kwargs) -> TeraResult<Value> {
match self.filters.and_then(|f| f.get(name)) {
Some(filter) => {
let val = filter.call(value, kwargs, self)?;
Ok(if filter.is_safe() {
val.mark_safe()
} else {
val
})
}
None => Err(crate::errors::Error::message(format!(
"Filter `{name}` is not registered"
))),
}
}
pub(crate) fn available_variables(&self) -> Vec<String> {
let mut vars = std::collections::BTreeSet::new();
if let Some(global) = self.global_context {
for k in global.data.keys() {
vars.insert(k.to_string());
}
}
for k in self.context.data.keys() {
vars.insert(k.to_string());
}
for k in self.set_variables.keys() {
vars.insert(k.clone());
}
for forloop in &self.for_loops {
for k in forloop.context.keys() {
vars.insert(k.clone());
}
}
vars.into_iter().collect()
}
}