blob: 801b3502658b675597dc3093ca929a0274f0ca88 [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 Ronacherbe4ae242008-04-18 09:49:08 +020019from itertools import imap, groupby
Armin Ronacher9a027f42008-04-17 11:13:40 +020020from 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
Armin Ronacherf59bac22008-04-20 13:11:43 +020047def 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))
52
53
Armin Ronacher9a027f42008-04-17 11:13:40 +020054def do_replace(s, old, new, count=None):
55 """Return a copy of the value with all occurrences of a substring
Armin Ronacher37a88512007-03-02 20:42:18 +010056 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:
Armin Ronacher92f572f2007-02-26 22:17:32 +010060
Armin Ronacher37a88512007-03-02 20:42:18 +010061 .. sourcecode:: jinja
62
63 {{ "Hello World"|replace("Hello", "Goodbye") }}
64 -> Goodbye World
65
66 {{ "aaaaargh"|replace("a", "d'oh, ", 2) }}
67 -> d'oh, d'oh, aaargh
Armin Ronacher92f572f2007-02-26 22:17:32 +010068 """
Armin Ronacher2da479f2007-04-02 10:50:18 +020069 if count is None:
Armin Ronacher9a027f42008-04-17 11:13:40 +020070 count = -1
71 if hasattr(old, '__html__') or hasattr(new, '__html__') and \
72 not hasattr(s, '__html__'):
73 s = escape(s)
74 else:
75 s = soft_unicode(s)
Armin Ronacher92f572f2007-02-26 22:17:32 +010076 return s.replace(old, new, count)
Armin Ronacher92f572f2007-02-26 22:17:32 +010077
78
79def do_upper(s):
Armin Ronacher8edbe492008-04-10 20:43:43 +020080 """Convert a value to uppercase."""
Armin Ronacher9a027f42008-04-17 11:13:40 +020081 return soft_unicode(s).upper()
Armin Ronacher92f572f2007-02-26 22:17:32 +010082
83
Christoph Hack2751d942008-04-09 12:14:55 +020084def do_lower(s):
Armin Ronacher8edbe492008-04-10 20:43:43 +020085 """Convert a value to lowercase."""
Armin Ronacher9a027f42008-04-17 11:13:40 +020086 return soft_unicode(s).lower()
Armin Ronacher92f572f2007-02-26 22:17:32 +010087
88
Christoph Hack2751d942008-04-09 12:14:55 +020089def do_xmlattr(d, autospace=False):
Armin Ronacher9a027f42008-04-17 11:13:40 +020090 """Create an SGML/XML attribute string based on the items in a dict.
Armin Ronacher450756b2007-04-15 15:13:59 +020091 All values that are neither `none` nor `undefined` are automatically
92 escaped:
93
Armin Ronacher8ca55df2007-04-15 15:16:08 +020094 .. sourcecode:: html+jinja
Armin Ronacher450756b2007-04-15 15:13:59 +020095
Armin Ronacherc7ddd1d2007-04-28 15:56:27 +020096 <ul{{ {'class': 'my_list', 'missing': None,
Armin Ronacherd459e272007-04-15 15:31:05 +020097 'id': 'list-%d'|format(variable)}|xmlattr }}>
Armin Ronacher450756b2007-04-15 15:13:59 +020098 ...
99 </ul>
100
101 Results in something like this:
102
Armin Ronacher8ca55df2007-04-15 15:16:08 +0200103 .. sourcecode:: html
104
Armin Ronacher450756b2007-04-15 15:13:59 +0200105 <ul class="my_list" id="list-42">
106 ...
107 </ul>
108
Armin Ronacher9bcd4112007-05-29 14:17:24 +0200109 As you can see it automatically prepends a space in front of the item
Armin Ronacherc7ddd1d2007-04-28 15:56:27 +0200110 if the filter returned something. You can disable this by passing
111 `false` as only argument to the filter.
Armin Ronacher450756b2007-04-15 15:13:59 +0200112 """
Christoph Hack2751d942008-04-09 12:14:55 +0200113 if not hasattr(d, 'iteritems'):
114 raise TypeError('a dict is required')
115 result = []
116 for key, value in d.iteritems():
Armin Ronacher5f514882008-04-16 15:29:52 +0200117 if value is not None and not isinstance(value, Undefined):
Armin Ronacherbe4ae242008-04-18 09:49:08 +0200118 result.append(u'%s="%s"' % (escape(key), escape(value)))
Armin Ronacher9a027f42008-04-17 11:13:40 +0200119 rv = u' '.join(
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)
123 )
Christoph Hack2751d942008-04-09 12:14:55 +0200124 if autospace:
125 rv = ' ' + rv
Armin Ronacher9a027f42008-04-17 11:13:40 +0200126 return Markup(rv)
Armin Ronacher450756b2007-04-15 15:13:59 +0200127
128
Armin Ronacher92f572f2007-02-26 22:17:32 +0100129def do_capitalize(s):
Armin Ronacher9a027f42008-04-17 11:13:40 +0200130 """Capitalize a value. The first character will be uppercase, all others
Armin Ronacher37a88512007-03-02 20:42:18 +0100131 lowercase.
Armin Ronacher92f572f2007-02-26 22:17:32 +0100132 """
Armin Ronacher9a027f42008-04-17 11:13:40 +0200133 return soft_unicode(s).capitalize()
Armin Ronacher92f572f2007-02-26 22:17:32 +0100134
135
136def do_title(s):
Armin Ronacher9a027f42008-04-17 11:13:40 +0200137 """Return a titlecased version of the value. I.e. words will start with
Armin Ronacher37a88512007-03-02 20:42:18 +0100138 uppercase letters, all remaining characters are lowercase.
Armin Ronacher92f572f2007-02-26 22:17:32 +0100139 """
Armin Ronacher9a027f42008-04-17 11:13:40 +0200140 return soft_unicode(s).title()
Armin Ronacher92f572f2007-02-26 22:17:32 +0100141
142
Christoph Hacke9e43bb2008-04-13 23:35:48 +0200143def do_dictsort(value, case_sensitive=False, by='key'):
Armin Ronacher9a027f42008-04-17 11:13:40 +0200144 """ Sort a dict and yield (key, value) pairs. Because python dicts are
Armin Ronacher2b765132007-03-13 16:48:10 +0100145 unsorted you may want to use this function to order them by either
146 key or value:
147
148 .. sourcecode:: jinja
149
150 {% for item in mydict|dictsort %}
151 sort the dict by key, case insensitive
152
153 {% for item in mydict|dicsort(true) %}
154 sort the dict by key, case sensitive
155
156 {% for item in mydict|dictsort(false, 'value') %}
157 sort the dict by key, case insensitive, sorted
158 normally and ordered by value.
159 """
160 if by == 'key':
161 pos = 0
162 elif by == 'value':
163 pos = 1
164 else:
165 raise FilterArgumentError('You can only sort by either '
166 '"key" or "value"')
Armin Ronacher9a027f42008-04-17 11:13:40 +0200167 def sort_func(item):
168 value = item[pos]
Armin Ronacher2b765132007-03-13 16:48:10 +0100169 if isinstance(value, basestring):
Christoph Hacke9e43bb2008-04-13 23:35:48 +0200170 value = unicode(value)
Armin Ronacher2b765132007-03-13 16:48:10 +0100171 if not case_sensitive:
172 value = value.lower()
173 return value
174
Armin Ronacher9a027f42008-04-17 11:13:40 +0200175 return sorted(value.items(), key=sort_func)
Armin Ronacher2b765132007-03-13 16:48:10 +0100176
177
Christoph Hack2751d942008-04-09 12:14:55 +0200178def do_default(value, default_value=u'', boolean=False):
Armin Ronacher9a027f42008-04-17 11:13:40 +0200179 """If the value is undefined it will return the passed default value,
Armin Ronacher37a88512007-03-02 20:42:18 +0100180 otherwise the value of the variable:
Armin Ronacher92f572f2007-02-26 22:17:32 +0100181
Armin Ronacher37a88512007-03-02 20:42:18 +0100182 .. sourcecode:: jinja
183
184 {{ my_variable|default('my_variable is not defined') }}
185
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`:
190
191 .. sourcecode:: jinja
192
193 {{ ''|default('the string was empty', true) }}
Armin Ronacher92f572f2007-02-26 22:17:32 +0100194 """
Christoph Hacke9e43bb2008-04-13 23:35:48 +0200195 if (boolean and not value) or isinstance(value, Undefined):
Christoph Hack2751d942008-04-09 12:14:55 +0200196 return default_value
197 return value
Armin Ronacher92f572f2007-02-26 22:17:32 +0100198
199
Christoph Hack2751d942008-04-09 12:14:55 +0200200def do_join(value, d=u''):
Armin Ronacher9a027f42008-04-17 11:13:40 +0200201 """Return a string which is the concatenation of the strings in the
Armin Ronacher37a88512007-03-02 20:42:18 +0100202 sequence. The separator between elements is an empty string per
203 default, you can define ith with the optional parameter:
204
205 .. sourcecode:: jinja
206
207 {{ [1, 2, 3]|join('|') }}
208 -> 1|2|3
209
210 {{ [1, 2, 3]|join }}
211 -> 123
Armin Ronacher92f572f2007-02-26 22:17:32 +0100212 """
Armin Ronacher9a027f42008-04-17 11:13:40 +0200213 # 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
Priit Laes4149a0e2008-04-17 19:04:44 +0200215 if not hasattr(d, '__html__'):
Armin Ronacher9a027f42008-04-17 11:13:40 +0200216 value = list(value)
217 do_escape = False
218 for idx, item in enumerate(value):
219 if hasattr(item, '__html__'):
220 do_escape = True
221 else:
222 value[idx] = unicode(item)
223 if do_escape:
224 d = escape(d)
225 else:
226 d = unicode(d)
227 return d.join(value)
228
229 # no html involved, to normal joining
230 return soft_unicode(d).join(imap(soft_unicode, value))
Armin Ronacher92f572f2007-02-26 22:17:32 +0100231
232
Armin Ronacherfed86c12007-02-27 10:31:14 +0100233def do_center(value, width=80):
Armin Ronacher9a027f42008-04-17 11:13:40 +0200234 """Centers the value in a field of a given width."""
Christoph Hack2751d942008-04-09 12:14:55 +0200235 return unicode(value).center(width)
Armin Ronacherfed86c12007-02-27 10:31:14 +0100236
237
Armin Ronacher9a027f42008-04-17 11:13:40 +0200238@environmentfilter
239def do_first(environment, seq):
240 """Return the frist item of a sequence."""
Christoph Hack2751d942008-04-09 12:14:55 +0200241 try:
242 return iter(seq).next()
243 except StopIteration:
Armin Ronacher9a822052008-04-17 18:44:07 +0200244 return environment.undefined('No first item, sequence was empty.')
Armin Ronacherfed86c12007-02-27 10:31:14 +0100245
246
Armin Ronacher9a027f42008-04-17 11:13:40 +0200247@environmentfilter
248def do_last(environment, seq):
249 """Return the last item of a sequence."""
Christoph Hack2751d942008-04-09 12:14:55 +0200250 try:
251 return iter(reversed(seq)).next()
252 except StopIteration:
Armin Ronacher9a822052008-04-17 18:44:07 +0200253 return environment.undefined('No last item, sequence was empty.')
Armin Ronacherfed86c12007-02-27 10:31:14 +0100254
255
Armin Ronacher9a027f42008-04-17 11:13:40 +0200256@environmentfilter
257def do_random(environment, seq):
258 """Return a random item from the sequence."""
Christoph Hack2751d942008-04-09 12:14:55 +0200259 try:
260 return choice(seq)
261 except IndexError:
Armin Ronacher9a822052008-04-17 18:44:07 +0200262 return environment.undefined('No random item, sequence was empty.')
Armin Ronacherfed86c12007-02-27 10:31:14 +0100263
264
Christoph Hacke9e43bb2008-04-13 23:35:48 +0200265def do_filesizeformat(value):
Armin Ronacher9a027f42008-04-17 11:13:40 +0200266 """Format the value like a 'human-readable' file size (i.e. 13 KB,
Armin Ronacher5f514882008-04-16 15:29:52 +0200267 4.1 MB, 102 bytes, etc).
Armin Ronacher2b765132007-03-13 16:48:10 +0100268 """
Christoph Hacke9e43bb2008-04-13 23:35:48 +0200269 # fail silently
270 try:
271 bytes = float(value)
272 except TypeError:
273 bytes = 0
Armin Ronacher2b765132007-03-13 16:48:10 +0100274
Christoph Hacke9e43bb2008-04-13 23:35:48 +0200275 if bytes < 1024:
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))
Armin Ronacher2b765132007-03-13 16:48:10 +0100282
283
Christoph Hack2751d942008-04-09 12:14:55 +0200284def do_pprint(value, verbose=False):
Armin Ronacher9a027f42008-04-17 11:13:40 +0200285 """Pretty print a variable. Useful for debugging.
Armin Ronacherf2ce1262007-10-21 21:51:51 +0200286
287 With Jinja 1.2 onwards you can pass it a parameter. If this parameter
Armin Ronacher5f3f1362007-10-21 22:15:04 +0200288 is truthy the output will be more verbose (this requires `pretty`)
Armin Ronacher2b765132007-03-13 16:48:10 +0100289 """
Christoph Hack2751d942008-04-09 12:14:55 +0200290 return pformat(value, verbose=verbose)
Armin Ronacher2b765132007-03-13 16:48:10 +0100291
292
293def do_urlize(value, trim_url_limit=None, nofollow=False):
Armin Ronacher9a027f42008-04-17 11:13:40 +0200294 """Converts URLs in plain text into clickable links.
Armin Ronacher2b765132007-03-13 16:48:10 +0100295
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
298 "nofollow":
299
300 .. sourcecode:: jinja
301
302 {{ mytext|urlize(40, True) }}
303 links are shortened to 40 chars and defined with rel="nofollow"
304 """
Armin Ronacher9a027f42008-04-17 11:13:40 +0200305 return urlize(soft_unicode(value), trim_url_limit, nofollow)
Armin Ronacher2b765132007-03-13 16:48:10 +0100306
307
308def do_indent(s, width=4, indentfirst=False):
309 """
310 {{ s|indent[ width[ indentfirst[ usetab]]] }}
311
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:
316
317 .. sourcecode:: jinja
318
319 {{ mytext|indent(2, True) }}
320 indent by two spaces and indent the first line too.
321 """
322 indention = ' ' * width
323 if indentfirst:
Armin Ronacher9a027f42008-04-17 11:13:40 +0200324 return u'\n'.join(indention + line for line in s.splitlines())
Armin Ronacher2b765132007-03-13 16:48:10 +0100325 return s.replace('\n', '\n' + indention)
Armin Ronacher2b765132007-03-13 16:48:10 +0100326
327
328def do_truncate(s, length=255, killwords=False, end='...'):
329 """
Armin Ronacher2b765132007-03-13 16:48:10 +0100330 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
336 third parameter.
337
338 .. sourcecode jinja::
339
340 {{ mytext|truncate(300, false, '&raquo;') }}
341 truncate mytext to 300 chars, don't split up words, use a
342 right pointing double arrow as ellipsis sign.
343 """
344 if len(s) <= length:
345 return s
346 elif killwords:
347 return s[:length] + end
348 words = s.split(' ')
349 result = []
350 m = 0
351 for word in words:
352 m += len(word) + 1
353 if m > length:
354 break
355 result.append(word)
356 result.append(end)
357 return u' '.join(result)
Armin Ronacher2b765132007-03-13 16:48:10 +0100358
359
360def do_wordwrap(s, pos=79, hard=False):
361 """
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
366 reading hard).
367 """
368 if len(s) < pos:
369 return s
370 if hard:
Armin Ronacher9a027f42008-04-17 11:13:40 +0200371 return u'\n'.join(s[idx:idx + pos] for idx in
372 xrange(0, len(s), pos))
373
374 # TODO: switch to wordwrap.wrap
Armin Ronacher2b765132007-03-13 16:48:10 +0100375 # 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)],
379 word), s.split(' '))
Armin Ronacher2b765132007-03-13 16:48:10 +0100380
Armin Ronacher963f97d2008-04-25 11:44:59 +0200381
Armin Ronacher2b765132007-03-13 16:48:10 +0100382def do_wordcount(s):
Armin Ronacher9a027f42008-04-17 11:13:40 +0200383 """Count the words in that string."""
Priit Laes284f2002008-04-17 21:22:00 +0200384 return len(s.split())
Christoph Hacke9e43bb2008-04-13 23:35:48 +0200385
Armin Ronacher963f97d2008-04-25 11:44:59 +0200386
Christoph Hacke9e43bb2008-04-13 23:35:48 +0200387def do_int(value, default=0):
Armin Ronacher9a027f42008-04-17 11:13:40 +0200388 """Convert the value into an integer. If the
Armin Ronacherab45b842007-03-18 20:47:50 +0100389 conversion doesn't work it will return ``0``. You can
390 override this default using the first parameter.
Armin Ronacher2b765132007-03-13 16:48:10 +0100391 """
Christoph Hacke9e43bb2008-04-13 23:35:48 +0200392 try:
393 return int(value)
394 except (TypeError, ValueError):
Armin Ronacherf59bac22008-04-20 13:11:43 +0200395 # this quirk is necessary so that "42.23"|int gives 42.
Armin Ronacherbe4ae242008-04-18 09:49:08 +0200396 try:
397 return int(float(value))
398 except (TypeError, ValueError):
399 return default
Armin Ronacher2b765132007-03-13 16:48:10 +0100400
401
Christoph Hacke9e43bb2008-04-13 23:35:48 +0200402def do_float(value, default=0.0):
Armin Ronacher9a027f42008-04-17 11:13:40 +0200403 """Convert the value into a floating point number. If the
Armin Ronacherab45b842007-03-18 20:47:50 +0100404 conversion doesn't work it will return ``0.0``. You can
405 override this default using the first parameter.
Armin Ronacher2b765132007-03-13 16:48:10 +0100406 """
Christoph Hacke9e43bb2008-04-13 23:35:48 +0200407 try:
408 return float(value)
409 except (TypeError, ValueError):
410 return default
Armin Ronacher2b765132007-03-13 16:48:10 +0100411
412
Armin Ronacher5f514882008-04-16 15:29:52 +0200413def do_format(value, *args, **kwargs):
Armin Ronacherf0f4deb2007-03-18 23:10:15 +0100414 """
415 Apply python string formatting on an object:
416
417 .. sourcecode:: jinja
418
419 {{ "%s - %s"|format("Hello?", "Foo!") }}
420 -> Hello? - Foo!
Armin Ronacherf0f4deb2007-03-18 23:10:15 +0100421 """
Armin Ronacher5f514882008-04-16 15:29:52 +0200422 if kwargs:
423 kwargs.update(idx, arg in enumerate(args))
424 args = kwargs
Armin Ronacher9a027f42008-04-17 11:13:40 +0200425 return soft_unicode(value) % args
Armin Ronacherf0f4deb2007-03-18 23:10:15 +0100426
427
Armin Ronacher566295e2007-03-19 13:19:34 +0100428def do_trim(value):
Armin Ronacher9a027f42008-04-17 11:13:40 +0200429 """Strip leading and trailing whitespace."""
430 return soft_unicode(value).strip()
Armin Ronacher566295e2007-03-19 13:19:34 +0100431
432
Armin Ronacher9bcd4112007-05-29 14:17:24 +0200433def do_striptags(value):
Armin Ronacher9a027f42008-04-17 11:13:40 +0200434 """Strip SGML/XML tags and replace adjacent whitespace by one space.
Georg Brandlaf31e4d2007-04-15 00:47:37 +0200435 """
Armin Ronacherf59bac22008-04-20 13:11:43 +0200436 if hasattr(value, '__html__'):
437 value = value.__html__()
438 return u' '.join(_striptags_re.sub('', value).split())
Georg Brandlaf31e4d2007-04-15 00:47:37 +0200439
440
Christoph Hacke9e43bb2008-04-13 23:35:48 +0200441def do_slice(value, slices, fill_with=None):
Armin Ronacher9a027f42008-04-17 11:13:40 +0200442 """Slice an iterator and return a list of lists containing
Armin Ronacherd071f952007-04-13 22:32:11 +0200443 those items. Useful if you want to create a div containing
444 three div tags that represent columns:
445
Armin Ronacher89376072007-04-13 22:34:35 +0200446 .. sourcecode:: html+jinja
Armin Ronacherd071f952007-04-13 22:32:11 +0200447
448 <div class="columwrapper">
449 {%- for column in items|slice(3) %}
450 <ul class="column-{{ loop.index }}">
451 {%- for item in column %}
452 <li>{{ item }}</li>
453 {%- endfor %}
454 </ul>
455 {%- endfor %}
456 </div>
457
Armin Ronachereec31382007-04-14 14:50:45 +0200458 If you pass it a second argument it's used to fill missing
459 values on the last iteration.
Armin Ronacherd071f952007-04-13 22:32:11 +0200460 """
Armin Ronacherbe4ae242008-04-18 09:49:08 +0200461 seq = list(value)
Christoph Hacke9e43bb2008-04-13 23:35:48 +0200462 length = len(seq)
463 items_per_slice = length // slices
464 slices_with_extra = length % slices
465 offset = 0
466 for slice_number in xrange(slices):
467 start = offset + slice_number * items_per_slice
468 if slice_number < slices_with_extra:
469 offset += 1
470 end = offset + (slice_number + 1) * items_per_slice
471 tmp = seq[start:end]
472 if fill_with is not None and slice_number >= slices_with_extra:
473 tmp.append(fill_with)
Armin Ronacher814f6c22008-04-17 15:52:23 +0200474 yield tmp
Armin Ronacherd071f952007-04-13 22:32:11 +0200475
476
Christoph Hacke9e43bb2008-04-13 23:35:48 +0200477def do_batch(value, linecount, fill_with=None):
Armin Ronacherd071f952007-04-13 22:32:11 +0200478 """
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:
483
Armin Ronacher89376072007-04-13 22:34:35 +0200484 .. sourcecode:: html+jinja
Armin Ronacherd071f952007-04-13 22:32:11 +0200485
486 <table>
487 {%- for row in items|batch(3, '&nbsp;') %}
488 <tr>
489 {%- for column in row %}
490 <tr>{{ column }}</td>
491 {%- endfor %}
492 </tr>
493 {%- endfor %}
494 </table>
Armin Ronacherd071f952007-04-13 22:32:11 +0200495 """
Christoph Hacke9e43bb2008-04-13 23:35:48 +0200496 result = []
497 tmp = []
498 for item in value:
499 if len(tmp) == linecount:
Armin Ronacher814f6c22008-04-17 15:52:23 +0200500 yield tmp
Christoph Hacke9e43bb2008-04-13 23:35:48 +0200501 tmp = []
502 tmp.append(item)
503 if tmp:
504 if fill_with is not None and len(tmp) < linecount:
505 tmp += [fill_with] * (linecount - len(tmp))
Armin Ronacher814f6c22008-04-17 15:52:23 +0200506 yield tmp
Armin Ronacherd071f952007-04-13 22:32:11 +0200507
508
Armin Ronacher9a027f42008-04-17 11:13:40 +0200509def do_round(value, precision=0, method='common'):
510 """Round the number to a given precision. The first
Armin Ronachereec31382007-04-14 14:50:45 +0200511 parameter specifies the precision (default is ``0``), the
512 second the rounding method:
513
514 - ``'common'`` rounds either up or down
515 - ``'ceil'`` always rounds up
516 - ``'floor'`` always rounds down
517
518 If you don't specify a method ``'common'`` is used.
519
520 .. sourcecode:: jinja
521
522 {{ 42.55|round }}
523 -> 43
524 {{ 42.55|round(1, 'floor') }}
525 -> 42.5
526 """
527 if not method in ('common', 'ceil', 'floor'):
528 raise FilterArgumentError('method must be common, ceil or floor')
529 if precision < 0:
530 raise FilterArgumentError('precision must be a postive integer '
531 'or zero.')
Armin Ronacher9a027f42008-04-17 11:13:40 +0200532 if method == 'common':
533 return round(value, precision)
534 func = getattr(math, method)
535 if precision:
536 return func(value * 10 * precision) / (10 * precision)
537 else:
538 return func(value)
Armin Ronachereec31382007-04-14 14:50:45 +0200539
540
Armin Ronacher9a027f42008-04-17 11:13:40 +0200541def do_sort(value, reverse=False):
542 """Sort a sequence. Per default it sorts ascending, if you pass it
Armin Ronacher9bcd4112007-05-29 14:17:24 +0200543 `True` as first argument it will reverse the sorting.
Armin Ronacher9bcd4112007-05-29 14:17:24 +0200544 """
Armin Ronacher9a027f42008-04-17 11:13:40 +0200545 return sorted(value, reverse=reverse)
Armin Ronacher9bcd4112007-05-29 14:17:24 +0200546
547
Armin Ronacher9a027f42008-04-17 11:13:40 +0200548@environmentfilter
549def do_groupby(environment, value, attribute):
550 """Group a sequence of objects by a common attribute.
Armin Ronachere39a5d22007-06-23 21:11:53 +0200551
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
555 snippet:
556
557 .. sourcecode:: html+jinja
558
559 <ul>
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>
565 {% endfor %}
566 </ul>
567
Armin Ronacher963f97d2008-04-25 11:44:59 +0200568 Additionally it's possible to use tuple unpacking for the grouper and
569 list:
570
571 .. sourcecode:: html+jinja
572
573 <ul>
574 {% for grouper, list in persons|groupby('gender') %}
575 ...
576 {% endfor %}
577 </ul>
578
Armin Ronachere39a5d22007-06-23 21:11:53 +0200579 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
581 in common.
582 """
Armin Ronacher9a027f42008-04-17 11:13:40 +0200583 expr = lambda x: environment.subscribe(x, attribute)
Armin Ronacher963f97d2008-04-25 11:44:59 +0200584 return sorted(map(_GroupTuple, groupby(sorted(value, key=expr), expr)),
585 key=itemgetter('grouper'))
586
587
588class _GroupTuple(tuple):
589 __slots__ = ()
590 grouper = property(itemgetter(0))
591 list = property(itemgetter(1))
Armin Ronachere39a5d22007-06-23 21:11:53 +0200592
593
Armin Ronacher92f572f2007-02-26 22:17:32 +0100594FILTERS = {
595 'replace': do_replace,
596 'upper': do_upper,
597 'lower': do_lower,
Armin Ronacher814f6c22008-04-17 15:52:23 +0200598 'escape': escape,
599 'e': escape,
Armin Ronacherf59bac22008-04-20 13:11:43 +0200600 'forceescape': do_forceescape,
Armin Ronacher92f572f2007-02-26 22:17:32 +0100601 'capitalize': do_capitalize,
602 'title': do_title,
603 'default': do_default,
604 'join': do_join,
Armin Ronacher5f514882008-04-16 15:29:52 +0200605 'count': len,
Armin Ronacher2b765132007-03-13 16:48:10 +0100606 'dictsort': do_dictsort,
Armin Ronacher5f514882008-04-16 15:29:52 +0200607 'length': len,
608 'reverse': reversed,
Armin Ronacherfed86c12007-02-27 10:31:14 +0100609 'center': do_center,
Priit Laes8464ab12008-04-17 21:21:04 +0200610 'indent': do_indent,
Armin Ronacherfed86c12007-02-27 10:31:14 +0100611 'title': do_title,
612 'capitalize': do_capitalize,
613 'first': do_first,
614 'last': do_last,
615 'random': do_random,
Armin Ronacher2b765132007-03-13 16:48:10 +0100616 'filesizeformat': do_filesizeformat,
617 'pprint': do_pprint,
Armin Ronacher2b765132007-03-13 16:48:10 +0100618 'truncate': do_truncate,
619 'wordwrap': do_wordwrap,
620 'wordcount': do_wordcount,
Armin Ronacher2b765132007-03-13 16:48:10 +0100621 'int': do_int,
622 'float': do_float,
Armin Ronacherbe4ae242008-04-18 09:49:08 +0200623 'string': soft_unicode,
624 'list': list,
Armin Ronacherf0f4deb2007-03-18 23:10:15 +0100625 'urlize': do_urlize,
626 'format': do_format,
Armin Ronacherd071f952007-04-13 22:32:11 +0200627 'trim': do_trim,
Georg Brandlaf31e4d2007-04-15 00:47:37 +0200628 'striptags': do_striptags,
Armin Ronacherd071f952007-04-13 22:32:11 +0200629 'slice': do_slice,
Armin Ronachereec31382007-04-14 14:50:45 +0200630 'batch': do_batch,
Armin Ronacher5f514882008-04-16 15:29:52 +0200631 'sum': sum,
632 'abs': abs,
Armin Ronacher9bcd4112007-05-29 14:17:24 +0200633 'round': do_round,
Armin Ronachere39a5d22007-06-23 21:11:53 +0200634 'sort': do_sort,
Armin Ronacher18c6ca02008-04-17 10:03:29 +0200635 'groupby': do_groupby,
Armin Ronacher0611e492008-04-25 23:44:14 +0200636 'safe': Markup,
637 'xmlattr': do_xmlattr
Armin Ronacher92f572f2007-02-26 22:17:32 +0100638}