minijinja/
filters.rs

1//! Filter functions and abstractions.
2//!
3//! MiniJinja inherits from Jinja2 the concept of filter functions.  These are functions
4//! which are applied to values to modify them.  For example the expression `{{ 42|filter(23) }}`
5//! invokes the filter `filter` with the arguments `42` and `23`.
6//!
7//! MiniJinja comes with some built-in filters that are listed below. To create a
8//! custom filter write a function that takes at least a value, then registers it
9//! with [`add_filter`](crate::Environment::add_filter).
10//!
11//! # Using Filters
12//!
13//! Using filters in templates is possible in all places an expression is permitted.
14//! This means they are not just used for printing but also are useful for iteration
15//! or similar situations.
16//!
17//! Motivating example:
18//!
19//! ```jinja
20//! <dl>
21//! {% for key, value in config|items %}
22//!   <dt>{{ key }}
23//!   <dd><pre>{{ value|tojson }}</pre>
24//! {% endfor %}
25//! </dl>
26//! ```
27//!
28//! # Custom Filters
29//!
30//! A custom filter is just a simple function which accepts its inputs
31//! as parameters and then returns a new value.  For instance the following
32//! shows a filter which takes an input value and replaces whitespace with
33//! dashes and converts it to lowercase:
34//!
35//! ```
36//! # use minijinja::Environment;
37//! # let mut env = Environment::new();
38//! fn slugify(value: String) -> String {
39//!     value.to_lowercase().split_whitespace().collect::<Vec<_>>().join("-")
40//! }
41//!
42//! env.add_filter("slugify", slugify);
43//! ```
44//!
45//! MiniJinja will perform the necessary conversions automatically.  For more
46//! information see the [`Function`](crate::functions::Function) trait.
47//!
48//! # Accessing State
49//!
50//! In some cases it can be necessary to access the execution [`State`].  Since a borrowed
51//! state implements [`ArgType`](crate::value::ArgType) it's possible to add a
52//! parameter that holds the state.  For instance the following filter appends
53//! the current template name to the string:
54//!
55//! ```
56//! # use minijinja::Environment;
57//! # let mut env = Environment::new();
58//! use minijinja::{Value, State};
59//!
60//! fn append_template(state: &State, value: &Value) -> String {
61//!     format!("{}-{}", value, state.name())
62//! }
63//!
64//! env.add_filter("append_template", append_template);
65//! ```
66//!
67//! # Filter configuration
68//!
69//! The recommended pattern for filters to change their behavior is to leverage global
70//! variables in the template.  For instance take a filter that performs date formatting.
71//! You might want to change the default time format format on a per-template basis
72//! without having to update every filter invocation.  In this case the recommended
73//! pattern is to reserve upper case variables and look them up in the filter:
74//!
75//! ```
76//! # use minijinja::Environment;
77//! # let mut env = Environment::new();
78//! # fn format_unix_timestamp(_: f64, _: &str) -> String { "".into() }
79//! use minijinja::State;
80//!
81//! fn timeformat(state: &State, ts: f64) -> String {
82//!     let configured_format = state.lookup("TIME_FORMAT");
83//!     let format = configured_format
84//!         .as_ref()
85//!         .and_then(|x| x.as_str())
86//!         .unwrap_or("HH:MM:SS");
87//!     format_unix_timestamp(ts, format)
88//! }
89//!
90//! env.add_filter("timeformat", timeformat);
91//! ```
92//!
93//! This then later lets a user override the default either by using
94//! [`add_global`](crate::Environment::add_global) or by passing it with the
95//! [`context!`] macro or similar.
96//!
97//! ```
98//! # use minijinja::context;
99//! # let other_variables = context!{};
100//! let ctx = context! {
101//!     TIME_FORMAT => "HH:MM",
102//!     ..other_variables
103//! };
104//! ```
105//!
106//! # Built-in Filters
107//!
108//! When the `builtins` feature is enabled a range of built-in filters are
109//! automatically added to the environment.  These are also all provided in
110//! this module.  Note though that these functions are not to be
111//! called from Rust code as their exact interface (arguments and return types)
112//! might change from one MiniJinja version to another.
113//!
114//! Some additional filters are available in the
115//! [`minijinja-contrib`](https://crates.io/crates/minijinja-contrib) crate.
116use std::sync::Arc;
117
118use crate::error::Error;
119use crate::utils::write_escaped;
120use crate::value::Value;
121use crate::vm::State;
122use crate::{AutoEscape, Output};
123
124/// Deprecated alias
125#[deprecated = "Use the minijinja::functions::Function instead"]
126#[doc(hidden)]
127pub use crate::functions::Function as Filter;
128
129/// Marks a value as safe.  This converts it into a string.
130///
131/// When a value is marked as safe, no further auto escaping will take place.
132pub fn safe(v: String) -> Value {
133    Value::from_safe_string(v)
134}
135
136/// Escapes a string.  By default to HTML.
137///
138/// By default this filter is also registered under the alias `e`.  Note that
139/// this filter escapes with the format that is native to the format or HTML
140/// otherwise.  This means that if the auto escape setting is set to
141/// `Json` for instance then this filter will serialize to JSON instead.
142pub fn escape(state: &State, v: &Value) -> Result<Value, Error> {
143    if v.is_safe() {
144        return Ok(v.clone());
145    }
146
147    // this tries to use the escaping flag of the current scope, then
148    // of the initial state and if that is also not set it falls back
149    // to HTML.
150    let auto_escape = match state.auto_escape() {
151        AutoEscape::None => match state.env().initial_auto_escape(state.name()) {
152            AutoEscape::None => AutoEscape::Html,
153            other => other,
154        },
155        other => other,
156    };
157    let mut rv = match v.as_str() {
158        Some(s) => String::with_capacity(s.len()),
159        None => String::new(),
160    };
161    let mut out = Output::new(&mut rv);
162    ok!(write_escaped(&mut out, auto_escape, v));
163    Ok(Value::from_safe_string(rv))
164}
165
166#[cfg(feature = "builtins")]
167mod builtins {
168    use super::*;
169
170    use crate::error::ErrorKind;
171    use crate::utils::{safe_sort, splitn_whitespace};
172    use crate::value::ops::{self, as_f64};
173    use crate::value::{Enumerator, Kwargs, Object, ObjectRepr, ValueKind, ValueRepr};
174    use std::borrow::Cow;
175    use std::cmp::Ordering;
176    use std::fmt::Write;
177    use std::mem;
178
179    /// Converts a value to uppercase.
180    ///
181    /// ```jinja
182    /// <h1>{{ chapter.title|upper }}</h1>
183    /// ```
184    #[cfg_attr(docsrs, doc(cfg(feature = "builtins")))]
185    pub fn upper(v: Cow<'_, str>) -> String {
186        v.to_uppercase()
187    }
188
189    /// Converts a value to lowercase.
190    ///
191    /// ```jinja
192    /// <h1>{{ chapter.title|lower }}</h1>
193    /// ```
194    #[cfg_attr(docsrs, doc(cfg(feature = "builtins")))]
195    pub fn lower(v: Cow<'_, str>) -> String {
196        v.to_lowercase()
197    }
198
199    /// Converts a value to title case.
200    ///
201    /// ```jinja
202    /// <h1>{{ chapter.title|title }}</h1>
203    /// ```
204    #[cfg_attr(docsrs, doc(cfg(feature = "builtins")))]
205    pub fn title(v: Cow<'_, str>) -> String {
206        let mut rv = String::new();
207        let mut capitalize = true;
208        for c in v.chars() {
209            if c.is_ascii_punctuation() || c.is_whitespace() {
210                rv.push(c);
211                capitalize = true;
212            } else if capitalize {
213                write!(rv, "{}", c.to_uppercase()).unwrap();
214                capitalize = false;
215            } else {
216                write!(rv, "{}", c.to_lowercase()).unwrap();
217            }
218        }
219        rv
220    }
221
222    /// Convert the string with all its characters lowercased
223    /// apart from the first char which is uppercased.
224    ///
225    /// ```jinja
226    /// <h1>{{ chapter.title|capitalize }}</h1>
227    /// ```
228    #[cfg_attr(docsrs, doc(cfg(feature = "builtins")))]
229    pub fn capitalize(text: Cow<'_, str>) -> String {
230        let mut chars = text.chars();
231        match chars.next() {
232            None => String::new(),
233            Some(f) => f.to_uppercase().collect::<String>() + &chars.as_str().to_lowercase(),
234        }
235    }
236
237    /// Does a string replace.
238    ///
239    /// It replaces all occurrences of the first parameter with the second.
240    ///
241    /// ```jinja
242    /// {{ "Hello World"|replace("Hello", "Goodbye") }}
243    ///   -> Goodbye World
244    /// ```
245    #[cfg_attr(docsrs, doc(cfg(feature = "builtins")))]
246    pub fn replace(
247        _state: &State,
248        v: Cow<'_, str>,
249        from: Cow<'_, str>,
250        to: Cow<'_, str>,
251    ) -> String {
252        v.replace(&from as &str, &to as &str)
253    }
254
255    /// Returns the "length" of the value
256    ///
257    /// By default this filter is also registered under the alias `count`.
258    ///
259    /// ```jinja
260    /// <p>Search results: {{ results|length }}
261    /// ```
262    #[cfg_attr(docsrs, doc(cfg(feature = "builtins")))]
263    pub fn length(v: &Value) -> Result<usize, Error> {
264        v.len().ok_or_else(|| {
265            Error::new(
266                ErrorKind::InvalidOperation,
267                format!("cannot calculate length of value of type {}", v.kind()),
268            )
269        })
270    }
271
272    fn cmp_helper(a: &Value, b: &Value, case_sensitive: bool) -> Ordering {
273        if !case_sensitive {
274            if let (Some(a), Some(b)) = (a.as_str(), b.as_str()) {
275                #[cfg(feature = "unicode")]
276                {
277                    return unicase::UniCase::new(a).cmp(&unicase::UniCase::new(b));
278                }
279                #[cfg(not(feature = "unicode"))]
280                {
281                    return a.to_ascii_lowercase().cmp(&b.to_ascii_lowercase());
282                }
283            }
284        }
285        a.cmp(b)
286    }
287
288    /// Dict sorting functionality.
289    ///
290    /// This filter works like `|items` but sorts the pairs by key first.
291    ///
292    /// The filter accepts a few keyword arguments:
293    ///
294    /// * `case_sensitive`: set to `true` to make the sorting of strings case sensitive.
295    /// * `by`: set to `"value"` to sort by value. Defaults to `"key"`.
296    /// * `reverse`: set to `true` to sort in reverse.
297    #[cfg_attr(docsrs, doc(cfg(feature = "builtins")))]
298    pub fn dictsort(v: &Value, kwargs: Kwargs) -> Result<Value, Error> {
299        if v.kind() != ValueKind::Map {
300            return Err(Error::new(
301                ErrorKind::InvalidOperation,
302                "cannot convert value into pair list",
303            ));
304        }
305
306        let by_value = matches!(ok!(kwargs.get("by")), Some("value"));
307        let case_sensitive = ok!(kwargs.get::<Option<bool>>("case_sensitive")).unwrap_or(false);
308        let mut rv: Vec<_> = ok!(v.try_iter())
309            .map(|key| (key.clone(), v.get_item(&key).unwrap_or(Value::UNDEFINED)))
310            .collect();
311        safe_sort(&mut rv, |a, b| {
312            let (a, b) = if by_value { (&a.1, &b.1) } else { (&a.0, &b.0) };
313            cmp_helper(a, b, case_sensitive)
314        })?;
315        if let Some(true) = ok!(kwargs.get("reverse")) {
316            rv.reverse();
317        }
318        kwargs.assert_all_used()?;
319        Ok(rv
320            .into_iter()
321            .map(|(k, v)| Value::from(vec![k, v]))
322            .collect())
323    }
324
325    /// Returns an iterable of pairs (items) from a mapping.
326    ///
327    /// This can be used to iterate over keys and values of a mapping
328    /// at once.  Note that this will use the original order of the map
329    /// which is typically arbitrary unless the `preserve_order` feature
330    /// is used in which case the original order of the map is retained.
331    /// It's generally better to use `|dictsort` which sorts the map by
332    /// key before iterating.
333    ///
334    /// ```jinja
335    /// <dl>
336    /// {% for key, value in my_dict|items %}
337    ///   <dt>{{ key }}
338    ///   <dd>{{ value }}
339    /// {% endfor %}
340    /// </dl>
341    /// ```
342    #[cfg_attr(docsrs, doc(cfg(feature = "builtins")))]
343    pub fn items(v: &Value) -> Result<Value, Error> {
344        if v.kind() == ValueKind::Map {
345            Ok(Value::make_object_iterable(v.clone(), |v| {
346                match v.as_object().and_then(|v| v.try_iter_pairs()) {
347                    Some(iter) => Box::new(iter.map(|(key, value)| Value::from(vec![key, value]))),
348                    None => Box::new(
349                        // this really should not happen unless the object changes it's shape
350                        // after the initial check
351                        Some(Value::from(Error::new(
352                            ErrorKind::InvalidOperation,
353                            format!("{} is not iterable", v.kind()),
354                        )))
355                        .into_iter(),
356                    ),
357                }
358            }))
359        } else {
360            Err(Error::new(
361                ErrorKind::InvalidOperation,
362                "cannot convert value into pairs",
363            ))
364        }
365    }
366
367    /// Reverses an iterable or string
368    ///
369    /// ```jinja
370    /// {% for user in users|reverse %}
371    ///   <li>{{ user.name }}
372    /// {% endfor %}
373    /// ```
374    #[cfg_attr(docsrs, doc(cfg(feature = "builtins")))]
375    pub fn reverse(v: &Value) -> Result<Value, Error> {
376        v.reverse()
377    }
378
379    /// Trims a value
380    #[cfg_attr(docsrs, doc(cfg(feature = "builtins")))]
381    pub fn trim(s: Cow<'_, str>, chars: Option<Cow<'_, str>>) -> String {
382        match chars {
383            Some(chars) => {
384                let chars = chars.chars().collect::<Vec<_>>();
385                s.trim_matches(&chars[..]).to_string()
386            }
387            None => s.trim().to_string(),
388        }
389    }
390
391    /// Joins a sequence by a character
392    ///
393    /// ```jinja
394    /// {{ "Foo Bar Baz" | join(", ") }} -> foo, bar, baz
395    /// ```
396    #[cfg_attr(docsrs, doc(cfg(feature = "builtins")))]
397    pub fn join(val: &Value, joiner: Option<Cow<'_, str>>) -> Result<String, Error> {
398        if val.is_undefined() || val.is_none() {
399            return Ok(String::new());
400        }
401
402        let joiner = joiner.as_ref().unwrap_or(&Cow::Borrowed(""));
403        let iter = ok!(val.try_iter().map_err(|err| {
404            Error::new(
405                ErrorKind::InvalidOperation,
406                format!("cannot join value of type {}", val.kind()),
407            )
408            .with_source(err)
409        }));
410
411        let mut rv = String::new();
412        for item in iter {
413            if !rv.is_empty() {
414                rv.push_str(joiner);
415            }
416            if let Some(s) = item.as_str() {
417                rv.push_str(s);
418            } else {
419                write!(rv, "{item}").ok();
420            }
421        }
422        Ok(rv)
423    }
424
425    /// Split a string into its substrings, using `split` as the separator string.
426    ///
427    /// If `split` is not provided or `none` the string is split at all whitespace
428    /// characters and multiple spaces and empty strings will be removed from the
429    /// result.
430    ///
431    /// The `maxsplits` parameter defines the maximum number of splits
432    /// (starting from the left).  Note that this follows Python conventions
433    /// rather than Rust ones so `1` means one split and two resulting items.
434    ///
435    /// ```jinja
436    /// {{ "hello world"|split|list }}
437    ///     -> ["hello", "world"]
438    ///
439    /// {{ "c,s,v"|split(",")|list }}
440    ///     -> ["c", "s", "v"]
441    /// ```
442    #[cfg_attr(docsrs, doc(cfg(feature = "builtins")))]
443    pub fn split(s: Arc<str>, split: Option<Arc<str>>, maxsplits: Option<i64>) -> Value {
444        let maxsplits = maxsplits.and_then(|x| if x >= 0 { Some(x as usize + 1) } else { None });
445
446        Value::make_object_iterable((s, split), move |(s, split)| match (split, maxsplits) {
447            (None, None) => Box::new(s.split_whitespace().map(Value::from)),
448            (Some(split), None) => Box::new(s.split(split as &str).map(Value::from)),
449            (None, Some(n)) => Box::new(splitn_whitespace(s, n).map(Value::from)),
450            (Some(split), Some(n)) => Box::new(s.splitn(n, split as &str).map(Value::from)),
451        })
452    }
453
454    /// Splits a string into lines.
455    ///
456    /// The newline character is removed in the process and not retained.  This
457    /// function supports both Windows and UNIX style newlines.
458    ///
459    /// ```jinja
460    /// {{ "foo\nbar\nbaz"|lines }}
461    ///     -> ["foo", "bar", "baz"]
462    /// ```
463    #[cfg_attr(docsrs, doc(cfg(feature = "builtins")))]
464    pub fn lines(s: Arc<str>) -> Value {
465        Value::from_iter(s.lines().map(|x| x.to_string()))
466    }
467
468    /// If the value is undefined it will return the passed default value,
469    /// otherwise the value of the variable:
470    ///
471    /// ```jinja
472    /// <p>{{ my_variable|default("my_variable was not defined") }}</p>
473    /// ```
474    ///
475    /// Setting the optional second parameter to `true` will also treat falsy
476    /// values as undefined, e.g. empty strings:
477    ///
478    /// ```jinja
479    /// <p>{{ ""|default("string was empty", true) }}</p>
480    /// ```
481    #[cfg_attr(docsrs, doc(cfg(feature = "builtins")))]
482    pub fn default(value: &Value, other: Option<Value>, lax: Option<bool>) -> Value {
483        if value.is_undefined() {
484            other.unwrap_or_else(|| Value::from(""))
485        } else if lax.unwrap_or(false) && !value.is_true() {
486            other.unwrap_or_else(|| Value::from(""))
487        } else {
488            value.clone()
489        }
490    }
491
492    /// Returns the absolute value of a number.
493    ///
494    /// ```jinja
495    /// |a - b| = {{ (a - b)|abs }}
496    ///   -> |2 - 4| = 2
497    /// ```
498    #[cfg_attr(docsrs, doc(cfg(feature = "builtins")))]
499    pub fn abs(value: Value) -> Result<Value, Error> {
500        match value.0 {
501            ValueRepr::U64(_) | ValueRepr::U128(_) => Ok(value),
502            ValueRepr::I64(x) => match x.checked_abs() {
503                Some(rv) => Ok(Value::from(rv)),
504                None => Ok(Value::from((x as i128).abs())), // this cannot overflow
505            },
506            ValueRepr::I128(x) => {
507                x.0.checked_abs()
508                    .map(Value::from)
509                    .ok_or_else(|| Error::new(ErrorKind::InvalidOperation, "overflow on abs"))
510            }
511            ValueRepr::F64(x) => Ok(Value::from(x.abs())),
512            _ => Err(Error::new(
513                ErrorKind::InvalidOperation,
514                "cannot get absolute value",
515            )),
516        }
517    }
518
519    /// Converts a value into an integer.
520    ///
521    /// ```jinja
522    /// {{ "42"|int == 42 }} -> true
523    /// ```
524    #[cfg_attr(docsrs, doc(cfg(feature = "builtins")))]
525    pub fn int(value: &Value) -> Result<Value, Error> {
526        match &value.0 {
527            ValueRepr::Undefined(_) | ValueRepr::None => Ok(Value::from(0)),
528            ValueRepr::Bool(x) => Ok(Value::from(*x as u64)),
529            ValueRepr::U64(_) | ValueRepr::I64(_) | ValueRepr::U128(_) | ValueRepr::I128(_) => {
530                Ok(value.clone())
531            }
532            ValueRepr::F64(v) => Ok(Value::from(*v as i128)),
533            ValueRepr::String(..) | ValueRepr::SmallStr(_) => {
534                let s = value.as_str().unwrap();
535                if let Ok(i) = s.parse::<i128>() {
536                    Ok(Value::from(i))
537                } else {
538                    match s.parse::<f64>() {
539                        Ok(f) => Ok(Value::from(f as i128)),
540                        Err(err) => Err(Error::new(ErrorKind::InvalidOperation, err.to_string())),
541                    }
542                }
543            }
544            ValueRepr::Bytes(_) | ValueRepr::Object(_) => Err(Error::new(
545                ErrorKind::InvalidOperation,
546                format!("cannot convert {} to integer", value.kind()),
547            )),
548            ValueRepr::Invalid(_) => value.clone().validate(),
549        }
550    }
551
552    /// Converts a value into a float.
553    ///
554    /// ```jinja
555    /// {{ "42.5"|float == 42.5 }} -> true
556    /// ```
557    #[cfg_attr(docsrs, doc(cfg(feature = "builtins")))]
558    pub fn float(value: &Value) -> Result<Value, Error> {
559        match &value.0 {
560            ValueRepr::Undefined(_) | ValueRepr::None => Ok(Value::from(0.0)),
561            ValueRepr::Bool(x) => Ok(Value::from(*x as u64 as f64)),
562            ValueRepr::String(..) | ValueRepr::SmallStr(_) => value
563                .as_str()
564                .unwrap()
565                .parse::<f64>()
566                .map(Value::from)
567                .map_err(|err| Error::new(ErrorKind::InvalidOperation, err.to_string())),
568            ValueRepr::Invalid(_) => value.clone().validate(),
569            _ => as_f64(value, true).map(Value::from).ok_or_else(|| {
570                Error::new(
571                    ErrorKind::InvalidOperation,
572                    format!("cannot convert {} to float", value.kind()),
573                )
574            }),
575        }
576    }
577
578    /// Sums up all the values in a sequence.
579    ///
580    /// ```jinja
581    /// {{ range(10)|sum }} -> 45
582    /// ```
583    #[cfg_attr(docsrs, doc(cfg(feature = "builtins")))]
584    pub fn sum(state: &State, values: Value) -> Result<Value, Error> {
585        let mut rv = Value::from(0);
586        let iter = ok!(state.undefined_behavior().try_iter(values));
587        for value in iter {
588            if value.is_undefined() {
589                ok!(state.undefined_behavior().handle_undefined(false));
590                continue;
591            } else if !value.is_number() {
592                return Err(Error::new(
593                    ErrorKind::InvalidOperation,
594                    format!("can only sum numbers, got {}", value.kind()),
595                ));
596            }
597            rv = ok!(ops::add(&rv, &value));
598        }
599
600        Ok(rv)
601    }
602
603    /// Looks up an attribute.
604    ///
605    /// In MiniJinja this is the same as the `[]` operator.  In Jinja2 there is a
606    /// small difference which is why this filter is sometimes used in Jinja2
607    /// templates.  For compatibility it's provided here as well.
608    ///
609    /// ```jinja
610    /// {{ value['key'] == value|attr('key') }} -> true
611    /// ```
612    #[cfg_attr(docsrs, doc(cfg(feature = "builtins")))]
613    pub fn attr(value: &Value, key: &Value) -> Result<Value, Error> {
614        value.get_item(key)
615    }
616
617    /// Round the number to a given precision.
618    ///
619    /// Round the number to a given precision. The first parameter specifies the
620    /// precision (default is 0).
621    ///
622    /// ```jinja
623    /// {{ 42.55|round }}
624    ///   -> 43.0
625    /// ```
626    #[cfg_attr(docsrs, doc(cfg(feature = "builtins")))]
627    pub fn round(value: Value, precision: Option<i32>) -> Result<Value, Error> {
628        match value.0 {
629            ValueRepr::I64(_) | ValueRepr::I128(_) | ValueRepr::U64(_) | ValueRepr::U128(_) => {
630                Ok(value)
631            }
632            ValueRepr::F64(val) => {
633                let x = 10f64.powi(precision.unwrap_or(0));
634                Ok(Value::from((x * val).round() / x))
635            }
636            _ => Err(Error::new(
637                ErrorKind::InvalidOperation,
638                format!("cannot round value ({})", value.kind()),
639            )),
640        }
641    }
642
643    /// Returns the first item from an iterable.
644    ///
645    /// If the list is empty `undefined` is returned.
646    ///
647    /// ```jinja
648    /// <dl>
649    ///   <dt>primary email
650    ///   <dd>{{ user.email_addresses|first|default('no user') }}
651    /// </dl>
652    /// ```
653    #[cfg_attr(docsrs, doc(cfg(feature = "builtins")))]
654    pub fn first(value: &Value) -> Result<Value, Error> {
655        if let Some(s) = value.as_str() {
656            Ok(s.chars().next().map_or(Value::UNDEFINED, Value::from))
657        } else if let Some(mut iter) = value.as_object().and_then(|x| x.try_iter()) {
658            Ok(iter.next().unwrap_or(Value::UNDEFINED))
659        } else {
660            Err(Error::new(
661                ErrorKind::InvalidOperation,
662                "cannot get first item from value",
663            ))
664        }
665    }
666
667    /// Returns the last item from an iterable.
668    ///
669    /// If the list is empty `undefined` is returned.
670    ///
671    /// ```jinja
672    /// <h2>Most Recent Update</h2>
673    /// {% with update = updates|last %}
674    ///   <dl>
675    ///     <dt>Location
676    ///     <dd>{{ update.location }}
677    ///     <dt>Status
678    ///     <dd>{{ update.status }}
679    ///   </dl>
680    /// {% endwith %}
681    /// ```
682    #[cfg_attr(docsrs, doc(cfg(feature = "builtins")))]
683    pub fn last(value: Value) -> Result<Value, Error> {
684        if let Some(s) = value.as_str() {
685            Ok(s.chars().next_back().map_or(Value::UNDEFINED, Value::from))
686        } else if matches!(value.kind(), ValueKind::Seq | ValueKind::Iterable) {
687            let rev = ok!(value.reverse());
688            let mut iter = ok!(rev.try_iter());
689            Ok(iter.next().unwrap_or_default())
690        } else {
691            Err(Error::new(
692                ErrorKind::InvalidOperation,
693                "cannot get last item from value",
694            ))
695        }
696    }
697
698    /// Returns the smallest item from an iterable.
699    ///
700    /// ```jinja
701    /// {{ [1, 2, 3, 4]|min }} -> 1
702    /// ```
703    #[cfg_attr(docsrs, doc(cfg(feature = "builtins")))]
704    pub fn min(state: &State, value: Value) -> Result<Value, Error> {
705        let iter = ok!(state.undefined_behavior().try_iter(value).map_err(|err| {
706            Error::new(ErrorKind::InvalidOperation, "cannot convert value to list").with_source(err)
707        }));
708        Ok(iter.min().unwrap_or(Value::UNDEFINED))
709    }
710
711    /// Returns the largest item from an iterable.
712    ///
713    /// ```jinja
714    /// {{ [1, 2, 3, 4]|max }} -> 4
715    /// ```
716    #[cfg_attr(docsrs, doc(cfg(feature = "builtins")))]
717    pub fn max(state: &State, value: Value) -> Result<Value, Error> {
718        let iter = ok!(state.undefined_behavior().try_iter(value).map_err(|err| {
719            Error::new(ErrorKind::InvalidOperation, "cannot convert value to list").with_source(err)
720        }));
721        Ok(iter.max().unwrap_or(Value::UNDEFINED))
722    }
723
724    /// Returns the sorted version of the given list.
725    ///
726    /// The filter accepts a few keyword arguments:
727    ///
728    /// * `case_sensitive`: set to `true` to make the sorting of strings case sensitive.
729    /// * `attribute`: can be set to an attribute or dotted path to sort by that attribute
730    /// * `reverse`: set to `true` to sort in reverse.
731    ///
732    /// ```jinja
733    /// {{ [1, 3, 2, 4]|sort }} -> [4, 3, 2, 1]
734    /// {{ [1, 3, 2, 4]|sort(reverse=true) }} -> [1, 2, 3, 4]
735    /// # Sort users by age attribute in descending order.
736    /// {{ users|sort(attribute="age") }}
737    /// # Sort users by age attribute in ascending order.
738    /// {{ users|sort(attribute="age", reverse=true) }}
739    /// ```
740    #[cfg_attr(docsrs, doc(cfg(feature = "builtins")))]
741    pub fn sort(state: &State, value: Value, kwargs: Kwargs) -> Result<Value, Error> {
742        let mut items = ok!(state.undefined_behavior().try_iter(value).map_err(|err| {
743            Error::new(ErrorKind::InvalidOperation, "cannot convert value to list").with_source(err)
744        }))
745        .collect::<Vec<_>>();
746        let case_sensitive = ok!(kwargs.get::<Option<bool>>("case_sensitive")).unwrap_or(false);
747        if let Some(attr) = ok!(kwargs.get::<Option<&str>>("attribute")) {
748            safe_sort(&mut items, |a, b| {
749                match (a.get_path(attr), b.get_path(attr)) {
750                    (Ok(a), Ok(b)) => cmp_helper(&a, &b, case_sensitive),
751                    _ => Ordering::Equal,
752                }
753            })?;
754        } else {
755            safe_sort(&mut items, |a, b| cmp_helper(a, b, case_sensitive))?;
756        }
757        if let Some(true) = ok!(kwargs.get("reverse")) {
758            items.reverse();
759        }
760        ok!(kwargs.assert_all_used());
761        Ok(Value::from(items))
762    }
763
764    /// Converts the input value into a list.
765    ///
766    /// If the value is already a list, then it's returned unchanged.
767    /// Applied to a map this returns the list of keys, applied to a
768    /// string this returns the characters.  If the value is undefined
769    /// an empty list is returned.
770    #[cfg_attr(docsrs, doc(cfg(feature = "builtins")))]
771    pub fn list(state: &State, value: Value) -> Result<Value, Error> {
772        let iter = ok!(state.undefined_behavior().try_iter(value).map_err(|err| {
773            Error::new(ErrorKind::InvalidOperation, "cannot convert value to list").with_source(err)
774        }));
775        Ok(Value::from(iter.collect::<Vec<_>>()))
776    }
777
778    /// Converts a value into a string if it's not one already.
779    ///
780    /// If the string has been marked as safe, that value is preserved.
781    #[cfg_attr(docsrs, doc(cfg(feature = "builtins")))]
782    pub fn string(value: &Value) -> Value {
783        if value.kind() == ValueKind::String {
784            value.clone()
785        } else {
786            value.to_string().into()
787        }
788    }
789
790    /// Converts the value into a boolean value.
791    ///
792    /// This behaves the same as the if statement does with regards to
793    /// handling of boolean values.
794    ///
795    /// ```jinja
796    /// {{ 42|bool }} -> true
797    /// ```
798    #[cfg_attr(docsrs, doc(cfg(feature = "builtins")))]
799    pub fn bool(value: &Value) -> bool {
800        value.is_true()
801    }
802
803    /// Slice an iterable and return a list of lists containing
804    /// those items.
805    ///
806    /// Useful if you want to create a div containing three ul tags that
807    /// represent columns:
808    ///
809    /// ```jinja
810    /// <div class="columnwrapper">
811    /// {% for column in items|slice(3) %}
812    ///   <ul class="column-{{ loop.index }}">
813    ///   {% for item in column %}
814    ///     <li>{{ item }}</li>
815    ///   {% endfor %}
816    ///   </ul>
817    /// {% endfor %}
818    /// </div>
819    /// ```
820    ///
821    /// If you pass it a second argument it’s used to fill missing values on the
822    /// last iteration.
823    #[cfg_attr(docsrs, doc(cfg(feature = "builtins")))]
824    pub fn slice(
825        state: &State,
826        value: Value,
827        count: usize,
828        fill_with: Option<Value>,
829    ) -> Result<Value, Error> {
830        if count == 0 {
831            return Err(Error::new(ErrorKind::InvalidOperation, "count cannot be 0"));
832        }
833        let items = ok!(state.undefined_behavior().try_iter(value)).collect::<Vec<_>>();
834        let len = items.len();
835        let items_per_slice = len / count;
836        let slices_with_extra = len % count;
837        let mut offset = 0;
838        let mut rv = Vec::with_capacity(count);
839
840        for slice in 0..count {
841            let start = offset + slice * items_per_slice;
842            if slice < slices_with_extra {
843                offset += 1;
844            }
845            let end = offset + (slice + 1) * items_per_slice;
846            let tmp = &items[start..end];
847
848            if let Some(ref filler) = fill_with {
849                if slice >= slices_with_extra {
850                    let mut tmp = tmp.to_vec();
851                    tmp.push(filler.clone());
852                    rv.push(Value::from(tmp));
853                    continue;
854                }
855            }
856
857            rv.push(Value::from(tmp.to_vec()));
858        }
859
860        Ok(Value::from(rv))
861    }
862
863    /// Batch items.
864    ///
865    /// This filter works pretty much like `slice` just the other way round. It
866    /// returns a list of lists with the given number of items. If you provide a
867    /// second parameter this is used to fill up missing items.
868    ///
869    /// ```jinja
870    /// <table>
871    ///   {% for row in items|batch(3, '&nbsp;') %}
872    ///   <tr>
873    ///   {% for column in row %}
874    ///     <td>{{ column }}</td>
875    ///   {% endfor %}
876    ///   </tr>
877    ///   {% endfor %}
878    /// </table>
879    /// ```
880    #[cfg_attr(docsrs, doc(cfg(feature = "builtins")))]
881    pub fn batch(
882        state: &State,
883        value: Value,
884        count: usize,
885        fill_with: Option<Value>,
886    ) -> Result<Value, Error> {
887        if count == 0 {
888            return Err(Error::new(ErrorKind::InvalidOperation, "count cannot be 0"));
889        }
890        let mut rv = Vec::with_capacity(value.len().unwrap_or(0) / count);
891        let mut tmp = Vec::with_capacity(count);
892
893        for item in ok!(state.undefined_behavior().try_iter(value)) {
894            if tmp.len() == count {
895                rv.push(Value::from(mem::replace(
896                    &mut tmp,
897                    Vec::with_capacity(count),
898                )));
899            }
900            tmp.push(item);
901        }
902
903        if !tmp.is_empty() {
904            if let Some(filler) = fill_with {
905                for _ in 0..count - tmp.len() {
906                    tmp.push(filler.clone());
907                }
908            }
909            rv.push(Value::from(tmp));
910        }
911
912        Ok(Value::from(rv))
913    }
914
915    /// Dumps a value to JSON.
916    ///
917    /// This filter is only available if the `json` feature is enabled.  The resulting
918    /// value is safe to use in HTML as well as it will not contain any special HTML
919    /// characters.  The optional parameter to the filter can be set to `true` to enable
920    /// pretty printing.  Not that the `"` character is left unchanged as it's the
921    /// JSON string delimiter.  If you want to pass JSON serialized this way into an
922    /// HTTP attribute use single quoted HTML attributes:
923    ///
924    /// ```jinja
925    /// <script>
926    ///   const GLOBAL_CONFIG = {{ global_config|tojson }};
927    /// </script>
928    /// <a href="#" data-info='{{ json_object|tojson }}'>...</a>
929    /// ```
930    ///
931    /// The filter takes one argument `indent` (which can also be passed as keyword
932    /// argument for compatibility with Jinja2) which can be set to `true` to enable
933    /// pretty printing or an integer to control the indentation of the pretty
934    /// printing feature.
935    ///
936    /// ```jinja
937    /// <script>
938    ///   const GLOBAL_CONFIG = {{ global_config|tojson(indent=2) }};
939    /// </script>
940    /// ```
941    #[cfg_attr(docsrs, doc(cfg(all(feature = "builtins", feature = "json"))))]
942    #[cfg(feature = "json")]
943    pub fn tojson(value: &Value, indent: Option<Value>, args: Kwargs) -> Result<Value, Error> {
944        let indent = match indent {
945            Some(indent) => Some(indent),
946            None => ok!(args.get("indent")),
947        };
948        let indent = match indent {
949            None => None,
950            Some(ref val) => match bool::try_from(val.clone()).ok() {
951                Some(true) => Some(2),
952                Some(false) => None,
953                None => Some(ok!(usize::try_from(val.clone()))),
954            },
955        };
956        ok!(args.assert_all_used());
957        if let Some(indent) = indent {
958            let mut out = Vec::<u8>::new();
959            let indentation = " ".repeat(indent);
960            let formatter = serde_json::ser::PrettyFormatter::with_indent(indentation.as_bytes());
961            let mut s = serde_json::Serializer::with_formatter(&mut out, formatter);
962            serde::Serialize::serialize(&value, &mut s)
963                .map(|_| unsafe { String::from_utf8_unchecked(out) })
964        } else {
965            serde_json::to_string(&value)
966        }
967        .map_err(|err| {
968            Error::new(ErrorKind::InvalidOperation, "cannot serialize to JSON").with_source(err)
969        })
970        .map(|s| {
971            // When this filter is used the return value is safe for both HTML and JSON
972            let mut rv = String::with_capacity(s.len());
973            for c in s.chars() {
974                match c {
975                    '<' => rv.push_str("\\u003c"),
976                    '>' => rv.push_str("\\u003e"),
977                    '&' => rv.push_str("\\u0026"),
978                    '\'' => rv.push_str("\\u0027"),
979                    _ => rv.push(c),
980                }
981            }
982            Value::from_safe_string(rv)
983        })
984    }
985
986    /// Indents Value with spaces
987    ///
988    /// The first optional parameter to the filter can be set to `true` to
989    /// indent the first line. The parameter defaults to false.
990    /// the second optional parameter to the filter can be set to `true`
991    /// to indent blank lines. The parameter defaults to false.
992    /// This filter is useful, if you want to template yaml-files
993    ///
994    /// ```jinja
995    /// example:
996    ///   config:
997    /// {{ global_config|indent(2) }}          # does not indent first line
998    /// {{ global_config|indent(2,true) }}     # indent whole Value with two spaces
999    /// {{ global_config|indent(2,true,true)}} # indent whole Value and all blank lines
1000    /// ```
1001    #[cfg_attr(docsrs, doc(cfg(all(feature = "builtins"))))]
1002    pub fn indent(
1003        mut value: String,
1004        width: usize,
1005        indent_first_line: Option<bool>,
1006        indent_blank_lines: Option<bool>,
1007    ) -> String {
1008        fn strip_trailing_newline(input: &mut String) {
1009            if input.ends_with('\n') {
1010                input.truncate(input.len() - 1);
1011            }
1012            if input.ends_with('\r') {
1013                input.truncate(input.len() - 1);
1014            }
1015        }
1016
1017        strip_trailing_newline(&mut value);
1018        let indent_with = " ".repeat(width);
1019        let mut output = String::new();
1020        let mut iterator = value.split('\n');
1021        if !indent_first_line.unwrap_or(false) {
1022            output.push_str(iterator.next().unwrap());
1023            output.push('\n');
1024        }
1025        for line in iterator {
1026            if line.is_empty() {
1027                if indent_blank_lines.unwrap_or(false) {
1028                    output.push_str(&indent_with);
1029                }
1030            } else {
1031                write!(output, "{}{}", indent_with, line).ok();
1032            }
1033            output.push('\n');
1034        }
1035        strip_trailing_newline(&mut output);
1036        output
1037    }
1038
1039    /// URL encodes a value.
1040    ///
1041    /// If given a map it encodes the parameters into a query set, otherwise it
1042    /// encodes the stringified value.  If the value is none or undefined, an
1043    /// empty string is returned.
1044    ///
1045    /// ```jinja
1046    /// <a href="/search?{{ {"q": "my search", "lang": "fr"}|urlencode }}">Search</a>
1047    /// ```
1048    #[cfg_attr(docsrs, doc(cfg(all(feature = "builtins", feature = "urlencode"))))]
1049    #[cfg(feature = "urlencode")]
1050    pub fn urlencode(value: &Value) -> Result<String, Error> {
1051        const SET: &percent_encoding::AsciiSet = &percent_encoding::NON_ALPHANUMERIC
1052            .remove(b'/')
1053            .remove(b'.')
1054            .remove(b'-')
1055            .remove(b'_')
1056            .add(b' ');
1057
1058        if value.kind() == ValueKind::Map {
1059            let mut rv = String::new();
1060            for k in ok!(value.try_iter()) {
1061                let v = ok!(value.get_item(&k));
1062                if v.is_none() || v.is_undefined() {
1063                    continue;
1064                }
1065                if !rv.is_empty() {
1066                    rv.push('&');
1067                }
1068                write!(
1069                    rv,
1070                    "{}={}",
1071                    percent_encoding::utf8_percent_encode(&k.to_string(), SET),
1072                    percent_encoding::utf8_percent_encode(&v.to_string(), SET)
1073                )
1074                .unwrap();
1075            }
1076            Ok(rv)
1077        } else {
1078            match &value.0 {
1079                ValueRepr::None | ValueRepr::Undefined(_) => Ok("".into()),
1080                ValueRepr::Bytes(b) => Ok(percent_encoding::percent_encode(b, SET).to_string()),
1081                ValueRepr::String(..) | ValueRepr::SmallStr(_) => Ok(
1082                    percent_encoding::utf8_percent_encode(value.as_str().unwrap(), SET).to_string(),
1083                ),
1084                _ => Ok(percent_encoding::utf8_percent_encode(&value.to_string(), SET).to_string()),
1085            }
1086        }
1087    }
1088
1089    fn select_or_reject(
1090        state: &State,
1091        invert: bool,
1092        value: Value,
1093        attr: Option<Cow<'_, str>>,
1094        test_name: Option<Cow<'_, str>>,
1095        args: crate::value::Rest<Value>,
1096    ) -> Result<Vec<Value>, Error> {
1097        let mut rv = vec![];
1098        let test = if let Some(test_name) = test_name {
1099            Some(ok!(state
1100                .env()
1101                .get_test(&test_name)
1102                .ok_or_else(|| Error::from(ErrorKind::UnknownTest))))
1103        } else {
1104            None
1105        };
1106        for value in ok!(state.undefined_behavior().try_iter(value)) {
1107            let test_value = if let Some(ref attr) = attr {
1108                ok!(value.get_path(attr))
1109            } else {
1110                value.clone()
1111            };
1112            let passed = if let Some(test) = test {
1113                let new_args = Some(test_value)
1114                    .into_iter()
1115                    .chain(args.0.iter().cloned())
1116                    .collect::<Vec<_>>();
1117                ok!(test.call(state, &new_args)).is_true()
1118            } else {
1119                test_value.is_true()
1120            };
1121            if passed != invert {
1122                rv.push(value);
1123            }
1124        }
1125        Ok(rv)
1126    }
1127
1128    /// Creates a new sequence of values that pass a test.
1129    ///
1130    /// Filters a sequence of objects by applying a test to each object.
1131    /// Only values that pass the test are included.
1132    ///
1133    /// If no test is specified, each object will be evaluated as a boolean.
1134    ///
1135    /// ```jinja
1136    /// {{ [1, 2, 3, 4]|select("odd") }} -> [1, 3]
1137    /// {{ [false, null, 42]|select }} -> [42]
1138    /// ```
1139    #[cfg_attr(docsrs, doc(cfg(feature = "builtins")))]
1140    pub fn select(
1141        state: &State,
1142        value: Value,
1143        test_name: Option<Cow<'_, str>>,
1144        args: crate::value::Rest<Value>,
1145    ) -> Result<Vec<Value>, Error> {
1146        select_or_reject(state, false, value, None, test_name, args)
1147    }
1148
1149    /// Creates a new sequence of values of which an attribute passes a test.
1150    ///
1151    /// This functions like [`select`] but it will test an attribute of the
1152    /// object itself:
1153    ///
1154    /// ```jinja
1155    /// {{ users|selectattr("is_active") }} -> all users where x.is_active is true
1156    /// {{ users|selectattr("id", "even") }} -> returns all users with an even id
1157    /// ```
1158    #[cfg_attr(docsrs, doc(cfg(feature = "builtins")))]
1159    pub fn selectattr(
1160        state: &State,
1161        value: Value,
1162        attr: Cow<'_, str>,
1163        test_name: Option<Cow<'_, str>>,
1164        args: crate::value::Rest<Value>,
1165    ) -> Result<Vec<Value>, Error> {
1166        select_or_reject(state, false, value, Some(attr), test_name, args)
1167    }
1168
1169    /// Creates a new sequence of values that don't pass a test.
1170    ///
1171    /// This is the inverse of [`select`].
1172    #[cfg_attr(docsrs, doc(cfg(feature = "builtins")))]
1173    pub fn reject(
1174        state: &State,
1175        value: Value,
1176        test_name: Option<Cow<'_, str>>,
1177        args: crate::value::Rest<Value>,
1178    ) -> Result<Vec<Value>, Error> {
1179        select_or_reject(state, true, value, None, test_name, args)
1180    }
1181
1182    /// Creates a new sequence of values of which an attribute does not pass a test.
1183    ///
1184    /// This functions like [`select`] but it will test an attribute of the
1185    /// object itself:
1186    ///
1187    /// ```jinja
1188    /// {{ users|rejectattr("is_active") }} -> all users where x.is_active is false
1189    /// {{ users|rejectattr("id", "even") }} -> returns all users with an odd id
1190    /// ```
1191    #[cfg_attr(docsrs, doc(cfg(feature = "builtins")))]
1192    pub fn rejectattr(
1193        state: &State,
1194        value: Value,
1195        attr: Cow<'_, str>,
1196        test_name: Option<Cow<'_, str>>,
1197        args: crate::value::Rest<Value>,
1198    ) -> Result<Vec<Value>, Error> {
1199        select_or_reject(state, true, value, Some(attr), test_name, args)
1200    }
1201
1202    /// Applies a filter to a sequence of objects or looks up an attribute.
1203    ///
1204    /// This is useful when dealing with lists of objects but you are really
1205    /// only interested in a certain value of it.
1206    ///
1207    /// The basic usage is mapping on an attribute. Given a list of users
1208    /// you can for instance quickly select the username and join on it:
1209    ///
1210    /// ```jinja
1211    /// {{ users|map(attribute='username')|join(', ') }}
1212    /// ```
1213    ///
1214    /// You can specify a `default` value to use if an object in the list does
1215    /// not have the given attribute.
1216    ///
1217    /// ```jinja
1218    /// {{ users|map(attribute="username", default="Anonymous")|join(", ") }}
1219    /// ```
1220    ///
1221    /// Alternatively you can have `map` invoke a filter by passing the name of the
1222    /// filter and the arguments afterwards. A good example would be applying a
1223    /// text conversion filter on a sequence:
1224    ///
1225    /// ```jinja
1226    /// Users on this page: {{ titles|map('lower')|join(', ') }}
1227    /// ```
1228    #[cfg_attr(docsrs, doc(cfg(feature = "builtins")))]
1229    pub fn map(
1230        state: &State,
1231        value: Value,
1232        args: crate::value::Rest<Value>,
1233    ) -> Result<Vec<Value>, Error> {
1234        let mut rv = Vec::with_capacity(value.len().unwrap_or(0));
1235
1236        // attribute mapping
1237        let (args, kwargs): (&[Value], Kwargs) = crate::value::from_args(&args)?;
1238
1239        if let Some(attr) = ok!(kwargs.get::<Option<Value>>("attribute")) {
1240            if !args.is_empty() {
1241                return Err(Error::from(ErrorKind::TooManyArguments));
1242            }
1243            let default = if kwargs.has("default") {
1244                ok!(kwargs.get::<Value>("default"))
1245            } else {
1246                Value::UNDEFINED
1247            };
1248            for value in ok!(state.undefined_behavior().try_iter(value)) {
1249                let sub_val = match attr.as_str() {
1250                    Some(path) => value.get_path(path),
1251                    None => value.get_item(&attr),
1252                };
1253                rv.push(match (sub_val, &default) {
1254                    (Ok(attr), _) => {
1255                        if attr.is_undefined() {
1256                            default.clone()
1257                        } else {
1258                            attr
1259                        }
1260                    }
1261                    (Err(_), default) if !default.is_undefined() => default.clone(),
1262                    (Err(err), _) => return Err(err),
1263                });
1264            }
1265            ok!(kwargs.assert_all_used());
1266            return Ok(rv);
1267        }
1268
1269        // filter mapping
1270        let filter_name = ok!(args
1271            .first()
1272            .ok_or_else(|| Error::new(ErrorKind::InvalidOperation, "filter name is required")));
1273        let filter_name = ok!(filter_name.as_str().ok_or_else(|| {
1274            Error::new(ErrorKind::InvalidOperation, "filter name must be a string")
1275        }));
1276
1277        let filter = ok!(state
1278            .env()
1279            .get_filter(filter_name)
1280            .ok_or_else(|| Error::from(ErrorKind::UnknownFilter)));
1281        for value in ok!(state.undefined_behavior().try_iter(value)) {
1282            let new_args = Some(value.clone())
1283                .into_iter()
1284                .chain(args.iter().skip(1).cloned())
1285                .collect::<Vec<_>>();
1286            rv.push(ok!(filter.call(state, &new_args)));
1287        }
1288        Ok(rv)
1289    }
1290
1291    /// Group a sequence of objects by an attribute.
1292    ///
1293    /// The attribute can use dot notation for nested access, like `"address.city"``.
1294    /// The values are sorted first so only one group is returned for each unique value.
1295    /// The attribute can be passed as first argument or as keyword argument named
1296    /// `attribute`.
1297    ///
1298    /// For example, a list of User objects with a city attribute can be
1299    /// rendered in groups. In this example, grouper refers to the city value of
1300    /// the group.
1301    ///
1302    /// ```jinja
1303    /// <ul>{% for city, items in users|groupby("city") %}
1304    ///   <li>{{ city }}
1305    ///   <ul>{% for user in items %}
1306    ///     <li>{{ user.name }}
1307    ///   {% endfor %}</ul>
1308    /// </li>
1309    /// {% endfor %}</ul>
1310    /// ```
1311    ///
1312    /// groupby yields named tuples of `(grouper, list)``, which can be used instead
1313    /// of the tuple unpacking above.  As such this example is equivalent:
1314    ///
1315    /// ```jinja
1316    /// <ul>{% for group in users|groupby(attribute="city") %}
1317    ///   <li>{{ group.grouper }}
1318    ///   <ul>{% for user in group.list %}
1319    ///     <li>{{ user.name }}
1320    ///   {% endfor %}</ul>
1321    /// </li>
1322    /// {% endfor %}</ul>
1323    /// ```
1324    ///
1325    /// You can specify a default value to use if an object in the list does not
1326    /// have the given attribute.
1327    ///
1328    /// ```jinja
1329    /// <ul>{% for city, items in users|groupby("city", default="NY") %}
1330    ///   <li>{{ city }}: {{ items|map(attribute="name")|join(", ") }}</li>
1331    /// {% endfor %}</ul>
1332    /// ```
1333    ///
1334    /// Like the [`sort`] filter, sorting and grouping is case-insensitive by default.
1335    /// The key for each group will have the case of the first item in that group
1336    /// of values. For example, if a list of users has cities `["CA", "NY", "ca"]``,
1337    /// the "CA" group will have two values.  This can be disabled by passing
1338    /// `case_sensitive=True`.
1339    #[cfg_attr(docsrs, doc(cfg(feature = "builtins")))]
1340    pub fn groupby(value: Value, attribute: Option<&str>, kwargs: Kwargs) -> Result<Value, Error> {
1341        let default = ok!(kwargs.get::<Option<Value>>("default")).unwrap_or_default();
1342        let case_sensitive = ok!(kwargs.get::<Option<bool>>("case_sensitive")).unwrap_or(false);
1343        let attr = match attribute {
1344            Some(attr) => attr,
1345            None => ok!(kwargs.get::<&str>("attribute")),
1346        };
1347        let mut items: Vec<Value> = ok!(value.try_iter()).collect();
1348        safe_sort(&mut items, |a, b| {
1349            let a = a.get_path_or_default(attr, &default);
1350            let b = b.get_path_or_default(attr, &default);
1351            cmp_helper(&a, &b, case_sensitive)
1352        })?;
1353        ok!(kwargs.assert_all_used());
1354
1355        #[derive(Debug)]
1356        pub struct GroupTuple {
1357            grouper: Value,
1358            list: Vec<Value>,
1359        }
1360
1361        impl Object for GroupTuple {
1362            fn repr(self: &Arc<Self>) -> ObjectRepr {
1363                ObjectRepr::Seq
1364            }
1365
1366            fn get_value(self: &Arc<Self>, key: &Value) -> Option<Value> {
1367                match (key.as_usize(), key.as_str()) {
1368                    (Some(0), None) | (None, Some("grouper")) => Some(self.grouper.clone()),
1369                    (Some(1), None) | (None, Some("list")) => {
1370                        Some(Value::make_object_iterable(self.clone(), |this| {
1371                            Box::new(this.list.iter().cloned())
1372                                as Box<dyn Iterator<Item = _> + Send + Sync>
1373                        }))
1374                    }
1375                    _ => None,
1376                }
1377            }
1378
1379            fn enumerate(self: &Arc<Self>) -> Enumerator {
1380                Enumerator::Seq(2)
1381            }
1382        }
1383
1384        let mut rv = Vec::new();
1385        let mut grouper = None::<Value>;
1386        let mut list = Vec::new();
1387
1388        for item in items {
1389            let group_by = item.get_path_or_default(attr, &default);
1390            if let Some(ref last_grouper) = grouper {
1391                if cmp_helper(last_grouper, &group_by, case_sensitive) != Ordering::Equal {
1392                    rv.push(Value::from_object(GroupTuple {
1393                        grouper: last_grouper.clone(),
1394                        list: std::mem::take(&mut list),
1395                    }));
1396                }
1397            }
1398            grouper = Some(group_by);
1399            list.push(item);
1400        }
1401
1402        if !list.is_empty() {
1403            rv.push(Value::from_object(GroupTuple {
1404                grouper: grouper.unwrap(),
1405                list,
1406            }));
1407        }
1408
1409        Ok(Value::from_object(rv))
1410    }
1411
1412    /// Returns a list of unique items from the given iterable.
1413    ///
1414    /// ```jinja
1415    /// {{ ['foo', 'bar', 'foobar', 'foobar']|unique|list }}
1416    ///   -> ['foo', 'bar', 'foobar']
1417    /// ```
1418    ///
1419    /// The unique items are yielded in the same order as their first occurrence
1420    /// in the iterable passed to the filter.  The filter will not detect
1421    /// duplicate objects or arrays, only primitives such as strings or numbers.
1422    ///
1423    /// Optionally the `attribute` keyword argument can be used to make the filter
1424    /// operate on an attribute instead of the value itself.  In this case only
1425    /// one city per state would be returned:
1426    ///
1427    /// ```jinja
1428    /// {{ list_of_cities|unique(attribute='state') }}
1429    /// ```
1430    ///
1431    /// Like the [`sort`] filter this operates case-insensitive by default.
1432    /// For example, if a list has the US state codes `["CA", "NY", "ca"]``,
1433    /// the resulting list will have `["CA", "NY"]`.  This can be disabled by
1434    /// passing `case_sensitive=True`.
1435    #[cfg_attr(docsrs, doc(cfg(feature = "builtins")))]
1436    pub fn unique(state: &State, values: Value, kwargs: Kwargs) -> Result<Value, Error> {
1437        use std::collections::BTreeSet;
1438
1439        let attr = ok!(kwargs.get::<Option<&str>>("attribute"));
1440        let case_sensitive = ok!(kwargs.get::<Option<bool>>("case_sensitive")).unwrap_or(false);
1441        ok!(kwargs.assert_all_used());
1442
1443        let mut rv = Vec::new();
1444        let mut seen = BTreeSet::new();
1445
1446        let iter = ok!(state.undefined_behavior().try_iter(values));
1447        for item in iter {
1448            let value_to_compare = if let Some(attr) = attr {
1449                item.get_path_or_default(attr, &Value::UNDEFINED)
1450            } else {
1451                item.clone()
1452            };
1453            let memorized_value = if case_sensitive {
1454                value_to_compare.clone()
1455            } else if let Some(s) = value_to_compare.as_str() {
1456                Value::from(s.to_lowercase())
1457            } else {
1458                value_to_compare.clone()
1459            };
1460
1461            if !seen.contains(&memorized_value) {
1462                rv.push(item);
1463                seen.insert(memorized_value);
1464            }
1465        }
1466
1467        Ok(Value::from(rv))
1468    }
1469
1470    /// Pretty print a variable.
1471    ///
1472    /// This is useful for debugging as it better shows what's inside an object.
1473    #[cfg_attr(docsrs, doc(cfg(feature = "builtins")))]
1474    pub fn pprint(value: &Value) -> String {
1475        format!("{:#?}", value)
1476    }
1477}
1478
1479#[cfg(feature = "builtins")]
1480pub use self::builtins::*;