use bytes::Bytes;
use crate::{
error::ESIError,
expression::{eval_expr, EvalContext, Value},
parser_types::{Element, Expr, IncludeAttributes, Tag, WhenBranch},
Result,
};
pub enum Flow {
Continue,
Break,
Return(Value),
}
pub trait ElementHandler {
fn ctx(&mut self) -> &mut EvalContext;
fn write_bytes(&mut self, bytes: Bytes) -> Result<()>;
fn on_return(&mut self, value: &Expr) -> Result<Flow>;
fn on_include(&mut self, attrs: &IncludeAttributes) -> Result<Flow>;
fn on_eval(&mut self, attrs: &IncludeAttributes) -> Result<Flow>;
fn on_try(
&mut self,
attempt_events: Vec<Vec<Element>>,
except_events: Vec<Element>,
) -> Result<Flow>;
fn on_function(&mut self, name: String, body: Vec<Element>) -> Result<Flow>;
fn process_queue(&mut self) -> Result<()> {
Ok(())
}
fn process_elements(&mut self, elements: &[Element]) -> Result<Flow> {
for elem in elements {
let flow = self.process(elem)?;
if !matches!(flow, Flow::Continue) {
return Ok(flow);
}
}
Ok(Flow::Continue)
}
fn process(&mut self, element: &Element) -> Result<Flow> {
match element {
Element::Content(text) | Element::Html(text) => {
self.write_bytes(text.clone())?;
Ok(Flow::Continue)
}
Element::Expr(expr) => {
let val = eval_expr(expr, self.ctx()).map_err(|e| {
ESIError::ExpressionError(format!("{e}, in expression: {expr}"))
})?;
if !matches!(val, Value::Null) {
let bytes = val.to_bytes();
if !bytes.is_empty() {
self.write_bytes(bytes)?;
}
}
Ok(Flow::Continue)
}
Element::Esi(Tag::Assign {
name,
subscript,
value,
}) => self.handle_assign(name, subscript.as_ref(), value),
Element::Esi(Tag::Vars { name }) => self.handle_vars(name.as_deref()),
Element::Esi(Tag::Include { attrs }) => self.on_include(attrs),
Element::Esi(Tag::Eval { attrs }) => self.on_eval(attrs),
Element::Esi(Tag::Choose {
when_branches,
otherwise_events,
}) => self.handle_choose(when_branches, otherwise_events),
Element::Esi(Tag::Foreach {
collection,
item,
content,
}) => self.handle_foreach(collection, item.as_deref(), content),
Element::Esi(Tag::Break) => Ok(Flow::Break),
Element::Esi(Tag::Try {
attempt_events,
except_events,
}) => self.on_try(attempt_events.clone(), except_events.clone()),
Element::Esi(Tag::Function { name, body }) => {
self.on_function(name.clone(), body.clone())
}
Element::Esi(Tag::Return { value }) => self.on_return(value),
Element::Esi(_) => Ok(Flow::Continue),
}
}
fn handle_assign(
&mut self,
name: &str,
subscript: Option<&Expr>,
value: &Expr,
) -> Result<Flow> {
let val = eval_expr(value, self.ctx()).map_err(|e| {
ESIError::ExpressionError(format!("{e}, in assignment '{name}' = {value}"))
})?;
if let Some(subscript_expr) = subscript {
if let Ok(subscript_val) = eval_expr(subscript_expr, self.ctx()) {
let key_str = subscript_val.to_string();
self.ctx().set_variable(name, Some(&key_str), val)?;
}
} else {
self.ctx().set_variable(name, None, val)?;
}
Ok(Flow::Continue)
}
fn handle_vars(&mut self, name: Option<&str>) -> Result<Flow> {
if let Some(n) = name {
self.ctx().set_match_name(n);
}
Ok(Flow::Continue)
}
fn handle_choose(
&mut self,
when_branches: &[WhenBranch],
otherwise_events: &[Element],
) -> Result<Flow> {
let mut chose_branch = false;
for when_branch in when_branches {
if let Some(ref match_name) = when_branch.match_name {
self.ctx().set_match_name(match_name);
}
match eval_expr(&when_branch.test, self.ctx()) {
Ok(test_result) if test_result.to_bool() => {
let flow = self.process_elements(&when_branch.content)?;
if !matches!(flow, Flow::Continue) {
return Ok(flow);
}
chose_branch = true;
break;
}
_ => continue,
}
}
if !chose_branch {
return self.process_elements(otherwise_events);
}
Ok(Flow::Continue)
}
fn handle_foreach(
&mut self,
collection: &Expr,
item: Option<&str>,
content: &[Element],
) -> Result<Flow> {
let collection_value = eval_expr(collection, self.ctx()).unwrap_or(Value::Null);
let items = match &collection_value {
Value::List(items) => items.borrow().clone(),
Value::Dict(map) => map
.borrow()
.iter()
.map(|(k, v)| {
Value::new_list(vec![Value::Text(k.clone().into()), v.clone()])
})
.collect(),
Value::Null => Vec::new(),
other => vec![other.clone()], };
let item_var = item.unwrap_or("item").to_string();
for item_value in items {
self.ctx().set_variable(&item_var, None, item_value)?;
match self.process_elements(content)? {
Flow::Continue => {}
Flow::Break => break,
ret @ Flow::Return(_) => return Ok(ret),
}
}
Ok(Flow::Continue)
}
}