use std::cell::RefCell;
use std::collections::{BTreeMap, HashSet};
use std::ops::Deref;
use std::sync::Arc;
use std::{fmt, io};
use crate::vendor::self_cell::self_cell;
use serde::Serialize;
use crate::compiler::codegen::CodeGenerator;
use crate::compiler::instructions::Instructions;
use crate::compiler::lexer::WhitespaceConfig;
use crate::compiler::meta::find_undeclared;
use crate::compiler::parser::parse;
use crate::environment::Environment;
use crate::error::{attach_basic_debug_info, Error};
use crate::output::{Output, WriteWrapper};
use crate::syntax::SyntaxConfig;
use crate::utils::AutoEscape;
use crate::value::Value;
use crate::vm::{prepare_blocks, Context, State, Vm};
pub type AutoEscapeFunc = dyn Fn(&str) -> AutoEscape + Sync + Send;
#[derive(Clone)]
pub struct TemplateConfig {
pub syntax_config: SyntaxConfig,
pub ws_config: WhitespaceConfig,
pub default_auto_escape: Arc<AutoEscapeFunc>,
}
impl TemplateConfig {
pub(crate) fn new(default_auto_escape: Arc<AutoEscapeFunc>) -> TemplateConfig {
TemplateConfig {
syntax_config: SyntaxConfig::default(),
ws_config: WhitespaceConfig::default(),
default_auto_escape,
}
}
}
#[derive(Clone)]
pub struct Template<'env: 'source, 'source> {
env: &'env Environment<'env>,
pub(crate) compiled: CompiledTemplateRef<'env, 'source>,
}
struct CapturedData<'state> {
output: String,
state: State<'state, 'state>,
}
self_cell! {
struct CapturedCell<'source> {
owner: Template<'source, 'source>,
#[covariant]
dependent: CapturedData,
}
}
pub struct Captured<'source> {
cell: CapturedCell<'source>,
}
impl fmt::Debug for Captured<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Captured")
.field("output", &self.output())
.field("state", &self.state())
.finish()
}
}
impl<'source> Captured<'source> {
pub fn output(&self) -> &str {
self.cell.borrow_dependent().output.as_str()
}
pub fn state(&self) -> &State<'_, '_> {
&self.cell.borrow_dependent().state
}
pub fn with_state_mut<R>(
&mut self,
f: impl for<'state> FnOnce(&mut State<'state, 'state>) -> R,
) -> R {
self.cell
.with_dependent_mut(|_, dependent| f(&mut dependent.state))
}
pub fn into_output(mut self) -> String {
self.cell
.with_dependent_mut(|_, dependent| std::mem::take(&mut dependent.output))
}
}
impl fmt::Debug for Template<'_, '_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut ds = f.debug_struct("Template");
ds.field("name", &self.name());
#[cfg(feature = "internal_debug")]
{
ds.field("instructions", &self.compiled.instructions);
ds.field("blocks", &self.compiled.blocks);
}
ds.field("initial_auto_escape", &self.compiled.initial_auto_escape);
ds.finish()
}
}
impl<'env, 'source> Template<'env, 'source> {
pub(crate) fn new(
env: &'env Environment<'env>,
compiled: CompiledTemplateRef<'env, 'source>,
) -> Template<'env, 'source> {
Template { env, compiled }
}
pub fn name(&self) -> &str {
self.compiled.instructions.name()
}
pub fn source(&self) -> &str {
self.compiled.instructions.source()
}
pub fn render<S: Serialize>(&self, ctx: S) -> Result<String, Error> {
self._render(Value::from_serialize(&ctx)).map(|x| x.0)
}
#[deprecated(since = "2.18.0", note = "use render_captured instead")]
pub fn render_and_return_state<S: Serialize>(
&self,
ctx: S,
) -> Result<(String, State<'_, 'env>), Error> {
self._render(Value::from_serialize(&ctx))
}
pub fn render_captured<S: Serialize>(&self, ctx: S) -> Result<Captured<'source>, Error> {
self.clone()
._capture_state(Value::from_serialize(&ctx), true)
}
pub fn render_captured_to<S: Serialize, W: io::Write>(
&self,
ctx: S,
w: W,
) -> Result<Captured<'source>, Error> {
let root = Value::from_serialize(&ctx);
let w = std::cell::RefCell::new(WriteWrapper { w, err: None });
self.clone()
._capture_state_with_output(root, &w)
.map_err(|err| w.into_inner().take_err(err))
}
fn _render(&self, root: Value) -> Result<(String, State<'_, 'env>), Error> {
let mut rv = String::with_capacity(self.compiled.buffer_size_hint);
self._eval(root, &mut Output::new(&mut rv))
.map(|(_, state)| (rv, state))
}
fn _capture_state(self, root: Value, capture_output: bool) -> Result<Captured<'source>, Error> {
let this: Template<'source, 'source> = self;
let cell = ok!(CapturedCell::try_new(
this,
move |template| -> Result<CapturedData<'_>, Error> {
let mut output = if capture_output {
String::with_capacity(template.compiled.buffer_size_hint)
} else {
String::new()
};
let (_, state) = if capture_output {
ok!(template._eval(root, &mut Output::new(&mut output)))
} else {
ok!(template._eval(root, &mut Output::null()))
};
Ok(CapturedData { output, state })
}
));
Ok(Captured { cell })
}
fn _capture_state_with_output<W: io::Write>(
self,
root: Value,
w: &RefCell<WriteWrapper<W>>,
) -> Result<Captured<'source>, Error> {
let this: Template<'source, 'source> = self;
let cell = ok!(CapturedCell::try_new(
this,
move |template| -> Result<CapturedData<'_>, Error> {
let (_, state) = ok!(template._eval(root, &mut Output::new(&mut *w.borrow_mut())));
Ok(CapturedData {
output: String::new(),
state,
})
}
));
Ok(Captured { cell })
}
#[deprecated(since = "2.18.0", note = "use render_captured_to instead")]
pub fn render_to_write<S: Serialize, W: io::Write>(
&self,
ctx: S,
w: W,
) -> Result<State<'_, 'env>, Error> {
let mut wrapper = WriteWrapper { w, err: None };
self._eval(Value::from_serialize(&ctx), &mut Output::new(&mut wrapper))
.map(|(_, state)| state)
.map_err(|err| wrapper.take_err(err))
}
#[deprecated(since = "2.18.0", note = "use render_captured instead")]
pub fn eval_to_state<S: Serialize>(&self, ctx: S) -> Result<State<'_, 'env>, Error> {
let root = Value::from_serialize(&ctx);
let mut out = Output::null();
let vm = Vm::new(self.env);
let state = ok!(vm.eval(
&self.compiled.instructions,
root,
&self.compiled.blocks,
&mut out,
self.compiled.initial_auto_escape,
))
.1;
Ok(state)
}
fn _eval(
&self,
root: Value,
out: &mut Output,
) -> Result<(Option<Value>, State<'_, 'env>), Error> {
Vm::new(self.env).eval(
&self.compiled.instructions,
root,
&self.compiled.blocks,
out,
self.compiled.initial_auto_escape,
)
}
pub fn undeclared_variables(&self, nested: bool) -> HashSet<String> {
match parse(
self.compiled.instructions.source(),
self.name(),
self.compiled.syntax_config.clone(),
Default::default(),
) {
Ok(ast) => find_undeclared(&ast, nested),
Err(_) => HashSet::new(),
}
}
pub fn new_state(&self) -> State<'_, 'env> {
State::new(
Context::new(self.env),
self.compiled.initial_auto_escape,
&self.compiled.instructions,
prepare_blocks(&self.compiled.blocks),
)
}
#[cfg(feature = "multi_template")]
pub(crate) fn instructions_and_blocks(
&self,
) -> Result<
(
&'env Instructions<'env>,
&'env BTreeMap<&'env str, Instructions<'env>>,
),
Error,
> {
match self.compiled {
CompiledTemplateRef::Borrowed(x) => Ok((&x.instructions, &x.blocks)),
CompiledTemplateRef::Owned(_) => Err(Error::new(
crate::ErrorKind::InvalidOperation,
"cannot extend or include template not borrowed from environment",
)),
}
}
#[cfg(feature = "multi_template")]
pub(crate) fn initial_auto_escape(&self) -> AutoEscape {
self.compiled.initial_auto_escape
}
}
#[derive(Clone)]
pub(crate) enum CompiledTemplateRef<'env: 'source, 'source> {
Owned(Arc<CompiledTemplate<'source>>),
Borrowed(&'env CompiledTemplate<'source>),
}
impl<'source> Deref for CompiledTemplateRef<'_, 'source> {
type Target = CompiledTemplate<'source>;
fn deref(&self) -> &Self::Target {
match *self {
CompiledTemplateRef::Owned(ref x) => x,
CompiledTemplateRef::Borrowed(x) => x,
}
}
}
pub struct CompiledTemplate<'source> {
pub instructions: Instructions<'source>,
pub blocks: BTreeMap<&'source str, Instructions<'source>>,
pub buffer_size_hint: usize,
pub syntax_config: SyntaxConfig,
pub initial_auto_escape: AutoEscape,
}
impl fmt::Debug for CompiledTemplate<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut ds = f.debug_struct("CompiledTemplate");
#[cfg(feature = "internal_debug")]
{
ds.field("instructions", &self.instructions);
ds.field("blocks", &self.blocks);
}
ds.finish()
}
}
impl<'source> CompiledTemplate<'source> {
pub fn new(
name: &'source str,
source: &'source str,
config: &TemplateConfig,
) -> Result<CompiledTemplate<'source>, Error> {
attach_basic_debug_info(Self::_new_impl(name, source, config), source)
}
fn _new_impl(
name: &'source str,
source: &'source str,
config: &TemplateConfig,
) -> Result<CompiledTemplate<'source>, Error> {
let ast = ok!(parse(
source,
name,
config.syntax_config.clone(),
config.ws_config
));
let mut g = CodeGenerator::new(name, source);
g.compile_stmt(&ast);
let buffer_size_hint = g.buffer_size_hint();
let (instructions, blocks) = g.finish();
Ok(CompiledTemplate {
instructions,
blocks,
buffer_size_hint,
syntax_config: config.syntax_config.clone(),
initial_auto_escape: (config.default_auto_escape)(name),
})
}
}