use std::borrow::Cow;
use std::collections::HashMap;
use serde_json::Value;
use crate::context::get_json_pointer;
use crate::renderer::for_loop::ForLoop;
use crate::template::Template;
pub type Val<'a> = Cow<'a, Value>;
pub type FrameContext<'a> = HashMap<&'a str, Val<'a>>;
#[inline]
pub fn value_by_pointer<'a>(pointer: &str, val: &Val<'a>) -> Option<Val<'a>> {
match *val {
Cow::Borrowed(r) => r.pointer(&get_json_pointer(pointer)).map(|found| Cow::Borrowed(found)),
Cow::Owned(ref r) => {
r.pointer(&get_json_pointer(pointer)).map(|found| Cow::Owned(found.clone()))
}
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum FrameType {
Origin,
Macro,
ForLoop,
Include,
}
#[derive(Debug)]
pub struct StackFrame<'a> {
pub kind: FrameType,
pub name: &'a str,
context: FrameContext<'a>,
pub active_template: &'a Template,
pub for_loop: Option<ForLoop<'a>>,
pub macro_namespace: Option<&'a str>,
}
impl<'a> StackFrame<'a> {
pub fn new(kind: FrameType, name: &'a str, tpl: &'a Template) -> Self {
StackFrame {
kind,
name,
context: FrameContext::new(),
active_template: tpl,
for_loop: None,
macro_namespace: None,
}
}
pub fn new_for_loop(name: &'a str, tpl: &'a Template, for_loop: ForLoop<'a>) -> Self {
StackFrame {
kind: FrameType::ForLoop,
name,
context: FrameContext::new(),
active_template: tpl,
for_loop: Some(for_loop),
macro_namespace: None,
}
}
pub fn new_macro(
name: &'a str,
tpl: &'a Template,
macro_namespace: &'a str,
context: FrameContext<'a>,
) -> Self {
StackFrame {
kind: FrameType::Macro,
name,
context,
active_template: tpl,
for_loop: None,
macro_namespace: Some(macro_namespace),
}
}
pub fn new_include(name: &'a str, tpl: &'a Template) -> Self {
StackFrame {
kind: FrameType::Include,
name,
context: FrameContext::new(),
active_template: tpl,
for_loop: None,
macro_namespace: None,
}
}
pub fn find_value(&self, key: &str) -> Option<Val<'a>> {
self.find_value_in_frame(key).or_else(|| self.find_value_in_for_loop(key))
}
pub fn find_value_in_frame(&self, key: &str) -> Option<Val<'a>> {
if let Some(dot) = key.find('.') {
if dot < key.len() + 1 {
if let Some(found_value) =
self.context.get(&key[0..dot]).map(|v| value_by_pointer(&key[dot + 1..], v))
{
return found_value;
}
}
} else if let Some(found) = self.context.get(key) {
return Some(found.clone());
}
None
}
pub fn find_value_in_for_loop(&self, key: &str) -> Option<Val<'a>> {
if let Some(ref for_loop) = self.for_loop {
if for_loop.is_key(key) {
return Some(Cow::Owned(Value::String(for_loop.get_current_key())));
}
let (real_key, tail) = if let Some(tail_pos) = key.find('.') {
(&key[..tail_pos], &key[tail_pos + 1..])
} else {
(key, "")
};
if real_key == "loop" {
match tail {
"index" => {
return Some(Cow::Owned(Value::Number((for_loop.current + 1).into())));
}
"index0" => {
return Some(Cow::Owned(Value::Number(for_loop.current.into())));
}
"first" => {
return Some(Cow::Owned(Value::Bool(for_loop.current == 0)));
}
"last" => {
return Some(Cow::Owned(Value::Bool(
for_loop.current == for_loop.len() - 1,
)));
}
_ => return None,
};
}
let v = for_loop.get_current_value();
if key == for_loop.value_name {
return Some(v);
}
if real_key == for_loop.value_name && !tail.is_empty() {
return value_by_pointer(tail, &v);
}
}
None
}
pub fn insert(&mut self, key: &'a str, value: Val<'a>) {
self.context.insert(key, value);
}
pub fn clear_context(&mut self) {
if self.for_loop.is_some() {
self.context.clear();
}
}
pub fn context_owned(&self) -> HashMap<String, Value> {
let mut context = HashMap::new();
for (key, val) in &self.context {
context.insert((*key).to_string(), val.clone().into_owned());
}
context
}
}