1 # -*- coding: utf-8 -*-
8 :copyright: 2008 by Armin Ronacher, Christoph Hack.
9 :license: BSD, see LICENSE for more details.
13 from random import choice
15 from operator import itemgetter
17 itemgetter = lambda a: lambda b: b[a]
18 from urllib import urlencode, quote
19 from itertools import imap, groupby
20 from jinja2.utils import Markup, escape, pformat, urlize, soft_unicode
21 from jinja2.runtime import Undefined
24 _striptags_re = re.compile(r'(<!--.*?-->|<[^>]*>)')
28 """Decorator for marking context dependent filters. The current context
29 argument will be passed as first argument.
31 if getattr(f, 'environmentfilter', False):
32 raise TypeError('filter already marked as environment filter')
33 f.contextfilter = True
37 def environmentfilter(f):
38 """Decorator for marking evironment dependent filters. The environment
39 used for the template is passed to the filter as first argument.
41 if getattr(f, 'contextfilter', False):
42 raise TypeError('filter already marked as context filter')
43 f.environmentfilter = True
47 def do_forceescape(value):
48 """Enforce HTML escaping. This will probably double escape variables."""
49 if hasattr(value, '__html__'):
50 value = value.__html__()
51 return escape(unicode(value))
54 def do_replace(s, old, new, count=None):
55 """Return a copy of the value with all occurrences of a substring
56 replaced with a new one. The first argument is the substring
57 that should be replaced, the second is the replacement string.
58 If the optional third argument ``count`` is given, only the first
59 ``count`` occurrences are replaced:
63 {{ "Hello World"|replace("Hello", "Goodbye") }}
66 {{ "aaaaargh"|replace("a", "d'oh, ", 2) }}
71 if hasattr(old, '__html__') or hasattr(new, '__html__') and \
72 not hasattr(s, '__html__'):
76 return s.replace(old, new, count)
80 """Convert a value to uppercase."""
81 return soft_unicode(s).upper()
85 """Convert a value to lowercase."""
86 return soft_unicode(s).lower()
89 def do_xmlattr(d, autospace=False):
90 """Create an SGML/XML attribute string based on the items in a dict.
91 All values that are neither `none` nor `undefined` are automatically
94 .. sourcecode:: html+jinja
96 <ul{{ {'class': 'my_list', 'missing': None,
97 'id': 'list-%d'|format(variable)}|xmlattr }}>
101 Results in something like this:
105 <ul class="my_list" id="list-42">
109 As you can see it automatically prepends a space in front of the item
110 if the filter returned something. You can disable this by passing
111 `false` as only argument to the filter.
113 if not hasattr(d, 'iteritems'):
114 raise TypeError('a dict is required')
116 for key, value in d.iteritems():
117 if value is not None and not isinstance(value, Undefined):
118 result.append(u'%s="%s"' % (escape(key), escape(value)))
120 u'%s="%s"' % (escape(key), escape(value))
121 for key, value in d.iteritems()
122 if value is not None and not isinstance(value, Undefined)
129 def do_capitalize(s):
130 """Capitalize a value. The first character will be uppercase, all others
133 return soft_unicode(s).capitalize()
137 """Return a titlecased version of the value. I.e. words will start with
138 uppercase letters, all remaining characters are lowercase.
140 return soft_unicode(s).title()
143 def do_dictsort(value, case_sensitive=False, by='key'):
144 """ Sort a dict and yield (key, value) pairs. Because python dicts are
145 unsorted you may want to use this function to order them by either
148 .. sourcecode:: jinja
150 {% for item in mydict|dictsort %}
151 sort the dict by key, case insensitive
153 {% for item in mydict|dicsort(true) %}
154 sort the dict by key, case sensitive
156 {% for item in mydict|dictsort(false, 'value') %}
157 sort the dict by key, case insensitive, sorted
158 normally and ordered by value.
165 raise FilterArgumentError('You can only sort by either '
169 if isinstance(value, basestring):
170 value = unicode(value)
171 if not case_sensitive:
172 value = value.lower()
175 return sorted(value.items(), key=sort_func)
178 def do_default(value, default_value=u'', boolean=False):
179 """If the value is undefined it will return the passed default value,
180 otherwise the value of the variable:
182 .. sourcecode:: jinja
184 {{ my_variable|default('my_variable is not defined') }}
186 This will output the value of ``my_variable`` if the variable was
187 defined, otherwise ``'my_variable is not defined'``. If you want
188 to use default with variables that evaluate to false you have to
189 set the second parameter to `true`:
191 .. sourcecode:: jinja
193 {{ ''|default('the string was empty', true) }}
195 if (boolean and not value) or isinstance(value, Undefined):
200 def do_join(value, d=u''):
201 """Return a string which is the concatenation of the strings in the
202 sequence. The separator between elements is an empty string per
203 default, you can define ith with the optional parameter:
205 .. sourcecode:: jinja
207 {{ [1, 2, 3]|join('|') }}
213 # if the delimiter doesn't have an html representation we check
214 # if any of the items has. If yes we do a coercion to Markup
215 if not hasattr(d, '__html__'):
218 for idx, item in enumerate(value):
219 if hasattr(item, '__html__'):
222 value[idx] = unicode(item)
229 # no html involved, to normal joining
230 return soft_unicode(d).join(imap(soft_unicode, value))
233 def do_center(value, width=80):
234 """Centers the value in a field of a given width."""
235 return unicode(value).center(width)
239 def do_first(environment, seq):
240 """Return the frist item of a sequence."""
242 return iter(seq).next()
243 except StopIteration:
244 return environment.undefined('No first item, sequence was empty.')
248 def do_last(environment, seq):
249 """Return the last item of a sequence."""
251 return iter(reversed(seq)).next()
252 except StopIteration:
253 return environment.undefined('No last item, sequence was empty.')
257 def do_random(environment, seq):
258 """Return a random item from the sequence."""
262 return environment.undefined('No random item, sequence was empty.')
265 def do_filesizeformat(value):
266 """Format the value like a 'human-readable' file size (i.e. 13 KB,
267 4.1 MB, 102 bytes, etc).
276 return "%d Byte%s" % (bytes, bytes != 1 and 's' or '')
277 elif bytes < 1024 * 1024:
278 return "%.1f KB" % (bytes / 1024)
279 elif bytes < 1024 * 1024 * 1024:
280 return "%.1f MB" % (bytes / (1024 * 1024))
281 return "%.1f GB" % (bytes / (1024 * 1024 * 1024))
284 def do_pprint(value, verbose=False):
285 """Pretty print a variable. Useful for debugging.
287 With Jinja 1.2 onwards you can pass it a parameter. If this parameter
288 is truthy the output will be more verbose (this requires `pretty`)
290 return pformat(value, verbose=verbose)
293 def do_urlize(value, trim_url_limit=None, nofollow=False):
294 """Converts URLs in plain text into clickable links.
296 If you pass the filter an additional integer it will shorten the urls
297 to that number. Also a third argument exists that makes the urls
300 .. sourcecode:: jinja
302 {{ mytext|urlize(40, True) }}
303 links are shortened to 40 chars and defined with rel="nofollow"
305 return urlize(soft_unicode(value), trim_url_limit, nofollow)
308 def do_indent(s, width=4, indentfirst=False):
310 {{ s|indent[ width[ indentfirst[ usetab]]] }}
312 Return a copy of the passed string, each line indented by
313 4 spaces. The first line is not indented. If you want to
314 change the number of spaces or indent the first line too
315 you can pass additional parameters to the filter:
317 .. sourcecode:: jinja
319 {{ mytext|indent(2, True) }}
320 indent by two spaces and indent the first line too.
322 indention = ' ' * width
324 return u'\n'.join(indention + line for line in s.splitlines())
325 return s.replace('\n', '\n' + indention)
328 def do_truncate(s, length=255, killwords=False, end='...'):
330 Return a truncated copy of the string. The length is specified
331 with the first parameter which defaults to ``255``. If the second
332 parameter is ``true`` the filter will cut the text at length. Otherwise
333 it will try to save the last word. If the text was in fact
334 truncated it will append an ellipsis sign (``"..."``). If you want a
335 different ellipsis sign than ``"..."`` you can specify it using the
338 .. sourcecode jinja::
340 {{ mytext|truncate(300, false, '»') }}
341 truncate mytext to 300 chars, don't split up words, use a
342 right pointing double arrow as ellipsis sign.
347 return s[:length] + end
357 return u' '.join(result)
360 def do_wordwrap(s, pos=79, hard=False):
362 Return a copy of the string passed to the filter wrapped after
363 ``79`` characters. You can override this default using the first
364 parameter. If you set the second parameter to `true` Jinja will
365 also split words apart (usually a bad idea because it makes
371 return u'\n'.join(s[idx:idx + pos] for idx in
372 xrange(0, len(s), pos))
374 # TODO: switch to wordwrap.wrap
375 # code from http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/148061
376 return reduce(lambda line, word, pos=pos: u'%s%s%s' %
377 (line, u' \n'[(len(line)-line.rfind('\n') - 1 +
378 len(word.split('\n', 1)[0]) >= pos)],
383 """Count the words in that string."""
384 return len(s.split())
387 def do_int(value, default=0):
388 """Convert the value into an integer. If the
389 conversion doesn't work it will return ``0``. You can
390 override this default using the first parameter.
394 except (TypeError, ValueError):
395 # this quirk is necessary so that "42.23"|int gives 42.
397 return int(float(value))
398 except (TypeError, ValueError):
402 def do_float(value, default=0.0):
403 """Convert the value into a floating point number. If the
404 conversion doesn't work it will return ``0.0``. You can
405 override this default using the first parameter.
409 except (TypeError, ValueError):
413 def do_format(value, *args, **kwargs):
415 Apply python string formatting on an object:
417 .. sourcecode:: jinja
419 {{ "%s - %s"|format("Hello?", "Foo!") }}
423 kwargs.update(idx, arg in enumerate(args))
425 return soft_unicode(value) % args
429 """Strip leading and trailing whitespace."""
430 return soft_unicode(value).strip()
433 def do_striptags(value):
434 """Strip SGML/XML tags and replace adjacent whitespace by one space.
436 if hasattr(value, '__html__'):
437 value = value.__html__()
438 return u' '.join(_striptags_re.sub('', value).split())
441 def do_slice(value, slices, fill_with=None):
442 """Slice an iterator and return a list of lists containing
443 those items. Useful if you want to create a div containing
444 three div tags that represent columns:
446 .. sourcecode:: html+jinja
448 <div class="columwrapper">
449 {%- for column in items|slice(3) %}
450 <ul class="column-{{ loop.index }}">
451 {%- for item in column %}
458 If you pass it a second argument it's used to fill missing
459 values on the last iteration.
463 items_per_slice = length // slices
464 slices_with_extra = length % slices
466 for slice_number in xrange(slices):
467 start = offset + slice_number * items_per_slice
468 if slice_number < slices_with_extra:
470 end = offset + (slice_number + 1) * items_per_slice
472 if fill_with is not None and slice_number >= slices_with_extra:
473 tmp.append(fill_with)
477 def do_batch(value, linecount, fill_with=None):
479 A filter that batches items. It works pretty much like `slice`
480 just the other way round. It returns a list of lists with the
481 given number of items. If you provide a second parameter this
482 is used to fill missing items. See this example:
484 .. sourcecode:: html+jinja
487 {%- for row in items|batch(3, ' ') %}
489 {%- for column in row %}
490 <tr>{{ column }}</td>
499 if len(tmp) == linecount:
504 if fill_with is not None and len(tmp) < linecount:
505 tmp += [fill_with] * (linecount - len(tmp))
509 def do_round(value, precision=0, method='common'):
510 """Round the number to a given precision. The first
511 parameter specifies the precision (default is ``0``), the
512 second the rounding method:
514 - ``'common'`` rounds either up or down
515 - ``'ceil'`` always rounds up
516 - ``'floor'`` always rounds down
518 If you don't specify a method ``'common'`` is used.
520 .. sourcecode:: jinja
524 {{ 42.55|round(1, 'floor') }}
527 if not method in ('common', 'ceil', 'floor'):
528 raise FilterArgumentError('method must be common, ceil or floor')
530 raise FilterArgumentError('precision must be a postive integer '
532 if method == 'common':
533 return round(value, precision)
534 func = getattr(math, method)
536 return func(value * 10 * precision) / (10 * precision)
541 def do_sort(value, reverse=False):
542 """Sort a sequence. Per default it sorts ascending, if you pass it
543 `True` as first argument it will reverse the sorting.
545 return sorted(value, reverse=reverse)
549 def do_groupby(environment, value, attribute):
550 """Group a sequence of objects by a common attribute.
552 If you for example have a list of dicts or objects that represent persons
553 with `gender`, `first_name` and `last_name` attributes and you want to
554 group all users by genders you can do something like the following
557 .. sourcecode:: html+jinja
560 {% for group in persons|groupby('gender') %}
561 <li>{{ group.grouper }}<ul>
562 {% for person in group.list %}
563 <li>{{ person.first_name }} {{ person.last_name }}</li>
564 {% endfor %}</ul></li>
568 Additionally it's possible to use tuple unpacking for the grouper and
571 .. sourcecode:: html+jinja
574 {% for grouper, list in persons|groupby('gender') %}
579 As you can see the item we're grouping by is stored in the `grouper`
580 attribute and the `list` contains all the objects that have this grouper
583 expr = lambda x: environment.subscribe(x, attribute)
584 return sorted(map(_GroupTuple, groupby(sorted(value, key=expr), expr)),
585 key=itemgetter('grouper'))
588 class _GroupTuple(tuple):
590 grouper = property(itemgetter(0))
591 list = property(itemgetter(1))
595 'replace': do_replace,
600 'forceescape': do_forceescape,
601 'capitalize': do_capitalize,
603 'default': do_default,
606 'dictsort': do_dictsort,
612 'capitalize': do_capitalize,
616 'filesizeformat': do_filesizeformat,
618 'truncate': do_truncate,
619 'wordwrap': do_wordwrap,
620 'wordcount': do_wordcount,
623 'string': soft_unicode,
628 'striptags': do_striptags,
635 'groupby': do_groupby,
637 'xmlattr': do_xmlattr