988 lines
		
	
	
		
			29 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			988 lines
		
	
	
		
			29 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| # -*- 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
 | |
| }
 | 
