#![cfg_attr(
feature = "deserialization",
doc = r#"
# Arguments in Custom Functions
All arguments in custom functions must implement the [`ArgType`] trait.
Standard types, such as `String`, `i32`, `bool`, `f64`, etc, already implement this trait.
There are also helper types that will make it easier to extract an arguments with custom types.
The [`ViaDeserialize<T>`](crate::value::ViaDeserialize) type, for instance, can accept any
type `T` that implements the `Deserialize` trait from `serde`.
```rust
# use minijinja::Environment;
# use serde::Deserialize;
# let mut env = Environment::new();
use minijinja::value::ViaDeserialize;
#[derive(Deserialize)]
struct Person {
name: String,
age: i32,
}
fn is_adult(person: ViaDeserialize<Person>) -> bool {
person.age >= 18
}
env.add_function("is_adult", is_adult);
```
"#
)]
use std::fmt;
use std::sync::Arc;
use crate::error::Error;
use crate::utils::SealedMarker;
use crate::value::{ArgType, FunctionArgs, FunctionResult, Object, ObjectRepr, Value};
use crate::vm::State;
type FuncFunc = dyn Fn(&State, &[Value]) -> Result<Value, Error> + Sync + Send + 'static;
#[derive(Clone)]
pub(crate) struct BoxedFunction(Arc<FuncFunc>, #[cfg(feature = "debug")] &'static str);
pub trait Function<Rv, Args: for<'a> FunctionArgs<'a>>: Send + Sync + 'static {
#[doc(hidden)]
fn invoke(&self, args: <Args as FunctionArgs<'_>>::Output, _: SealedMarker) -> Rv;
}
trait FunctionHelper<Rv, Args> {
fn invoke_nested(&self, args: Args) -> Rv;
}
macro_rules! tuple_impls {
( $( $name:ident )* ) => {
impl<Func, Rv, $($name),*> FunctionHelper<Rv, ($($name,)*)> for Func
where
Func: Fn($($name),*) -> Rv
{
fn invoke_nested(&self, args: ($($name,)*)) -> Rv {
#[allow(non_snake_case)]
let ($($name,)*) = args;
(self)($($name,)*)
}
}
impl<Func, Rv, $($name),*> Function<Rv, ($($name,)*)> for Func
where
Func: Send + Sync + 'static,
Func: Fn($($name),*) -> Rv + for<'a> FunctionHelper<Rv, ($(<$name as ArgType<'a>>::Output,)*)>,
Rv: FunctionResult,
$($name: for<'a> ArgType<'a>,)*
{
#[allow(clippy::needless_lifetimes)]
fn invoke<'a>(&self, args: ($(<$name as ArgType<'a>>::Output,)*), _: SealedMarker) -> Rv {
self.invoke_nested(args)
}
}
};
}
tuple_impls! {}
tuple_impls! { A }
tuple_impls! { A B }
tuple_impls! { A B C }
tuple_impls! { A B C D }
tuple_impls! { A B C D E }
impl BoxedFunction {
pub fn new<F, Rv, Args>(f: F) -> BoxedFunction
where
F: Function<Rv, Args>,
Rv: FunctionResult,
Args: for<'a> FunctionArgs<'a>,
{
BoxedFunction(
Arc::new(move |state, args| -> Result<Value, Error> {
f.invoke(ok!(Args::from_values(Some(state), args)), SealedMarker)
.into_result()
}),
#[cfg(feature = "debug")]
std::any::type_name::<F>(),
)
}
pub fn invoke(&self, state: &State, args: &[Value]) -> Result<Value, Error> {
(self.0)(state, args)
}
pub fn to_value(&self) -> Value {
Value::from_object(self.clone())
}
}
impl fmt::Debug for BoxedFunction {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
#[cfg(feature = "debug")]
{
if !self.1.is_empty() {
return f.write_str(self.1);
}
}
f.write_str("function")
}
}
impl Object for BoxedFunction {
fn repr(self: &Arc<Self>) -> ObjectRepr {
ObjectRepr::Plain
}
fn call(self: &Arc<Self>, state: &State, args: &[Value]) -> Result<Value, Error> {
self.invoke(state, args)
}
}
#[cfg(feature = "builtins")]
mod builtins {
use std::cmp::Ordering;
use super::*;
use crate::error::ErrorKind;
use crate::value::{Rest, ValueMap, ValueRepr};
#[cfg_attr(docsrs, doc(cfg(feature = "builtins")))]
pub fn range(lower: isize, upper: Option<isize>, step: Option<isize>) -> Result<Value, Error> {
fn to_result<I: ExactSizeIterator<Item = isize> + Send + Sync + Clone + 'static>(
i: I,
) -> Result<Value, Error> {
if i.len() > 100000 {
Err(Error::new(
ErrorKind::InvalidOperation,
"range has too many elements",
))
} else {
Ok(Value::make_iterable(move || i.clone()))
}
}
let rng = match upper {
Some(upper) => lower..upper,
None => 0..lower,
};
let Some(step) = step else {
return to_result(rng);
};
match step.cmp(&0) {
Ordering::Equal => Err(Error::new(
ErrorKind::InvalidOperation,
"cannot create range with step of 0",
)),
Ordering::Greater => to_result(rng.step_by(step as usize)),
Ordering::Less => {
debug_assert!(step < 0);
let (start, end) = match upper {
Some(upper) => (lower, upper),
None => (0, lower),
};
let len = if start <= end {
0
} else {
((start - end + (-step) - 1) / (-step)) as usize
};
let iter = (0..len).map(move |i| start + (i as isize) * step);
to_result(iter)
}
}
}
#[cfg_attr(docsrs, doc(cfg(feature = "builtins")))]
pub fn dict(value: Option<Value>, update_with: crate::value::Kwargs) -> Result<Value, Error> {
let mut rv = match value {
None => ValueMap::default(),
Some(value) => match value.0 {
ValueRepr::Undefined(_) => ValueMap::default(),
ValueRepr::Object(obj) if obj.repr() == ObjectRepr::Map => {
obj.try_iter_pairs().into_iter().flatten().collect()
}
_ => return Err(Error::from(ErrorKind::InvalidOperation)),
},
};
if update_with.values.is_true() {
rv.extend(
update_with
.values
.iter()
.map(|(k, v)| (k.clone(), v.clone())),
);
}
Ok(Value::from_object(rv))
}
#[cfg_attr(docsrs, doc(cfg(feature = "builtins")))]
pub fn debug(state: &State, args: Rest<Value>) -> String {
if args.is_empty() {
format!("{state:#?}")
} else if args.len() == 1 {
format!("{:#?}", args.0[0])
} else {
format!("{:#?}", &args.0[..])
}
}
#[cfg_attr(docsrs, doc(cfg(feature = "builtins")))]
pub fn namespace(defaults: Option<Value>) -> Result<Value, Error> {
let ns = crate::value::namespace_object::Namespace::default();
if let Some(defaults) = defaults {
if let Some(pairs) = defaults
.as_object()
.filter(|x| matches!(x.repr(), ObjectRepr::Map))
.and_then(|x| x.try_iter_pairs())
{
for (key, value) in pairs {
if let Some(key) = key.as_str() {
ns.set_value(key, value);
}
}
} else {
return Err(Error::new(
ErrorKind::InvalidOperation,
format!(
"expected object or keyword arguments, got {}",
defaults.kind()
),
));
}
}
Ok(Value::from_object(ns))
}
}
#[cfg(feature = "builtins")]
pub use self::builtins::*;