| 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
|
| +}
|
|
|