Index: mojo/public/third_party/jinja2/filters.py |
diff --git a/mojo/public/third_party/jinja2/filters.py b/mojo/public/third_party/jinja2/filters.py |
new file mode 100644 |
index 0000000000000000000000000000000000000000..fd0db04aa41026500910347cf401fe620c556188 |
--- /dev/null |
+++ b/mojo/public/third_party/jinja2/filters.py |
@@ -0,0 +1,987 @@ |
+# -*- coding: utf-8 -*- |
+""" |
+ jinja2.filters |
+ ~~~~~~~~~~~~~~ |
+ |
+ Bundled jinja filters. |
+ |
+ :copyright: (c) 2010 by the Jinja Team. |
+ :license: BSD, see LICENSE for more details. |
+""" |
+import re |
+import math |
+ |
+from random import choice |
+from operator import itemgetter |
+from itertools import groupby |
+from jinja2.utils import Markup, escape, pformat, urlize, soft_unicode, \ |
+ unicode_urlencode |
+from jinja2.runtime import Undefined |
+from jinja2.exceptions import FilterArgumentError |
+from jinja2._compat import next, imap, string_types, text_type, iteritems |
+ |
+ |
+_word_re = re.compile(r'\w+(?u)') |
+ |
+ |
+def contextfilter(f): |
+ """Decorator for marking context dependent filters. The current |
+ :class:`Context` will be passed as first argument. |
+ """ |
+ f.contextfilter = True |
+ return f |
+ |
+ |
+def evalcontextfilter(f): |
+ """Decorator for marking eval-context dependent filters. An eval |
+ context object is passed as first argument. For more information |
+ about the eval context, see :ref:`eval-context`. |
+ |
+ .. versionadded:: 2.4 |
+ """ |
+ f.evalcontextfilter = True |
+ return f |
+ |
+ |
+def environmentfilter(f): |
+ """Decorator for marking evironment dependent filters. The current |
+ :class:`Environment` is passed to the filter as first argument. |
+ """ |
+ f.environmentfilter = True |
+ return f |
+ |
+ |
+def make_attrgetter(environment, attribute): |
+ """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. |
+ """ |
+ if not isinstance(attribute, string_types) \ |
+ or ('.' not in attribute and not attribute.isdigit()): |
+ return lambda x: environment.getitem(x, attribute) |
+ attribute = attribute.split('.') |
+ def attrgetter(item): |
+ for part in attribute: |
+ if part.isdigit(): |
+ part = int(part) |
+ item = environment.getitem(item, part) |
+ return item |
+ return attrgetter |
+ |
+ |
+def do_forceescape(value): |
+ """Enforce HTML escaping. This will probably double escape variables.""" |
+ if hasattr(value, '__html__'): |
+ value = value.__html__() |
+ return escape(text_type(value)) |
+ |
+ |
+def do_urlencode(value): |
+ """Escape strings for use in URLs (uses UTF-8 encoding). It accepts both |
+ dictionaries and regular strings as well as pairwise iterables. |
+ |
+ .. versionadded:: 2.7 |
+ """ |
+ itemiter = None |
+ if isinstance(value, dict): |
+ itemiter = iteritems(value) |
+ elif not isinstance(value, string_types): |
+ try: |
+ itemiter = iter(value) |
+ except TypeError: |
+ pass |
+ if itemiter is None: |
+ return unicode_urlencode(value) |
+ return u'&'.join(unicode_urlencode(k) + '=' + |
+ unicode_urlencode(v) for k, v in itemiter) |
+ |
+ |
+@evalcontextfilter |
+def do_replace(eval_ctx, s, old, new, count=None): |
+ """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 text_type(s).replace(text_type(old), text_type(new), count) |
+ if hasattr(old, '__html__') or hasattr(new, '__html__') and \ |
+ not hasattr(s, '__html__'): |
+ s = escape(s) |
+ else: |
+ s = soft_unicode(s) |
+ return s.replace(soft_unicode(old), soft_unicode(new), count) |
+ |
+ |
+def do_upper(s): |
+ """Convert a value to uppercase.""" |
+ return soft_unicode(s).upper() |
+ |
+ |
+def do_lower(s): |
+ """Convert a value to lowercase.""" |
+ return soft_unicode(s).lower() |
+ |
+ |
+@evalcontextfilter |
+def do_xmlattr(_eval_ctx, d, autospace=True): |
+ """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 = u' '.join( |
+ u'%s="%s"' % (escape(key), escape(value)) |
+ for key, value in iteritems(d) |
+ if value is not None and not isinstance(value, Undefined) |
+ ) |
+ if autospace and rv: |
+ rv = u' ' + rv |
+ if _eval_ctx.autoescape: |
+ rv = Markup(rv) |
+ return rv |
+ |
+ |
+def do_capitalize(s): |
+ """Capitalize a value. The first character will be uppercase, all others |
+ lowercase. |
+ """ |
+ return soft_unicode(s).capitalize() |
+ |
+ |
+def do_title(s): |
+ """Return a titlecased version of the value. I.e. words will start with |
+ uppercase letters, all remaining characters are lowercase. |
+ """ |
+ rv = [] |
+ for item in re.compile(r'([-\s]+)(?u)').split(s): |
+ if not item: |
+ continue |
+ rv.append(item[0].upper() + item[1:].lower()) |
+ return ''.join(rv) |
+ |
+ |
+def do_dictsort(value, case_sensitive=False, by='key'): |
+ """Sort a dict and yield (key, value) pairs. Because python dicts are |
+ unsorted you may want to use this function to order them by either |
+ key or value: |
+ |
+ .. sourcecode:: jinja |
+ |
+ {% for item in mydict|dictsort %} |
+ sort the dict by key, case insensitive |
+ |
+ {% for item in mydict|dictsort(true) %} |
+ sort the dict by key, case sensitive |
+ |
+ {% for item in mydict|dictsort(false, 'value') %} |
+ sort the dict by key, case insensitive, sorted |
+ normally and ordered by value. |
+ """ |
+ 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): |
+ value = item[pos] |
+ if isinstance(value, string_types) and not case_sensitive: |
+ value = value.lower() |
+ return value |
+ |
+ return sorted(value.items(), key=sort_func) |
+ |
+ |
+@environmentfilter |
+def do_sort(environment, value, reverse=False, case_sensitive=False, |
+ attribute=None): |
+ """Sort an iterable. Per default it sorts ascending, if you pass it |
+ true as first argument it will reverse the sorting. |
+ |
+ If the iterable is made of strings the third parameter can be used to |
+ control the case sensitiveness of the comparison which is disabled by |
+ default. |
+ |
+ .. sourcecode:: jinja |
+ |
+ {% for item in iterable|sort %} |
+ ... |
+ {% endfor %} |
+ |
+ It is also possible to sort by an attribute (for example to sort |
+ by the date of an object) by specifying the `attribute` parameter: |
+ |
+ .. sourcecode:: jinja |
+ |
+ {% for item in iterable|sort(attribute='date') %} |
+ ... |
+ {% endfor %} |
+ |
+ .. versionchanged:: 2.6 |
+ The `attribute` parameter was added. |
+ """ |
+ if not case_sensitive: |
+ def sort_func(item): |
+ if isinstance(item, string_types): |
+ item = item.lower() |
+ return item |
+ else: |
+ sort_func = None |
+ if attribute is not None: |
+ getter = make_attrgetter(environment, attribute) |
+ def sort_func(item, processor=sort_func or (lambda x: x)): |
+ return processor(getter(item)) |
+ return sorted(value, key=sort_func, reverse=reverse) |
+ |
+ |
+def do_default(value, default_value=u'', boolean=False): |
+ """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) }} |
+ """ |
+ if isinstance(value, Undefined) or (boolean and not value): |
+ return default_value |
+ return value |
+ |
+ |
+@evalcontextfilter |
+def do_join(eval_ctx, value, d=u'', attribute=None): |
+ """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 = imap(make_attrgetter(eval_ctx.environment, attribute), value) |
+ |
+ # no automatic escaping? joining is a lot eaiser then |
+ if not eval_ctx.autoescape: |
+ return text_type(d).join(imap(text_type, 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] = text_type(item) |
+ if do_escape: |
+ d = escape(d) |
+ else: |
+ d = text_type(d) |
+ return d.join(value) |
+ |
+ # no html involved, to normal joining |
+ return soft_unicode(d).join(imap(soft_unicode, value)) |
+ |
+ |
+def do_center(value, width=80): |
+ """Centers the value in a field of a given width.""" |
+ return text_type(value).center(width) |
+ |
+ |
+@environmentfilter |
+def do_first(environment, seq): |
+ """Return the first item of a sequence.""" |
+ try: |
+ return next(iter(seq)) |
+ except StopIteration: |
+ return environment.undefined('No first item, sequence was empty.') |
+ |
+ |
+@environmentfilter |
+def do_last(environment, seq): |
+ """Return the last item of a sequence.""" |
+ try: |
+ return next(iter(reversed(seq))) |
+ except StopIteration: |
+ return environment.undefined('No last item, sequence was empty.') |
+ |
+ |
+@environmentfilter |
+def do_random(environment, seq): |
+ """Return a random item from the sequence.""" |
+ try: |
+ return choice(seq) |
+ except IndexError: |
+ return environment.undefined('No random item, sequence was empty.') |
+ |
+ |
+def do_filesizeformat(value, binary=False): |
+ """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 = binary and 1024 or 1000 |
+ prefixes = [ |
+ (binary and 'KiB' or 'kB'), |
+ (binary and 'MiB' or 'MB'), |
+ (binary and 'GiB' or 'GB'), |
+ (binary and 'TiB' or 'TB'), |
+ (binary and 'PiB' or 'PB'), |
+ (binary and 'EiB' or 'EB'), |
+ (binary and 'ZiB' or 'ZB'), |
+ (binary and 'YiB' or 'YB') |
+ ] |
+ if bytes == 1: |
+ return '1 Byte' |
+ elif bytes < base: |
+ return '%d Bytes' % bytes |
+ else: |
+ for i, prefix in enumerate(prefixes): |
+ unit = base ** (i + 2) |
+ if bytes < unit: |
+ return '%.1f %s' % ((base * bytes / unit), prefix) |
+ return '%.1f %s' % ((base * bytes / unit), prefix) |
+ |
+ |
+def do_pprint(value, verbose=False): |
+ """Pretty print a variable. Useful for debugging. |
+ |
+ With Jinja 1.2 onwards you can pass it a parameter. If this parameter |
+ is truthy the output will be more verbose (this requires `pretty`) |
+ """ |
+ return pformat(value, verbose=verbose) |
+ |
+ |
+@evalcontextfilter |
+def do_urlize(eval_ctx, value, trim_url_limit=None, nofollow=False): |
+ """Converts URLs in plain text into clickable links. |
+ |
+ If you pass the filter an additional integer it will shorten the urls |
+ to that number. Also a third argument exists that makes the urls |
+ "nofollow": |
+ |
+ .. sourcecode:: jinja |
+ |
+ {{ mytext|urlize(40, true) }} |
+ links are shortened to 40 chars and defined with rel="nofollow" |
+ """ |
+ rv = urlize(value, trim_url_limit, nofollow) |
+ if eval_ctx.autoescape: |
+ rv = Markup(rv) |
+ return rv |
+ |
+ |
+def do_indent(s, width=4, indentfirst=False): |
+ """Return a copy of the passed string, each line indented by |
+ 4 spaces. The first line is not indented. If you want to |
+ change the number of spaces or indent the first line too |
+ you can pass additional parameters to the filter: |
+ |
+ .. sourcecode:: jinja |
+ |
+ {{ mytext|indent(2, true) }} |
+ indent by two spaces and indent the first line too. |
+ """ |
+ indention = u' ' * width |
+ rv = (u'\n' + indention).join(s.splitlines()) |
+ if indentfirst: |
+ rv = indention + rv |
+ return rv |
+ |
+ |
+def do_truncate(s, length=255, killwords=False, end='...'): |
+ """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. |
+ |
+ .. sourcecode:: jinja |
+ |
+ {{ "foo bar"|truncate(5) }} |
+ -> "foo ..." |
+ {{ "foo bar"|truncate(5, True) }} |
+ -> "foo b..." |
+ """ |
+ if len(s) <= length: |
+ return s |
+ elif killwords: |
+ return s[:length] + end |
+ words = s.split(' ') |
+ result = [] |
+ m = 0 |
+ for word in words: |
+ m += len(word) + 1 |
+ if m > length: |
+ break |
+ result.append(word) |
+ result.append(end) |
+ return u' '.join(result) |
+ |
+@environmentfilter |
+def do_wordwrap(environment, s, width=79, break_long_words=True, |
+ wrapstring=None): |
+ """ |
+ Return a copy of the string passed to the filter wrapped after |
+ ``79`` characters. You can override this default using the first |
+ parameter. If you set the second parameter to `false` Jinja will not |
+ split words apart if they are longer than `width`. By default, the newlines |
+ will be the default newlines for the environment, but this can be changed |
+ using the wrapstring keyword argument. |
+ |
+ .. versionadded:: 2.7 |
+ Added support for the `wrapstring` parameter. |
+ """ |
+ if not wrapstring: |
+ wrapstring = environment.newline_sequence |
+ import textwrap |
+ return wrapstring.join(textwrap.wrap(s, width=width, expand_tabs=False, |
+ replace_whitespace=False, |
+ break_long_words=break_long_words)) |
+ |
+ |
+def do_wordcount(s): |
+ """Count the words in that string.""" |
+ return len(_word_re.findall(s)) |
+ |
+ |
+def do_int(value, default=0): |
+ """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. |
+ """ |
+ try: |
+ 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, default=0.0): |
+ """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, *args, **kwargs): |
+ """ |
+ Apply python string formatting on an object: |
+ |
+ .. sourcecode:: jinja |
+ |
+ {{ "%s - %s"|format("Hello?", "Foo!") }} |
+ -> Hello? - Foo! |
+ """ |
+ if args and kwargs: |
+ raise FilterArgumentError('can\'t handle positional and keyword ' |
+ 'arguments at the same time') |
+ return soft_unicode(value) % (kwargs or args) |
+ |
+ |
+def do_trim(value): |
+ """Strip leading and trailing whitespace.""" |
+ return soft_unicode(value).strip() |
+ |
+ |
+def do_striptags(value): |
+ """Strip SGML/XML tags and replace adjacent whitespace by one space. |
+ """ |
+ if hasattr(value, '__html__'): |
+ value = value.__html__() |
+ return Markup(text_type(value)).striptags() |
+ |
+ |
+def do_slice(value, slices, fill_with=None): |
+ """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="columwrapper"> |
+ {%- 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 |
+ |
+ |
+def do_batch(value, linecount, fill_with=None): |
+ """ |
+ 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> |
+ """ |
+ result = [] |
+ tmp = [] |
+ 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, precision=0, method='common'): |
+ """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 not method 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 func(value * (10 ** precision)) / (10 ** precision) |
+ |
+ |
+@environmentfilter |
+def do_groupby(environment, value, attribute): |
+ """Group a sequence of objects by a common attribute. |
+ |
+ If you for example have a list of dicts or objects that represent persons |
+ with `gender`, `first_name` and `last_name` attributes and you want to |
+ group all users by genders you can do something like the following |
+ snippet: |
+ |
+ .. sourcecode:: html+jinja |
+ |
+ <ul> |
+ {% for group in persons|groupby('gender') %} |
+ <li>{{ group.grouper }}<ul> |
+ {% for person in group.list %} |
+ <li>{{ person.first_name }} {{ person.last_name }}</li> |
+ {% endfor %}</ul></li> |
+ {% endfor %} |
+ </ul> |
+ |
+ Additionally it's possible to use tuple unpacking for the grouper and |
+ list: |
+ |
+ .. sourcecode:: html+jinja |
+ |
+ <ul> |
+ {% for grouper, list in persons|groupby('gender') %} |
+ ... |
+ {% endfor %} |
+ </ul> |
+ |
+ As you can see the item we're grouping by is stored in the `grouper` |
+ attribute and the `list` contains all the objects that have this grouper |
+ in common. |
+ |
+ .. versionchanged:: 2.6 |
+ It's now possible to use dotted notation to group by the child |
+ attribute of another attribute. |
+ """ |
+ expr = make_attrgetter(environment, attribute) |
+ return sorted(map(_GroupTuple, groupby(sorted(value, key=expr), expr))) |
+ |
+ |
+class _GroupTuple(tuple): |
+ __slots__ = () |
+ grouper = property(itemgetter(0)) |
+ list = property(itemgetter(1)) |
+ |
+ def __new__(cls, xxx_todo_changeme): |
+ (key, value) = xxx_todo_changeme |
+ return tuple.__new__(cls, (key, list(value))) |
+ |
+ |
+@environmentfilter |
+def do_sum(environment, iterable, attribute=None, start=0): |
+ """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 suming up over |
+ attributes. Also the `start` parameter was moved on to the right. |
+ """ |
+ if attribute is not None: |
+ iterable = imap(make_attrgetter(environment, attribute), iterable) |
+ return sum(iterable, start) |
+ |
+ |
+def do_list(value): |
+ """Convert the value into a list. If it was a string the returned list |
+ will be a list of characters. |
+ """ |
+ return list(value) |
+ |
+ |
+def do_mark_safe(value): |
+ """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): |
+ """Mark a value as unsafe. This is the reverse operation for :func:`safe`.""" |
+ return text_type(value) |
+ |
+ |
+def do_reverse(value): |
+ """Reverse the object or return an iterator the iterates over it the other |
+ way round. |
+ """ |
+ if isinstance(value, string_types): |
+ return value[::-1] |
+ try: |
+ return reversed(value) |
+ except TypeError: |
+ try: |
+ rv = list(value) |
+ rv.reverse() |
+ return rv |
+ except TypeError: |
+ raise FilterArgumentError('argument must be iterable') |
+ |
+ |
+@environmentfilter |
+def do_attr(environment, obj, name): |
+ """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 and not \ |
+ environment.is_safe_attribute(obj, name, value): |
+ return environment.unsafe_undefined(obj, name) |
+ return value |
+ return environment.undefined(obj=obj, name=name) |
+ |
+ |
+@contextfilter |
+def do_map(*args, **kwargs): |
+ """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(', ') }} |
+ |
+ 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(', ') }} |
+ |
+ .. versionadded:: 2.7 |
+ """ |
+ context = args[0] |
+ seq = args[1] |
+ |
+ if len(args) == 2 and 'attribute' in kwargs: |
+ attribute = kwargs.pop('attribute') |
+ if kwargs: |
+ raise FilterArgumentError('Unexpected keyword argument %r' % |
+ next(iter(kwargs))) |
+ func = make_attrgetter(context.environment, attribute) |
+ else: |
+ try: |
+ name = args[2] |
+ args = args[3:] |
+ except LookupError: |
+ raise FilterArgumentError('map requires a filter argument') |
+ func = lambda item: context.environment.call_filter( |
+ name, item, args, kwargs, context=context) |
+ |
+ if seq: |
+ for item in seq: |
+ yield func(item) |
+ |
+ |
+@contextfilter |
+def do_select(*args, **kwargs): |
+ """Filters a sequence of objects by appying a test to either the object |
+ or the attribute and only selecting the ones with the test succeeding. |
+ |
+ Example usage: |
+ |
+ .. sourcecode:: jinja |
+ |
+ {{ numbers|select("odd") }} |
+ |
+ .. versionadded:: 2.7 |
+ """ |
+ return _select_or_reject(args, kwargs, lambda x: x, False) |
+ |
+ |
+@contextfilter |
+def do_reject(*args, **kwargs): |
+ """Filters a sequence of objects by appying a test to either the object |
+ or the attribute and rejecting the ones with the test succeeding. |
+ |
+ Example usage: |
+ |
+ .. sourcecode:: jinja |
+ |
+ {{ numbers|reject("odd") }} |
+ |
+ .. versionadded:: 2.7 |
+ """ |
+ return _select_or_reject(args, kwargs, lambda x: not x, False) |
+ |
+ |
+@contextfilter |
+def do_selectattr(*args, **kwargs): |
+ """Filters a sequence of objects by appying a test to either the object |
+ or the attribute and only selecting the ones with the test succeeding. |
+ |
+ Example usage: |
+ |
+ .. sourcecode:: jinja |
+ |
+ {{ users|selectattr("is_active") }} |
+ {{ users|selectattr("email", "none") }} |
+ |
+ .. versionadded:: 2.7 |
+ """ |
+ return _select_or_reject(args, kwargs, lambda x: x, True) |
+ |
+ |
+@contextfilter |
+def do_rejectattr(*args, **kwargs): |
+ """Filters a sequence of objects by appying a test to either the object |
+ or the attribute and rejecting the ones with the test succeeding. |
+ |
+ .. sourcecode:: jinja |
+ |
+ {{ users|rejectattr("is_active") }} |
+ {{ users|rejectattr("email", "none") }} |
+ |
+ .. versionadded:: 2.7 |
+ """ |
+ return _select_or_reject(args, kwargs, lambda x: not x, True) |
+ |
+ |
+def _select_or_reject(args, kwargs, modfunc, lookup_attr): |
+ context = args[0] |
+ seq = args[1] |
+ if lookup_attr: |
+ try: |
+ attr = args[2] |
+ except LookupError: |
+ raise FilterArgumentError('Missing parameter for attribute name') |
+ transfunc = make_attrgetter(context.environment, attr) |
+ off = 1 |
+ else: |
+ off = 0 |
+ transfunc = lambda x: x |
+ |
+ try: |
+ name = args[2 + off] |
+ args = args[3 + off:] |
+ func = lambda item: context.environment.call_test( |
+ name, item, args, kwargs) |
+ except LookupError: |
+ func = bool |
+ |
+ if seq: |
+ for item in seq: |
+ if modfunc(func(transfunc(item))): |
+ yield item |
+ |
+ |
+FILTERS = { |
+ 'attr': do_attr, |
+ 'replace': do_replace, |
+ 'upper': do_upper, |
+ 'lower': do_lower, |
+ 'escape': escape, |
+ 'e': escape, |
+ 'forceescape': do_forceescape, |
+ 'capitalize': do_capitalize, |
+ 'title': do_title, |
+ 'default': do_default, |
+ 'd': do_default, |
+ 'join': do_join, |
+ 'count': len, |
+ 'dictsort': do_dictsort, |
+ 'sort': do_sort, |
+ 'length': len, |
+ 'reverse': do_reverse, |
+ 'center': do_center, |
+ 'indent': do_indent, |
+ 'title': do_title, |
+ 'capitalize': do_capitalize, |
+ 'first': do_first, |
+ 'last': do_last, |
+ 'map': do_map, |
+ 'random': do_random, |
+ 'reject': do_reject, |
+ 'rejectattr': do_rejectattr, |
+ 'filesizeformat': do_filesizeformat, |
+ 'pprint': do_pprint, |
+ 'truncate': do_truncate, |
+ 'wordwrap': do_wordwrap, |
+ 'wordcount': do_wordcount, |
+ 'int': do_int, |
+ 'float': do_float, |
+ 'string': soft_unicode, |
+ 'list': do_list, |
+ 'urlize': do_urlize, |
+ 'format': do_format, |
+ 'trim': do_trim, |
+ 'striptags': do_striptags, |
+ 'select': do_select, |
+ 'selectattr': do_selectattr, |
+ 'slice': do_slice, |
+ 'batch': do_batch, |
+ 'sum': do_sum, |
+ 'abs': abs, |
+ 'round': do_round, |
+ 'groupby': do_groupby, |
+ 'safe': do_mark_safe, |
+ 'xmlattr': do_xmlattr, |
+ 'urlencode': do_urlencode |
+} |