revamped jinja2 import system. the behavior is less confusing now, but it's not...
[jinja2.git] / jinja2 / filters.py
1 # -*- coding: utf-8 -*-
2 """
3     jinja2.filters
4     ~~~~~~~~~~~~~~
5
6     Bundled jinja filters.
7
8     :copyright: 2008 by Armin Ronacher, Christoph Hack.
9     :license: BSD, see LICENSE for more details.
10 """
11 import re
12 import math
13 from random import choice
14 try:
15     from operator import itemgetter
16 except ImportError:
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
22
23
24 _striptags_re = re.compile(r'(<!--.*?-->|<[^>]*>)')
25
26
27 def contextfilter(f):
28     """Decorator for marking context dependent filters. The current context
29     argument will be passed as first argument.
30     """
31     if getattr(f, 'environmentfilter', False):
32         raise TypeError('filter already marked as environment filter')
33     f.contextfilter = True
34     return f
35
36
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.
40     """
41     if getattr(f, 'contextfilter', False):
42         raise TypeError('filter already marked as context filter')
43     f.environmentfilter = True
44     return f
45
46
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))
52
53
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:
60
61     .. 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
68     """
69     if count is None:
70         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)
76     return s.replace(old, new, count)
77
78
79 def do_upper(s):
80     """Convert a value to uppercase."""
81     return soft_unicode(s).upper()
82
83
84 def do_lower(s):
85     """Convert a value to lowercase."""
86     return soft_unicode(s).lower()
87
88
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
92     escaped:
93
94     .. sourcecode:: html+jinja
95
96         <ul{{ {'class': 'my_list', 'missing': None,
97                 'id': 'list-%d'|format(variable)}|xmlattr }}>
98         ...
99         </ul>
100
101     Results in something like this:
102
103     .. sourcecode:: html
104
105         <ul class="my_list" id="list-42">
106         ...
107         </ul>
108
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.
112     """
113     if not hasattr(d, 'iteritems'):
114         raise TypeError('a dict is required')
115     result = []
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)))
119     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     )
124     if autospace:
125         rv = ' ' + rv
126     return Markup(rv)
127
128
129 def do_capitalize(s):
130     """Capitalize a value. The first character will be uppercase, all others
131     lowercase.
132     """
133     return soft_unicode(s).capitalize()
134
135
136 def do_title(s):
137     """Return a titlecased version of the value. I.e. words will start with
138     uppercase letters, all remaining characters are lowercase.
139     """
140     return soft_unicode(s).title()
141
142
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
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"')
167     def sort_func(item):
168         value = item[pos]
169         if isinstance(value, basestring):
170             value = unicode(value)
171             if not case_sensitive:
172                 value = value.lower()
173         return value
174
175     return sorted(value.items(), key=sort_func)
176
177
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:
181
182     .. 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) }}
194     """
195     if (boolean and not value) or isinstance(value, Undefined):
196         return default_value
197     return value
198
199
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:
204
205     .. sourcecode:: jinja
206
207         {{ [1, 2, 3]|join('|') }}
208             -> 1|2|3
209
210         {{ [1, 2, 3]|join }}
211             -> 123
212     """
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__'):
216         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))
231
232
233 def do_center(value, width=80):
234     """Centers the value in a field of a given width."""
235     return unicode(value).center(width)
236
237
238 @environmentfilter
239 def do_first(environment, seq):
240     """Return the frist item of a sequence."""
241     try:
242         return iter(seq).next()
243     except StopIteration:
244         return environment.undefined('No first item, sequence was empty.')
245
246
247 @environmentfilter
248 def do_last(environment, seq):
249     """Return the last item of a sequence."""
250     try:
251         return iter(reversed(seq)).next()
252     except StopIteration:
253         return environment.undefined('No last item, sequence was empty.')
254
255
256 @environmentfilter
257 def do_random(environment, seq):
258     """Return a random item from the sequence."""
259     try:
260         return choice(seq)
261     except IndexError:
262         return environment.undefined('No random item, sequence was empty.')
263
264
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).
268     """
269     # fail silently
270     try:
271         bytes = float(value)
272     except TypeError:
273         bytes = 0
274
275     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))
282
283
284 def do_pprint(value, verbose=False):
285     """Pretty print a variable. Useful for debugging.
286
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`)
289     """
290     return pformat(value, verbose=verbose)
291
292
293 def do_urlize(value, trim_url_limit=None, nofollow=False):
294     """Converts URLs in plain text into clickable links.
295
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     """
305     return urlize(soft_unicode(value), trim_url_limit, nofollow)
306
307
308 def 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:
324         return u'\n'.join(indention + line for line in s.splitlines())
325     return s.replace('\n', '\n' + indention)
326
327
328 def do_truncate(s, length=255, killwords=False, end='...'):
329     """
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
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)
358
359
360 def 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:
371         return u'\n'.join(s[idx:idx + pos] for idx in
372                           xrange(0, len(s), pos))
373
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)],
379                    word), s.split(' '))
380
381
382 def do_wordcount(s):
383     """Count the words in that string."""
384     return len(s.split())
385
386
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.
391     """
392     try:
393         return int(value)
394     except (TypeError, ValueError):
395         # this quirk is necessary so that "42.23"|int gives 42.
396         try:
397             return int(float(value))
398         except (TypeError, ValueError):
399             return default
400
401
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.
406     """
407     try:
408         return float(value)
409     except (TypeError, ValueError):
410         return default
411
412
413 def do_format(value, *args, **kwargs):
414     """
415     Apply python string formatting on an object:
416
417     .. sourcecode:: jinja
418
419         {{ "%s - %s"|format("Hello?", "Foo!") }}
420             -> Hello? - Foo!
421     """
422     if kwargs:
423         kwargs.update(idx, arg in enumerate(args))
424         args = kwargs
425     return soft_unicode(value) % args
426
427
428 def do_trim(value):
429     """Strip leading and trailing whitespace."""
430     return soft_unicode(value).strip()
431
432
433 def do_striptags(value):
434     """Strip SGML/XML tags and replace adjacent whitespace by one space.
435     """
436     if hasattr(value, '__html__'):
437         value = value.__html__()
438     return u' '.join(_striptags_re.sub('', value).split())
439
440
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:
445
446     .. sourcecode:: html+jinja
447
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
458     If you pass it a second argument it's used to fill missing
459     values on the last iteration.
460     """
461     seq = list(value)
462     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)
474         yield tmp
475
476
477 def do_batch(value, linecount, fill_with=None):
478     """
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
484     .. sourcecode:: html+jinja
485
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>
495     """
496     result = []
497     tmp = []
498     for item in value:
499         if len(tmp) == linecount:
500             yield tmp
501             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))
506         yield tmp
507
508
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:
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.')
532     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)
539
540
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.
544     """
545     return sorted(value, reverse=reverse)
546
547
548 @environmentfilter
549 def do_groupby(environment, value, attribute):
550     """Group a sequence of objects by a common attribute.
551
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
568     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
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
581     in common.
582     """
583     expr = lambda x: environment.subscribe(x, attribute)
584     return sorted(map(_GroupTuple, groupby(sorted(value, key=expr), expr)),
585                   key=itemgetter('grouper'))
586
587
588 class _GroupTuple(tuple):
589     __slots__ = ()
590     grouper = property(itemgetter(0))
591     list = property(itemgetter(1))
592
593
594 FILTERS = {
595     'replace':              do_replace,
596     'upper':                do_upper,
597     'lower':                do_lower,
598     'escape':               escape,
599     'e':                    escape,
600     'forceescape':          do_forceescape,
601     'capitalize':           do_capitalize,
602     'title':                do_title,
603     'default':              do_default,
604     'join':                 do_join,
605     'count':                len,
606     'dictsort':             do_dictsort,
607     'length':               len,
608     'reverse':              reversed,
609     'center':               do_center,
610     'indent':               do_indent,
611     'title':                do_title,
612     'capitalize':           do_capitalize,
613     'first':                do_first,
614     'last':                 do_last,
615     'random':               do_random,
616     'filesizeformat':       do_filesizeformat,
617     'pprint':               do_pprint,
618     'truncate':             do_truncate,
619     'wordwrap':             do_wordwrap,
620     'wordcount':            do_wordcount,
621     'int':                  do_int,
622     'float':                do_float,
623     'string':               soft_unicode,
624     'list':                 list,
625     'urlize':               do_urlize,
626     'format':               do_format,
627     'trim':                 do_trim,
628     'striptags':            do_striptags,
629     'slice':                do_slice,
630     'batch':                do_batch,
631     'sum':                  sum,
632     'abs':                  abs,
633     'round':                do_round,
634     'sort':                 do_sort,
635     'groupby':              do_groupby,
636     'safe':                 Markup,
637     'xmlattr':              do_xmlattr
638 }