# -*- coding: utf-8 -*-
"""
- jinja.filters
- ~~~~~~~~~~~~~
+ jinja2.filters
+ ~~~~~~~~~~~~~~
Bundled jinja filters.
- :copyright: 2008 by Armin Ronacher, Christoph Hack.
+ :copyright: (c) 2010 by the Jinja Team.
:license: BSD, see LICENSE for more details.
"""
import re
+import math
+import urllib
from random import choice
-try:
- from operator import itemgetter
-except ImportError:
- itemgetter = lambda a: lambda b: b[a]
-from urllib import urlencode, quote
-from jinja.utils import escape
+from operator import itemgetter
+from itertools import imap, groupby
+from jinja2.utils import Markup, escape, pformat, urlize, soft_unicode
+from jinja2.runtime import Undefined
+from jinja2.exceptions import FilterArgumentError
-_striptags_re = re.compile(r'(<!--.*?-->|<[^>]*>)')
+_word_re = re.compile(r'\w+(?u)')
def contextfilter(f):
- """
- Decorator for marking context dependent filters. The current context
- argument will be passed as first argument.
+ """Decorator for marking context dependent filters. The current
+ :class:`Context` will be passed as first argument.
"""
f.contextfilter = True
return f
-def do_replace(s, old, new, count=None):
+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.
"""
- Return a copy of the value with all occurrences of a substring
+ 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.
+ """
+ if not isinstance(attribute, basestring) or '.' not in attribute:
+ return lambda x: environment.getitem(x, attribute)
+ attribute = attribute.split('.')
+ def attrgetter(item):
+ for part in attribute:
+ 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(unicode(value))
+
+def do_urlescape(value):
+ """Escape strings for use in URLs (uses UTF-8 encoding)."""
+ def utf8(o):
+ return unicode(o).encode('utf8')
+
+ if isinstance(value, basestring):
+ return urllib.quote(utf8(value))
+
+ if hasattr(value, 'items'):
+ # convert dictionaries to list of 2-tuples
+ value = value.items()
+
+ if hasattr(value, 'next'):
+ # convert generators to list
+ value = list(value)
+
+ return urllib.urlencode([(utf8(k), utf8(v)) for (k, v) in value])
+
+@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
{{ "aaaaargh"|replace("a", "d'oh, ", 2) }}
-> d'oh, d'oh, aaargh
"""
- if not isinstance(old, basestring) or \
- not isinstance(new, basestring):
- raise FilterArgumentError('the replace filter requires '
- 'string replacement arguments')
if count is None:
- return s.replace(old, new)
- if not isinstance(count, (int, long)):
- raise FilterArgumentError('the count parameter of the '
- 'replace filter requires '
- 'an integer')
- return s.replace(old, new, count)
+ count = -1
+ if not eval_ctx.autoescape:
+ return unicode(s).replace(unicode(old), unicode(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 s.upper()
+ """Convert a value to uppercase."""
+ return soft_unicode(s).upper()
def do_lower(s):
- """
- Convert a value to lowercase.
- """
- return s.lower()
+ """Convert a value to lowercase."""
+ return soft_unicode(s).lower()
-def do_escape(s, attribute=False):
- """
- XML escape ``&``, ``<``, and ``>`` in a string of data. If the
- optional parameter is `true` this filter will also convert
- ``"`` to ``"``. This filter is just used if the environment
- was configured with disabled `auto_escape`.
-
- This method will have no effect it the value is already escaped.
- """
- # XXX: Does this still exists?
- #if isinstance(s, TemplateData):
- # return s
- if hasattr(s, '__html__'):
- return s.__html__()
- return escape(unicode(s), attribute)
-
-
-def do_xmlattr(d, autospace=False):
- """
- Create an SGML/XML attribute string based on the items in a dict.
+@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,
+ <ul{{ {'class': 'my_list', 'missing': none,
'id': 'list-%d'|format(variable)}|xmlattr }}>
...
</ul>
</ul>
As you can see it automatically prepends a space in front of the item
- if the filter returned something. You can disable this by passing
- `false` as only argument to the filter.
-
- *New in Jinja 1.1*
- """
- if not hasattr(d, 'iteritems'):
- raise TypeError('a dict is required')
- result = []
- for key, value in d.iteritems():
- if value not in (None, env.undefined_singleton):
- result.append(u'%s="%s"' % (
- escape(env.to_unicode(key)),
- escape(env.to_unicode(value), True)
- ))
- rv = u' '.join(result)
- if autospace:
- rv = ' ' + rv
+ 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 d.iteritems()
+ 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
+ """Capitalize a value. The first character will be uppercase, all others
lowercase.
"""
- return unicode(s).capitalize()
+ return soft_unicode(s).capitalize()
def do_title(s):
- """
- Return a titlecased version of the value. I.e. words will start with
+ """Return a titlecased version of the value. I.e. words will start with
uppercase letters, all remaining characters are lowercase.
"""
- return unicode(s).title()
+ return soft_unicode(s).title()
-def do_dictsort(case_sensitive=False, by='key'):
- """
- Sort a dict and yield (key, value) pairs. Because python dicts are
+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:
else:
raise FilterArgumentError('You can only sort by either '
'"key" or "value"')
- def sort_func(value, env):
- if isinstance(value, basestring):
- value = env.to_unicode(value)
- if not case_sensitive:
- value = value.lower()
+ def sort_func(item):
+ value = item[pos]
+ if isinstance(value, basestring) and not case_sensitive:
+ value = value.lower()
return value
- def wrapped(env, context, value):
- items = value.items()
- items.sort(lambda a, b: cmp(sort_func(a[pos], env),
- sort_func(b[pos], env)))
- return items
- return wrapped
+ return sorted(value.items(), key=sort_func)
-def do_default(value, default_value=u'', boolean=False):
+@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 the value is undefined it will return the passed default value,
+ if not case_sensitive:
+ def sort_func(item):
+ if isinstance(item, basestring):
+ 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
{{ ''|default('the string was empty', true) }}
"""
- # XXX: undefined_sigleton
- if (boolean and not value) or value in (env.undefined_singleton, None):
+ if (boolean and not value) or isinstance(value, Undefined):
return default_value
return value
-def do_join(value, d=u''):
- """
- Return a string which is the concatenation of the strings in the
+@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 ith with the optional parameter:
+ default, you can define it with the optional parameter:
.. sourcecode:: jinja
{{ [1, 2, 3]|join }}
-> 123
- """
- return unicode(d).join([unicode(x) for x in value])
-
-
-def do_count():
- """
- Return the length of the value. In case if getting an integer or float
- it will convert it into a string an return the length of the new
- string. If the object has no length it will of corse return 0.
- """
- try:
- if type(value) in (int, float, long):
- return len(str(value))
- return len(value)
- except TypeError:
- return 0
-
-def do_reverse(l):
- """
- Return a reversed list of the sequence filtered. You can use this
- for example for reverse iteration:
+ It is also possible to join certain attributes of an object:
.. sourcecode:: jinja
- {% for item in seq|reverse %}
- {{ item|e }}
- {% endfor %}
- """
- try:
- return value[::-1]
- except:
- l = list(value)
- l.reverse()
- return l
+ {{ 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 unicode(d).join(imap(unicode, 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] = unicode(item)
+ if do_escape:
+ d = escape(d)
+ else:
+ d = unicode(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.
- """
+ """Centers the value in a field of a given width."""
return unicode(value).center(width)
-def do_first(seq):
- """
- Return the frist item of a sequence.
- """
+@environmentfilter
+def do_first(environment, seq):
+ """Return the first item of a sequence."""
try:
return iter(seq).next()
except StopIteration:
- return env.undefined_singleton
+ return environment.undefined('No first item, sequence was empty.')
-def do_last(seq):
- """
- Return the last item of a sequence.
- """
+@environmentfilter
+def do_last(environment, seq):
+ """Return the last item of a sequence."""
try:
return iter(reversed(seq)).next()
except StopIteration:
- return env.undefined_singleton
+ return environment.undefined('No last item, sequence was empty.')
-def do_random():
- """
- Return a random item from the sequence.
- """
+@environmentfilter
+def do_random(environment, seq):
+ """Return a random item from the sequence."""
try:
return choice(seq)
except IndexError:
- return env.undefined_singleton
-
-
-def do_urlencode(value):
- """
- urlencode a string or directory.
-
- .. sourcecode:: jinja
-
- {{ {'foo': 'bar', 'blub': 'blah'}|urlencode }}
- -> foo=bar&blub=blah
-
- {{ 'Hello World' }}
- -> Hello%20World
- """
- if isinstance(value, dict):
- tmp = {}
- for key, value in value.iteritems():
- # XXX env.charset?
- key = unicode(key).encode(env.charset)
- value = unicode(value).encode(env.charset)
- tmp[key] = value
- return urlencode(tmp)
+ 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:
- # XXX: env.charset?
- return quote(unicode(value).encode(env.charset))
-
-
-def do_jsonencode(value):
- """
- JSON dump a variable. just works if simplejson is installed.
-
- .. sourcecode:: jinja
-
- {{ 'Hello World'|jsonencode }}
- -> "Hello World"
- """
- global simplejson
- try:
- simplejson
- except NameError:
- import simplejson
- return simplejson.dumps(value)
-
-
-def do_filesizeformat():
- """
- Format the value like a 'human-readable' file size (i.e. 13 KB, 4.1 MB, 102
- bytes, etc).
- """
- def wrapped(env, context, value):
- # fail silently
- try:
- bytes = float(value)
- except TypeError:
- bytes = 0
-
- if bytes < 1024:
- return "%d Byte%s" % (bytes, bytes != 1 and 's' or '')
- elif bytes < 1024 * 1024:
- return "%.1f KB" % (bytes / 1024)
- elif bytes < 1024 * 1024 * 1024:
- return "%.1f MB" % (bytes / (1024 * 1024))
- return "%.1f GB" % (bytes / (1024 * 1024 * 1024))
- return wrapped
+ 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.
+ """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)
-def do_urlize(value, trim_url_limit=None, nofollow=False):
- """
- Converts URLs in plain text into clickable links.
+@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
.. sourcecode:: jinja
- {{ mytext|urlize(40, True) }}
+ {{ mytext|urlize(40, true) }}
links are shortened to 40 chars and defined with rel="nofollow"
"""
- return urlize(unicode(value), trim_url_limit, 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):
- """
- {{ s|indent[ width[ indentfirst[ usetab]]] }}
-
- Return a copy of the passed string, each line indented by
+ """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) }}
+ {{ mytext|indent(2, true) }}
indent by two spaces and indent the first line too.
"""
- indention = ' ' * width
+ indention = u' ' * width
+ rv = (u'\n' + indention).join(s.splitlines())
if indentfirst:
- return u'\n'.join([indention + line for line in s.splitlines()])
- return s.replace('\n', '\n' + indention)
+ 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
+ """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
result.append(end)
return u' '.join(result)
-
-def do_wordwrap(s, pos=79, hard=False):
+@environmentfilter
+def do_wordwrap(environment, s, width=79, break_long_words=True):
"""
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 `true` Jinja will
- also split words apart (usually a bad idea because it makes
- reading hard).
+ ``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`.
"""
- if len(s) < pos:
- return s
- if hard:
- return u'\n'.join([s[idx:idx + pos] for idx in
- xrange(0, len(s), pos)])
- # code from http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/148061
- return reduce(lambda line, word, pos=pos: u'%s%s%s' %
- (line, u' \n'[(len(line)-line.rfind('\n') - 1 +
- len(word.split('\n', 1)[0]) >= pos)],
- word), s.split(' '))
+ import textwrap
+ return environment.newline_sequence.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([x for x in s.split() if x])
-
-
-def do_textile(s):
- """
- Prase the string using textile.
-
- requires the `PyTextile`_ library.
+ """Count the words in that string."""
+ return len(_word_re.findall(s))
- .. _PyTextile: http://dealmeida.net/projects/textile/
- """
- from textile import textile
- return textile(s.encode('utf-8')).decode('utf-8')
-
-
-def do_markdown(s):
- """
- Parse the string using markdown.
-
- requires the `Python-markdown`_ library.
-
- .. _Python-markdown: http://www.freewisdom.org/projects/python-markdown/
- """
- from markdown import markdown
- return markdown(s.encode('utf-8')).decode('utf-8')
-
-def do_rst(s):
- """
- Parse the string using the reStructuredText parser from the
- docutils package.
-
- requires `docutils`_.
-
- .. _docutils: http://docutils.sourceforge.net/
- """
- from docutils.core import publish_parts
- parts = publish_parts(source=s, writer_name='html4css1')
- return parts['fragment']
-
-def do_int(default=0):
- """
- Convert the value into an integer. If the
+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.
"""
- def wrapped(env, context, value):
+ try:
+ return int(value)
+ except (TypeError, ValueError):
+ # this quirk is necessary so that "42.23"|int gives 42.
try:
- return int(value)
+ return int(float(value))
except (TypeError, ValueError):
- try:
- return int(float(value))
- except (TypeError, ValueError):
- return default
- return wrapped
+ return default
-def do_float(default=0.0):
- """
- Convert the value into a floating point number. If the
+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.
"""
- def wrapped(env, context, value):
- try:
- return float(value)
- except (TypeError, ValueError):
- return default
- return wrapped
-
-
-def do_string():
- """
- Convert the value into an string.
- """
- return lambda e, c, v: e.to_unicode(v)
+ try:
+ return float(value)
+ except (TypeError, ValueError):
+ return default
-def do_format(*args):
+def do_format(value, *args, **kwargs):
"""
Apply python string formatting on an object:
{{ "%s - %s"|format("Hello?", "Foo!") }}
-> Hello? - Foo!
-
- Note that you cannot use the mapping syntax (``%(name)s``)
- like in python. Use `|dformat` for that.
- """
- def wrapped(env, context, value):
- return env.to_unicode(value) % args
- return wrapped
-
-
-def do_dformat(d):
- """
- Apply python mapping string formatting on an object:
-
- .. sourcecode:: jinja
-
- {{ "Hello %(username)s!"|dformat({'username': 'John Doe'}) }}
- -> Hello John Doe!
-
- This is useful when adding variables to translateable
- string expressions.
-
- *New in Jinja 1.1*
"""
- if not isinstance(d, dict):
- raise FilterArgumentError('dict required')
- def wrapped(env, context, value):
- return env.to_unicode(value) % d
- return wrapped
+ 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 value.strip()
-
-
-def do_capture(name='captured', clean=False):
- """
- Store the value in a variable called ``captured`` or a variable
- with the name provided. Useful for filter blocks:
-
- .. sourcecode:: jinja
-
- {% filter capture('foo') %}
- ...
- {% endfilter %}
- {{ foo }}
-
- This will output "..." two times. One time from the filter block
- and one time from the variable. If you don't want the filter to
- output something you can use it in `clean` mode:
-
- .. sourcecode:: jinja
-
- {% filter capture('foo', True) %}
- ...
- {% endfilter %}
- {{ foo }}
- """
- if not isinstance(name, basestring):
- raise FilterArgumentError('You can only capture into variables')
- def wrapped(env, context, value):
- context[name] = value
- if clean:
- return TemplateData()
- return value
- return wrapped
+ """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.
"""
- Strip SGML/XML tags and replace adjacent whitespace by one space.
-
- *new in Jinja 1.1*
- """
- return ' '.join(_striptags_re.sub('', value).split())
+ if hasattr(value, '__html__'):
+ value = value.__html__()
+ return Markup(unicode(value)).striptags()
-def do_slice(slices, fill_with=None):
- """
- Slice an iterator and return a list of lists containing
+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 div tags that represent columns:
+ three ul tags that represent columns:
.. sourcecode:: html+jinja
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 xrange(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
+
- *new in Jinja 1.1*
- """
- def wrapped(env, context, value):
- result = []
- seq = list(value)
- length = len(seq)
- items_per_slice = length // slices
- slices_with_extra = length % slices
- offset = 0
- for slice_number in xrange(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)
- result.append(tmp)
- return result
- return wrapped
-
-
-def do_batch(linecount, fill_with=None):
+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
{%- for row in items|batch(3, ' ') %}
<tr>
{%- for column in row %}
- <tr>{{ column }}</td>
+ <td>{{ column }}</td>
{%- endfor %}
</tr>
{%- endfor %}
</table>
-
- *new in Jinja 1.1*
- """
- def wrapped(env, context, value):
- result = []
- tmp = []
- for item in value:
- if len(tmp) == linecount:
- result.append(tmp)
- tmp = []
- tmp.append(item)
- if tmp:
- if fill_with is not None and len(tmp) < linecount:
- tmp += [fill_with] * (linecount - len(tmp))
- result.append(tmp)
- return result
- return wrapped
-
-
-def do_sum():
- """
- Sum up the given sequence of numbers.
-
- *new in Jinja 1.1*
- """
- def wrapped(env, context, value):
- return sum(value)
- return wrapped
-
-
-def do_abs():
- """
- Return the absolute value of a number.
-
- *new in Jinja 1.1*
"""
- def wrapped(env, context, value):
- return abs(value)
- return wrapped
-
-
-def do_round(precision=0, method='common'):
- """
- Round the number to a given precision. The first
+ 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:
.. sourcecode:: jinja
{{ 42.55|round }}
- -> 43
+ -> 43.0
{{ 42.55|round(1, 'floor') }}
-> 42.5
- *new in Jinja 1.1*
- """
- if not method in ('common', 'ceil', 'floor'):
- raise FilterArgumentError('method must be common, ceil or floor')
- if precision < 0:
- raise FilterArgumentError('precision must be a postive integer '
- 'or zero.')
- def wrapped(env, context, value):
- if method == 'common':
- return round(value, precision)
- import math
- func = getattr(math, method)
- if precision:
- return func(value * 10 * precision) / (10 * precision)
- else:
- return func(value)
- return wrapped
-
+ Note that even if rounded to 0 precision, a float is returned. If
+ you need a real integer, pipe it through `int`:
-def do_sort(reverse=False):
- """
- Sort a sequence. Per default it sorts ascending, if you pass it
- `True` as first argument it will reverse the sorting.
+ .. sourcecode:: jinja
- *new in Jinja 1.1*
+ {{ 42.55|round|int }}
+ -> 43
"""
- def wrapped(env, context, value):
- return sorted(value, reverse=reverse)
- return wrapped
+ 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)
-def do_groupby(attribute):
- """
- Group a sequence of objects by a common attribute.
+@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
{% 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.
- *New in Jinja 1.2*
+ .. versionchanged:: 2.6
+ It's now possible to use dotted notation to group by the child
+ attribute of another attribute.
"""
- def wrapped(env, context, value):
- expr = lambda x: env.get_attribute(x, attribute)
- return sorted([{
- 'grouper': a,
- 'list': list(b)
- } for a, b in groupby(sorted(value, key=expr), expr)],
- key=itemgetter('grouper'))
- return wrapped
+ expr = make_attrgetter(environment, attribute)
+ return sorted(map(_GroupTuple, groupby(sorted(value, key=expr), expr)))
-def do_getattribute(attribute):
- """
- Get one attribute from an object. Normally you don't have to use this
- filter because the attribute and subscript expressions try to either
- get an attribute of an object or an item. In some situations it could
- be that there is an item *and* an attribute with the same name. In that
- situation only the item is returned, never the attribute.
+class _GroupTuple(tuple):
+ __slots__ = ()
+ grouper = property(itemgetter(0))
+ list = property(itemgetter(1))
+
+ def __new__(cls, (key, value)):
+ 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
- {{ foo.bar }} -> {{ foo|getattribute('bar') }}
+ Total: {{ items|sum(attribute='price') }}
- *New in Jinja 1.2*
+ .. versionchanged:: 2.6
+ The `attribute` parameter was added to allow suming up over
+ attributes. Also the `start` parameter was moved on to the right.
"""
- def wrapped(env, context, value):
- try:
- return get_attribute(value, attribute)
- except (SecurityException, AttributeError):
- return env.undefined_singleton
- return wrapped
+ if attribute is not None:
+ iterable = imap(make_attrgetter(environment, attribute), iterable)
+ return sum(iterable, start)
-def do_getitem(key):
+def do_list(value):
+ """Convert the value into a list. If it was a string the returned list
+ will be a list of characters.
"""
- This filter basically works like the normal subscript expression but
- it doesn't fall back to attribute lookup. If an item does not exist for
- an object undefined is returned.
+ return list(value)
- .. sourcecode:: jinja
- {{ foo.bar }} -> {{ foo|getitem('bar') }}
+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 unicode(value)
+
+
+def do_reverse(value):
+ """Reverse the object or return an iterator the iterates over it the other
+ way round.
+ """
+ if isinstance(value, basestring):
+ 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')
+
- *New in Jinja 1.2*
+@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.
"""
- def wrapped(env, context, value):
+ try:
+ name = str(name)
+ except UnicodeError:
+ pass
+ else:
try:
- return value[key]
- except (TypeError, KeyError, IndexError, AttributeError):
- return env.undefined_singleton
- return wrapped
+ 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)
FILTERS = {
+ 'attr': do_attr,
'replace': do_replace,
'upper': do_upper,
'lower': do_lower,
- 'escape': do_escape,
- 'e': do_escape,
- 'xmlattr': do_xmlattr,
+ 'escape': escape,
+ 'e': escape,
+ 'forceescape': do_forceescape,
'capitalize': do_capitalize,
'title': do_title,
'default': do_default,
+ 'd': do_default,
'join': do_join,
- 'count': do_count,
+ 'count': len,
'dictsort': do_dictsort,
- 'length': do_count,
+ '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,
'random': do_random,
- 'urlencode': do_urlencode,
- 'jsonencode': do_jsonencode,
'filesizeformat': do_filesizeformat,
'pprint': do_pprint,
- 'indent': do_indent,
'truncate': do_truncate,
'wordwrap': do_wordwrap,
'wordcount': do_wordcount,
- 'textile': do_textile,
- 'markdown': do_markdown,
- 'rst': do_rst,
'int': do_int,
'float': do_float,
- 'string': do_string,
+ 'string': soft_unicode,
+ 'list': do_list,
'urlize': do_urlize,
'format': do_format,
- 'dformat': do_dformat,
- 'capture': do_capture,
'trim': do_trim,
'striptags': do_striptags,
'slice': do_slice,
'batch': do_batch,
'sum': do_sum,
- 'abs': do_abs,
+ 'abs': abs,
'round': do_round,
- 'sort': do_sort,
'groupby': do_groupby,
- 'getattribute': do_getattribute,
- 'getitem': do_getitem
+ 'safe': do_mark_safe,
+ 'xmlattr': do_xmlattr,
+ 'urlescape': do_urlescape
}