This repository has been archived on 2026-08-31. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
donglora/vendor/minijinja/src/environment.rs
T
swaitsandClaude Opus 4.6 3102fc747a feat: add display UI with splash screen, dashboard, and RSSI sparkline
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>
2026-03-29 20:42:54 -06:00

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;