blob: fd3d66fa49b89b0f0e667967562fd27dc05089f5 [file] [log] [blame]
Armin Ronacher92f572f2007-02-26 22:17:32 +01001# -*- coding: utf-8 -*-
2"""
Armin Ronacher26c0f512008-04-17 11:16:01 +02003 jinja2.filters
4 ~~~~~~~~~~~~~~
Armin Ronacher92f572f2007-02-26 22:17:32 +01005
6 Bundled jinja filters.
7
Christoph Hack2751d942008-04-09 12:14:55 +02008 :copyright: 2008 by Armin Ronacher, Christoph Hack.
Armin Ronacher92f572f2007-02-26 22:17:32 +01009 :license: BSD, see LICENSE for more details.
10"""
Georg Brandlaf31e4d2007-04-15 00:47:37 +020011import re
Armin Ronacher9a027f42008-04-17 11:13:40 +020012import math
Armin Ronacherfed86c12007-02-27 10:31:14 +010013from random import choice
Armin Ronacher1dcdac52007-12-09 22:53:46 +010014try:
15 from operator import itemgetter
16except ImportError:
17 itemgetter = lambda a: lambda b: b[a]
Armin Ronacherfed86c12007-02-27 10:31:14 +010018from urllib import urlencode, quote
Armin Ronacher9a027f42008-04-17 11:13:40 +020019from itertools import imap
20from jinja2.utils import Markup, escape, pformat, urlize, soft_unicode
Christoph Hack80909862008-04-14 01:35:10 +020021from jinja2.runtime import Undefined
Christoph Hacke9e43bb2008-04-13 23:35:48 +020022
Armin Ronacherfed86c12007-02-27 10:31:14 +010023
Armin Ronachereaf493e2007-10-01 22:31:16 +020024_striptags_re = re.compile(r'(<!--.*?-->|<[^>]*>)')
Armin Ronacher9bcd4112007-05-29 14:17:24 +020025
26
Christoph Hack2751d942008-04-09 12:14:55 +020027def contextfilter(f):
Armin Ronacher8edbe492008-04-10 20:43:43 +020028 """Decorator for marking context dependent filters. The current context
Christoph Hack2751d942008-04-09 12:14:55 +020029 argument will be passed as first argument.
Armin Ronacher92f572f2007-02-26 22:17:32 +010030 """
Armin Ronacher9a027f42008-04-17 11:13:40 +020031 if getattr(f, 'environmentfilter', False):
32 raise TypeError('filter already marked as environment filter')
Christoph Hack2751d942008-04-09 12:14:55 +020033 f.contextfilter = True
34 return f
Armin Ronacher9bcd4112007-05-29 14:17:24 +020035
36
Armin Ronacher9a027f42008-04-17 11:13:40 +020037def environmentfilter(f):
38 """Decorator for marking evironment dependent filters. The environment
39 used for the template is passed to the filter as first argument.
Armin Ronacher92f572f2007-02-26 22:17:32 +010040 """
Armin Ronacher9a027f42008-04-17 11:13:40 +020041 if getattr(f, 'contextfilter', False):
42 raise TypeError('filter already marked as context filter')
43 f.environmentfilter = True
44 return f
45
46
47def do_replace(s, old, new, count=None):
48 """Return a copy of the value with all occurrences of a substring
Armin Ronacher37a88512007-03-02 20:42:18 +010049 replaced with a new one. The first argument is the substring
50 that should be replaced, the second is the replacement string.
51 If the optional third argument ``count`` is given, only the first
52 ``count`` occurrences are replaced:
Armin Ronacher92f572f2007-02-26 22:17:32 +010053
Armin Ronacher37a88512007-03-02 20:42:18 +010054 .. sourcecode:: jinja
55
56 {{ "Hello World"|replace("Hello", "Goodbye") }}
57 -> Goodbye World
58
59 {{ "aaaaargh"|replace("a", "d'oh, ", 2) }}
60 -> d'oh, d'oh, aaargh
Armin Ronacher92f572f2007-02-26 22:17:32 +010061 """
Armin Ronacher2da479f2007-04-02 10:50:18 +020062 if count is None:
Armin Ronacher9a027f42008-04-17 11:13:40 +020063 count = -1
64 if hasattr(old, '__html__') or hasattr(new, '__html__') and \
65 not hasattr(s, '__html__'):
66 s = escape(s)
67 else:
68 s = soft_unicode(s)
Armin Ronacher92f572f2007-02-26 22:17:32 +010069 return s.replace(old, new, count)
Armin Ronacher92f572f2007-02-26 22:17:32 +010070
71
72def do_upper(s):
Armin Ronacher8edbe492008-04-10 20:43:43 +020073 """Convert a value to uppercase."""
Armin Ronacher9a027f42008-04-17 11:13:40 +020074 return soft_unicode(s).upper()
Armin Ronacher92f572f2007-02-26 22:17:32 +010075
76
Christoph Hack2751d942008-04-09 12:14:55 +020077def do_lower(s):
Armin Ronacher8edbe492008-04-10 20:43:43 +020078 """Convert a value to lowercase."""
Armin Ronacher9a027f42008-04-17 11:13:40 +020079 return soft_unicode(s).lower()
Armin Ronacher92f572f2007-02-26 22:17:32 +010080
81
Christoph Hack2751d942008-04-09 12:14:55 +020082def do_xmlattr(d, autospace=False):
Armin Ronacher9a027f42008-04-17 11:13:40 +020083 """Create an SGML/XML attribute string based on the items in a dict.
Armin Ronacher450756b2007-04-15 15:13:59 +020084 All values that are neither `none` nor `undefined` are automatically
85 escaped:
86
Armin Ronacher8ca55df2007-04-15 15:16:08 +020087 .. sourcecode:: html+jinja
Armin Ronacher450756b2007-04-15 15:13:59 +020088
Armin Ronacherc7ddd1d2007-04-28 15:56:27 +020089 <ul{{ {'class': 'my_list', 'missing': None,
Armin Ronacherd459e272007-04-15 15:31:05 +020090 'id': 'list-%d'|format(variable)}|xmlattr }}>
Armin Ronacher450756b2007-04-15 15:13:59 +020091 ...
92 </ul>
93
94 Results in something like this:
95
Armin Ronacher8ca55df2007-04-15 15:16:08 +020096 .. sourcecode:: html
97
Armin Ronacher450756b2007-04-15 15:13:59 +020098 <ul class="my_list" id="list-42">
99 ...
100 </ul>
101
Armin Ronacher9bcd4112007-05-29 14:17:24 +0200102 As you can see it automatically prepends a space in front of the item
Armin Ronacherc7ddd1d2007-04-28 15:56:27 +0200103 if the filter returned something. You can disable this by passing
104 `false` as only argument to the filter.
Armin Ronacher450756b2007-04-15 15:13:59 +0200105 """
Christoph Hack2751d942008-04-09 12:14:55 +0200106 if not hasattr(d, 'iteritems'):
107 raise TypeError('a dict is required')
108 result = []
109 for key, value in d.iteritems():
Armin Ronacher5f514882008-04-16 15:29:52 +0200110 if value is not None and not isinstance(value, Undefined):
Christoph Hack2751d942008-04-09 12:14:55 +0200111 result.append(u'%s="%s"' % (
112 escape(env.to_unicode(key)),
113 escape(env.to_unicode(value), True)
114 ))
Armin Ronacher9a027f42008-04-17 11:13:40 +0200115 rv = u' '.join(
116 u'%s="%s"' % (escape(key), escape(value))
117 for key, value in d.iteritems()
118 if value is not None and not isinstance(value, Undefined)
119 )
Christoph Hack2751d942008-04-09 12:14:55 +0200120 if autospace:
121 rv = ' ' + rv
Armin Ronacher9a027f42008-04-17 11:13:40 +0200122 return Markup(rv)
Armin Ronacher450756b2007-04-15 15:13:59 +0200123
124
Armin Ronacher92f572f2007-02-26 22:17:32 +0100125def do_capitalize(s):
Armin Ronacher9a027f42008-04-17 11:13:40 +0200126 """Capitalize a value. The first character will be uppercase, all others
Armin Ronacher37a88512007-03-02 20:42:18 +0100127 lowercase.
Armin Ronacher92f572f2007-02-26 22:17:32 +0100128 """
Armin Ronacher9a027f42008-04-17 11:13:40 +0200129 return soft_unicode(s).capitalize()
Armin Ronacher92f572f2007-02-26 22:17:32 +0100130
131
132def do_title(s):
Armin Ronacher9a027f42008-04-17 11:13:40 +0200133 """Return a titlecased version of the value. I.e. words will start with
Armin Ronacher37a88512007-03-02 20:42:18 +0100134 uppercase letters, all remaining characters are lowercase.
Armin Ronacher92f572f2007-02-26 22:17:32 +0100135 """
Armin Ronacher9a027f42008-04-17 11:13:40 +0200136 return soft_unicode(s).title()
Armin Ronacher92f572f2007-02-26 22:17:32 +0100137
138
Christoph Hacke9e43bb2008-04-13 23:35:48 +0200139def do_dictsort(value, case_sensitive=False, by='key'):
Armin Ronacher9a027f42008-04-17 11:13:40 +0200140 """ Sort a dict and yield (key, value) pairs. Because python dicts are
Armin Ronacher2b765132007-03-13 16:48:10 +0100141 unsorted you may want to use this function to order them by either
142 key or value:
143
144 .. sourcecode:: jinja
145
146 {% for item in mydict|dictsort %}
147 sort the dict by key, case insensitive
148
149 {% for item in mydict|dicsort(true) %}
150 sort the dict by key, case sensitive
151
152 {% for item in mydict|dictsort(false, 'value') %}
153 sort the dict by key, case insensitive, sorted
154 normally and ordered by value.
155 """
156 if by == 'key':
157 pos = 0
158 elif by == 'value':
159 pos = 1
160 else:
161 raise FilterArgumentError('You can only sort by either '
162 '"key" or "value"')
Armin Ronacher9a027f42008-04-17 11:13:40 +0200163 def sort_func(item):
164 value = item[pos]
Armin Ronacher2b765132007-03-13 16:48:10 +0100165 if isinstance(value, basestring):
Christoph Hacke9e43bb2008-04-13 23:35:48 +0200166 value = unicode(value)
Armin Ronacher2b765132007-03-13 16:48:10 +0100167 if not case_sensitive:
168 value = value.lower()
169 return value
170
Armin Ronacher9a027f42008-04-17 11:13:40 +0200171 return sorted(value.items(), key=sort_func)
Armin Ronacher2b765132007-03-13 16:48:10 +0100172
173
Christoph Hack2751d942008-04-09 12:14:55 +0200174def do_default(value, default_value=u'', boolean=False):
Armin Ronacher9a027f42008-04-17 11:13:40 +0200175 """If the value is undefined it will return the passed default value,
Armin Ronacher37a88512007-03-02 20:42:18 +0100176 otherwise the value of the variable:
Armin Ronacher92f572f2007-02-26 22:17:32 +0100177
Armin Ronacher37a88512007-03-02 20:42:18 +0100178 .. sourcecode:: jinja
179
180 {{ my_variable|default('my_variable is not defined') }}
181
182 This will output the value of ``my_variable`` if the variable was
183 defined, otherwise ``'my_variable is not defined'``. If you want
184 to use default with variables that evaluate to false you have to
185 set the second parameter to `true`:
186
187 .. sourcecode:: jinja
188
189 {{ ''|default('the string was empty', true) }}
Armin Ronacher92f572f2007-02-26 22:17:32 +0100190 """
Christoph Hacke9e43bb2008-04-13 23:35:48 +0200191 if (boolean and not value) or isinstance(value, Undefined):
Christoph Hack2751d942008-04-09 12:14:55 +0200192 return default_value
193 return value
Armin Ronacher92f572f2007-02-26 22:17:32 +0100194
195
Christoph Hack2751d942008-04-09 12:14:55 +0200196def do_join(value, d=u''):
Armin Ronacher9a027f42008-04-17 11:13:40 +0200197 """Return a string which is the concatenation of the strings in the
Armin Ronacher37a88512007-03-02 20:42:18 +0100198 sequence. The separator between elements is an empty string per
199 default, you can define ith with the optional parameter:
200
201 .. sourcecode:: jinja
202
203 {{ [1, 2, 3]|join('|') }}
204 -> 1|2|3
205
206 {{ [1, 2, 3]|join }}
207 -> 123
Armin Ronacher92f572f2007-02-26 22:17:32 +0100208 """
Armin Ronacher9a027f42008-04-17 11:13:40 +0200209 # if the delimiter doesn't have an html representation we check
210 # if any of the items has. If yes we do a coercion to Markup
Priit Laes4149a0e2008-04-17 19:04:44 +0200211 if not hasattr(d, '__html__'):
Armin Ronacher9a027f42008-04-17 11:13:40 +0200212 value = list(value)
213 do_escape = False
214 for idx, item in enumerate(value):
215 if hasattr(item, '__html__'):
216 do_escape = True
217 else:
218 value[idx] = unicode(item)
219 if do_escape:
220 d = escape(d)
221 else:
222 d = unicode(d)
223 return d.join(value)
224
225 # no html involved, to normal joining
226 return soft_unicode(d).join(imap(soft_unicode, value))
Armin Ronacher92f572f2007-02-26 22:17:32 +0100227
228
Armin Ronacherfed86c12007-02-27 10:31:14 +0100229def do_center(value, width=80):
Armin Ronacher9a027f42008-04-17 11:13:40 +0200230 """Centers the value in a field of a given width."""
Christoph Hack2751d942008-04-09 12:14:55 +0200231 return unicode(value).center(width)
Armin Ronacherfed86c12007-02-27 10:31:14 +0100232
233
Armin Ronacher9a027f42008-04-17 11:13:40 +0200234@environmentfilter
235def do_first(environment, seq):
236 """Return the frist item of a sequence."""
Christoph Hack2751d942008-04-09 12:14:55 +0200237 try:
238 return iter(seq).next()
239 except StopIteration:
Armin Ronacher9a822052008-04-17 18:44:07 +0200240 return environment.undefined('No first item, sequence was empty.')
Armin Ronacherfed86c12007-02-27 10:31:14 +0100241
242
Armin Ronacher9a027f42008-04-17 11:13:40 +0200243@environmentfilter
244def do_last(environment, seq):
245 """Return the last item of a sequence."""
Christoph Hack2751d942008-04-09 12:14:55 +0200246 try:
247 return iter(reversed(seq)).next()
248 except StopIteration:
Armin Ronacher9a822052008-04-17 18:44:07 +0200249 return environment.undefined('No last item, sequence was empty.')
Armin Ronacherfed86c12007-02-27 10:31:14 +0100250
251
Armin Ronacher9a027f42008-04-17 11:13:40 +0200252@environmentfilter
253def do_random(environment, seq):
254 """Return a random item from the sequence."""
Christoph Hack2751d942008-04-09 12:14:55 +0200255 try:
256 return choice(seq)
257 except IndexError:
Armin Ronacher9a822052008-04-17 18:44:07 +0200258 return environment.undefined('No random item, sequence was empty.')
Armin Ronacherfed86c12007-02-27 10:31:14 +0100259
260
Christoph Hacke9e43bb2008-04-13 23:35:48 +0200261def do_filesizeformat(value):
Armin Ronacher9a027f42008-04-17 11:13:40 +0200262 """Format the value like a 'human-readable' file size (i.e. 13 KB,
Armin Ronacher5f514882008-04-16 15:29:52 +0200263 4.1 MB, 102 bytes, etc).
Armin Ronacher2b765132007-03-13 16:48:10 +0100264 """
Christoph Hacke9e43bb2008-04-13 23:35:48 +0200265 # fail silently
266 try:
267 bytes = float(value)
268 except TypeError:
269 bytes = 0
Armin Ronacher2b765132007-03-13 16:48:10 +0100270
Christoph Hacke9e43bb2008-04-13 23:35:48 +0200271 if bytes < 1024:
272 return "%d Byte%s" % (bytes, bytes != 1 and 's' or '')
273 elif bytes < 1024 * 1024:
274 return "%.1f KB" % (bytes / 1024)
275 elif bytes < 1024 * 1024 * 1024:
276 return "%.1f MB" % (bytes / (1024 * 1024))
277 return "%.1f GB" % (bytes / (1024 * 1024 * 1024))
Armin Ronacher2b765132007-03-13 16:48:10 +0100278
279
Christoph Hack2751d942008-04-09 12:14:55 +0200280def do_pprint(value, verbose=False):
Armin Ronacher9a027f42008-04-17 11:13:40 +0200281 """Pretty print a variable. Useful for debugging.
Armin Ronacherf2ce1262007-10-21 21:51:51 +0200282
283 With Jinja 1.2 onwards you can pass it a parameter. If this parameter
Armin Ronacher5f3f1362007-10-21 22:15:04 +0200284 is truthy the output will be more verbose (this requires `pretty`)
Armin Ronacher2b765132007-03-13 16:48:10 +0100285 """
Christoph Hack2751d942008-04-09 12:14:55 +0200286 return pformat(value, verbose=verbose)
Armin Ronacher2b765132007-03-13 16:48:10 +0100287
288
289def do_urlize(value, trim_url_limit=None, nofollow=False):
Armin Ronacher9a027f42008-04-17 11:13:40 +0200290 """Converts URLs in plain text into clickable links.
Armin Ronacher2b765132007-03-13 16:48:10 +0100291
292 If you pass the filter an additional integer it will shorten the urls
293 to that number. Also a third argument exists that makes the urls
294 "nofollow":
295
296 .. sourcecode:: jinja
297
298 {{ mytext|urlize(40, True) }}
299 links are shortened to 40 chars and defined with rel="nofollow"
300 """
Armin Ronacher9a027f42008-04-17 11:13:40 +0200301 return urlize(soft_unicode(value), trim_url_limit, nofollow)
Armin Ronacher2b765132007-03-13 16:48:10 +0100302
303
304def do_indent(s, width=4, indentfirst=False):
305 """
306 {{ s|indent[ width[ indentfirst[ usetab]]] }}
307
308 Return a copy of the passed string, each line indented by
309 4 spaces. The first line is not indented. If you want to
310 change the number of spaces or indent the first line too
311 you can pass additional parameters to the filter:
312
313 .. sourcecode:: jinja
314
315 {{ mytext|indent(2, True) }}
316 indent by two spaces and indent the first line too.
317 """
318 indention = ' ' * width
319 if indentfirst:
Armin Ronacher9a027f42008-04-17 11:13:40 +0200320 return u'\n'.join(indention + line for line in s.splitlines())
Armin Ronacher2b765132007-03-13 16:48:10 +0100321 return s.replace('\n', '\n' + indention)
Armin Ronacher2b765132007-03-13 16:48:10 +0100322
323
324def do_truncate(s, length=255, killwords=False, end='...'):
325 """
Armin Ronacher2b765132007-03-13 16:48:10 +0100326 Return a truncated copy of the string. The length is specified
327 with the first parameter which defaults to ``255``. If the second
328 parameter is ``true`` the filter will cut the text at length. Otherwise
329 it will try to save the last word. If the text was in fact
330 truncated it will append an ellipsis sign (``"..."``). If you want a
331 different ellipsis sign than ``"..."`` you can specify it using the
332 third parameter.
333
334 .. sourcecode jinja::
335
336 {{ mytext|truncate(300, false, '&raquo;') }}
337 truncate mytext to 300 chars, don't split up words, use a
338 right pointing double arrow as ellipsis sign.
339 """
340 if len(s) <= length:
341 return s
342 elif killwords:
343 return s[:length] + end
344 words = s.split(' ')
345 result = []
346 m = 0
347 for word in words:
348 m += len(word) + 1
349 if m > length:
350 break
351 result.append(word)
352 result.append(end)
353 return u' '.join(result)
Armin Ronacher2b765132007-03-13 16:48:10 +0100354
355
356def do_wordwrap(s, pos=79, hard=False):
357 """
358 Return a copy of the string passed to the filter wrapped after
359 ``79`` characters. You can override this default using the first
360 parameter. If you set the second parameter to `true` Jinja will
361 also split words apart (usually a bad idea because it makes
362 reading hard).
363 """
364 if len(s) < pos:
365 return s
366 if hard:
Armin Ronacher9a027f42008-04-17 11:13:40 +0200367 return u'\n'.join(s[idx:idx + pos] for idx in
368 xrange(0, len(s), pos))
369
370 # TODO: switch to wordwrap.wrap
Armin Ronacher2b765132007-03-13 16:48:10 +0100371 # code from http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/148061
372 return reduce(lambda line, word, pos=pos: u'%s%s%s' %
373 (line, u' \n'[(len(line)-line.rfind('\n') - 1 +
374 len(word.split('\n', 1)[0]) >= pos)],
375 word), s.split(' '))
Armin Ronacher2b765132007-03-13 16:48:10 +0100376
377
378def do_wordcount(s):
Armin Ronacher9a027f42008-04-17 11:13:40 +0200379 """Count the words in that string."""
380 return len(x for x in s.split() if x)
Armin Ronacher2b765132007-03-13 16:48:10 +0100381
Christoph Hacke9e43bb2008-04-13 23:35:48 +0200382
383def do_int(value, default=0):
Armin Ronacher9a027f42008-04-17 11:13:40 +0200384 """Convert the value into an integer. If the
Armin Ronacherab45b842007-03-18 20:47:50 +0100385 conversion doesn't work it will return ``0``. You can
386 override this default using the first parameter.
Armin Ronacher2b765132007-03-13 16:48:10 +0100387 """
Christoph Hacke9e43bb2008-04-13 23:35:48 +0200388 try:
389 return int(value)
390 except (TypeError, ValueError):
Armin Ronacher814f6c22008-04-17 15:52:23 +0200391 return default
Armin Ronacher2b765132007-03-13 16:48:10 +0100392
393
Christoph Hacke9e43bb2008-04-13 23:35:48 +0200394def do_float(value, default=0.0):
Armin Ronacher9a027f42008-04-17 11:13:40 +0200395 """Convert the value into a floating point number. If the
Armin Ronacherab45b842007-03-18 20:47:50 +0100396 conversion doesn't work it will return ``0.0``. You can
397 override this default using the first parameter.
Armin Ronacher2b765132007-03-13 16:48:10 +0100398 """
Christoph Hacke9e43bb2008-04-13 23:35:48 +0200399 try:
400 return float(value)
401 except (TypeError, ValueError):
402 return default
Armin Ronacher2b765132007-03-13 16:48:10 +0100403
404
Christoph Hacke9e43bb2008-04-13 23:35:48 +0200405def do_string(value):
Armin Ronacher9a027f42008-04-17 11:13:40 +0200406 """Convert the value into an string."""
407 return soft_unicode(value)
Armin Ronacher2b765132007-03-13 16:48:10 +0100408
409
Armin Ronacher5f514882008-04-16 15:29:52 +0200410def do_format(value, *args, **kwargs):
Armin Ronacherf0f4deb2007-03-18 23:10:15 +0100411 """
412 Apply python string formatting on an object:
413
414 .. sourcecode:: jinja
415
416 {{ "%s - %s"|format("Hello?", "Foo!") }}
417 -> Hello? - Foo!
Armin Ronacherf0f4deb2007-03-18 23:10:15 +0100418 """
Armin Ronacher5f514882008-04-16 15:29:52 +0200419 if kwargs:
420 kwargs.update(idx, arg in enumerate(args))
421 args = kwargs
Armin Ronacher9a027f42008-04-17 11:13:40 +0200422 return soft_unicode(value) % args
Armin Ronacherf0f4deb2007-03-18 23:10:15 +0100423
424
Armin Ronacher566295e2007-03-19 13:19:34 +0100425def do_trim(value):
Armin Ronacher9a027f42008-04-17 11:13:40 +0200426 """Strip leading and trailing whitespace."""
427 return soft_unicode(value).strip()
Armin Ronacher566295e2007-03-19 13:19:34 +0100428
429
Armin Ronacher9bcd4112007-05-29 14:17:24 +0200430def do_striptags(value):
Armin Ronacher9a027f42008-04-17 11:13:40 +0200431 """Strip SGML/XML tags and replace adjacent whitespace by one space.
Georg Brandlaf31e4d2007-04-15 00:47:37 +0200432 """
Armin Ronacher9bcd4112007-05-29 14:17:24 +0200433 return ' '.join(_striptags_re.sub('', value).split())
Georg Brandlaf31e4d2007-04-15 00:47:37 +0200434
435
Christoph Hacke9e43bb2008-04-13 23:35:48 +0200436def do_slice(value, slices, fill_with=None):
Armin Ronacher9a027f42008-04-17 11:13:40 +0200437 """Slice an iterator and return a list of lists containing
Armin Ronacherd071f952007-04-13 22:32:11 +0200438 those items. Useful if you want to create a div containing
439 three div tags that represent columns:
440
Armin Ronacher89376072007-04-13 22:34:35 +0200441 .. sourcecode:: html+jinja
Armin Ronacherd071f952007-04-13 22:32:11 +0200442
443 <div class="columwrapper">
444 {%- for column in items|slice(3) %}
445 <ul class="column-{{ loop.index }}">
446 {%- for item in column %}
447 <li>{{ item }}</li>
448 {%- endfor %}
449 </ul>
450 {%- endfor %}
451 </div>
452
Armin Ronachereec31382007-04-14 14:50:45 +0200453 If you pass it a second argument it's used to fill missing
454 values on the last iteration.
Armin Ronacherd071f952007-04-13 22:32:11 +0200455 """
Armin Ronacher814f6c22008-04-17 15:52:23 +0200456 seq = list(seq)
Christoph Hacke9e43bb2008-04-13 23:35:48 +0200457 length = len(seq)
458 items_per_slice = length // slices
459 slices_with_extra = length % slices
460 offset = 0
461 for slice_number in xrange(slices):
462 start = offset + slice_number * items_per_slice
463 if slice_number < slices_with_extra:
464 offset += 1
465 end = offset + (slice_number + 1) * items_per_slice
466 tmp = seq[start:end]
467 if fill_with is not None and slice_number >= slices_with_extra:
468 tmp.append(fill_with)
Armin Ronacher814f6c22008-04-17 15:52:23 +0200469 yield tmp
Armin Ronacherd071f952007-04-13 22:32:11 +0200470
471
Christoph Hacke9e43bb2008-04-13 23:35:48 +0200472def do_batch(value, linecount, fill_with=None):
Armin Ronacherd071f952007-04-13 22:32:11 +0200473 """
474 A filter that batches items. It works pretty much like `slice`
475 just the other way round. It returns a list of lists with the
476 given number of items. If you provide a second parameter this
477 is used to fill missing items. See this example:
478
Armin Ronacher89376072007-04-13 22:34:35 +0200479 .. sourcecode:: html+jinja
Armin Ronacherd071f952007-04-13 22:32:11 +0200480
481 <table>
482 {%- for row in items|batch(3, '&nbsp;') %}
483 <tr>
484 {%- for column in row %}
485 <tr>{{ column }}</td>
486 {%- endfor %}
487 </tr>
488 {%- endfor %}
489 </table>
Armin Ronacherd071f952007-04-13 22:32:11 +0200490 """
Christoph Hacke9e43bb2008-04-13 23:35:48 +0200491 result = []
492 tmp = []
493 for item in value:
494 if len(tmp) == linecount:
Armin Ronacher814f6c22008-04-17 15:52:23 +0200495 yield tmp
Christoph Hacke9e43bb2008-04-13 23:35:48 +0200496 tmp = []
497 tmp.append(item)
498 if tmp:
499 if fill_with is not None and len(tmp) < linecount:
500 tmp += [fill_with] * (linecount - len(tmp))
Armin Ronacher814f6c22008-04-17 15:52:23 +0200501 yield tmp
Armin Ronacherd071f952007-04-13 22:32:11 +0200502
503
Armin Ronacher9a027f42008-04-17 11:13:40 +0200504def do_round(value, precision=0, method='common'):
505 """Round the number to a given precision. The first
Armin Ronachereec31382007-04-14 14:50:45 +0200506 parameter specifies the precision (default is ``0``), the
507 second the rounding method:
508
509 - ``'common'`` rounds either up or down
510 - ``'ceil'`` always rounds up
511 - ``'floor'`` always rounds down
512
513 If you don't specify a method ``'common'`` is used.
514
515 .. sourcecode:: jinja
516
517 {{ 42.55|round }}
518 -> 43
519 {{ 42.55|round(1, 'floor') }}
520 -> 42.5
521 """
522 if not method in ('common', 'ceil', 'floor'):
523 raise FilterArgumentError('method must be common, ceil or floor')
524 if precision < 0:
525 raise FilterArgumentError('precision must be a postive integer '
526 'or zero.')
Armin Ronacher9a027f42008-04-17 11:13:40 +0200527 if method == 'common':
528 return round(value, precision)
529 func = getattr(math, method)
530 if precision:
531 return func(value * 10 * precision) / (10 * precision)
532 else:
533 return func(value)
Armin Ronachereec31382007-04-14 14:50:45 +0200534
535
Armin Ronacher9a027f42008-04-17 11:13:40 +0200536def do_sort(value, reverse=False):
537 """Sort a sequence. Per default it sorts ascending, if you pass it
Armin Ronacher9bcd4112007-05-29 14:17:24 +0200538 `True` as first argument it will reverse the sorting.
Armin Ronacher9bcd4112007-05-29 14:17:24 +0200539 """
Armin Ronacher9a027f42008-04-17 11:13:40 +0200540 return sorted(value, reverse=reverse)
Armin Ronacher9bcd4112007-05-29 14:17:24 +0200541
542
Armin Ronacher9a027f42008-04-17 11:13:40 +0200543@environmentfilter
544def do_groupby(environment, value, attribute):
545 """Group a sequence of objects by a common attribute.
Armin Ronachere39a5d22007-06-23 21:11:53 +0200546
547 If you for example have a list of dicts or objects that represent persons
548 with `gender`, `first_name` and `last_name` attributes and you want to
549 group all users by genders you can do something like the following
550 snippet:
551
552 .. sourcecode:: html+jinja
553
554 <ul>
555 {% for group in persons|groupby('gender') %}
556 <li>{{ group.grouper }}<ul>
557 {% for person in group.list %}
558 <li>{{ person.first_name }} {{ person.last_name }}</li>
559 {% endfor %}</ul></li>
560 {% endfor %}
561 </ul>
562
563 As you can see the item we're grouping by is stored in the `grouper`
564 attribute and the `list` contains all the objects that have this grouper
565 in common.
566 """
Armin Ronacher9a027f42008-04-17 11:13:40 +0200567 expr = lambda x: environment.subscribe(x, attribute)
568 return sorted([{
569 'grouper': a,
570 'list': b
571 } for a, b in groupby(sorted(value, key=expr), expr)],
572 key=itemgetter('grouper'))
Armin Ronachere39a5d22007-06-23 21:11:53 +0200573
574
Armin Ronacher92f572f2007-02-26 22:17:32 +0100575FILTERS = {
576 'replace': do_replace,
577 'upper': do_upper,
578 'lower': do_lower,
Armin Ronacher814f6c22008-04-17 15:52:23 +0200579 'escape': escape,
580 'e': escape,
Armin Ronacher450756b2007-04-15 15:13:59 +0200581 'xmlattr': do_xmlattr,
Armin Ronacher92f572f2007-02-26 22:17:32 +0100582 'capitalize': do_capitalize,
583 'title': do_title,
584 'default': do_default,
585 'join': do_join,
Armin Ronacher5f514882008-04-16 15:29:52 +0200586 'count': len,
Armin Ronacher2b765132007-03-13 16:48:10 +0100587 'dictsort': do_dictsort,
Armin Ronacher5f514882008-04-16 15:29:52 +0200588 'length': len,
589 'reverse': reversed,
Armin Ronacherfed86c12007-02-27 10:31:14 +0100590 'center': do_center,
Priit Laes8464ab12008-04-17 21:21:04 +0200591 'indent': do_indent,
Armin Ronacherfed86c12007-02-27 10:31:14 +0100592 'title': do_title,
593 'capitalize': do_capitalize,
594 'first': do_first,
595 'last': do_last,
596 'random': do_random,
Armin Ronacher2b765132007-03-13 16:48:10 +0100597 'filesizeformat': do_filesizeformat,
598 'pprint': do_pprint,
Armin Ronacher2b765132007-03-13 16:48:10 +0100599 'truncate': do_truncate,
600 'wordwrap': do_wordwrap,
601 'wordcount': do_wordcount,
Armin Ronacher2b765132007-03-13 16:48:10 +0100602 'int': do_int,
603 'float': do_float,
Tassilo Schweyer934cb712007-03-14 18:04:07 +0100604 'string': do_string,
Armin Ronacherf0f4deb2007-03-18 23:10:15 +0100605 'urlize': do_urlize,
606 'format': do_format,
Armin Ronacherd071f952007-04-13 22:32:11 +0200607 'trim': do_trim,
Georg Brandlaf31e4d2007-04-15 00:47:37 +0200608 'striptags': do_striptags,
Armin Ronacherd071f952007-04-13 22:32:11 +0200609 'slice': do_slice,
Armin Ronachereec31382007-04-14 14:50:45 +0200610 'batch': do_batch,
Armin Ronacher5f514882008-04-16 15:29:52 +0200611 'sum': sum,
612 'abs': abs,
Armin Ronacher9bcd4112007-05-29 14:17:24 +0200613 'round': do_round,
Armin Ronachere39a5d22007-06-23 21:11:53 +0200614 'sort': do_sort,
Armin Ronacher18c6ca02008-04-17 10:03:29 +0200615 'groupby': do_groupby,
616 'safe': Markup
Armin Ronacher92f572f2007-02-26 22:17:32 +0100617}