1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840 |
- """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,
- }
|