|
"""Built-in template filters used with the ``|`` operator.""" |
|
import math |
|
import random |
|
import re |
|
import typing |
|
import typing as t |
|
from collections import abc |
|
from itertools import chain |
|
from itertools import groupby |
|
|
|
from markupsafe import escape |
|
from markupsafe import Markup |
|
from markupsafe import soft_str |
|
|
|
from .async_utils import async_variant |
|
from .async_utils import auto_aiter |
|
from .async_utils import auto_await |
|
from .async_utils import auto_to_list |
|
from .exceptions import FilterArgumentError |
|
from .runtime import Undefined |
|
from .utils import htmlsafe_json_dumps |
|
from .utils import pass_context |
|
from .utils import pass_environment |
|
from .utils import pass_eval_context |
|
from .utils import pformat |
|
from .utils import url_quote |
|
from .utils import urlize |
|
|
|
if t.TYPE_CHECKING: |
|
import typing_extensions as te |
|
from .environment import Environment |
|
from .nodes import EvalContext |
|
from .runtime import Context |
|
from .sandbox import SandboxedEnvironment |
|
|
|
class HasHTML(te.Protocol): |
|
def __html__(self) -> str: |
|
pass |
|
|
|
|
|
F = t.TypeVar("F", bound=t.Callable[..., t.Any]) |
|
K = t.TypeVar("K") |
|
V = t.TypeVar("V") |
|
|
|
|
|
def ignore_case(value: V) -> V: |
|
"""For use as a postprocessor for :func:`make_attrgetter`. Converts strings |
|
to lowercase and returns other types as-is.""" |
|
if isinstance(value, str): |
|
return t.cast(V, value.lower()) |
|
|
|
return value |
|
|
|
|
|
def make_attrgetter( |
|
environment: "Environment", |
|
attribute: t.Optional[t.Union[str, int]], |
|
postprocess: t.Optional[t.Callable[[t.Any], t.Any]] = None, |
|
default: t.Optional[t.Any] = None, |
|
) -> t.Callable[[t.Any], t.Any]: |
|
"""Returns a callable that looks up the given attribute from a |
|
passed object with the rules of the environment. Dots are allowed |
|
to access attributes of attributes. Integer parts in paths are |
|
looked up as integers. |
|
""" |
|
parts = _prepare_attribute_parts(attribute) |
|
|
|
def attrgetter(item: t.Any) -> t.Any: |
|
for part in parts: |
|
item = environment.getitem(item, part) |
|
|
|
if default is not None and isinstance(item, Undefined): |
|
item = default |
|
|
|
if postprocess is not None: |
|
item = postprocess(item) |
|
|
|
return item |
|
|
|
return attrgetter |
|
|
|
|
|
def make_multi_attrgetter( |
|
environment: "Environment", |
|
attribute: t.Optional[t.Union[str, int]], |
|
postprocess: t.Optional[t.Callable[[t.Any], t.Any]] = None, |
|
) -> t.Callable[[t.Any], t.List[t.Any]]: |
|
"""Returns a callable that looks up the given comma separated |
|
attributes from a passed object with the rules of the environment. |
|
Dots are allowed to access attributes of each attribute. Integer |
|
parts in paths are looked up as integers. |
|
|
|
The value returned by the returned callable is a list of extracted |
|
attribute values. |
|
|
|
Examples of attribute: "attr1,attr2", "attr1.inner1.0,attr2.inner2.0", etc. |
|
""" |
|
if isinstance(attribute, str): |
|
split: t.Sequence[t.Union[str, int, None]] = attribute.split(",") |
|
else: |
|
split = [attribute] |
|
|
|
parts = [_prepare_attribute_parts(item) for item in split] |
|
|
|
def attrgetter(item: t.Any) -> t.List[t.Any]: |
|
items = [None] * len(parts) |
|
|
|
for i, attribute_part in enumerate(parts): |
|
item_i = item |
|
|
|
for part in attribute_part: |
|
item_i = environment.getitem(item_i, part) |
|
|
|
if postprocess is not None: |
|
item_i = postprocess(item_i) |
|
|
|
items[i] = item_i |
|
|
|
return items |
|
|
|
return attrgetter |
|
|
|
|
|
def _prepare_attribute_parts( |
|
attr: t.Optional[t.Union[str, int]] |
|
) -> t.List[t.Union[str, int]]: |
|
if attr is None: |
|
return [] |
|
|
|
if isinstance(attr, str): |
|
return [int(x) if x.isdigit() else x for x in attr.split(".")] |
|
|
|
return [attr] |
|
|
|
|
|
def do_forceescape(value: "t.Union[str, HasHTML]") -> Markup: |
|
"""Enforce HTML escaping. This will probably double escape variables.""" |
|
if hasattr(value, "__html__"): |
|
value = t.cast("HasHTML", value).__html__() |
|
|
|
return escape(str(value)) |
|
|
|
|
|
def do_urlencode( |
|
value: t.Union[str, t.Mapping[str, t.Any], t.Iterable[t.Tuple[str, t.Any]]] |
|
) -> str: |
|
"""Quote data for use in a URL path or query using UTF-8. |
|
|
|
Basic wrapper around :func:`urllib.parse.quote` when given a |
|
string, or :func:`urllib.parse.urlencode` for a dict or iterable. |
|
|
|
:param value: Data to quote. A string will be quoted directly. A |
|
dict or iterable of ``(key, value)`` pairs will be joined as a |
|
query string. |
|
|
|
When given a string, "/" is not quoted. HTTP servers treat "/" and |
|
"%2F" equivalently in paths. If you need quoted slashes, use the |
|
``|replace("/", "%2F")`` filter. |
|
|
|
.. versionadded:: 2.7 |
|
""" |
|
if isinstance(value, str) or not isinstance(value, abc.Iterable): |
|
return url_quote(value) |
|
|
|
if isinstance(value, dict): |
|
items: t.Iterable[t.Tuple[str, t.Any]] = value.items() |
|
else: |
|
items = value |
|
|
|
return "&".join( |
|
f"{url_quote(k, for_qs=True)}={url_quote(v, for_qs=True)}" for k, v in items |
|
) |
|
|
|
|
|
@pass_eval_context |
|
def do_replace( |
|
eval_ctx: "EvalContext", s: str, old: str, new: str, count: t.Optional[int] = None |
|
) -> str: |
|
"""Return a copy of the value with all occurrences of a substring |
|
replaced with a new one. The first argument is the substring |
|
that should be replaced, the second is the replacement string. |
|
If the optional third argument ``count`` is given, only the first |
|
``count`` occurrences are replaced: |
|
|
|
.. sourcecode:: jinja |
|
|
|
{{ "Hello World"|replace("Hello", "Goodbye") }} |
|
-> Goodbye World |
|
|
|
{{ "aaaaargh"|replace("a", "d'oh, ", 2) }} |
|
-> d'oh, d'oh, aaargh |
|
""" |
|
if count is None: |
|
count = -1 |
|
|
|
if not eval_ctx.autoescape: |
|
return str(s).replace(str(old), str(new), count) |
|
|
|
if ( |
|
hasattr(old, "__html__") |
|
or hasattr(new, "__html__") |
|
and not hasattr(s, "__html__") |
|
): |
|
s = escape(s) |
|
else: |
|
s = soft_str(s) |
|
|
|
return s.replace(soft_str(old), soft_str(new), count) |
|
|
|
|
|
def do_upper(s: str) -> str: |
|
"""Convert a value to uppercase.""" |
|
return soft_str(s).upper() |
|
|
|
|
|
def do_lower(s: str) -> str: |
|
"""Convert a value to lowercase.""" |
|
return soft_str(s).lower() |
|
|
|
|
|
def do_items(value: t.Union[t.Mapping[K, V], Undefined]) -> t.Iterator[t.Tuple[K, V]]: |
|
"""Return an iterator over the ``(key, value)`` items of a mapping. |
|
|
|
``x|items`` is the same as ``x.items()``, except if ``x`` is |
|
undefined an empty iterator is returned. |
|
|
|
This filter is useful if you expect the template to be rendered with |
|
an implementation of Jinja in another programming language that does |
|
not have a ``.items()`` method on its mapping type. |
|
|
|
.. code-block:: html+jinja |
|
|
|
<dl> |
|
{% for key, value in my_dict|items %} |
|
<dt>{{ key }} |
|
<dd>{{ value }} |
|
{% endfor %} |
|
</dl> |
|
|
|
.. versionadded:: 3.1 |
|
""" |
|
if isinstance(value, Undefined): |
|
return |
|
|
|
if not isinstance(value, abc.Mapping): |
|
raise TypeError("Can only get item pairs from a mapping.") |
|
|
|
yield from value.items() |
|
|
|
|
|
@pass_eval_context |
|
def do_xmlattr( |
|
eval_ctx: "EvalContext", d: t.Mapping[str, t.Any], autospace: bool = True |
|
) -> str: |
|
"""Create an SGML/XML attribute string based on the items in a dict. |
|
All values that are neither `none` nor `undefined` are automatically |
|
escaped: |
|
|
|
.. sourcecode:: html+jinja |
|
|
|
<ul{{ {'class': 'my_list', 'missing': none, |
|
'id': 'list-%d'|format(variable)}|xmlattr }}> |
|
... |
|
</ul> |
|
|
|
Results in something like this: |
|
|
|
.. sourcecode:: html |
|
|
|
<ul class="my_list" id="list-42"> |
|
... |
|
</ul> |
|
|
|
As you can see it automatically prepends a space in front of the item |
|
if the filter returned something unless the second parameter is false. |
|
""" |
|
rv = " ".join( |
|
f'{escape(key)}="{escape(value)}"' |
|
for key, value in d.items() |
|
if value is not None and not isinstance(value, Undefined) |
|
) |
|
|
|
if autospace and rv: |
|
rv = " " + rv |
|
|
|
if eval_ctx.autoescape: |
|
rv = Markup(rv) |
|
|
|
return rv |
|
|
|
|
|
def do_capitalize(s: str) -> str: |
|
"""Capitalize a value. The first character will be uppercase, all others |
|
lowercase. |
|
""" |
|
return soft_str(s).capitalize() |
|
|
|
|
|
_word_beginning_split_re = re.compile(r"([-\s({\[<]+)") |
|
|
|
|
|
def do_title(s: str) -> str: |
|
"""Return a titlecased version of the value. I.e. words will start with |
|
uppercase letters, all remaining characters are lowercase. |
|
""" |
|
return "".join( |
|
[ |
|
item[0].upper() + item[1:].lower() |
|
for item in _word_beginning_split_re.split(soft_str(s)) |
|
if item |
|
] |
|
) |
|
|
|
|
|
def do_dictsort( |
|
value: t.Mapping[K, V], |
|
case_sensitive: bool = False, |
|
by: 'te.Literal["key", "value"]' = "key", |
|
reverse: bool = False, |
|
) -> t.List[t.Tuple[K, V]]: |
|
"""Sort a dict and yield (key, value) pairs. Python dicts may not |
|
be in the order you want to display them in, so sort them first. |
|
|
|
.. sourcecode:: jinja |
|
|
|
{% for key, value in mydict|dictsort %} |
|
sort the dict by key, case insensitive |
|
|
|
{% for key, value in mydict|dictsort(reverse=true) %} |
|
sort the dict by key, case insensitive, reverse order |
|
|
|
{% for key, value in mydict|dictsort(true) %} |
|
sort the dict by key, case sensitive |
|
|
|
{% for key, value in mydict|dictsort(false, 'value') %} |
|
sort the dict by value, case insensitive |
|
""" |
|
if by == "key": |
|
pos = 0 |
|
elif by == "value": |
|
pos = 1 |
|
else: |
|
raise FilterArgumentError('You can only sort by either "key" or "value"') |
|
|
|
def sort_func(item: t.Tuple[t.Any, t.Any]) -> t.Any: |
|
value = item[pos] |
|
|
|
if not case_sensitive: |
|
value = ignore_case(value) |
|
|
|
return value |
|
|
|
return sorted(value.items(), key=sort_func, reverse=reverse) |
|
|
|
|
|
@pass_environment |
|
def do_sort( |
|
environment: "Environment", |
|
value: "t.Iterable[V]", |
|
reverse: bool = False, |
|
case_sensitive: bool = False, |
|
attribute: t.Optional[t.Union[str, int]] = None, |
|
) -> "t.List[V]": |
|
"""Sort an iterable using Python's :func:`sorted`. |
|
|
|
.. sourcecode:: jinja |
|
|
|
{% for city in cities|sort %} |
|
... |
|
{% endfor %} |
|
|
|
:param reverse: Sort descending instead of ascending. |
|
:param case_sensitive: When sorting strings, sort upper and lower |
|
case separately. |
|
:param attribute: When sorting objects or dicts, an attribute or |
|
key to sort by. Can use dot notation like ``"address.city"``. |
|
Can be a list of attributes like ``"age,name"``. |
|
|
|
The sort is stable, it does not change the relative order of |
|
elements that compare equal. This makes it is possible to chain |
|
sorts on different attributes and ordering. |
|
|
|
.. sourcecode:: jinja |
|
|
|
{% for user in users|sort(attribute="name") |
|
|sort(reverse=true, attribute="age") %} |
|
... |
|
{% endfor %} |
|
|
|
As a shortcut to chaining when the direction is the same for all |
|
attributes, pass a comma separate list of attributes. |
|
|
|
.. sourcecode:: jinja |
|
|
|
{% for user in users|sort(attribute="age,name") %} |
|
... |
|
{% endfor %} |
|
|
|
.. versionchanged:: 2.11.0 |
|
The ``attribute`` parameter can be a comma separated list of |
|
attributes, e.g. ``"age,name"``. |
|
|
|
.. versionchanged:: 2.6 |
|
The ``attribute`` parameter was added. |
|
""" |
|
key_func = make_multi_attrgetter( |
|
environment, attribute, postprocess=ignore_case if not case_sensitive else None |
|
) |
|
return sorted(value, key=key_func, reverse=reverse) |
|
|
|
|
|
@pass_environment |
|
def do_unique( |
|
environment: "Environment", |
|
value: "t.Iterable[V]", |
|
case_sensitive: bool = False, |
|
attribute: t.Optional[t.Union[str, int]] = None, |
|
) -> "t.Iterator[V]": |
|
"""Returns a list of unique items from the given iterable. |
|
|
|
.. sourcecode:: jinja |
|
|
|
{{ ['foo', 'bar', 'foobar', 'FooBar']|unique|list }} |
|
-> ['foo', 'bar', 'foobar'] |
|
|
|
The unique items are yielded in the same order as their first occurrence in |
|
the iterable passed to the filter. |
|
|
|
:param case_sensitive: Treat upper and lower case strings as distinct. |
|
:param attribute: Filter objects with unique values for this attribute. |
|
""" |
|
getter = make_attrgetter( |
|
environment, attribute, postprocess=ignore_case if not case_sensitive else None |
|
) |
|
seen = set() |
|
|
|
for item in value: |
|
key = getter(item) |
|
|
|
if key not in seen: |
|
seen.add(key) |
|
yield item |
|
|
|
|
|
def _min_or_max( |
|
environment: "Environment", |
|
value: "t.Iterable[V]", |
|
func: "t.Callable[..., V]", |
|
case_sensitive: bool, |
|
attribute: t.Optional[t.Union[str, int]], |
|
) -> "t.Union[V, Undefined]": |
|
it = iter(value) |
|
|
|
try: |
|
first = next(it) |
|
except StopIteration: |
|
return environment.undefined("No aggregated item, sequence was empty.") |
|
|
|
key_func = make_attrgetter( |
|
environment, attribute, postprocess=ignore_case if not case_sensitive else None |
|
) |
|
return func(chain([first], it), key=key_func) |
|
|
|
|
|
@pass_environment |
|
def do_min( |
|
environment: "Environment", |
|
value: "t.Iterable[V]", |
|
case_sensitive: bool = False, |
|
attribute: t.Optional[t.Union[str, int]] = None, |
|
) -> "t.Union[V, Undefined]": |
|
"""Return the smallest item from the sequence. |
|
|
|
.. sourcecode:: jinja |
|
|
|
{{ [1, 2, 3]|min }} |
|
-> 1 |
|
|
|
:param case_sensitive: Treat upper and lower case strings as distinct. |
|
:param attribute: Get the object with the min value of this attribute. |
|
""" |
|
return _min_or_max(environment, value, min, case_sensitive, attribute) |
|
|
|
|
|
@pass_environment |
|
def do_max( |
|
environment: "Environment", |
|
value: "t.Iterable[V]", |
|
case_sensitive: bool = False, |
|
attribute: t.Optional[t.Union[str, int]] = None, |
|
) -> "t.Union[V, Undefined]": |
|
"""Return the largest item from the sequence. |
|
|
|
.. sourcecode:: jinja |
|
|
|
{{ [1, 2, 3]|max }} |
|
-> 3 |
|
|
|
:param case_sensitive: Treat upper and lower case strings as distinct. |
|
:param attribute: Get the object with the max value of this attribute. |
|
""" |
|
return _min_or_max(environment, value, max, case_sensitive, attribute) |
|
|
|
|
|
def do_default( |
|
value: V, |
|
default_value: V = "", |
|
boolean: bool = False, |
|
) -> V: |
|
"""If the value is undefined it will return the passed default value, |
|
otherwise the value of the variable: |
|
|
|
.. sourcecode:: jinja |
|
|
|
{{ my_variable|default('my_variable is not defined') }} |
|
|
|
This will output the value of ``my_variable`` if the variable was |
|
defined, otherwise ``'my_variable is not defined'``. If you want |
|
to use default with variables that evaluate to false you have to |
|
set the second parameter to `true`: |
|
|
|
.. sourcecode:: jinja |
|
|
|
{{ ''|default('the string was empty', true) }} |
|
|
|
.. versionchanged:: 2.11 |
|
It's now possible to configure the :class:`~jinja2.Environment` with |
|
:class:`~jinja2.ChainableUndefined` to make the `default` filter work |
|
on nested elements and attributes that may contain undefined values |
|
in the chain without getting an :exc:`~jinja2.UndefinedError`. |
|
""" |
|
if isinstance(value, Undefined) or (boolean and not value): |
|
return default_value |
|
|
|
return value |
|
|
|
|
|
@pass_eval_context |
|
def sync_do_join( |
|
eval_ctx: "EvalContext", |
|
value: t.Iterable, |
|
d: str = "", |
|
attribute: t.Optional[t.Union[str, int]] = None, |
|
) -> str: |
|
"""Return a string which is the concatenation of the strings in the |
|
sequence. The separator between elements is an empty string per |
|
default, you can define it with the optional parameter: |
|
|
|
.. sourcecode:: jinja |
|
|
|
{{ [1, 2, 3]|join('|') }} |
|
-> 1|2|3 |
|
|
|
{{ [1, 2, 3]|join }} |
|
-> 123 |
|
|
|
It is also possible to join certain attributes of an object: |
|
|
|
.. sourcecode:: jinja |
|
|
|
{{ users|join(', ', attribute='username') }} |
|
|
|
.. versionadded:: 2.6 |
|
The `attribute` parameter was added. |
|
""" |
|
if attribute is not None: |
|
value = map(make_attrgetter(eval_ctx.environment, attribute), value) |
|
|
|
|
|
if not eval_ctx.autoescape: |
|
return str(d).join(map(str, value)) |
|
|
|
|
|
|
|
if not hasattr(d, "__html__"): |
|
value = list(value) |
|
do_escape = False |
|
|
|
for idx, item in enumerate(value): |
|
if hasattr(item, "__html__"): |
|
do_escape = True |
|
else: |
|
value[idx] = str(item) |
|
|
|
if do_escape: |
|
d = escape(d) |
|
else: |
|
d = str(d) |
|
|
|
return d.join(value) |
|
|
|
|
|
return soft_str(d).join(map(soft_str, value)) |
|
|
|
|
|
@async_variant(sync_do_join) |
|
async def do_join( |
|
eval_ctx: "EvalContext", |
|
value: t.Union[t.AsyncIterable, t.Iterable], |
|
d: str = "", |
|
attribute: t.Optional[t.Union[str, int]] = None, |
|
) -> str: |
|
return sync_do_join(eval_ctx, await auto_to_list(value), d, attribute) |
|
|
|
|
|
def do_center(value: str, width: int = 80) -> str: |
|
"""Centers the value in a field of a given width.""" |
|
return soft_str(value).center(width) |
|
|
|
|
|
@pass_environment |
|
def sync_do_first( |
|
environment: "Environment", seq: "t.Iterable[V]" |
|
) -> "t.Union[V, Undefined]": |
|
"""Return the first item of a sequence.""" |
|
try: |
|
return next(iter(seq)) |
|
except StopIteration: |
|
return environment.undefined("No first item, sequence was empty.") |
|
|
|
|
|
@async_variant(sync_do_first) |
|
async def do_first( |
|
environment: "Environment", seq: "t.Union[t.AsyncIterable[V], t.Iterable[V]]" |
|
) -> "t.Union[V, Undefined]": |
|
try: |
|
return await auto_aiter(seq).__anext__() |
|
except StopAsyncIteration: |
|
return environment.undefined("No first item, sequence was empty.") |
|
|
|
|
|
@pass_environment |
|
def do_last( |
|
environment: "Environment", seq: "t.Reversible[V]" |
|
) -> "t.Union[V, Undefined]": |
|
"""Return the last item of a sequence. |
|
|
|
Note: Does not work with generators. You may want to explicitly |
|
convert it to a list: |
|
|
|
.. sourcecode:: jinja |
|
|
|
{{ data | selectattr('name', '==', 'Jinja') | list | last }} |
|
""" |
|
try: |
|
return next(iter(reversed(seq))) |
|
except StopIteration: |
|
return environment.undefined("No last item, sequence was empty.") |
|
|
|
|
|
|
|
|
|
|
|
@pass_context |
|
def do_random(context: "Context", seq: "t.Sequence[V]") -> "t.Union[V, Undefined]": |
|
"""Return a random item from the sequence.""" |
|
try: |
|
return random.choice(seq) |
|
except IndexError: |
|
return context.environment.undefined("No random item, sequence was empty.") |
|
|
|
|
|
def do_filesizeformat(value: t.Union[str, float, int], binary: bool = False) -> str: |
|
"""Format the value like a 'human-readable' file size (i.e. 13 kB, |
|
4.1 MB, 102 Bytes, etc). Per default decimal prefixes are used (Mega, |
|
Giga, etc.), if the second parameter is set to `True` the binary |
|
prefixes are used (Mebi, Gibi). |
|
""" |
|
bytes = float(value) |
|
base = 1024 if binary else 1000 |
|
prefixes = [ |
|
("KiB" if binary else "kB"), |
|
("MiB" if binary else "MB"), |
|
("GiB" if binary else "GB"), |
|
("TiB" if binary else "TB"), |
|
("PiB" if binary else "PB"), |
|
("EiB" if binary else "EB"), |
|
("ZiB" if binary else "ZB"), |
|
("YiB" if binary else "YB"), |
|
] |
|
|
|
if bytes == 1: |
|
return "1 Byte" |
|
elif bytes < base: |
|
return f"{int(bytes)} Bytes" |
|
else: |
|
for i, prefix in enumerate(prefixes): |
|
unit = base ** (i + 2) |
|
|
|
if bytes < unit: |
|
return f"{base * bytes / unit:.1f} {prefix}" |
|
|
|
return f"{base * bytes / unit:.1f} {prefix}" |
|
|
|
|
|
def do_pprint(value: t.Any) -> str: |
|
"""Pretty print a variable. Useful for debugging.""" |
|
return pformat(value) |
|
|
|
|
|
_uri_scheme_re = re.compile(r"^([\w.+-]{2,}:(/){0,2})$") |
|
|
|
|
|
@pass_eval_context |
|
def do_urlize( |
|
eval_ctx: "EvalContext", |
|
value: str, |
|
trim_url_limit: t.Optional[int] = None, |
|
nofollow: bool = False, |
|
target: t.Optional[str] = None, |
|
rel: t.Optional[str] = None, |
|
extra_schemes: t.Optional[t.Iterable[str]] = None, |
|
) -> str: |
|
"""Convert URLs in text into clickable links. |
|
|
|
This may not recognize links in some situations. Usually, a more |
|
comprehensive formatter, such as a Markdown library, is a better |
|
choice. |
|
|
|
Works on ``http://``, ``https://``, ``www.``, ``mailto:``, and email |
|
addresses. Links with trailing punctuation (periods, commas, closing |
|
parentheses) and leading punctuation (opening parentheses) are |
|
recognized excluding the punctuation. Email addresses that include |
|
header fields are not recognized (for example, |
|
``mailto:address@example.com?cc=copy@example.com``). |
|
|
|
:param value: Original text containing URLs to link. |
|
:param trim_url_limit: Shorten displayed URL values to this length. |
|
:param nofollow: Add the ``rel=nofollow`` attribute to links. |
|
:param target: Add the ``target`` attribute to links. |
|
:param rel: Add the ``rel`` attribute to links. |
|
:param extra_schemes: Recognize URLs that start with these schemes |
|
in addition to the default behavior. Defaults to |
|
``env.policies["urlize.extra_schemes"]``, which defaults to no |
|
extra schemes. |
|
|
|
.. versionchanged:: 3.0 |
|
The ``extra_schemes`` parameter was added. |
|
|
|
.. versionchanged:: 3.0 |
|
Generate ``https://`` links for URLs without a scheme. |
|
|
|
.. versionchanged:: 3.0 |
|
The parsing rules were updated. Recognize email addresses with |
|
or without the ``mailto:`` scheme. Validate IP addresses. Ignore |
|
parentheses and brackets in more cases. |
|
|
|
.. versionchanged:: 2.8 |
|
The ``target`` parameter was added. |
|
""" |
|
policies = eval_ctx.environment.policies |
|
rel_parts = set((rel or "").split()) |
|
|
|
if nofollow: |
|
rel_parts.add("nofollow") |
|
|
|
rel_parts.update((policies["urlize.rel"] or "").split()) |
|
rel = " ".join(sorted(rel_parts)) or None |
|
|
|
if target is None: |
|
target = policies["urlize.target"] |
|
|
|
if extra_schemes is None: |
|
extra_schemes = policies["urlize.extra_schemes"] or () |
|
|
|
for scheme in extra_schemes: |
|
if _uri_scheme_re.fullmatch(scheme) is None: |
|
raise FilterArgumentError(f"{scheme!r} is not a valid URI scheme prefix.") |
|
|
|
rv = urlize( |
|
value, |
|
trim_url_limit=trim_url_limit, |
|
rel=rel, |
|
target=target, |
|
extra_schemes=extra_schemes, |
|
) |
|
|
|
if eval_ctx.autoescape: |
|
rv = Markup(rv) |
|
|
|
return rv |
|
|
|
|
|
def do_indent( |
|
s: str, width: t.Union[int, str] = 4, first: bool = False, blank: bool = False |
|
) -> str: |
|
"""Return a copy of the string with each line indented by 4 spaces. The |
|
first line and blank lines are not indented by default. |
|
|
|
:param width: Number of spaces, or a string, to indent by. |
|
:param first: Don't skip indenting the first line. |
|
:param blank: Don't skip indenting empty lines. |
|
|
|
.. versionchanged:: 3.0 |
|
``width`` can be a string. |
|
|
|
.. versionchanged:: 2.10 |
|
Blank lines are not indented by default. |
|
|
|
Rename the ``indentfirst`` argument to ``first``. |
|
""" |
|
if isinstance(width, str): |
|
indention = width |
|
else: |
|
indention = " " * width |
|
|
|
newline = "\n" |
|
|
|
if isinstance(s, Markup): |
|
indention = Markup(indention) |
|
newline = Markup(newline) |
|
|
|
s += newline |
|
|
|
if blank: |
|
rv = (newline + indention).join(s.splitlines()) |
|
else: |
|
lines = s.splitlines() |
|
rv = lines.pop(0) |
|
|
|
if lines: |
|
rv += newline + newline.join( |
|
indention + line if line else line for line in lines |
|
) |
|
|
|
if first: |
|
rv = indention + rv |
|
|
|
return rv |
|
|
|
|
|
@pass_environment |
|
def do_truncate( |
|
env: "Environment", |
|
s: str, |
|
length: int = 255, |
|
killwords: bool = False, |
|
end: str = "...", |
|
leeway: t.Optional[int] = None, |
|
) -> str: |
|
"""Return a truncated copy of the string. The length is specified |
|
with the first parameter which defaults to ``255``. If the second |
|
parameter is ``true`` the filter will cut the text at length. Otherwise |
|
it will discard the last word. If the text was in fact |
|
truncated it will append an ellipsis sign (``"..."``). If you want a |
|
different ellipsis sign than ``"..."`` you can specify it using the |
|
third parameter. Strings that only exceed the length by the tolerance |
|
margin given in the fourth parameter will not be truncated. |
|
|
|
.. sourcecode:: jinja |
|
|
|
{{ "foo bar baz qux"|truncate(9) }} |
|
-> "foo..." |
|
{{ "foo bar baz qux"|truncate(9, True) }} |
|
-> "foo ba..." |
|
{{ "foo bar baz qux"|truncate(11) }} |
|
-> "foo bar baz qux" |
|
{{ "foo bar baz qux"|truncate(11, False, '...', 0) }} |
|
-> "foo bar..." |
|
|
|
The default leeway on newer Jinja versions is 5 and was 0 before but |
|
can be reconfigured globally. |
|
""" |
|
if leeway is None: |
|
leeway = env.policies["truncate.leeway"] |
|
|
|
assert length >= len(end), f"expected length >= {len(end)}, got {length}" |
|
assert leeway >= 0, f"expected leeway >= 0, got {leeway}" |
|
|
|
if len(s) <= length + leeway: |
|
return s |
|
|
|
if killwords: |
|
return s[: length - len(end)] + end |
|
|
|
result = s[: length - len(end)].rsplit(" ", 1)[0] |
|
return result + end |
|
|
|
|
|
@pass_environment |
|
def do_wordwrap( |
|
environment: "Environment", |
|
s: str, |
|
width: int = 79, |
|
break_long_words: bool = True, |
|
wrapstring: t.Optional[str] = None, |
|
break_on_hyphens: bool = True, |
|
) -> str: |
|
"""Wrap a string to the given width. Existing newlines are treated |
|
as paragraphs to be wrapped separately. |
|
|
|
:param s: Original text to wrap. |
|
:param width: Maximum length of wrapped lines. |
|
:param break_long_words: If a word is longer than ``width``, break |
|
it across lines. |
|
:param break_on_hyphens: If a word contains hyphens, it may be split |
|
across lines. |
|
:param wrapstring: String to join each wrapped line. Defaults to |
|
:attr:`Environment.newline_sequence`. |
|
|
|
.. versionchanged:: 2.11 |
|
Existing newlines are treated as paragraphs wrapped separately. |
|
|
|
.. versionchanged:: 2.11 |
|
Added the ``break_on_hyphens`` parameter. |
|
|
|
.. versionchanged:: 2.7 |
|
Added the ``wrapstring`` parameter. |
|
""" |
|
import textwrap |
|
|
|
if wrapstring is None: |
|
wrapstring = environment.newline_sequence |
|
|
|
|
|
|
|
|
|
|
|
return wrapstring.join( |
|
[ |
|
wrapstring.join( |
|
textwrap.wrap( |
|
line, |
|
width=width, |
|
expand_tabs=False, |
|
replace_whitespace=False, |
|
break_long_words=break_long_words, |
|
break_on_hyphens=break_on_hyphens, |
|
) |
|
) |
|
for line in s.splitlines() |
|
] |
|
) |
|
|
|
|
|
_word_re = re.compile(r"\w+") |
|
|
|
|
|
def do_wordcount(s: str) -> int: |
|
"""Count the words in that string.""" |
|
return len(_word_re.findall(soft_str(s))) |
|
|
|
|
|
def do_int(value: t.Any, default: int = 0, base: int = 10) -> int: |
|
"""Convert the value into an integer. If the |
|
conversion doesn't work it will return ``0``. You can |
|
override this default using the first parameter. You |
|
can also override the default base (10) in the second |
|
parameter, which handles input with prefixes such as |
|
0b, 0o and 0x for bases 2, 8 and 16 respectively. |
|
The base is ignored for decimal numbers and non-string values. |
|
""" |
|
try: |
|
if isinstance(value, str): |
|
return int(value, base) |
|
|
|
return int(value) |
|
except (TypeError, ValueError): |
|
|
|
try: |
|
return int(float(value)) |
|
except (TypeError, ValueError): |
|
return default |
|
|
|
|
|
def do_float(value: t.Any, default: float = 0.0) -> float: |
|
"""Convert the value into a floating point number. If the |
|
conversion doesn't work it will return ``0.0``. You can |
|
override this default using the first parameter. |
|
""" |
|
try: |
|
return float(value) |
|
except (TypeError, ValueError): |
|
return default |
|
|
|
|
|
def do_format(value: str, *args: t.Any, **kwargs: t.Any) -> str: |
|
"""Apply the given values to a `printf-style`_ format string, like |
|
``string % values``. |
|
|
|
.. sourcecode:: jinja |
|
|
|
{{ "%s, %s!"|format(greeting, name) }} |
|
Hello, World! |
|
|
|
In most cases it should be more convenient and efficient to use the |
|
``%`` operator or :meth:`str.format`. |
|
|
|
.. code-block:: text |
|
|
|
{{ "%s, %s!" % (greeting, name) }} |
|
{{ "{}, {}!".format(greeting, name) }} |
|
|
|
.. _printf-style: https://docs.python.org/library/stdtypes.html |
|
#printf-style-string-formatting |
|
""" |
|
if args and kwargs: |
|
raise FilterArgumentError( |
|
"can't handle positional and keyword arguments at the same time" |
|
) |
|
|
|
return soft_str(value) % (kwargs or args) |
|
|
|
|
|
def do_trim(value: str, chars: t.Optional[str] = None) -> str: |
|
"""Strip leading and trailing characters, by default whitespace.""" |
|
return soft_str(value).strip(chars) |
|
|
|
|
|
def do_striptags(value: "t.Union[str, HasHTML]") -> str: |
|
"""Strip SGML/XML tags and replace adjacent whitespace by one space.""" |
|
if hasattr(value, "__html__"): |
|
value = t.cast("HasHTML", value).__html__() |
|
|
|
return Markup(str(value)).striptags() |
|
|
|
|
|
def sync_do_slice( |
|
value: "t.Collection[V]", slices: int, fill_with: "t.Optional[V]" = None |
|
) -> "t.Iterator[t.List[V]]": |
|
"""Slice an iterator and return a list of lists containing |
|
those items. Useful if you want to create a div containing |
|
three ul tags that represent columns: |
|
|
|
.. sourcecode:: html+jinja |
|
|
|
<div class="columnwrapper"> |
|
{%- for column in items|slice(3) %} |
|
<ul class="column-{{ loop.index }}"> |
|
{%- for item in column %} |
|
<li>{{ item }}</li> |
|
{%- endfor %} |
|
</ul> |
|
{%- endfor %} |
|
</div> |
|
|
|
If you pass it a second argument it's used to fill missing |
|
values on the last iteration. |
|
""" |
|
seq = list(value) |
|
length = len(seq) |
|
items_per_slice = length // slices |
|
slices_with_extra = length % slices |
|
offset = 0 |
|
|
|
for slice_number in range(slices): |
|
start = offset + slice_number * items_per_slice |
|
|
|
if slice_number < slices_with_extra: |
|
offset += 1 |
|
|
|
end = offset + (slice_number + 1) * items_per_slice |
|
tmp = seq[start:end] |
|
|
|
if fill_with is not None and slice_number >= slices_with_extra: |
|
tmp.append(fill_with) |
|
|
|
yield tmp |
|
|
|
|
|
@async_variant(sync_do_slice) |
|
async def do_slice( |
|
value: "t.Union[t.AsyncIterable[V], t.Iterable[V]]", |
|
slices: int, |
|
fill_with: t.Optional[t.Any] = None, |
|
) -> "t.Iterator[t.List[V]]": |
|
return sync_do_slice(await auto_to_list(value), slices, fill_with) |
|
|
|
|
|
def do_batch( |
|
value: "t.Iterable[V]", linecount: int, fill_with: "t.Optional[V]" = None |
|
) -> "t.Iterator[t.List[V]]": |
|
""" |
|
A filter that batches items. It works pretty much like `slice` |
|
just the other way round. It returns a list of lists with the |
|
given number of items. If you provide a second parameter this |
|
is used to fill up missing items. See this example: |
|
|
|
.. sourcecode:: html+jinja |
|
|
|
<table> |
|
{%- for row in items|batch(3, ' ') %} |
|
<tr> |
|
{%- for column in row %} |
|
<td>{{ column }}</td> |
|
{%- endfor %} |
|
</tr> |
|
{%- endfor %} |
|
</table> |
|
""" |
|
tmp: "t.List[V]" = [] |
|
|
|
for item in value: |
|
if len(tmp) == linecount: |
|
yield tmp |
|
tmp = [] |
|
|
|
tmp.append(item) |
|
|
|
if tmp: |
|
if fill_with is not None and len(tmp) < linecount: |
|
tmp += [fill_with] * (linecount - len(tmp)) |
|
|
|
yield tmp |
|
|
|
|
|
def do_round( |
|
value: float, |
|
precision: int = 0, |
|
method: 'te.Literal["common", "ceil", "floor"]' = "common", |
|
) -> float: |
|
"""Round the number to a given precision. The first |
|
parameter specifies the precision (default is ``0``), the |
|
second the rounding method: |
|
|
|
- ``'common'`` rounds either up or down |
|
- ``'ceil'`` always rounds up |
|
- ``'floor'`` always rounds down |
|
|
|
If you don't specify a method ``'common'`` is used. |
|
|
|
.. sourcecode:: jinja |
|
|
|
{{ 42.55|round }} |
|
-> 43.0 |
|
{{ 42.55|round(1, 'floor') }} |
|
-> 42.5 |
|
|
|
Note that even if rounded to 0 precision, a float is returned. If |
|
you need a real integer, pipe it through `int`: |
|
|
|
.. sourcecode:: jinja |
|
|
|
{{ 42.55|round|int }} |
|
-> 43 |
|
""" |
|
if method not in {"common", "ceil", "floor"}: |
|
raise FilterArgumentError("method must be common, ceil or floor") |
|
|
|
if method == "common": |
|
return round(value, precision) |
|
|
|
func = getattr(math, method) |
|
return t.cast(float, func(value * (10**precision)) / (10**precision)) |
|
|
|
|
|
class _GroupTuple(t.NamedTuple): |
|
grouper: t.Any |
|
list: t.List |
|
|
|
|
|
|
|
def __repr__(self) -> str: |
|
return tuple.__repr__(self) |
|
|
|
def __str__(self) -> str: |
|
return tuple.__str__(self) |
|
|
|
|
|
@pass_environment |
|
def sync_do_groupby( |
|
environment: "Environment", |
|
value: "t.Iterable[V]", |
|
attribute: t.Union[str, int], |
|
default: t.Optional[t.Any] = None, |
|
case_sensitive: bool = False, |
|
) -> "t.List[_GroupTuple]": |
|
"""Group a sequence of objects by an attribute using Python's |
|
:func:`itertools.groupby`. The attribute can use dot notation for |
|
nested access, like ``"address.city"``. Unlike Python's ``groupby``, |
|
the values are sorted first so only one group is returned for each |
|
unique value. |
|
|
|
For example, a list of ``User`` objects with a ``city`` attribute |
|
can be rendered in groups. In this example, ``grouper`` refers to |
|
the ``city`` value of the group. |
|
|
|
.. sourcecode:: html+jinja |
|
|
|
<ul>{% for city, items in users|groupby("city") %} |
|
<li>{{ city }} |
|
<ul>{% for user in items %} |
|
<li>{{ user.name }} |
|
{% endfor %}</ul> |
|
</li> |
|
{% endfor %}</ul> |
|
|
|
``groupby`` yields namedtuples of ``(grouper, list)``, which |
|
can be used instead of the tuple unpacking above. ``grouper`` is the |
|
value of the attribute, and ``list`` is the items with that value. |
|
|
|
.. sourcecode:: html+jinja |
|
|
|
<ul>{% for group in users|groupby("city") %} |
|
<li>{{ group.grouper }}: {{ group.list|join(", ") }} |
|
{% endfor %}</ul> |
|
|
|
You can specify a ``default`` value to use if an object in the list |
|
does not have the given attribute. |
|
|
|
.. sourcecode:: jinja |
|
|
|
<ul>{% for city, items in users|groupby("city", default="NY") %} |
|
<li>{{ city }}: {{ items|map(attribute="name")|join(", ") }}</li> |
|
{% endfor %}</ul> |
|
|
|
Like the :func:`~jinja-filters.sort` filter, sorting and grouping is |
|
case-insensitive by default. The ``key`` for each group will have |
|
the case of the first item in that group of values. For example, if |
|
a list of users has cities ``["CA", "NY", "ca"]``, the "CA" group |
|
will have two values. This can be disabled by passing |
|
``case_sensitive=True``. |
|
|
|
.. versionchanged:: 3.1 |
|
Added the ``case_sensitive`` parameter. Sorting and grouping is |
|
case-insensitive by default, matching other filters that do |
|
comparisons. |
|
|
|
.. versionchanged:: 3.0 |
|
Added the ``default`` parameter. |
|
|
|
.. versionchanged:: 2.6 |
|
The attribute supports dot notation for nested access. |
|
""" |
|
expr = make_attrgetter( |
|
environment, |
|
attribute, |
|
postprocess=ignore_case if not case_sensitive else None, |
|
default=default, |
|
) |
|
out = [ |
|
_GroupTuple(key, list(values)) |
|
for key, values in groupby(sorted(value, key=expr), expr) |
|
] |
|
|
|
if not case_sensitive: |
|
|
|
output_expr = make_attrgetter(environment, attribute, default=default) |
|
out = [_GroupTuple(output_expr(values[0]), values) for _, values in out] |
|
|
|
return out |
|
|
|
|
|
@async_variant(sync_do_groupby) |
|
async def do_groupby( |
|
environment: "Environment", |
|
value: "t.Union[t.AsyncIterable[V], t.Iterable[V]]", |
|
attribute: t.Union[str, int], |
|
default: t.Optional[t.Any] = None, |
|
case_sensitive: bool = False, |
|
) -> "t.List[_GroupTuple]": |
|
expr = make_attrgetter( |
|
environment, |
|
attribute, |
|
postprocess=ignore_case if not case_sensitive else None, |
|
default=default, |
|
) |
|
out = [ |
|
_GroupTuple(key, await auto_to_list(values)) |
|
for key, values in groupby(sorted(await auto_to_list(value), key=expr), expr) |
|
] |
|
|
|
if not case_sensitive: |
|
|
|
output_expr = make_attrgetter(environment, attribute, default=default) |
|
out = [_GroupTuple(output_expr(values[0]), values) for _, values in out] |
|
|
|
return out |
|
|
|
|
|
@pass_environment |
|
def sync_do_sum( |
|
environment: "Environment", |
|
iterable: "t.Iterable[V]", |
|
attribute: t.Optional[t.Union[str, int]] = None, |
|
start: V = 0, |
|
) -> V: |
|
"""Returns the sum of a sequence of numbers plus the value of parameter |
|
'start' (which defaults to 0). When the sequence is empty it returns |
|
start. |
|
|
|
It is also possible to sum up only certain attributes: |
|
|
|
.. sourcecode:: jinja |
|
|
|
Total: {{ items|sum(attribute='price') }} |
|
|
|
.. versionchanged:: 2.6 |
|
The ``attribute`` parameter was added to allow summing up over |
|
attributes. Also the ``start`` parameter was moved on to the right. |
|
""" |
|
if attribute is not None: |
|
iterable = map(make_attrgetter(environment, attribute), iterable) |
|
|
|
return sum(iterable, start) |
|
|
|
|
|
@async_variant(sync_do_sum) |
|
async def do_sum( |
|
environment: "Environment", |
|
iterable: "t.Union[t.AsyncIterable[V], t.Iterable[V]]", |
|
attribute: t.Optional[t.Union[str, int]] = None, |
|
start: V = 0, |
|
) -> V: |
|
rv = start |
|
|
|
if attribute is not None: |
|
func = make_attrgetter(environment, attribute) |
|
else: |
|
|
|
def func(x: V) -> V: |
|
return x |
|
|
|
async for item in auto_aiter(iterable): |
|
rv += func(item) |
|
|
|
return rv |
|
|
|
|
|
def sync_do_list(value: "t.Iterable[V]") -> "t.List[V]": |
|
"""Convert the value into a list. If it was a string the returned list |
|
will be a list of characters. |
|
""" |
|
return list(value) |
|
|
|
|
|
@async_variant(sync_do_list) |
|
async def do_list(value: "t.Union[t.AsyncIterable[V], t.Iterable[V]]") -> "t.List[V]": |
|
return await auto_to_list(value) |
|
|
|
|
|
def do_mark_safe(value: str) -> Markup: |
|
"""Mark the value as safe which means that in an environment with automatic |
|
escaping enabled this variable will not be escaped. |
|
""" |
|
return Markup(value) |
|
|
|
|
|
def do_mark_unsafe(value: str) -> str: |
|
"""Mark a value as unsafe. This is the reverse operation for :func:`safe`.""" |
|
return str(value) |
|
|
|
|
|
@typing.overload |
|
def do_reverse(value: str) -> str: |
|
... |
|
|
|
|
|
@typing.overload |
|
def do_reverse(value: "t.Iterable[V]") -> "t.Iterable[V]": |
|
... |
|
|
|
|
|
def do_reverse(value: t.Union[str, t.Iterable[V]]) -> t.Union[str, t.Iterable[V]]: |
|
"""Reverse the object or return an iterator that iterates over it the other |
|
way round. |
|
""" |
|
if isinstance(value, str): |
|
return value[::-1] |
|
|
|
try: |
|
return reversed(value) |
|
except TypeError: |
|
try: |
|
rv = list(value) |
|
rv.reverse() |
|
return rv |
|
except TypeError as e: |
|
raise FilterArgumentError("argument must be iterable") from e |
|
|
|
|
|
@pass_environment |
|
def do_attr( |
|
environment: "Environment", obj: t.Any, name: str |
|
) -> t.Union[Undefined, t.Any]: |
|
"""Get an attribute of an object. ``foo|attr("bar")`` works like |
|
``foo.bar`` just that always an attribute is returned and items are not |
|
looked up. |
|
|
|
See :ref:`Notes on subscriptions <notes-on-subscriptions>` for more details. |
|
""" |
|
try: |
|
name = str(name) |
|
except UnicodeError: |
|
pass |
|
else: |
|
try: |
|
value = getattr(obj, name) |
|
except AttributeError: |
|
pass |
|
else: |
|
if environment.sandboxed: |
|
environment = t.cast("SandboxedEnvironment", environment) |
|
|
|
if not environment.is_safe_attribute(obj, name, value): |
|
return environment.unsafe_undefined(obj, name) |
|
|
|
return value |
|
|
|
return environment.undefined(obj=obj, name=name) |
|
|
|
|
|
@typing.overload |
|
def sync_do_map( |
|
context: "Context", value: t.Iterable, name: str, *args: t.Any, **kwargs: t.Any |
|
) -> t.Iterable: |
|
... |
|
|
|
|
|
@typing.overload |
|
def sync_do_map( |
|
context: "Context", |
|
value: t.Iterable, |
|
*, |
|
attribute: str = ..., |
|
default: t.Optional[t.Any] = None, |
|
) -> t.Iterable: |
|
... |
|
|
|
|
|
@pass_context |
|
def sync_do_map( |
|
context: "Context", value: t.Iterable, *args: t.Any, **kwargs: t.Any |
|
) -> t.Iterable: |
|
"""Applies a filter on a sequence of objects or looks up an attribute. |
|
This is useful when dealing with lists of objects but you are really |
|
only interested in a certain value of it. |
|
|
|
The basic usage is mapping on an attribute. Imagine you have a list |
|
of users but you are only interested in a list of usernames: |
|
|
|
.. sourcecode:: jinja |
|
|
|
Users on this page: {{ users|map(attribute='username')|join(', ') }} |
|
|
|
You can specify a ``default`` value to use if an object in the list |
|
does not have the given attribute. |
|
|
|
.. sourcecode:: jinja |
|
|
|
{{ users|map(attribute="username", default="Anonymous")|join(", ") }} |
|
|
|
Alternatively you can let it invoke a filter by passing the name of the |
|
filter and the arguments afterwards. A good example would be applying a |
|
text conversion filter on a sequence: |
|
|
|
.. sourcecode:: jinja |
|
|
|
Users on this page: {{ titles|map('lower')|join(', ') }} |
|
|
|
Similar to a generator comprehension such as: |
|
|
|
.. code-block:: python |
|
|
|
(u.username for u in users) |
|
(getattr(u, "username", "Anonymous") for u in users) |
|
(do_lower(x) for x in titles) |
|
|
|
.. versionchanged:: 2.11.0 |
|
Added the ``default`` parameter. |
|
|
|
.. versionadded:: 2.7 |
|
""" |
|
if value: |
|
func = prepare_map(context, args, kwargs) |
|
|
|
for item in value: |
|
yield func(item) |
|
|
|
|
|
@typing.overload |
|
def do_map( |
|
context: "Context", |
|
value: t.Union[t.AsyncIterable, t.Iterable], |
|
name: str, |
|
*args: t.Any, |
|
**kwargs: t.Any, |
|
) -> t.Iterable: |
|
... |
|
|
|
|
|
@typing.overload |
|
def do_map( |
|
context: "Context", |
|
value: t.Union[t.AsyncIterable, t.Iterable], |
|
*, |
|
attribute: str = ..., |
|
default: t.Optional[t.Any] = None, |
|
) -> t.Iterable: |
|
... |
|
|
|
|
|
@async_variant(sync_do_map) |
|
async def do_map( |
|
context: "Context", |
|
value: t.Union[t.AsyncIterable, t.Iterable], |
|
*args: t.Any, |
|
**kwargs: t.Any, |
|
) -> t.AsyncIterable: |
|
if value: |
|
func = prepare_map(context, args, kwargs) |
|
|
|
async for item in auto_aiter(value): |
|
yield await auto_await(func(item)) |
|
|
|
|
|
@pass_context |
|
def sync_do_select( |
|
context: "Context", value: "t.Iterable[V]", *args: t.Any, **kwargs: t.Any |
|
) -> "t.Iterator[V]": |
|
"""Filters a sequence of objects by applying a test to each object, |
|
and only selecting the objects with the test succeeding. |
|
|
|
If no test is specified, each object will be evaluated as a boolean. |
|
|
|
Example usage: |
|
|
|
.. sourcecode:: jinja |
|
|
|
{{ numbers|select("odd") }} |
|
{{ numbers|select("odd") }} |
|
{{ numbers|select("divisibleby", 3) }} |
|
{{ numbers|select("lessthan", 42) }} |
|
{{ strings|select("equalto", "mystring") }} |
|
|
|
Similar to a generator comprehension such as: |
|
|
|
.. code-block:: python |
|
|
|
(n for n in numbers if test_odd(n)) |
|
(n for n in numbers if test_divisibleby(n, 3)) |
|
|
|
.. versionadded:: 2.7 |
|
""" |
|
return select_or_reject(context, value, args, kwargs, lambda x: x, False) |
|
|
|
|
|
@async_variant(sync_do_select) |
|
async def do_select( |
|
context: "Context", |
|
value: "t.Union[t.AsyncIterable[V], t.Iterable[V]]", |
|
*args: t.Any, |
|
**kwargs: t.Any, |
|
) -> "t.AsyncIterator[V]": |
|
return async_select_or_reject(context, value, args, kwargs, lambda x: x, False) |
|
|
|
|
|
@pass_context |
|
def sync_do_reject( |
|
context: "Context", value: "t.Iterable[V]", *args: t.Any, **kwargs: t.Any |
|
) -> "t.Iterator[V]": |
|
"""Filters a sequence of objects by applying a test to each object, |
|
and rejecting the objects with the test succeeding. |
|
|
|
If no test is specified, each object will be evaluated as a boolean. |
|
|
|
Example usage: |
|
|
|
.. sourcecode:: jinja |
|
|
|
{{ numbers|reject("odd") }} |
|
|
|
Similar to a generator comprehension such as: |
|
|
|
.. code-block:: python |
|
|
|
(n for n in numbers if not test_odd(n)) |
|
|
|
.. versionadded:: 2.7 |
|
""" |
|
return select_or_reject(context, value, args, kwargs, lambda x: not x, False) |
|
|
|
|
|
@async_variant(sync_do_reject) |
|
async def do_reject( |
|
context: "Context", |
|
value: "t.Union[t.AsyncIterable[V], t.Iterable[V]]", |
|
*args: t.Any, |
|
**kwargs: t.Any, |
|
) -> "t.AsyncIterator[V]": |
|
return async_select_or_reject(context, value, args, kwargs, lambda x: not x, False) |
|
|
|
|
|
@pass_context |
|
def sync_do_selectattr( |
|
context: "Context", value: "t.Iterable[V]", *args: t.Any, **kwargs: t.Any |
|
) -> "t.Iterator[V]": |
|
"""Filters a sequence of objects by applying a test to the specified |
|
attribute of each object, and only selecting the objects with the |
|
test succeeding. |
|
|
|
If no test is specified, the attribute's value will be evaluated as |
|
a boolean. |
|
|
|
Example usage: |
|
|
|
.. sourcecode:: jinja |
|
|
|
{{ users|selectattr("is_active") }} |
|
{{ users|selectattr("email", "none") }} |
|
|
|
Similar to a generator comprehension such as: |
|
|
|
.. code-block:: python |
|
|
|
(u for user in users if user.is_active) |
|
(u for user in users if test_none(user.email)) |
|
|
|
.. versionadded:: 2.7 |
|
""" |
|
return select_or_reject(context, value, args, kwargs, lambda x: x, True) |
|
|
|
|
|
@async_variant(sync_do_selectattr) |
|
async def do_selectattr( |
|
context: "Context", |
|
value: "t.Union[t.AsyncIterable[V], t.Iterable[V]]", |
|
*args: t.Any, |
|
**kwargs: t.Any, |
|
) -> "t.AsyncIterator[V]": |
|
return async_select_or_reject(context, value, args, kwargs, lambda x: x, True) |
|
|
|
|
|
@pass_context |
|
def sync_do_rejectattr( |
|
context: "Context", value: "t.Iterable[V]", *args: t.Any, **kwargs: t.Any |
|
) -> "t.Iterator[V]": |
|
"""Filters a sequence of objects by applying a test to the specified |
|
attribute of each object, and rejecting the objects with the test |
|
succeeding. |
|
|
|
If no test is specified, the attribute's value will be evaluated as |
|
a boolean. |
|
|
|
.. sourcecode:: jinja |
|
|
|
{{ users|rejectattr("is_active") }} |
|
{{ users|rejectattr("email", "none") }} |
|
|
|
Similar to a generator comprehension such as: |
|
|
|
.. code-block:: python |
|
|
|
(u for user in users if not user.is_active) |
|
(u for user in users if not test_none(user.email)) |
|
|
|
.. versionadded:: 2.7 |
|
""" |
|
return select_or_reject(context, value, args, kwargs, lambda x: not x, True) |
|
|
|
|
|
@async_variant(sync_do_rejectattr) |
|
async def do_rejectattr( |
|
context: "Context", |
|
value: "t.Union[t.AsyncIterable[V], t.Iterable[V]]", |
|
*args: t.Any, |
|
**kwargs: t.Any, |
|
) -> "t.AsyncIterator[V]": |
|
return async_select_or_reject(context, value, args, kwargs, lambda x: not x, True) |
|
|
|
|
|
@pass_eval_context |
|
def do_tojson( |
|
eval_ctx: "EvalContext", value: t.Any, indent: t.Optional[int] = None |
|
) -> Markup: |
|
"""Serialize an object to a string of JSON, and mark it safe to |
|
render in HTML. This filter is only for use in HTML documents. |
|
|
|
The returned string is safe to render in HTML documents and |
|
``<script>`` tags. The exception is in HTML attributes that are |
|
double quoted; either use single quotes or the ``|forceescape`` |
|
filter. |
|
|
|
:param value: The object to serialize to JSON. |
|
:param indent: The ``indent`` parameter passed to ``dumps``, for |
|
pretty-printing the value. |
|
|
|
.. versionadded:: 2.9 |
|
""" |
|
policies = eval_ctx.environment.policies |
|
dumps = policies["json.dumps_function"] |
|
kwargs = policies["json.dumps_kwargs"] |
|
|
|
if indent is not None: |
|
kwargs = kwargs.copy() |
|
kwargs["indent"] = indent |
|
|
|
return htmlsafe_json_dumps(value, dumps=dumps, **kwargs) |
|
|
|
|
|
def prepare_map( |
|
context: "Context", args: t.Tuple, kwargs: t.Dict[str, t.Any] |
|
) -> t.Callable[[t.Any], t.Any]: |
|
if not args and "attribute" in kwargs: |
|
attribute = kwargs.pop("attribute") |
|
default = kwargs.pop("default", None) |
|
|
|
if kwargs: |
|
raise FilterArgumentError( |
|
f"Unexpected keyword argument {next(iter(kwargs))!r}" |
|
) |
|
|
|
func = make_attrgetter(context.environment, attribute, default=default) |
|
else: |
|
try: |
|
name = args[0] |
|
args = args[1:] |
|
except LookupError: |
|
raise FilterArgumentError("map requires a filter argument") from None |
|
|
|
def func(item: t.Any) -> t.Any: |
|
return context.environment.call_filter( |
|
name, item, args, kwargs, context=context |
|
) |
|
|
|
return func |
|
|
|
|
|
def prepare_select_or_reject( |
|
context: "Context", |
|
args: t.Tuple, |
|
kwargs: t.Dict[str, t.Any], |
|
modfunc: t.Callable[[t.Any], t.Any], |
|
lookup_attr: bool, |
|
) -> t.Callable[[t.Any], t.Any]: |
|
if lookup_attr: |
|
try: |
|
attr = args[0] |
|
except LookupError: |
|
raise FilterArgumentError("Missing parameter for attribute name") from None |
|
|
|
transfunc = make_attrgetter(context.environment, attr) |
|
off = 1 |
|
else: |
|
off = 0 |
|
|
|
def transfunc(x: V) -> V: |
|
return x |
|
|
|
try: |
|
name = args[off] |
|
args = args[1 + off :] |
|
|
|
def func(item: t.Any) -> t.Any: |
|
return context.environment.call_test(name, item, args, kwargs) |
|
|
|
except LookupError: |
|
func = bool |
|
|
|
return lambda item: modfunc(func(transfunc(item))) |
|
|
|
|
|
def select_or_reject( |
|
context: "Context", |
|
value: "t.Iterable[V]", |
|
args: t.Tuple, |
|
kwargs: t.Dict[str, t.Any], |
|
modfunc: t.Callable[[t.Any], t.Any], |
|
lookup_attr: bool, |
|
) -> "t.Iterator[V]": |
|
if value: |
|
func = prepare_select_or_reject(context, args, kwargs, modfunc, lookup_attr) |
|
|
|
for item in value: |
|
if func(item): |
|
yield item |
|
|
|
|
|
async def async_select_or_reject( |
|
context: "Context", |
|
value: "t.Union[t.AsyncIterable[V], t.Iterable[V]]", |
|
args: t.Tuple, |
|
kwargs: t.Dict[str, t.Any], |
|
modfunc: t.Callable[[t.Any], t.Any], |
|
lookup_attr: bool, |
|
) -> "t.AsyncIterator[V]": |
|
if value: |
|
func = prepare_select_or_reject(context, args, kwargs, modfunc, lookup_attr) |
|
|
|
async for item in auto_aiter(value): |
|
if func(item): |
|
yield item |
|
|
|
|
|
FILTERS = { |
|
"abs": abs, |
|
"attr": do_attr, |
|
"batch": do_batch, |
|
"capitalize": do_capitalize, |
|
"center": do_center, |
|
"count": len, |
|
"d": do_default, |
|
"default": do_default, |
|
"dictsort": do_dictsort, |
|
"e": escape, |
|
"escape": escape, |
|
"filesizeformat": do_filesizeformat, |
|
"first": do_first, |
|
"float": do_float, |
|
"forceescape": do_forceescape, |
|
"format": do_format, |
|
"groupby": do_groupby, |
|
"indent": do_indent, |
|
"int": do_int, |
|
"join": do_join, |
|
"last": do_last, |
|
"length": len, |
|
"list": do_list, |
|
"lower": do_lower, |
|
"items": do_items, |
|
"map": do_map, |
|
"min": do_min, |
|
"max": do_max, |
|
"pprint": do_pprint, |
|
"random": do_random, |
|
"reject": do_reject, |
|
"rejectattr": do_rejectattr, |
|
"replace": do_replace, |
|
"reverse": do_reverse, |
|
"round": do_round, |
|
"safe": do_mark_safe, |
|
"select": do_select, |
|
"selectattr": do_selectattr, |
|
"slice": do_slice, |
|
"sort": do_sort, |
|
"string": soft_str, |
|
"striptags": do_striptags, |
|
"sum": do_sum, |
|
"title": do_title, |
|
"trim": do_trim, |
|
"truncate": do_truncate, |
|
"unique": do_unique, |
|
"upper": do_upper, |
|
"urlencode": do_urlencode, |
|
"urlize": do_urlize, |
|
"wordcount": do_wordcount, |
|
"wordwrap": do_wordwrap, |
|
"xmlattr": do_xmlattr, |
|
"tojson": do_tojson, |
|
} |
|
|