Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(137)

Unified Diff: third_party/jinja2/filters.py

Issue 23506004: Update Jinja2 (Python template library) to 2.7.1 (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Rebased Created 7 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « third_party/jinja2/ext.py ('k') | third_party/jinja2/get_jinja2.sh » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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, '&raquo;') }}
- 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
}
« no previous file with comments | « third_party/jinja2/ext.py ('k') | third_party/jinja2/get_jinja2.sh » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698