| """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 # noqa: F401 |
| |
| 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 # type: ignore |
| |
| 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 = "", # type: ignore |
| 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) |
| |
| # no automatic escaping? joining is a lot easier then |
| if not eval_ctx.autoescape: |
| return str(d).join(map(str, value)) |
| |
| # if the delimiter doesn't have an html representation we check |
| # if any of the items has. If yes we do a coercion to Markup |
| 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) |
| |
| # no html involved, to normal joining |
| return soft_str(d).join(map(soft_str, value)) |
| |
| |
| @async_variant(sync_do_join) # type: ignore |
| 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) # type: ignore |
| 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.") |
| |
| |
| # No async do_last, it may not be safe in async mode. |
| |
| |
| @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 # this quirk is necessary for splitlines method |
| |
| 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 |
| |
| # textwrap.wrap doesn't consider existing newlines when wrapping. |
| # If the string has a newline before width, wrap will still insert |
| # a newline at width, resulting in a short line. Instead, split and |
| # wrap each paragraph individually. |
| 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): |
| # this quirk is necessary so that "42.23"|int gives 42. |
| 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) # type: ignore |
| 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 |
| |
| # Use the regular tuple repr to hide this subclass if users print |
| # out the value during debugging. |
| 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: |
| # Return the real key from the first value instead of the lowercase key. |
| 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) # type: ignore |
| 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: |
| # Return the real key from the first value instead of the lowercase key. |
| 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, # type: ignore |
| ) -> 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) # type: ignore[no-any-return, call-overload] |
| |
| |
| @async_variant(sync_do_sum) # type: ignore |
| 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, # type: ignore |
| ) -> 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) # type: ignore |
| 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) # type: ignore |
| 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) # type: ignore |
| 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) # type: ignore |
| 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) # type: ignore |
| 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) # type: ignore |
| 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) # type: ignore |
| 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 # type: ignore |
| |
| 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, |
| } |