blob: 93939803cb0cd7f07dac07dad5f65f158e9b44c1 [file] [log] [blame]
# -*- coding: utf-8 -*-
"""
jinja.filters
~~~~~~~~~~~~~
Bundled jinja filters.
:copyright: 2007 by Armin Ronacher.
:license: BSD, see LICENSE for more details.
"""
from random import choice
from urllib import urlencode, quote
from jinja.utils import escape, urlize
from jinja.datastructure import Undefined
from jinja.exceptions import FilterArgumentError
try:
_reversed = reversed
except NameError:
# python2.3 compatibility hack for the do_reverse function
def _reversed(seq):
try:
return seq[::-1]
except:
try:
return list(seq)[::-1]
except:
raise TypeError('argument to _reversed must '
'be a sequence')
def stringfilter(f):
"""
Decorator for filters that just work on unicode objects.
"""
def decorator(*args):
def wrapped(env, context, value):
nargs = list(args)
for idx, var in enumerate(nargs):
if isinstance(var, str):
nargs[idx] = env.to_unicode(var)
return f(env.to_unicode(value), *nargs)
return wrapped
try:
decorator.__doc__ = f.__doc__
decorator.__name__ = f.__name__
except:
pass
return decorator
def do_replace(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 not isinstance(old, basestring) or \
not isinstance(new, basestring):
raise FilterArgumentException('the replace filter requires '
'string replacement arguments')
elif not isinstance(count, (int, long)):
raise FilterArgumentException('the count parameter of the '
'replace filter requires '
'an integer')
if count is None:
return s.replace(old, new)
return s.replace(old, new, count)
do_replace = stringfilter(do_replace)
def do_upper(s):
"""
Convert a value to uppercase.
"""
return s.upper()
do_upper = stringfilter(do_upper)
def do_lower(s):
"""
Convert a value to lowercase.
"""
return s.lower()
do_lower = stringfilter(do_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 ``&quot;``. 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.
"""
return escape(s, attribute)
do_escape = stringfilter(do_escape)
def do_addslashes(s):
"""
Add backslashes in front of special characters to s. This method
might be useful if you try to fill javascript strings. Also have
a look at the `jsonencode` filter.
"""
return s.encode('utf-8').encode('string-escape').decode('utf-8')
do_addslashes = stringfilter(do_addslashes)
def do_capitalize(s):
"""
Capitalize a value. The first character will be uppercase, all others
lowercase.
"""
return s.capitalize()
do_capitalize = stringfilter(do_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.
"""
return s.title()
do_title = stringfilter(do_title)
def do_dictsort(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|dicsort(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(value, env):
if isinstance(value, basestring):
value = env.to_unicode(value)
if 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
def do_default(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) }}
"""
def wrapped(env, context, value):
if (boolean and not v) or v in (Undefined, None):
return default_value
return v
return wrapped
do_default = stringfilter(do_default)
def do_join(d=u''):
"""
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:
.. sourcecode:: jinja
{{ [1, 2, 3]|join('|') }}
-> 1|2|3
{{ [1, 2, 3]|join }}
-> 123
"""
def wrapped(env, context, value):
return env.to_unicode(d).join([env.to_unicode(x) for x in value])
return wrapped
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.
"""
def wrapped(env, context, value):
try:
if type(value) in (int, float, long):
return len(str(value))
return len(value)
except TypeError:
return 0
return wrapped
def do_reverse():
"""
Return a reversed list of the sequence filtered. You can use this
for example for reverse iteration:
.. sourcecode:: jinja
{% for item in seq|reverse %}
{{ item|e }}
{% endfor %}
"""
def wrapped(env, context, value):
try:
return value[::-1]
except:
l = list(value)
l.reverse()
return l
return wrapped
def do_center(value, width=80):
"""
Centers the value in a field of a given width.
"""
return value.center(width)
do_center = stringfilter(do_center)
def do_first():
"""
Return the frist item of a sequence.
"""
def wrapped(env, context, seq):
try:
return iter(seq).next()
except StopIteration:
return Undefined
return wrapped
def do_last():
"""
Return the last item of a sequence.
"""
def wrapped(env, context, seq):
try:
return iter(_reversed(seq)).next()
except (TypeError, StopIteration):
return Undefined
return wrapped
def do_random():
"""
Return a random item from the sequence.
"""
def wrapped(env, context, seq):
try:
return choice(seq)
except:
return Undefined
return wrapped
def do_urlencode():
"""
urlencode a string or directory.
.. sourcecode:: jinja
{{ {'foo': 'bar', 'blub': 'blah'}|urlencode }}
-> foo=bar&blub=blah
{{ 'Hello World' }}
-> Hello%20World
"""
def wrapped(env, context, value):
if isinstance(value, dict):
tmp = {}
for key, value in value.iteritems():
tmp[env.to_unicode(key)] = env.to_unicode(value)
return urlencode(tmp)
else:
return quote(env.to_unicode(value))
return wrapped
def do_jsonencode():
"""
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 lambda e, c, v: simplejson.dumps(v)
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
def do_pprint():
"""
Pretty print a variable. Useful for debugging.
"""
def wrapped(env, context, value):
from pprint import pformat
return pformat(value)
return wrapped
def do_urlize(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"
"""
return urlize(value, trim_url_limit, nofollow)
do_urlize = stringfilter(do_urlize)
def do_indent(s, width=4, indentfirst=False):
"""
{{ s|indent[ width[ indentfirst[ usetab]]] }}
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 = ' ' * width
if indentfirst:
return u'\n'.join([indention + line for line in s.splitlines()])
return s.replace('\n', '\n' + indention)
do_indent = stringfilter(do_indent)
def do_truncate(s, length=255, killwords=False, end='...'):
"""
{{ s|truncate[ length[ killwords[ 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
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::
{{ mytext|truncate(300, false, '&raquo;') }}
truncate mytext to 300 chars, don't split up words, use a
right pointing double arrow as ellipsis sign.
"""
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)
do_truncate = stringfilter(do_truncate)
def do_wordwrap(s, pos=79, hard=False):
"""
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).
"""
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(' '))
do_wordwrap = stringfilter(do_wordwrap)
def do_wordcount(s):
"""
Count the words in that string.
"""
return len([x for x in s.split() if x])
do_wordcount = stringfilter(do_wordcount)
def do_textile(s):
"""
Prase the string using textile.
requires the `PyTextile`_ library.
.. _PyTextile: http://dealmeida.net/projects/textile/
"""
from textile import textile
return textile(s)
do_textile = stringfilter(do_textile)
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)
do_markdown = stringfilter(do_markdown)
def do_rst(s):
"""
Parse the string using the reStructuredText parser from the
docutils package.
requires `docutils`_.
.. _docutils: from http://docutils.sourceforge.net/
"""
try:
from docutils.core import publish_parts
parts = publish_parts(source=s, writer_name='html4css1')
return parts['fragment']
except:
return s
do_rst = stringfilter(do_rst)
def do_int():
"""
Convert the value into an integer.
"""
def wrapped(env, context, value):
return int(value)
return wrapped
def do_float():
"""
Convert the value into a floating point number.
"""
def wrapped(env, context, value):
return float(value)
return wrapped
def do_string():
"""
Convert the value into an string.
"""
return lambda e, c, v: e.to_unicode(v)
FILTERS = {
'replace': do_replace,
'upper': do_upper,
'lower': do_lower,
'escape': do_escape,
'e': do_escape,
'addslashes': do_addslashes,
'capitalize': do_capitalize,
'title': do_title,
'default': do_default,
'join': do_join,
'count': do_count,
'dictsort': do_dictsort,
'length': do_count,
'reverse': do_reverse,
'center': do_center,
'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,
'urlize': do_urlize
}