Implement full SSD1306 OLED display support: - Splash screen on boot (1.5s) with project name, version, board - Dashboard showing radio state, config, counters, RSSI/SNR - RSSI history sparkline (64-sample ring buffer, 2px-wide bars) - DisplayOn/DisplayOff host commands routed via dedicated channel - Pure render functions against generic DrawTarget (no hardware coupling) - Display on/off control bypasses radio task entirely Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
860 lines
32 KiB
Rust
860 lines
32 KiB
Rust
use std::borrow::Cow;
|
|
use std::collections::BTreeMap;
|
|
use std::fmt;
|
|
use std::sync::{Arc, OnceLock};
|
|
|
|
use serde::Serialize;
|
|
|
|
use crate::compiler::codegen::CodeGenerator;
|
|
use crate::compiler::instructions::Instructions;
|
|
use crate::compiler::parser::parse_expr;
|
|
use crate::error::{attach_basic_debug_info, Error, ErrorKind};
|
|
use crate::expression::Expression;
|
|
use crate::output::Output;
|
|
use crate::template::{
|
|
AutoEscapeFunc, CompiledTemplate, CompiledTemplateRef, Template, TemplateConfig,
|
|
};
|
|
use crate::utils::{write_escaped, AutoEscape, BTreeMapKeysDebug, UndefinedBehavior};
|
|
use crate::value::{FunctionArgs, FunctionResult, UndefinedType, Value, ValueRepr};
|
|
use crate::vm::State;
|
|
use crate::{defaults, functions};
|
|
|
|
type FormatterFunc = dyn Fn(&mut Output, &State, &Value) -> Result<(), Error> + Sync + Send;
|
|
type PathJoinFunc = dyn for<'s> Fn(&'s str, &'s str) -> Cow<'s, str> + Sync + Send;
|
|
type UnknownMethodFunc =
|
|
dyn Fn(&State, &Value, &str, &[Value]) -> Result<Value, Error> + Sync + Send;
|
|
|
|
fn default_auto_escape_callback() -> Arc<AutoEscapeFunc> {
|
|
static DEFAULT_AUTO_ESCAPE: OnceLock<Arc<AutoEscapeFunc>> = OnceLock::new();
|
|
DEFAULT_AUTO_ESCAPE
|
|
.get_or_init(|| Arc::new(defaults::default_auto_escape_callback))
|
|
.clone()
|
|
}
|
|
|
|
fn no_auto_escape_callback() -> Arc<AutoEscapeFunc> {
|
|
static NO_AUTO_ESCAPE: OnceLock<Arc<AutoEscapeFunc>> = OnceLock::new();
|
|
NO_AUTO_ESCAPE
|
|
.get_or_init(|| Arc::new(defaults::no_auto_escape))
|
|
.clone()
|
|
}
|
|
|
|
fn default_formatter() -> Arc<FormatterFunc> {
|
|
static FORMATTER: OnceLock<Arc<FormatterFunc>> = OnceLock::new();
|
|
FORMATTER
|
|
.get_or_init(|| Arc::new(defaults::escape_formatter))
|
|
.clone()
|
|
}
|
|
|
|
/// The maximum recursion in the VM. Normally each stack frame
|
|
/// adds one to this counter (e.g., every time a frame is added).
|
|
/// However in some situations more depth is pushed if the cost
|
|
/// of the stack frame is higher. Raising this limit
|
|
/// requires enabling the `stacker` feature.
|
|
const MAX_RECURSION: usize = 500;
|
|
|
|
/// An abstraction that holds the engine configuration.
|
|
///
|
|
/// This object holds the central configuration state for templates. It is also
|
|
/// the container for all loaded templates.
|
|
///
|
|
/// The environment holds references to the source the templates were created from.
|
|
/// This makes it very inconvenient to pass around unless the templates are static
|
|
/// strings.
|
|
///
|
|
/// There are generally two ways to construct an environment:
|
|
///
|
|
/// * [`Environment::new`] creates an environment preconfigured with sensible
|
|
/// defaults. It will contain all built-in filters, tests and globals as well
|
|
/// as a callback for auto escaping based on file extension.
|
|
/// * [`Environment::empty`] creates a completely blank environment.
|
|
#[derive(Clone)]
|
|
pub struct Environment<'source> {
|
|
templates: TemplateStore<'source>,
|
|
filters: Arc<BTreeMap<Cow<'source, str>, Value>>,
|
|
tests: Arc<BTreeMap<Cow<'source, str>, Value>>,
|
|
globals: Arc<BTreeMap<Cow<'source, str>, Value>>,
|
|
path_join_callback: Option<Arc<PathJoinFunc>>,
|
|
pub(crate) unknown_method_callback: Option<Arc<UnknownMethodFunc>>,
|
|
undefined_behavior: UndefinedBehavior,
|
|
formatter: Arc<FormatterFunc>,
|
|
formatter_is_default: bool,
|
|
#[cfg(feature = "debug")]
|
|
debug: bool,
|
|
#[cfg(feature = "fuel")]
|
|
fuel: Option<u64>,
|
|
recursion_limit: usize,
|
|
}
|
|
|
|
impl Default for Environment<'_> {
|
|
fn default() -> Self {
|
|
Environment::empty()
|
|
}
|
|
}
|
|
|
|
impl fmt::Debug for Environment<'_> {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
f.debug_struct("Environment")
|
|
.field("globals", &self.globals)
|
|
.field("tests", &BTreeMapKeysDebug(&self.tests))
|
|
.field("filters", &BTreeMapKeysDebug(&self.filters))
|
|
.field("templates", &self.templates)
|
|
.finish()
|
|
}
|
|
}
|
|
|
|
impl<'source> Environment<'source> {
|
|
/// Creates a new environment with sensible defaults.
|
|
///
|
|
/// This environment does not yet contain any templates but it will have all
|
|
/// the default filters, tests and globals loaded. If you do not want any
|
|
/// default configuration you can use the alternative
|
|
/// [`empty`](Environment::empty) method.
|
|
#[cfg_attr(
|
|
not(feature = "serde"),
|
|
deprecated(
|
|
since = "2.0.4",
|
|
note = "Attempted to instantiate an environment with serde. Future \
|
|
versions of MiniJinja will require enabling the 'serde' feature to use \
|
|
serde types. To silence this warning add 'serde' to the list of features of minijinja."
|
|
)
|
|
)]
|
|
pub fn new() -> Environment<'source> {
|
|
Environment {
|
|
templates: TemplateStore::new(TemplateConfig::new(default_auto_escape_callback())),
|
|
filters: defaults::get_builtin_filters(),
|
|
tests: defaults::get_builtin_tests(),
|
|
globals: defaults::get_globals(),
|
|
path_join_callback: None,
|
|
unknown_method_callback: None,
|
|
undefined_behavior: UndefinedBehavior::default(),
|
|
formatter: default_formatter(),
|
|
formatter_is_default: true,
|
|
#[cfg(feature = "debug")]
|
|
debug: cfg!(debug_assertions),
|
|
#[cfg(feature = "fuel")]
|
|
fuel: None,
|
|
recursion_limit: MAX_RECURSION,
|
|
}
|
|
}
|
|
|
|
/// Creates a completely empty environment.
|
|
///
|
|
/// This environment has no filters, no templates, no globals and no default
|
|
/// logic for auto escaping configured.
|
|
pub fn empty() -> Environment<'source> {
|
|
Environment {
|
|
templates: TemplateStore::new(TemplateConfig::new(no_auto_escape_callback())),
|
|
filters: Default::default(),
|
|
tests: Default::default(),
|
|
globals: Default::default(),
|
|
path_join_callback: None,
|
|
unknown_method_callback: None,
|
|
undefined_behavior: UndefinedBehavior::default(),
|
|
formatter: default_formatter(),
|
|
formatter_is_default: true,
|
|
#[cfg(feature = "debug")]
|
|
debug: cfg!(debug_assertions),
|
|
#[cfg(feature = "fuel")]
|
|
fuel: None,
|
|
recursion_limit: MAX_RECURSION,
|
|
}
|
|
}
|
|
|
|
/// Loads a template from a string into the environment.
|
|
///
|
|
/// The `name` parameter defines the name of the template which identifies
|
|
/// it. To look up a loaded template use the [`get_template`](Self::get_template)
|
|
/// method.
|
|
///
|
|
/// ```
|
|
/// # use minijinja::Environment;
|
|
/// let mut env = Environment::new();
|
|
/// env.add_template("index.html", "Hello {{ name }}!").unwrap();
|
|
/// ```
|
|
/// This method fails if the template has a syntax error.
|
|
///
|
|
/// Note that there are situations where the interface of this method is
|
|
/// too restrictive as you need to hold on to the strings for the lifetime
|
|
/// of the environment. To avoid this restriction use
|
|
/// [`add_template_owned`](Self::add_template_owned).
|
|
pub fn add_template(&mut self, name: &'source str, source: &'source str) -> Result<(), Error> {
|
|
self.templates.insert(name, source)
|
|
}
|
|
|
|
/// Adds a template without borrowing.
|
|
///
|
|
/// This lets you place an owned [`String`] in the environment rather than the
|
|
/// borrowed `&str` without having to worry about lifetimes.
|
|
///
|
|
/// ```
|
|
/// # use minijinja::Environment;
|
|
/// let mut env = Environment::new();
|
|
/// env.add_template_owned("index.html".to_string(), "Hello {{ name }}!".to_string()).unwrap();
|
|
/// ```
|
|
///
|
|
/// **Note**: the name is a bit of a misnomer as this API also allows borrowing,
|
|
/// as the parameters are actually [`Cow`]. This method fails if the template
|
|
/// has a syntax error.
|
|
pub fn add_template_owned<N, S>(&mut self, name: N, source: S) -> Result<(), Error>
|
|
where
|
|
N: Into<Cow<'source, str>>,
|
|
S: Into<Cow<'source, str>>,
|
|
{
|
|
self.templates.insert_cow(name.into(), source.into())
|
|
}
|
|
|
|
/// Register a template loader as source of templates.
|
|
///
|
|
/// When a template loader is registered, the environment gains the ability
|
|
/// to dynamically load templates. The loader is invoked with the name of
|
|
/// the template. If this template exists `Ok(Some(template_source))` has
|
|
/// to be returned, otherwise `Ok(None)`. Once a template has been loaded
|
|
/// it's stored on the environment. This means the loader is only invoked
|
|
/// once per template name.
|
|
///
|
|
/// For loading templates from the file system, you can use the
|
|
/// [`path_loader`](crate::path_loader) function.
|
|
///
|
|
/// # Example
|
|
///
|
|
/// ```rust
|
|
/// # use minijinja::Environment;
|
|
/// fn create_env() -> Environment<'static> {
|
|
/// let mut env = Environment::new();
|
|
/// env.set_loader(|name| {
|
|
/// if name == "layout.html" {
|
|
/// Ok(Some("...".into()))
|
|
/// } else {
|
|
/// Ok(None)
|
|
/// }
|
|
/// });
|
|
/// env
|
|
/// }
|
|
/// ```
|
|
pub fn set_loader<F>(&mut self, f: F)
|
|
where
|
|
F: Fn(&str) -> Result<Option<String>, Error> + Send + Sync + 'static,
|
|
{
|
|
self.templates.set_loader(f);
|
|
}
|
|
|
|
/// Preserve the trailing newline when rendering templates.
|
|
///
|
|
/// The default is `false`, which causes a single newline, if present, to be
|
|
/// stripped from the end of the template.
|
|
///
|
|
/// This setting is used whenever a template is loaded into the environment.
|
|
/// Changing it at a later point only affects future templates loaded.
|
|
pub fn set_keep_trailing_newline(&mut self, yes: bool) {
|
|
self.templates
|
|
.template_config
|
|
.ws_config
|
|
.keep_trailing_newline = yes;
|
|
}
|
|
|
|
/// Returns the value of the trailing newline preservation flag.
|
|
pub fn keep_trailing_newline(&self) -> bool {
|
|
self.templates
|
|
.template_config
|
|
.ws_config
|
|
.keep_trailing_newline
|
|
}
|
|
|
|
/// Remove the first newline after a block.
|
|
///
|
|
/// If this is set to `true` then the first newline after a block is removed
|
|
/// (block, not variable tag!). Defaults to `false`.
|
|
pub fn set_trim_blocks(&mut self, yes: bool) {
|
|
self.templates.template_config.ws_config.trim_blocks = yes;
|
|
}
|
|
|
|
/// Returns the value of the trim blocks flag.
|
|
pub fn trim_blocks(&self) -> bool {
|
|
self.templates.template_config.ws_config.trim_blocks
|
|
}
|
|
|
|
/// Remove leading spaces and tabs from the start of a line to a block.
|
|
///
|
|
/// If this is set to `true` then leading spaces and tabs from the start of a line
|
|
/// to the block tag are removed.
|
|
pub fn set_lstrip_blocks(&mut self, yes: bool) {
|
|
self.templates.template_config.ws_config.lstrip_blocks = yes;
|
|
}
|
|
|
|
/// Returns the value of the lstrip blocks flag.
|
|
pub fn lstrip_blocks(&self) -> bool {
|
|
self.templates.template_config.ws_config.lstrip_blocks
|
|
}
|
|
|
|
/// Removes a template by name.
|
|
pub fn remove_template(&mut self, name: &str) {
|
|
self.templates.remove(name);
|
|
}
|
|
|
|
/// Sets a callback to join template paths.
|
|
///
|
|
/// By default this returns the template path unchanged, but it can be used
|
|
/// to implement relative path resolution between templates. The first
|
|
/// argument to the callback is the name of the template to be loaded, the
|
|
/// second argument is the parent path.
|
|
///
|
|
/// The following example demonstrates how a basic path joining algorithm
|
|
/// can be implemented.
|
|
///
|
|
/// ```
|
|
/// # let mut env = minijinja::Environment::new();
|
|
/// env.set_path_join_callback(|name, parent| {
|
|
/// let mut rv = parent.split('/').collect::<Vec<_>>();
|
|
/// rv.pop();
|
|
/// name.split('/').for_each(|segment| match segment {
|
|
/// "." => {}
|
|
/// ".." => { rv.pop(); }
|
|
/// _ => { rv.push(segment); }
|
|
/// });
|
|
/// rv.join("/").into()
|
|
/// });
|
|
/// ```
|
|
pub fn set_path_join_callback<F>(&mut self, f: F)
|
|
where
|
|
F: for<'s> Fn(&'s str, &'s str) -> Cow<'s, str> + Send + Sync + 'static,
|
|
{
|
|
self.path_join_callback = Some(Arc::new(f));
|
|
}
|
|
|
|
/// Sets a callback invoked for unknown methods on objects.
|
|
///
|
|
/// This registers a function with the environment that is invoked when invoking a method
|
|
/// on a value results in a [`UnknownMethod`](crate::ErrorKind::UnknownMethod) error.
|
|
/// In that case the callback is invoked with [`State`], the [`Value`], the name of
|
|
/// the method as `&str` as well as all arguments in a slice.
|
|
///
|
|
/// This for instance implements a `.items()` method that invokes the `|items` filter:
|
|
///
|
|
/// ```rust
|
|
/// use minijinja::value::{ValueKind, from_args};
|
|
/// use minijinja::{Error, ErrorKind};
|
|
/// # let mut env = minijinja::Environment::new();
|
|
///
|
|
/// env.set_unknown_method_callback(|state, value, method, args| {
|
|
/// if value.kind() == ValueKind::Map && method == "items" {
|
|
/// let _: () = from_args(args)?;
|
|
/// state.apply_filter("items", &[value.clone()])
|
|
/// } else {
|
|
/// Err(Error::from(ErrorKind::UnknownMethod))
|
|
/// }
|
|
/// });
|
|
/// ```
|
|
///
|
|
/// This can be used to increase the compatibility with Jinja2 templates that might
|
|
/// call Python methods on objects which are not available in minijinja. A range of
|
|
/// common Python methods is implemented in `minijinja-contrib`. For more information
|
|
/// see [minijinja_contrib::pycompat](https://docs.rs/minijinja-contrib/latest/minijinja_contrib/pycompat/).
|
|
pub fn set_unknown_method_callback<F>(&mut self, f: F)
|
|
where
|
|
F: Fn(&State, &Value, &str, &[Value]) -> Result<Value, Error> + Sync + Send + 'static,
|
|
{
|
|
self.unknown_method_callback = Some(Arc::new(f));
|
|
}
|
|
|
|
/// Removes all stored templates.
|
|
///
|
|
/// This method is mainly useful when combined with a loader as it causes
|
|
/// the loader to "reload" templates. By calling this method one can trigger
|
|
/// a reload.
|
|
pub fn clear_templates(&mut self) {
|
|
self.templates.clear();
|
|
}
|
|
|
|
/// Returns an iterator over the already loaded templates and their names.
|
|
///
|
|
/// Only templates that are already loaded will be returned.
|
|
///
|
|
/// ```
|
|
/// # use minijinja::{Environment, context};
|
|
/// let mut env = Environment::new();
|
|
/// env.add_template("hello.txt", "Hello {{ name }}!").unwrap();
|
|
/// env.add_template("goodbye.txt", "Goodbye {{ name }}!").unwrap();
|
|
///
|
|
/// # assert_eq!(env.templates().count(), 2);
|
|
/// for (name, tmpl) in env.templates() {
|
|
/// println!("{}", tmpl.render(context!{ name => "World" }).unwrap());
|
|
/// }
|
|
/// ```
|
|
pub fn templates(&self) -> impl Iterator<Item = (&str, Template<'_, '_>)> {
|
|
self.templates.iter().map(|(name, template)| {
|
|
let template = Template::new(self, CompiledTemplateRef::Borrowed(template));
|
|
(name, template)
|
|
})
|
|
}
|
|
|
|
/// Fetches a template by name.
|
|
///
|
|
/// This requires that the template has been loaded with
|
|
/// [`add_template`](Environment::add_template) beforehand. If the template was
|
|
/// not loaded an error of kind `TemplateNotFound` is returned. If a loader was
|
|
/// added to the engine this can also dynamically load templates.
|
|
///
|
|
/// ```
|
|
/// # use minijinja::{Environment, context};
|
|
/// let mut env = Environment::new();
|
|
/// env.add_template("hello.txt", "Hello {{ name }}!").unwrap();
|
|
/// let tmpl = env.get_template("hello.txt").unwrap();
|
|
/// println!("{}", tmpl.render(context!{ name => "World" }).unwrap());
|
|
/// ```
|
|
pub fn get_template(&self, name: &str) -> Result<Template<'_, '_>, Error> {
|
|
let compiled = ok!(self.templates.get(name));
|
|
Ok(Template::new(self, CompiledTemplateRef::Borrowed(compiled)))
|
|
}
|
|
|
|
/// Loads a template from a string.
|
|
///
|
|
/// In some cases you only need to work with (e.g., render) a template once.
|
|
///
|
|
/// ```
|
|
/// # use minijinja::{Environment, context};
|
|
/// let env = Environment::new();
|
|
/// let tmpl = env.template_from_named_str("template_name", "Hello {{ name }}").unwrap();
|
|
/// let rv = tmpl.render(context! { name => "World" });
|
|
/// println!("{}", rv.unwrap());
|
|
/// ```
|
|
pub fn template_from_named_str(
|
|
&self,
|
|
name: &'source str,
|
|
source: &'source str,
|
|
) -> Result<Template<'_, 'source>, Error> {
|
|
Ok(Template::new(
|
|
self,
|
|
CompiledTemplateRef::Owned(Arc::new(ok!(CompiledTemplate::new(
|
|
name,
|
|
source,
|
|
&self.templates.template_config,
|
|
)))),
|
|
))
|
|
}
|
|
|
|
/// Loads a template from a string, with name `<string>`.
|
|
///
|
|
/// This is a shortcut to [`template_from_named_str`](Self::template_from_named_str)
|
|
/// with name set to `<string>`.
|
|
pub fn template_from_str(&self, source: &'source str) -> Result<Template<'_, 'source>, Error> {
|
|
self.template_from_named_str("<string>", source)
|
|
}
|
|
|
|
/// Parses and renders a template from a string in one go with name.
|
|
///
|
|
/// Like [`render_str`](Self::render_str), but provide a name for the
|
|
/// template to be used instead of the default `<string>`. This is an
|
|
/// alias for [`template_from_named_str`](Self::template_from_named_str) paired with
|
|
/// [`render`](Template::render).
|
|
///
|
|
/// ```
|
|
/// # use minijinja::{Environment, context};
|
|
/// let env = Environment::new();
|
|
/// let rv = env.render_named_str(
|
|
/// "template_name",
|
|
/// "Hello {{ name }}",
|
|
/// context!{ name => "World" }
|
|
/// );
|
|
/// println!("{}", rv.unwrap());
|
|
/// ```
|
|
///
|
|
/// **Note on values:** The [`Value`] type implements `Serialize` and can be
|
|
/// efficiently passed to render. It does not undergo actual serialization.
|
|
pub fn render_named_str<S: Serialize>(
|
|
&self,
|
|
name: &str,
|
|
source: &str,
|
|
ctx: S,
|
|
) -> Result<String, Error> {
|
|
ok!(self.template_from_named_str(name, source)).render(ctx)
|
|
}
|
|
|
|
/// Parses and renders a template from a string in one go.
|
|
///
|
|
/// In some cases you really only need a template to be rendered once from
|
|
/// a string and returned. The internal name of the template is `<string>`.
|
|
///
|
|
/// This is an alias for [`template_from_str`](Self::template_from_str) paired with
|
|
/// [`render`](Template::render).
|
|
///
|
|
/// **Note on values:** The [`Value`] type implements `Serialize` and can be
|
|
/// efficiently passed to render. It does not undergo actual serialization.
|
|
pub fn render_str<S: Serialize>(&self, source: &str, ctx: S) -> Result<String, Error> {
|
|
// reduce total amount of code falling under mono morphization into
|
|
// this function, and share the rest in _eval.
|
|
ok!(self.template_from_str(source)).render(ctx)
|
|
}
|
|
|
|
/// Sets a new function to select the default auto escaping.
|
|
///
|
|
/// This function is invoked when templates are loaded into the environment
|
|
/// to determine the default auto escaping behavior. The function is
|
|
/// invoked with the name of the template and can make an initial auto
|
|
/// escaping decision based on that. The default implementation
|
|
/// ([`default_auto_escape_callback`](defaults::default_auto_escape_callback))
|
|
/// turns on escaping depending on the file extension.
|
|
///
|
|
/// ```
|
|
/// # use minijinja::{Environment, AutoEscape};
|
|
/// # let mut env = Environment::new();
|
|
/// env.set_auto_escape_callback(|name| {
|
|
/// if matches!(name.rsplit('.').next().unwrap_or(""), "html" | "htm" | "aspx") {
|
|
/// AutoEscape::Html
|
|
/// } else {
|
|
/// AutoEscape::None
|
|
/// }
|
|
/// });
|
|
/// ```
|
|
pub fn set_auto_escape_callback<F>(&mut self, f: F)
|
|
where
|
|
F: Fn(&str) -> AutoEscape + 'static + Sync + Send,
|
|
{
|
|
self.templates.template_config.default_auto_escape = Arc::new(f);
|
|
}
|
|
|
|
/// Changes the undefined behavior.
|
|
///
|
|
/// This changes the runtime behavior of [`undefined`](Value::UNDEFINED) values in
|
|
/// the template engine. For more information see [`UndefinedBehavior`]. The
|
|
/// default is [`UndefinedBehavior::Lenient`].
|
|
pub fn set_undefined_behavior(&mut self, behavior: UndefinedBehavior) {
|
|
self.undefined_behavior = behavior;
|
|
}
|
|
|
|
/// Returns the current undefined behavior.
|
|
///
|
|
/// This is particularly useful if a filter function or similar wants to change its
|
|
/// behavior with regards to undefined values.
|
|
#[inline(always)]
|
|
pub fn undefined_behavior(&self) -> UndefinedBehavior {
|
|
self.undefined_behavior
|
|
}
|
|
|
|
/// Sets a different formatter function.
|
|
///
|
|
/// The formatter is invoked to format the given value into the provided
|
|
/// [`Output`]. The default implementation is
|
|
/// [`escape_formatter`](defaults::escape_formatter).
|
|
///
|
|
/// When implementing a custom formatter it depends on if auto escaping
|
|
/// should be supported or not. If auto escaping should be supported then
|
|
/// it's easiest to just wrap the default formatter. The
|
|
/// following example swaps out `None` values before rendering for
|
|
/// `Undefined` which renders as an empty string instead.
|
|
///
|
|
/// The current value of the auto escape flag can be retrieved directly
|
|
/// from the [`State`].
|
|
///
|
|
/// ```
|
|
/// # use minijinja::Environment;
|
|
/// # let mut env = Environment::new();
|
|
/// use minijinja::escape_formatter;
|
|
/// use minijinja::value::Value;
|
|
///
|
|
/// env.set_formatter(|out, state, value| {
|
|
/// escape_formatter(
|
|
/// out,
|
|
/// state,
|
|
/// if value.is_none() {
|
|
/// &Value::UNDEFINED
|
|
/// } else {
|
|
/// value
|
|
/// },
|
|
/// )
|
|
///});
|
|
/// # assert_eq!(env.render_str("{{ none }}", ()).unwrap(), "");
|
|
/// ```
|
|
pub fn set_formatter<F>(&mut self, f: F)
|
|
where
|
|
F: Fn(&mut Output, &State, &Value) -> Result<(), Error> + 'static + Sync + Send,
|
|
{
|
|
self.formatter = Arc::new(f);
|
|
self.formatter_is_default = false;
|
|
}
|
|
|
|
/// Enable or disable the debug mode.
|
|
///
|
|
/// When the debug mode is enabled the engine will dump out some of the
|
|
/// execution state together with the source information of the executing
|
|
/// template when an error is created. The cost of this is relatively
|
|
/// high as the data including the template source is cloned.
|
|
///
|
|
/// When this is enabled templates will print debug information with source
|
|
/// context when the error is printed.
|
|
///
|
|
/// This requires the `debug` feature. This is enabled by default if
|
|
/// debug assertions are enabled and false otherwise.
|
|
#[cfg(feature = "debug")]
|
|
#[cfg_attr(docsrs, doc(cfg(feature = "debug")))]
|
|
pub fn set_debug(&mut self, enabled: bool) {
|
|
self.debug = enabled;
|
|
}
|
|
|
|
/// Returns the current value of the debug flag.
|
|
#[cfg(feature = "debug")]
|
|
pub fn debug(&self) -> bool {
|
|
self.debug
|
|
}
|
|
|
|
/// Sets the optional fuel of the engine.
|
|
///
|
|
/// When MiniJinja is compiled with the `fuel` feature then every
|
|
/// instruction consumes a certain amount of fuel. Usually `1`, some will
|
|
/// consume no fuel. By default the engine has the fuel feature disabled
|
|
/// (`None`). To turn on fuel set something like `Some(50000)` which will
|
|
/// allow 50.000 instructions to execute before running out of fuel.
|
|
///
|
|
/// To find out how much fuel is consumed, you can access the fuel levels
|
|
/// from the [`State`](crate::State).
|
|
///
|
|
/// Fuel consumed per-render.
|
|
#[cfg(feature = "fuel")]
|
|
#[cfg_attr(docsrs, doc(cfg(feature = "fuel")))]
|
|
pub fn set_fuel(&mut self, fuel: Option<u64>) {
|
|
self.fuel = fuel;
|
|
}
|
|
|
|
/// Returns the configured fuel.
|
|
#[cfg(feature = "fuel")]
|
|
#[cfg_attr(docsrs, doc(cfg(feature = "fuel")))]
|
|
pub fn fuel(&self) -> Option<u64> {
|
|
self.fuel
|
|
}
|
|
|
|
/// Sets the syntax for the environment.
|
|
///
|
|
/// This setting is used whenever a template is loaded into the environment.
|
|
/// Changing it at a later point only affects future templates loaded.
|
|
///
|
|
/// See [`SyntaxConfig`](crate::syntax::SyntaxConfig) for more information.
|
|
#[cfg(feature = "custom_syntax")]
|
|
#[cfg_attr(docsrs, doc(cfg(feature = "custom_syntax")))]
|
|
pub fn set_syntax(&mut self, syntax: crate::syntax::SyntaxConfig) {
|
|
self.templates.template_config.syntax_config = syntax;
|
|
}
|
|
|
|
/// Returns the current syntax config.
|
|
#[cfg(feature = "custom_syntax")]
|
|
#[cfg_attr(docsrs, doc(cfg(feature = "custom_syntax")))]
|
|
pub fn syntax(&self) -> &crate::syntax::SyntaxConfig {
|
|
&self.templates.template_config.syntax_config
|
|
}
|
|
|
|
/// Reconfigures the runtime recursion limit.
|
|
///
|
|
/// This defaults to `500`. Raising it above that level requires the `stacker`
|
|
/// feature to be enabled. Otherwise the limit is silently capped at that safe
|
|
/// maximum. Note that the maximum is not necessarily safe if the thread uses
|
|
/// a lot of stack space already, it's just a value that was validated once to
|
|
/// provide basic protection.
|
|
///
|
|
/// Every operation that requires recursion in MiniJinja increments an internal
|
|
/// recursion counter. The actual cost attributed to that recursion depends on
|
|
/// the cost of the operation. If statements and for loops for instance only
|
|
/// increase the counter by 1, whereas template includes and macros might increase
|
|
/// it to 10 or more.
|
|
///
|
|
/// **Note on stack growth:** even if the stacker feature is enabled it does not
|
|
/// mean that in all cases stack can grow to the limits desired. For instance in
|
|
/// WASM the maximum limits are additionally enforced by the runtime.
|
|
pub fn set_recursion_limit(&mut self, level: usize) {
|
|
#[cfg(not(feature = "stacker"))]
|
|
{
|
|
self.recursion_limit = level.min(MAX_RECURSION);
|
|
}
|
|
#[cfg(feature = "stacker")]
|
|
{
|
|
self.recursion_limit = level;
|
|
}
|
|
}
|
|
|
|
/// Returns the current max recursion limit.
|
|
pub fn recursion_limit(&self) -> usize {
|
|
self.recursion_limit
|
|
}
|
|
|
|
/// Compiles an expression.
|
|
///
|
|
/// This lets you compile an expression in the template language and evaluate it.
|
|
/// This makes it possible to use the language's expressions as a minimal
|
|
/// scripting language. For more information and an
|
|
/// example see [`Expression`].
|
|
pub fn compile_expression(&self, expr: &'source str) -> Result<Expression<'_, 'source>, Error> {
|
|
self._compile_expression(expr)
|
|
.map(|instr| Expression::new(self, instr))
|
|
}
|
|
|
|
/// Compiles an expression without capturing the lifetime.
|
|
///
|
|
/// This works exactly like [`compile_expression`](Self::compile_expression) but
|
|
/// lets you pass an owned string without capturing the lifetime.
|
|
pub fn compile_expression_owned<E>(&self, expr: E) -> Result<Expression<'_, 'source>, Error>
|
|
where
|
|
E: Into<Cow<'source, str>>,
|
|
{
|
|
crate::loader::OwnedInstructions::try_new(Box::from(expr.into()), |expr| {
|
|
self._compile_expression(expr)
|
|
})
|
|
.map(|instr| Expression::new_owned(self, instr))
|
|
}
|
|
|
|
fn _compile_expression<'expr>(&self, expr: &'expr str) -> Result<Instructions<'expr>, Error> {
|
|
attach_basic_debug_info(
|
|
parse_expr(expr).map(|ast| {
|
|
let mut g = CodeGenerator::new("<expression>", expr);
|
|
g.compile_expr(&ast);
|
|
g.finish().0
|
|
}),
|
|
expr,
|
|
)
|
|
}
|
|
|
|
/// Adds a new filter function.
|
|
///
|
|
/// Filter functions are functions that can be applied to values in
|
|
/// templates. For details about filters have a look at
|
|
/// [`filters`](crate::filters).
|
|
pub fn add_filter<N, F, Rv, Args>(&mut self, name: N, f: F)
|
|
where
|
|
N: Into<Cow<'source, str>>,
|
|
F: functions::Function<Rv, Args>,
|
|
Rv: FunctionResult,
|
|
Args: for<'a> FunctionArgs<'a>,
|
|
{
|
|
Arc::make_mut(&mut self.filters).insert(name.into(), Value::from_function(f));
|
|
}
|
|
|
|
/// Removes a filter by name.
|
|
pub fn remove_filter(&mut self, name: &str) {
|
|
Arc::make_mut(&mut self.filters).remove(name);
|
|
}
|
|
|
|
/// Adds a new test function.
|
|
///
|
|
/// Test functions are similar to filters but perform a check on a value
|
|
/// where the return value is always true or false. For details about tests
|
|
/// have a look at [`tests`](crate::tests).
|
|
pub fn add_test<N, F, Rv, Args>(&mut self, name: N, f: F)
|
|
where
|
|
N: Into<Cow<'source, str>>,
|
|
F: functions::Function<Rv, Args>,
|
|
Rv: FunctionResult,
|
|
Args: for<'a> FunctionArgs<'a>,
|
|
{
|
|
Arc::make_mut(&mut self.tests).insert(name.into(), Value::from_function(f));
|
|
}
|
|
|
|
/// Removes a test by name.
|
|
pub fn remove_test(&mut self, name: &str) {
|
|
Arc::make_mut(&mut self.tests).remove(name);
|
|
}
|
|
|
|
/// Adds a new global function.
|
|
///
|
|
/// For details about functions have a look at [`functions`]. Note that
|
|
/// functions and other global variables share the same namespace.
|
|
/// For more details about functions have a look at
|
|
/// [`Function`](crate::functions::Function).
|
|
///
|
|
/// This is a shortcut for calling [`add_global`](Self::add_global) with the
|
|
/// function wrapped with [`Value::from_function`].
|
|
pub fn add_function<N, F, Rv, Args>(&mut self, name: N, f: F)
|
|
where
|
|
N: Into<Cow<'source, str>>,
|
|
F: functions::Function<Rv, Args>,
|
|
Rv: FunctionResult,
|
|
Args: for<'a> FunctionArgs<'a>,
|
|
{
|
|
self.add_global(name.into(), Value::from_function(f))
|
|
}
|
|
|
|
/// Adds a global variable.
|
|
pub fn add_global<N, V>(&mut self, name: N, value: V)
|
|
where
|
|
N: Into<Cow<'source, str>>,
|
|
V: Into<Value>,
|
|
{
|
|
Arc::make_mut(&mut self.globals).insert(name.into(), value.into());
|
|
}
|
|
|
|
/// Removes a global function or variable by name.
|
|
pub fn remove_global(&mut self, name: &str) {
|
|
Arc::make_mut(&mut self.globals).remove(name);
|
|
}
|
|
|
|
/// Returns an iterator of all global variables.
|
|
pub fn globals(&self) -> impl Iterator<Item = (&str, Value)> {
|
|
self.globals
|
|
.iter()
|
|
.map(|(key, value)| (key as &str, value.clone()))
|
|
}
|
|
|
|
/// Returns an empty [`State`] for testing purposes and similar.
|
|
pub fn empty_state(&self) -> State<'_, '_> {
|
|
State::new_for_env(self)
|
|
}
|
|
|
|
/// Looks up a global.
|
|
pub(crate) fn get_global(&self, name: &str) -> Option<Value> {
|
|
self.globals.get(name).cloned()
|
|
}
|
|
|
|
/// Looks up a filter.
|
|
pub(crate) fn get_filter(&self, name: &str) -> Option<&Value> {
|
|
self.filters.get(name)
|
|
}
|
|
|
|
/// Looks up a test function.
|
|
pub(crate) fn get_test(&self, name: &str) -> Option<&Value> {
|
|
self.tests.get(name)
|
|
}
|
|
|
|
pub(crate) fn initial_auto_escape(&self, name: &str) -> AutoEscape {
|
|
(self.templates.template_config.default_auto_escape)(name)
|
|
}
|
|
|
|
#[inline(always)]
|
|
pub(crate) fn is_default_formatter(&self) -> bool {
|
|
self.formatter_is_default
|
|
}
|
|
|
|
/// Formats a value into the final format.
|
|
///
|
|
/// This step is called finalization in Jinja2 but since we are writing into
|
|
/// the output stream rather than converting values, it's renamed to format
|
|
/// here.
|
|
pub(crate) fn format(
|
|
&self,
|
|
value: &Value,
|
|
state: &State,
|
|
out: &mut Output,
|
|
) -> Result<(), Error> {
|
|
match (self.undefined_behavior, &value.0) {
|
|
// this intentionally does not check for SilentUndefined. SilentUndefined is
|
|
// exclusively used in the missing else condition of an if expression to match
|
|
// Jinja2 behavior. Those go straight to the formatter.
|
|
(
|
|
UndefinedBehavior::Strict | UndefinedBehavior::SemiStrict,
|
|
&ValueRepr::Undefined(UndefinedType::Default),
|
|
) => Err(Error::from(ErrorKind::UndefinedError)),
|
|
_ => {
|
|
if self.formatter_is_default {
|
|
write_escaped(out, state.auto_escape(), value)
|
|
} else {
|
|
(self.formatter)(out, state, value)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Performs a template path join.
|
|
pub(crate) fn join_template_path<'s>(&self, name: &'s str, parent: &'s str) -> Cow<'s, str> {
|
|
match self.path_join_callback {
|
|
Some(ref cb) => cb(name, parent),
|
|
None => Cow::Borrowed(name),
|
|
}
|
|
}
|
|
}
|
|
|
|
use crate::loader::LoaderStore as TemplateStore;
|