| Index: third_party/jinja2/filters.py
|
| diff --git a/third_party/jinja2/filters.py b/third_party/jinja2/filters.py
|
| index 1ef47f95161b7ef8f07dcd79bc86dc3582f6c985..fd0db04aa41026500910347cf401fe620c556188 100644
|
| --- a/third_party/jinja2/filters.py
|
| +++ b/third_party/jinja2/filters.py
|
| @@ -10,12 +10,15 @@
|
| """
|
| import re
|
| import math
|
| +
|
| from random import choice
|
| from operator import itemgetter
|
| -from itertools import imap, groupby
|
| -from jinja2.utils import Markup, escape, pformat, urlize, soft_unicode
|
| +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, SecurityError
|
| +from jinja2.exceptions import FilterArgumentError
|
| +from jinja2._compat import next, imap, string_types, text_type, iteritems
|
|
|
|
|
| _word_re = re.compile(r'\w+(?u)')
|
| @@ -51,13 +54,17 @@ def environmentfilter(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.
|
| + to access attributes of attributes. Integer parts in paths are
|
| + looked up as integers.
|
| """
|
| - if not isinstance(attribute, basestring) or '.' not in attribute:
|
| + 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
|
| @@ -67,7 +74,27 @@ def do_forceescape(value):
|
| """Enforce HTML escaping. This will probably double escape variables."""
|
| if hasattr(value, '__html__'):
|
| value = value.__html__()
|
| - return escape(unicode(value))
|
| + 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
|
| @@ -89,7 +116,7 @@ def do_replace(eval_ctx, s, old, new, count=None):
|
| if count is None:
|
| count = -1
|
| if not eval_ctx.autoescape:
|
| - return unicode(s).replace(unicode(old), unicode(new), count)
|
| + 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)
|
| @@ -134,7 +161,7 @@ def do_xmlattr(_eval_ctx, d, autospace=True):
|
| """
|
| rv = u' '.join(
|
| u'%s="%s"' % (escape(key), escape(value))
|
| - for key, value in d.iteritems()
|
| + for key, value in iteritems(d)
|
| if value is not None and not isinstance(value, Undefined)
|
| )
|
| if autospace and rv:
|
| @@ -155,7 +182,12 @@ def do_title(s):
|
| """Return a titlecased version of the value. I.e. words will start with
|
| uppercase letters, all remaining characters are lowercase.
|
| """
|
| - return soft_unicode(s).title()
|
| + 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'):
|
| @@ -168,7 +200,7 @@ def do_dictsort(value, case_sensitive=False, by='key'):
|
| {% for item in mydict|dictsort %}
|
| sort the dict by key, case insensitive
|
|
|
| - {% for item in mydict|dicsort(true) %}
|
| + {% for item in mydict|dictsort(true) %}
|
| sort the dict by key, case sensitive
|
|
|
| {% for item in mydict|dictsort(false, 'value') %}
|
| @@ -184,7 +216,7 @@ def do_dictsort(value, case_sensitive=False, by='key'):
|
| '"key" or "value"')
|
| def sort_func(item):
|
| value = item[pos]
|
| - if isinstance(value, basestring) and not case_sensitive:
|
| + if isinstance(value, string_types) and not case_sensitive:
|
| value = value.lower()
|
| return value
|
|
|
| @@ -221,7 +253,7 @@ def do_sort(environment, value, reverse=False, case_sensitive=False,
|
| """
|
| if not case_sensitive:
|
| def sort_func(item):
|
| - if isinstance(item, basestring):
|
| + if isinstance(item, string_types):
|
| item = item.lower()
|
| return item
|
| else:
|
| @@ -250,7 +282,7 @@ def do_default(value, default_value=u'', boolean=False):
|
|
|
| {{ ''|default('the string was empty', true) }}
|
| """
|
| - if (boolean and not value) or isinstance(value, Undefined):
|
| + if isinstance(value, Undefined) or (boolean and not value):
|
| return default_value
|
| return value
|
|
|
| @@ -283,7 +315,7 @@ def do_join(eval_ctx, value, d=u'', attribute=None):
|
|
|
| # no automatic escaping? joining is a lot eaiser then
|
| if not eval_ctx.autoescape:
|
| - return unicode(d).join(imap(unicode, value))
|
| + 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
|
| @@ -294,11 +326,11 @@ def do_join(eval_ctx, value, d=u'', attribute=None):
|
| if hasattr(item, '__html__'):
|
| do_escape = True
|
| else:
|
| - value[idx] = unicode(item)
|
| + value[idx] = text_type(item)
|
| if do_escape:
|
| d = escape(d)
|
| else:
|
| - d = unicode(d)
|
| + d = text_type(d)
|
| return d.join(value)
|
|
|
| # no html involved, to normal joining
|
| @@ -307,14 +339,14 @@ def do_join(eval_ctx, value, d=u'', attribute=None):
|
|
|
| def do_center(value, width=80):
|
| """Centers the value in a field of a given width."""
|
| - return unicode(value).center(width)
|
| + return text_type(value).center(width)
|
|
|
|
|
| @environmentfilter
|
| def do_first(environment, seq):
|
| """Return the first item of a sequence."""
|
| try:
|
| - return iter(seq).next()
|
| + return next(iter(seq))
|
| except StopIteration:
|
| return environment.undefined('No first item, sequence was empty.')
|
|
|
| @@ -323,7 +355,7 @@ def do_first(environment, seq):
|
| def do_last(environment, seq):
|
| """Return the last item of a sequence."""
|
| try:
|
| - return iter(reversed(seq)).next()
|
| + return next(iter(reversed(seq)))
|
| except StopIteration:
|
| return environment.undefined('No last item, sequence was empty.')
|
|
|
| @@ -346,25 +378,25 @@ def do_filesizeformat(value, binary=False):
|
| 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")
|
| + (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"
|
| + return '1 Byte'
|
| elif bytes < base:
|
| - return "%d Bytes" % bytes
|
| + return '%d Bytes' % bytes
|
| else:
|
| for i, prefix in enumerate(prefixes):
|
| - unit = base * base ** (i + 1)
|
| + unit = base ** (i + 2)
|
| if bytes < unit:
|
| - return "%.1f %s" % ((bytes / unit), prefix)
|
| - return "%.1f %s" % ((bytes / unit), prefix)
|
| + return '%.1f %s' % ((base * bytes / unit), prefix)
|
| + return '%.1f %s' % ((base * bytes / unit), prefix)
|
|
|
|
|
| def do_pprint(value, verbose=False):
|
| @@ -417,16 +449,17 @@ 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 try to save the last word. If the text was in fact
|
| + 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::
|
| + .. sourcecode:: jinja
|
|
|
| - {{ mytext|truncate(300, false, '»') }}
|
| - truncate mytext to 300 chars, don't split up words, use a
|
| - right pointing double arrow as ellipsis sign.
|
| + {{ "foo bar"|truncate(5) }}
|
| + -> "foo ..."
|
| + {{ "foo bar"|truncate(5, True) }}
|
| + -> "foo b..."
|
| """
|
| if len(s) <= length:
|
| return s
|
| @@ -444,15 +477,23 @@ def do_truncate(s, length=255, killwords=False, end='...'):
|
| return u' '.join(result)
|
|
|
| @environmentfilter
|
| -def do_wordwrap(environment, s, width=79, break_long_words=True):
|
| +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`.
|
| + 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 environment.newline_sequence.join(textwrap.wrap(s, width=width, expand_tabs=False,
|
| + return wrapstring.join(textwrap.wrap(s, width=width, expand_tabs=False,
|
| replace_whitespace=False,
|
| break_long_words=break_long_words))
|
|
|
| @@ -513,7 +554,7 @@ def do_striptags(value):
|
| """
|
| if hasattr(value, '__html__'):
|
| value = value.__html__()
|
| - return Markup(unicode(value)).striptags()
|
| + return Markup(text_type(value)).striptags()
|
|
|
|
|
| def do_slice(value, slices, fill_with=None):
|
| @@ -541,7 +582,7 @@ def do_slice(value, slices, fill_with=None):
|
| items_per_slice = length // slices
|
| slices_with_extra = length % slices
|
| offset = 0
|
| - for slice_number in xrange(slices):
|
| + for slice_number in range(slices):
|
| start = offset + slice_number * items_per_slice
|
| if slice_number < slices_with_extra:
|
| offset += 1
|
| @@ -557,7 +598,7 @@ 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 missing items. See this example:
|
| + is used to fill up missing items. See this example:
|
|
|
| .. sourcecode:: html+jinja
|
|
|
| @@ -666,7 +707,8 @@ class _GroupTuple(tuple):
|
| grouper = property(itemgetter(0))
|
| list = property(itemgetter(1))
|
|
|
| - def __new__(cls, (key, value)):
|
| + def __new__(cls, xxx_todo_changeme):
|
| + (key, value) = xxx_todo_changeme
|
| return tuple.__new__(cls, (key, list(value)))
|
|
|
|
|
| @@ -707,14 +749,14 @@ def do_mark_safe(value):
|
|
|
| def do_mark_unsafe(value):
|
| """Mark a value as unsafe. This is the reverse operation for :func:`safe`."""
|
| - return unicode(value)
|
| + 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, basestring):
|
| + if isinstance(value, string_types):
|
| return value[::-1]
|
| try:
|
| return reversed(value)
|
| @@ -752,6 +794,144 @@ def do_attr(environment, obj, name):
|
| 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,
|
| @@ -776,7 +956,10 @@ FILTERS = {
|
| '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,
|
| @@ -790,6 +973,8 @@ FILTERS = {
|
| 'format': do_format,
|
| 'trim': do_trim,
|
| 'striptags': do_striptags,
|
| + 'select': do_select,
|
| + 'selectattr': do_selectattr,
|
| 'slice': do_slice,
|
| 'batch': do_batch,
|
| 'sum': do_sum,
|
| @@ -797,5 +982,6 @@ FILTERS = {
|
| 'round': do_round,
|
| 'groupby': do_groupby,
|
| 'safe': do_mark_safe,
|
| - 'xmlattr': do_xmlattr
|
| + 'xmlattr': do_xmlattr,
|
| + 'urlencode': do_urlencode
|
| }
|
|
|