#![deny(unsafe_code)]
#![cfg_attr(docsrs, feature(doc_cfg))]
#[cfg(feature = "filters")]
#[cfg_attr(docsrs, doc(cfg(feature = "filters")))]
pub mod filters;
pub mod fmt;
#[cfg(doc)]
pub mod syntax;
mod compile;
mod error;
#[cfg(feature = "serde")]
mod macros;
mod render;
mod types;
mod value;
use std::borrow::Cow;
use std::collections::BTreeMap;
pub use crate::error::Error;
pub use crate::render::Renderer;
#[cfg(feature = "syntax")]
#[cfg_attr(docsrs, doc(cfg(feature = "syntax")))]
pub use crate::types::syntax::{Syntax, SyntaxBuilder};
#[cfg(feature = "serde")]
#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
pub use crate::value::to_value;
pub use crate::value::Value;
use crate::compile::Searcher;
#[cfg(feature = "filters")]
use crate::filters::{Filter, FilterArgs, FilterFn, FilterReturn};
use crate::fmt::FormatFn;
use crate::types::program;
pub type Result<T> = std::result::Result<T, Error>;
pub struct Engine<'engine> {
searcher: Searcher,
default_formatter: &'engine FormatFn,
functions: BTreeMap<Cow<'engine, str>, EngineBoxFn>,
templates: BTreeMap<Cow<'engine, str>, program::Template<'engine>>,
max_include_depth: usize,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EngineFn {
Formatter,
#[cfg_attr(docsrs, doc(cfg(feature = "filters")))]
#[cfg(feature = "filters")]
Filter,
}
enum EngineBoxFn {
Formatter(Box<FormatFn>),
#[cfg(feature = "filters")]
Filter(Box<FilterFn>),
}
type ValueFn<'a> = dyn Fn(&[ValueMember]) -> std::result::Result<Value, String> + 'a;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct ValueMember<'a> {
pub op: ValueAccessOp,
pub access: ValueAccess<'a>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ValueAccess<'a> {
Index(usize),
Key(&'a str),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ValueAccessOp {
Direct,
Optional,
}
pub struct Template<'source> {
template: program::Template<'source>,
}
#[derive(Clone, Copy)]
pub struct TemplateRef<'engine> {
engine: &'engine Engine<'engine>,
name: &'engine str,
template: &'engine program::Template<'engine>,
}
impl Default for Engine<'_> {
#[inline]
fn default() -> Self {
Self::new()
}
}
impl<'engine> Engine<'engine> {
#[inline]
pub fn new() -> Self {
Self::with_searcher(Searcher::new())
}
#[cfg_attr(docsrs, doc(cfg(feature = "syntax")))]
#[cfg(feature = "syntax")]
#[inline]
pub fn with_syntax(syntax: Syntax<'engine>) -> Self {
Self::with_searcher(Searcher::with_syntax(syntax))
}
#[inline]
fn with_searcher(searcher: Searcher) -> Self {
Self {
searcher,
default_formatter: &fmt::default,
functions: BTreeMap::new(),
templates: BTreeMap::new(),
max_include_depth: 64,
}
}
#[inline]
pub fn set_max_include_depth(&mut self, depth: usize) {
self.max_include_depth = depth;
}
#[inline]
pub fn set_default_formatter<F>(&mut self, f: &'engine F)
where
F: Fn(&mut fmt::Formatter<'_>, &Value) -> fmt::Result + Sync + Send + 'static,
{
self.default_formatter = f;
}
#[inline]
pub fn add_formatter<N, F>(&mut self, name: N, f: F) -> Option<EngineFn>
where
N: Into<Cow<'engine, str>>,
F: Fn(&mut fmt::Formatter<'_>, &Value) -> fmt::Result + Sync + Send + 'static,
{
self.functions
.insert(name.into(), EngineBoxFn::Formatter(Box::new(f)))
.map(|f| f.discriminant())
}
#[cfg(feature = "filters")]
#[cfg_attr(docsrs, doc(cfg(feature = "filters")))]
#[inline]
pub fn add_filter<N, F, R, A>(&mut self, name: N, f: F) -> Option<EngineFn>
where
N: Into<Cow<'engine, str>>,
F: Filter<R, A> + Send + Sync + 'static,
R: FilterReturn,
A: FilterArgs,
{
self.functions
.insert(name.into(), EngineBoxFn::Filter(filters::new(f)))
.map(|f| f.discriminant())
}
pub fn remove_function(&mut self, name: &str) -> Option<EngineFn> {
self.functions.remove(name).map(|f| f.discriminant())
}
#[inline]
pub fn add_template<N, S>(&mut self, name: N, source: S) -> Result<()>
where
N: Into<Cow<'engine, str>>,
S: Into<Cow<'engine, str>>,
{
match compile::template(self, source.into()) {
Ok(template) => {
self.templates.insert(name.into(), template);
Ok(())
}
Err(err) => Err(err.with_template_name(name.into().into())),
}
}
#[inline]
#[track_caller]
pub fn template(&self, name: &str) -> TemplateRef<'_> {
match self.get_template(name) {
Some(template) => template,
None => panic!("template with name '{}' does not exist in engine", name),
}
}
#[inline]
pub fn get_template(&self, name: &str) -> Option<TemplateRef<'_>> {
self.templates
.get_key_value(name)
.map(|(name, template)| TemplateRef {
engine: self,
name,
template,
})
}
#[inline]
pub fn remove_template(&mut self, name: &str) -> bool {
self.templates.remove(name).is_some()
}
#[inline]
pub fn compile<'source, S>(&self, source: S) -> Result<Template<'source>>
where
S: Into<Cow<'source, str>>,
{
let template = compile::template(self, source.into())?;
Ok(Template { template })
}
}
impl std::fmt::Debug for Engine<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Engine")
.field("searcher", &(..))
.field("default_formatter", &(..))
.field("functions", &self.functions)
.field("templates", &self.templates)
.field("max_include_depth", &self.max_include_depth)
.finish()
}
}
impl EngineBoxFn {
fn discriminant(&self) -> EngineFn {
match self {
#[cfg(feature = "filters")]
Self::Filter(_) => EngineFn::Filter,
Self::Formatter(_) => EngineFn::Formatter,
}
}
}
impl std::fmt::Debug for EngineBoxFn {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let name = match self {
#[cfg(feature = "filters")]
Self::Filter(_) => "Filter",
Self::Formatter(_) => "Formatter",
};
f.debug_tuple(name).finish()
}
}
impl<'render> Template<'render> {
#[cfg(feature = "serde")]
#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
#[inline]
pub fn render<S>(&self, engine: &'render Engine<'render>, ctx: S) -> Renderer<'_>
where
S: serde::Serialize,
{
Renderer::with_serde(engine, &self.template, None, ctx)
}
#[inline]
pub fn render_from(
&self,
engine: &'render Engine<'render>,
ctx: &'render Value,
) -> Renderer<'_> {
Renderer::with_value(engine, &self.template, None, ctx)
}
#[inline]
pub fn render_from_fn<F>(&self, engine: &'render Engine<'render>, value_fn: F) -> Renderer<'_>
where
F: Fn(&[ValueMember<'_>]) -> std::result::Result<Value, String> + 'render,
{
Renderer::with_value_fn(engine, &self.template, None, Box::new(value_fn))
}
#[inline]
pub fn source(&self) -> &str {
&self.template.source
}
}
impl std::fmt::Debug for Template<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Template")
.field("engine", &(..))
.field("template", &self.template)
.finish()
}
}
impl<'render> TemplateRef<'render> {
#[cfg(feature = "serde")]
#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
#[inline]
pub fn render<S>(&self, ctx: S) -> Renderer<'_>
where
S: serde::Serialize,
{
Renderer::with_serde(self.engine, self.template, Some(self.name), ctx)
}
#[inline]
pub fn render_from(&self, ctx: &'render Value) -> Renderer<'render> {
Renderer::with_value(self.engine, self.template, Some(self.name), ctx)
}
#[inline]
pub fn render_from_fn<F>(&self, value_fn: F) -> Renderer<'render>
where
F: Fn(&[ValueMember<'_>]) -> std::result::Result<Value, String> + 'render,
{
Renderer::with_value_fn(
self.engine,
self.template,
Some(self.name),
Box::new(value_fn),
)
}
#[inline]
pub fn source(&self) -> &'render str {
&self.template.source
}
}
impl std::fmt::Debug for TemplateRef<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("TemplateRef")
.field("engine", &(..))
.field("name", &self.name)
.field("template", &self.template)
.finish()
}
}