use std::cell::RefCell;
use std::collections::HashMap;
use std::fmt;
use std::rc::Rc;
use serde::Serialize;
use serde_json::{Map, Value};
use crate::{
error::{HelperError, RenderError},
escape::EscapeFn,
helper::{Helper, HelperRegistry, HelperResult, LocalHelper},
json,
output::{Output, StringOutput},
parser::{
ast::{Block, Call, CallTarget, Node, ParameterValue, Path, Slice},
path,
},
template::Templates,
trim::{TrimHint, TrimState},
RenderResult,
};
static PARTIAL_BLOCK: &str = "@partial-block";
static HELPER_MISSING: &str = "helperMissing";
static BLOCK_HELPER_MISSING: &str = "blockHelperMissing";
type HelperValue = Option<Value>;
pub mod context;
pub mod scope;
pub use context::{Context, Property, Type};
pub use scope::Scope;
static STACK_MAX: usize = 32;
#[derive(Eq, PartialEq, Hash, Debug, Clone)]
enum CallSite {
Partial(String),
Helper(String),
BlockHelper(String),
}
impl fmt::Display for CallSite {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", {
match *self {
CallSite::Partial(ref name) => format!("partial#{}", name),
CallSite::Helper(ref name) => format!("helper#{}", name),
CallSite::BlockHelper(ref name) => format!("block#{}", name),
}
})
}
}
impl Into<String> for CallSite {
fn into(self) -> String {
match self {
CallSite::Partial(name)
| CallSite::Helper(name)
| CallSite::BlockHelper(name) => name,
}
}
}
pub struct Render<'render> {
strict: bool,
escape: &'render EscapeFn,
helpers: &'render HelperRegistry<'render>,
local_helpers: Rc<RefCell<HashMap<String, Box<dyn LocalHelper + 'render>>>>,
templates: &'render Templates<'render>,
partials: HashMap<String, &'render Node<'render>>,
name: &'render str,
root: Value,
writer: Box<&'render mut dyn Output>,
scopes: Vec<Scope>,
trim: TrimState,
hint: Option<TrimHint>,
end_tag_hint: Option<TrimHint>,
stack: Vec<CallSite>,
}
impl<'render> Render<'render> {
pub fn new<T>(
strict: bool,
escape: &'render EscapeFn,
helpers: &'render HelperRegistry<'render>,
templates: &'render Templates<'render>,
name: &'render str,
data: &T,
writer: Box<&'render mut dyn Output>,
) -> RenderResult<Self>
where
T: Serialize,
{
let root = serde_json::to_value(data).map_err(RenderError::from)?;
let scopes: Vec<Scope> = Vec::new();
Ok(Self {
strict,
escape,
helpers,
local_helpers: Rc::new(RefCell::new(HashMap::new())),
templates,
partials: HashMap::new(),
name,
root,
writer,
scopes,
trim: Default::default(),
hint: None,
end_tag_hint: None,
stack: Vec::new(),
})
}
pub fn render(&mut self, node: &'render Node<'render>) -> RenderResult<()> {
for event in node.into_iter().event(Default::default()) {
self.render_node(event.node, event.trim)?;
}
Ok(())
}
pub fn out(&mut self) -> &mut Box<&'render mut dyn Output> {
&mut self.writer
}
pub fn write(&mut self, s: &str) -> HelperResult<usize> {
self.write_str(s, false)
.map_err(Box::new)
.map_err(HelperError::from)
}
pub fn write_escaped(&mut self, s: &str) -> HelperResult<usize> {
self.write_str(s, true)
.map_err(Box::new)
.map_err(HelperError::from)
}
pub fn push_scope(&mut self, scope: Scope) {
self.scopes.push(scope);
}
pub fn pop_scope(&mut self) -> Option<Scope> {
self.scopes.pop()
}
pub fn scope_mut(&mut self) -> Option<&mut Scope> {
self.scopes.last_mut()
}
pub fn root(&self) -> &Value {
&self.root
}
pub fn inverse<'a>(
&mut self,
template: &'a Node<'a>,
) -> Result<Option<&'a Node<'a>>, HelperError> {
let mut alt: Option<&'a Node<'_>> = None;
let mut branch: Option<&'a Node<'_>> = None;
match template {
Node::Block(ref block) => {
if !block.conditions().is_empty() {
for node in block.conditions().iter() {
match node {
Node::Block(clause) => {
if clause.call().is_empty() {
alt = Some(node);
} else {
if let Some(value) = self
.call(clause.call())
.map_err(Box::new)?
{
if json::is_truthy(&value) {
branch = Some(node);
break;
}
}
}
}
_ => {}
}
}
}
}
_ => {}
}
Ok(branch.or(alt))
}
pub fn template(
&mut self,
node: &'render Node<'render>,
) -> Result<(), HelperError> {
let mut hint: Option<TrimHint> = None;
for event in node.into_iter().event(self.hint) {
let mut trim = event.trim;
if event.first {
let hint = node.trim();
if hint.after {
trim.start = true;
}
}
if event.last {
match node {
Node::Block(ref block) => {
let last_hint = block.trim_close();
if last_hint.before {
trim.end = true;
}
hint = Some(last_hint);
}
_ => {}
}
}
self.render_node(event.node, trim)
.map_err(|e| HelperError::Render(Box::new(e)))?;
}
self.end_tag_hint = hint;
Ok(())
}
pub fn buffer(&self, node: &'render Node<'render>) -> Result<String, HelperError> {
let mut writer = StringOutput::new();
let mut rc = Render::new(
self.strict,
self.escape,
self.helpers,
self.templates,
self.name,
&self.root,
Box::new(&mut writer),
).map_err(Box::new)?;
rc.stack = self.stack.clone();
rc.scopes = self.scopes.clone();
rc.render(node).map_err(Box::new)?;
drop(rc);
Ok(writer.into())
}
pub fn evaluate<'a>(
&'a self,
value: &str,
) -> HelperResult<Option<&'a Value>> {
if let Some(path) = path::from_str(value)? {
return Ok(self.lookup(&path));
}
Ok(None)
}
fn lookup<'a>(&'a self, path: &Path<'_>) -> Option<&'a Value> {
if path.is_root() {
json::find_parts(
path.components().iter().skip(1).map(|c| c.as_value()),
&self.root,
)
} else if path.is_explicit() {
let value = if let Some(scope) = self.scopes.last() {
if let Some(base) = scope.base_value() {
base
} else {
&self.root
}
} else {
&self.root
};
if path.components().len() == 1 {
Some(value)
} else {
json::find_parts(
path.components().iter().skip(1).map(|c| c.as_value()),
value,
)
}
} else if path.is_local() {
if let Some(scope) = self.scopes.last() {
json::find_parts(
path.components().iter().map(|c| c.as_value()),
scope.locals(),
)
} else {
None
}
} else if path.parents() > 0 {
let mut all = vec![&self.root];
let mut values: Vec<&'a Value> =
self.scopes.iter().map(|s| s.locals()).collect();
all.append(&mut values);
if all.len() > path.parents() as usize {
let index: usize = all.len() - (path.parents() as usize + 1);
if let Some(value) = all.get(index) {
json::find_parts(
path.components().iter().map(|c| c.as_value()),
value,
)
} else {
None
}
} else {
None
}
} else {
if let Some(scope) = self.scopes.last() {
json::find_parts(
path.components().iter().map(|c| c.as_value()),
scope.locals(),
)
.or(json::find_parts(
path.components().iter().map(|c| c.as_value()),
&self.root,
))
} else {
json::find_parts(
path.components().iter().map(|c| c.as_value()),
&self.root,
)
}
}
}
fn arguments(&mut self, call: &Call<'_>) -> RenderResult<Vec<Value>> {
let mut out: Vec<Value> = Vec::new();
for p in call.arguments() {
let arg = match p {
ParameterValue::Json(val) => val.clone(),
ParameterValue::Path(ref path) => {
self.lookup(path).cloned().unwrap_or(Value::Null)
}
ParameterValue::SubExpr(ref call) => {
self.statement(call)?.unwrap_or(Value::Null)
}
};
out.push(arg);
}
Ok(out)
}
fn hash(&mut self, call: &Call<'_>) -> RenderResult<Map<String, Value>> {
let mut out = Map::new();
for (k, p) in call.hash() {
let (key, value) = match p {
ParameterValue::Json(val) => (k.to_string(), val.clone()),
ParameterValue::Path(ref path) => {
let val = self.lookup(path).cloned().unwrap_or(Value::Null);
(k.to_string(), val)
}
ParameterValue::SubExpr(ref call) => (
k.to_string(),
self.statement(call)?.unwrap_or(Value::Null),
),
};
out.insert(key, value);
}
Ok(out)
}
pub fn register_local_helper(
&mut self,
name: &'render str,
helper: Box<dyn LocalHelper + 'render>,
) {
let registry = Rc::make_mut(&mut self.local_helpers);
registry.borrow_mut().insert(name.to_string(), helper);
}
pub fn unregister_local_helper(&mut self, name: &'render str) {
let registry = Rc::make_mut(&mut self.local_helpers);
registry.borrow_mut().remove(name);
}
fn invoke(
&mut self,
name: &str,
call: &Call<'_>,
content: Option<&'render Node<'render>>,
text: Option<&'render str>,
property: Option<Property>,
) -> RenderResult<HelperValue> {
let site = if content.is_some() {
CallSite::BlockHelper(name.to_string())
} else {
CallSite::Helper(name.to_string())
};
let amount = self.stack.iter().filter(|&n| *n == site).count();
if amount >= STACK_MAX {
return Err(RenderError::HelperCycle(site.into()));
}
self.stack.push(site);
let args = self.arguments(call)?;
let hash = self.hash(call)?;
let mut context =
Context::new(call, name.to_owned(), args, hash, text, property);
let local_helpers = Rc::clone(&self.local_helpers);
let value: Option<Value> =
if let Some(helper) = local_helpers.borrow().get(name) {
helper.call(self, &mut context, content)?
} else if let Some(helper) = self.helpers.get(name) {
helper.call(self, &mut context, content)?
} else {
None
};
drop(local_helpers);
self.stack.pop();
Ok(value)
}
fn has_helper(&mut self, name: &str) -> bool {
self.local_helpers.borrow().get(name).is_some()
|| self.helpers.get(name).is_some()
}
fn resolve(&mut self, path: &Path<'_>) -> RenderResult<HelperValue> {
if let Some(value) = self.lookup(path).cloned().take() {
Ok(Some(value))
} else {
if self.strict {
Err(RenderError::VariableNotFound(path.as_str().to_string()))
} else {
Ok(None)
}
}
}
pub(crate) fn call(
&mut self,
call: &Call<'_>,
) -> RenderResult<HelperValue> {
match call.target() {
CallTarget::Path(ref path) => {
if path.is_explicit() {
Ok(self.lookup(path).cloned())
} else if path.is_simple() {
if self.has_helper(path.as_str()) {
self.invoke(path.as_str(), call, None, None, None)
} else {
let value = self.lookup(path).cloned();
if let None = value {
if self.has_helper(HELPER_MISSING) {
return self.invoke(
HELPER_MISSING,
call,
None,
None,
None,
);
} else {
if self.strict {
return Err(RenderError::VariableNotFound(
path.as_str().to_string(),
));
}
}
}
Ok(value)
}
} else {
self.resolve(path)
}
}
CallTarget::SubExpr(ref sub) => self.call(sub),
}
}
fn statement(&mut self, call: &Call<'_>) -> RenderResult<HelperValue> {
if call.is_partial() {
self.render_partial(call, None)?;
Ok(None)
} else {
Ok(self.call(call)?)
}
}
fn get_partial_name<'a>(
&mut self,
call: &Call<'_>,
) -> RenderResult<String> {
match call.target() {
CallTarget::Path(ref path) => {
if path.as_str() == PARTIAL_BLOCK {
return Ok(PARTIAL_BLOCK.to_string());
} else if path.is_simple() {
return Ok(path.as_str().to_string());
} else {
panic!("Partials must be simple identifiers");
}
}
CallTarget::SubExpr(ref call) => {
let result = self.statement(call)?.unwrap_or(Value::Null);
return Ok(json::stringify(&result));
}
}
}
fn render_partial(
&mut self,
call: &Call<'_>,
partial_block: Option<&'render Node<'render>>,
) -> RenderResult<()> {
let name = self.get_partial_name(call)?;
let site = CallSite::Partial(name.to_string());
if self.stack.contains(&site) {
return Err(RenderError::PartialCycle(site.into()));
}
self.stack.push(site);
if let Some(node) = partial_block {
self.partials.insert(PARTIAL_BLOCK.to_string(), node);
}
let node = if let Some(local_partial) = self.partials.get(&name) {
local_partial
} else {
let template = self
.templates
.get(&name)
.ok_or_else(|| RenderError::PartialNotFound(name))?;
template.node()
};
let hash = self.hash(call)?;
let scope = Scope::from(hash);
self.scopes.push(scope);
for event in node.into_iter().event(self.hint) {
self.render_node(event.node, event.trim)?;
}
self.scopes.pop();
self.stack.pop();
Ok(())
}
fn block_helper_missing(
&mut self,
node: &'render Node<'render>,
block: &'render Block<'render>,
call: &'render Call<'render>,
text: Option<&str>,
raw: bool,
) -> RenderResult<()> {
if raw {
if let Some(text) = text {
self.write_str(text, false)?;
}
} else {
match call.target() {
CallTarget::Path(ref path) => {
if let Some(value) = self.lookup(path).cloned() {
if self.has_helper(BLOCK_HELPER_MISSING) {
let prop = Property {
name: path.as_str().to_string(),
value,
};
self.invoke(
BLOCK_HELPER_MISSING,
call,
Some(node),
None,
Some(prop),
)?;
} else {
self.template(node)?;
}
} else if self.has_helper(HELPER_MISSING) {
self.invoke(HELPER_MISSING, call, None, None, None)?;
} else {
if self.strict {
return Err(RenderError::HelperNotFound(
path.as_str().to_string(),
));
}
}
}
_ => {}
}
}
Ok(())
}
fn block(
&mut self,
node: &'render Node<'render>,
block: &'render Block<'render>,
) -> RenderResult<()> {
let call = block.call();
let raw = block.is_raw();
if call.is_partial() {
self.render_partial(call, Some(node))?;
} else {
match call.target() {
CallTarget::Path(ref path) => {
if path.is_simple() {
let mut text: Option<&str> = None;
if raw {
text = if !block.nodes().is_empty() {
Some(block.nodes().get(0).unwrap().as_str())
} else {
Some("")
};
match node {
Node::Block(ref block) => {
let hint = block.trim_close();
if node.trim().after {
if let Some(ref content) = text {
text = Some(content.trim_start());
}
}
if hint.before {
if let Some(ref content) = text {
text = Some(content.trim_end());
}
}
self.end_tag_hint = Some(hint);
}
_ => {}
}
}
if self.has_helper(path.as_str()) {
self.invoke(
path.as_str(),
call,
Some(node),
text,
None,
)?;
} else {
return self.block_helper_missing(
node, block, call, text, raw,
);
}
} else {
panic!(
"Block helpers identifiers must be simple paths"
);
}
}
_ => todo!("Handle block sub expression for call target"),
}
}
Ok(())
}
pub(crate) fn render_node(
&mut self,
node: &'render Node<'render>,
trim: TrimState,
) -> RenderResult<()> {
self.trim = trim;
self.hint = Some(node.trim());
if let Some(hint) = self.end_tag_hint.take() {
if hint.after {
self.trim.start = true;
}
}
match node {
Node::Text(ref n) => {
self.write_str(n.as_str(), false)?;
}
Node::RawStatement(ref n) => {
let raw = &n.as_str()[1..];
self.write_str(raw, false)?;
}
Node::RawComment(_) => {}
Node::Comment(_) => {}
Node::Document(_) => {}
Node::Statement(ref call) => {
if let Some(ref value) = self.statement(call)? {
let val = json::stringify(value);
self.write_str(&val, call.is_escaped())?;
}
}
Node::Block(ref block) => {
self.block(node, block)?;
}
}
Ok(())
}
fn write_str(&mut self, s: &str, escape: bool) -> RenderResult<usize> {
let val = if self.trim.start { s.trim_start() } else { s };
let val = if self.trim.end { val.trim_end() } else { val };
if val.is_empty() {
return Ok(0);
}
if escape {
let escaped = (self.escape)(val);
Ok(self.writer.write_str(&escaped).map_err(RenderError::from)?)
} else {
Ok(self.writer.write_str(val).map_err(RenderError::from)?)
}
}
}