Various cleanups and added custom cycler.
[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 from operator import itemgetter
15 from itertools import imap, groupby
16 from jinja2.utils import Markup, escape, pformat, urlize, soft_unicode
17 from jinja2.runtime import Undefined
18 from jinja2.exceptions import FilterArgumentError, SecurityError
19
20
21 _word_re = re.compile(r'\w+')
22
23
24 def contextfilter(f):
25     """Decorator for marking context dependent filters. The current
26     :class:`Context` will be passed as first argument.
27     """
28     if getattr(f, 'environmentfilter', False):
29         raise TypeError('filter already marked as environment filter')
30     f.contextfilter = True
31     return f
32
33
34 def environmentfilter(f):
35     """Decorator for marking evironment dependent filters.  The current
36     :class:`Environment` is passed to the filter as first argument.
37     """
38     if getattr(f, 'contextfilter', False):
39         raise TypeError('filter already marked as context filter')
40     f.environmentfilter = True
41     return f
42
43
44 def do_forceescape(value):
45     """Enforce HTML escaping.  This will probably double escape variables."""
46     if hasattr(value, '__html__'):
47         value = value.__html__()
48     return escape(unicode(value))
49
50
51 @environmentfilter
52 def do_replace(environment, s, old, new, count=None):
53     """Return a copy of the value with all occurrences of a substring
54     replaced with a new one. The first argument is the substring
55     that should be replaced, the second is the replacement string.
56     If the optional third argument ``count`` is given, only the first
57     ``count`` occurrences are replaced:
58
59     .. sourcecode:: jinja
60
61         {{ "Hello World"|replace("Hello", "Goodbye") }}
62             -> Goodbye World
63
64         {{ "aaaaargh"|replace("a", "d'oh, ", 2) }}
65             -> d'oh, d'oh, aaargh
66     """
67     if count is None:
68         count = -1
69     if not environment.autoescape:
70         return unicode(s).replace(unicode(old), unicode(new), count)
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(soft_unicode(old), soft_unicode(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 @environmentfilter
90 def do_xmlattr(_environment, d, autospace=True):
91     """Create an SGML/XML attribute string based on the items in a dict.
92     All values that are neither `none` nor `undefined` are automatically
93     escaped:
94
95     .. sourcecode:: html+jinja
96
97         <ul{{ {'class': 'my_list', 'missing': none,
98                 'id': 'list-%d'|format(variable)}|xmlattr }}>
99         ...
100         </ul>
101
102     Results in something like this:
103
104     .. sourcecode:: html
105
106         <ul class="my_list" id="list-42">
107         ...
108         </ul>
109
110     As you can see it automatically prepends a space in front of the item
111     if the filter returned something unless the second parameter is false.
112     """
113     rv = u' '.join(
114         u'%s="%s"' % (escape(key), escape(value))
115         for key, value in d.iteritems()
116         if value is not None and not isinstance(value, Undefined)
117     )
118     if autospace and rv:
119         rv = u' ' + rv
120     if _environment.autoescape:
121         rv = Markup(rv)
122     return rv
123
124
125 def do_capitalize(s):
126     """Capitalize a value. The first character will be uppercase, all others
127     lowercase.
128     """
129     return soft_unicode(s).capitalize()
130
131
132 def do_title(s):
133     """Return a titlecased version of the value. I.e. words will start with
134     uppercase letters, all remaining characters are lowercase.
135     """
136     return soft_unicode(s).title()
137
138
139 def do_dictsort(value, case_sensitive=False, by='key'):
140     """Sort a dict and yield (key, value) pairs. Because python dicts are
141     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"')
163     def sort_func(item):
164         value = item[pos]
165         if isinstance(value, basestring) and not case_sensitive:
166             value = value.lower()
167         return value
168
169     return sorted(value.items(), key=sort_func)
170
171
172 def do_sort(value, case_sensitive=False):
173     """Sort an iterable.  If the iterable is made of strings the second
174     parameter can be used to control the case sensitiveness of the
175     comparison which is disabled by default.
176
177     .. sourcecode:: jinja
178
179         {% for item in iterable|sort %}
180             ...
181         {% endfor %}
182     """
183     if not case_sensitive:
184         def sort_func(item):
185             if isinstance(item, basestring):
186                 item = item.lower()
187             return item
188     else:
189         sort_func = None
190     return sorted(seq, key=sort_func)
191
192
193 def do_default(value, default_value=u'', boolean=False):
194     """If the value is undefined it will return the passed default value,
195     otherwise the value of the variable:
196
197     .. sourcecode:: jinja
198
199         {{ my_variable|default('my_variable is not defined') }}
200
201     This will output the value of ``my_variable`` if the variable was
202     defined, otherwise ``'my_variable is not defined'``. If you want
203     to use default with variables that evaluate to false you have to
204     set the second parameter to `true`:
205
206     .. sourcecode:: jinja
207
208         {{ ''|default('the string was empty', true) }}
209     """
210     if (boolean and not value) or isinstance(value, Undefined):
211         return default_value
212     return value
213
214
215 @environmentfilter
216 def do_join(environment, value, d=u''):
217     """Return a string which is the concatenation of the strings in the
218     sequence. The separator between elements is an empty string per
219     default, you can define it with the optional parameter:
220
221     .. sourcecode:: jinja
222
223         {{ [1, 2, 3]|join('|') }}
224             -> 1|2|3
225
226         {{ [1, 2, 3]|join }}
227             -> 123
228     """
229     # no automatic escaping?  joining is a lot eaiser then
230     if not environment.autoescape:
231         return unicode(d).join(imap(unicode, value))
232
233     # if the delimiter doesn't have an html representation we check
234     # if any of the items has.  If yes we do a coercion to Markup
235     if not hasattr(d, '__html__'):
236         value = list(value)
237         do_escape = False
238         for idx, item in enumerate(value):
239             if hasattr(item, '__html__'):
240                 do_escape = True
241             else:
242                 value[idx] = unicode(item)
243         if do_escape:
244             d = escape(d)
245         else:
246             d = unicode(d)
247         return d.join(value)
248
249     # no html involved, to normal joining
250     return soft_unicode(d).join(imap(soft_unicode, value))
251
252
253 def do_center(value, width=80):
254     """Centers the value in a field of a given width."""
255     return unicode(value).center(width)
256
257
258 @environmentfilter
259 def do_first(environment, seq):
260     """Return the first item of a sequence."""
261     try:
262         return iter(seq).next()
263     except StopIteration:
264         return environment.undefined('No first item, sequence was empty.')
265
266
267 @environmentfilter
268 def do_last(environment, seq):
269     """Return the last item of a sequence."""
270     try:
271         return iter(reversed(seq)).next()
272     except StopIteration:
273         return environment.undefined('No last item, sequence was empty.')
274
275
276 @environmentfilter
277 def do_random(environment, seq):
278     """Return a random item from the sequence."""
279     try:
280         return choice(seq)
281     except IndexError:
282         return environment.undefined('No random item, sequence was empty.')
283
284
285 def do_filesizeformat(value, binary=False):
286     """Format the value like a 'human-readable' file size (i.e. 13 KB,
287     4.1 MB, 102 bytes, etc).  Per default decimal prefixes are used (mega,
288     giga etc.), if the second parameter is set to `True` the binary
289     prefixes are (mebi, gibi).
290     """
291     bytes = float(value)
292     base = binary and 1024 or 1000
293     middle = binary and 'i' or ''
294     if bytes < base:
295         return "%d Byte%s" % (bytes, bytes != 1 and 's' or '')
296     elif bytes < base * base:
297         return "%.1f K%sB" % (bytes / base, middle)
298     elif bytes < base * base * base:
299         return "%.1f M%sB" % (bytes / (base * base), middle)
300     return "%.1f G%sB" % (bytes / (base * base * base), middle)
301
302
303 def do_pprint(value, verbose=False):
304     """Pretty print a variable. Useful for debugging.
305
306     With Jinja 1.2 onwards you can pass it a parameter.  If this parameter
307     is truthy the output will be more verbose (this requires `pretty`)
308     """
309     return pformat(value, verbose=verbose)
310
311
312 @environmentfilter
313 def do_urlize(environment, value, trim_url_limit=None, nofollow=False):
314     """Converts URLs in plain text into clickable links.
315
316     If you pass the filter an additional integer it will shorten the urls
317     to that number. Also a third argument exists that makes the urls
318     "nofollow":
319
320     .. sourcecode:: jinja
321
322         {{ mytext|urlize(40, true) }}
323             links are shortened to 40 chars and defined with rel="nofollow"
324     """
325     rv = urlize(soft_unicode(value), trim_url_limit, nofollow)
326     if environment.autoescape:
327         rv = Markup(rv)
328     return rv
329
330
331 def do_indent(s, width=4, indentfirst=False):
332     """Return a copy of the passed string, each line indented by
333     4 spaces. The first line is not indented. If you want to
334     change the number of spaces or indent the first line too
335     you can pass additional parameters to the filter:
336
337     .. sourcecode:: jinja
338
339         {{ mytext|indent(2, true) }}
340             indent by two spaces and indent the first line too.
341     """
342     indention = u' ' * width
343     rv = (u'\n' + indention).join(s.splitlines())
344     if indentfirst:
345         rv = indention + rv
346     return rv
347
348
349 def do_truncate(s, length=255, killwords=False, end='...'):
350     """Return a truncated copy of the string. The length is specified
351     with the first parameter which defaults to ``255``. If the second
352     parameter is ``true`` the filter will cut the text at length. Otherwise
353     it will try to save the last word. If the text was in fact
354     truncated it will append an ellipsis sign (``"..."``). If you want a
355     different ellipsis sign than ``"..."`` you can specify it using the
356     third parameter.
357
358     .. sourcecode jinja::
359
360         {{ mytext|truncate(300, false, '&raquo;') }}
361             truncate mytext to 300 chars, don't split up words, use a
362             right pointing double arrow as ellipsis sign.
363     """
364     if len(s) <= length:
365         return s
366     elif killwords:
367         return s[:length] + end
368     words = s.split(' ')
369     result = []
370     m = 0
371     for word in words:
372         m += len(word) + 1
373         if m > length:
374             break
375         result.append(word)
376     result.append(end)
377     return u' '.join(result)
378
379
380 def do_wordwrap(s, width=79, break_long_words=True):
381     """
382     Return a copy of the string passed to the filter wrapped after
383     ``79`` characters.  You can override this default using the first
384     parameter.  If you set the second parameter to `false` Jinja will not
385     split words apart if they are longer than `width`.
386     """
387     import textwrap
388     return u'\n'.join(textwrap.wrap(s, width=width, expand_tabs=False,
389                                    replace_whitespace=False,
390                                    break_long_words=break_long_words))
391
392
393 def do_wordcount(s):
394     """Count the words in that string."""
395     return len(_word_re.findall(s))
396
397
398 def do_int(value, default=0):
399     """Convert the value into an integer. If the
400     conversion doesn't work it will return ``0``. You can
401     override this default using the first parameter.
402     """
403     try:
404         return int(value)
405     except (TypeError, ValueError):
406         # this quirk is necessary so that "42.23"|int gives 42.
407         try:
408             return int(float(value))
409         except (TypeError, ValueError):
410             return default
411
412
413 def do_float(value, default=0.0):
414     """Convert the value into a floating point number. If the
415     conversion doesn't work it will return ``0.0``. You can
416     override this default using the first parameter.
417     """
418     try:
419         return float(value)
420     except (TypeError, ValueError):
421         return default
422
423
424 def do_format(value, *args, **kwargs):
425     """
426     Apply python string formatting on an object:
427
428     .. sourcecode:: jinja
429
430         {{ "%s - %s"|format("Hello?", "Foo!") }}
431             -> Hello? - Foo!
432     """
433     if args and kwargs:
434         raise FilterArgumentError('can\'t handle positional and keyword '
435                                   'arguments at the same time')
436     return soft_unicode(value) % (kwargs or args)
437
438
439 def do_trim(value):
440     """Strip leading and trailing whitespace."""
441     return soft_unicode(value).strip()
442
443
444 def do_striptags(value):
445     """Strip SGML/XML tags and replace adjacent whitespace by one space.
446     """
447     if hasattr(value, '__html__'):
448         value = value.__html__()
449     return Markup(unicode(value)).striptags()
450
451
452 def do_slice(value, slices, fill_with=None):
453     """Slice an iterator and return a list of lists containing
454     those items. Useful if you want to create a div containing
455     three div tags that represent columns:
456
457     .. sourcecode:: html+jinja
458
459         <div class="columwrapper">
460           {%- for column in items|slice(3) %}
461             <ul class="column-{{ loop.index }}">
462             {%- for item in column %}
463               <li>{{ item }}</li>
464             {%- endfor %}
465             </ul>
466           {%- endfor %}
467         </div>
468
469     If you pass it a second argument it's used to fill missing
470     values on the last iteration.
471     """
472     seq = list(value)
473     length = len(seq)
474     items_per_slice = length // slices
475     slices_with_extra = length % slices
476     offset = 0
477     for slice_number in xrange(slices):
478         start = offset + slice_number * items_per_slice
479         if slice_number < slices_with_extra:
480             offset += 1
481         end = offset + (slice_number + 1) * items_per_slice
482         tmp = seq[start:end]
483         if fill_with is not None and slice_number >= slices_with_extra:
484             tmp.append(fill_with)
485         yield tmp
486
487
488 def do_batch(value, linecount, fill_with=None):
489     """
490     A filter that batches items. It works pretty much like `slice`
491     just the other way round. It returns a list of lists with the
492     given number of items. If you provide a second parameter this
493     is used to fill missing items. See this example:
494
495     .. sourcecode:: html+jinja
496
497         <table>
498         {%- for row in items|batch(3, '&nbsp;') %}
499           <tr>
500           {%- for column in row %}
501             <tr>{{ column }}</td>
502           {%- endfor %}
503           </tr>
504         {%- endfor %}
505         </table>
506     """
507     result = []
508     tmp = []
509     for item in value:
510         if len(tmp) == linecount:
511             yield tmp
512             tmp = []
513         tmp.append(item)
514     if tmp:
515         if fill_with is not None and len(tmp) < linecount:
516             tmp += [fill_with] * (linecount - len(tmp))
517         yield tmp
518
519
520 def do_round(value, precision=0, method='common'):
521     """Round the number to a given precision. The first
522     parameter specifies the precision (default is ``0``), the
523     second the rounding method:
524
525     - ``'common'`` rounds either up or down
526     - ``'ceil'`` always rounds up
527     - ``'floor'`` always rounds down
528
529     If you don't specify a method ``'common'`` is used.
530
531     .. sourcecode:: jinja
532
533         {{ 42.55|round }}
534             -> 43
535         {{ 42.55|round(1, 'floor') }}
536             -> 42.5
537     """
538     if not method in ('common', 'ceil', 'floor'):
539         raise FilterArgumentError('method must be common, ceil or floor')
540     if precision < 0:
541         raise FilterArgumentError('precision must be a postive integer '
542                                   'or zero.')
543     if method == 'common':
544         return round(value, precision)
545     func = getattr(math, method)
546     if precision:
547         return func(value * 10 * precision) / (10 * precision)
548     else:
549         return func(value)
550
551
552 def do_sort(value, reverse=False):
553     """Sort a sequence. Per default it sorts ascending, if you pass it
554     true as first argument it will reverse the sorting.
555     """
556     return sorted(value, reverse=reverse)
557
558
559 @environmentfilter
560 def do_groupby(environment, value, attribute):
561     """Group a sequence of objects by a common attribute.
562
563     If you for example have a list of dicts or objects that represent persons
564     with `gender`, `first_name` and `last_name` attributes and you want to
565     group all users by genders you can do something like the following
566     snippet:
567
568     .. sourcecode:: html+jinja
569
570         <ul>
571         {% for group in persons|groupby('gender') %}
572             <li>{{ group.grouper }}<ul>
573             {% for person in group.list %}
574                 <li>{{ person.first_name }} {{ person.last_name }}</li>
575             {% endfor %}</ul></li>
576         {% endfor %}
577         </ul>
578
579     Additionally it's possible to use tuple unpacking for the grouper and
580     list:
581
582     .. sourcecode:: html+jinja
583
584         <ul>
585         {% for grouper, list in persons|groupby('gender') %}
586             ...
587         {% endfor %}
588         </ul>
589
590     As you can see the item we're grouping by is stored in the `grouper`
591     attribute and the `list` contains all the objects that have this grouper
592     in common.
593     """
594     expr = lambda x: environment.getitem(x, attribute)
595     return sorted(map(_GroupTuple, groupby(sorted(value, key=expr), expr)))
596
597
598 class _GroupTuple(tuple):
599     __slots__ = ()
600     grouper = property(itemgetter(0))
601     list = property(itemgetter(1))
602
603     def __new__(cls, (key, value)):
604         return tuple.__new__(cls, (key, list(value)))
605
606
607 def do_list(value):
608     """Convert the value into a list.  If it was a string the returned list
609     will be a list of characters.
610     """
611     return list(value)
612
613
614 def do_mark_safe(value):
615     """Mark the value as safe which means that in an environment with automatic
616     escaping enabled this variable will not be escaped.
617     """
618     return Markup(value)
619
620
621 def do_mark_unsafe(value):
622     """Mark a value as unsafe.  This is the reverse operation for :func:`safe`."""
623     return unicode(value)
624
625
626 def do_reverse(value):
627     """Reverse the object or return an iterator the iterates over it the other
628     way round.
629     """
630     if isinstance(value, basestring):
631         return value[::-1]
632     try:
633         return reversed(value)
634     except TypeError:
635         try:
636             rv = list(value)
637             rv.reverse()
638             return rv
639         except TypeError:
640             raise FilterArgumentError('argument must be iterable')
641
642
643 @environmentfilter
644 def do_attr(environment, obj, name):
645     """Get an attribute of an object.  ``foo|attr("bar")`` works like
646     ``foo["bar"]`` just that always an attribute is returned and items are not
647     looked up.
648
649     See :ref:`Notes on subscriptions <notes-on-subscriptions>` for more details.
650     """
651     try:
652         name = str(name)
653     except UnicodeError:
654         pass
655     else:
656         try:
657             value = getattr(obj, name)
658         except AttributeError:
659             pass
660         else:
661             if environment.sandboxed and not \
662                environment.is_safe_attribute(obj, name, value):
663                 return environment.unsafe_undefined(obj, name)
664             return value
665     return environment.undefined(obj=obj, name=name)
666
667
668 FILTERS = {
669     'attr':                 do_attr,
670     'replace':              do_replace,
671     'upper':                do_upper,
672     'lower':                do_lower,
673     'escape':               escape,
674     'e':                    escape,
675     'forceescape':          do_forceescape,
676     'capitalize':           do_capitalize,
677     'title':                do_title,
678     'default':              do_default,
679     'd':                    do_default,
680     'join':                 do_join,
681     'count':                len,
682     'dictsort':             do_dictsort,
683     'sort':                 do_sort,
684     'length':               len,
685     'reverse':              do_reverse,
686     'center':               do_center,
687     'indent':               do_indent,
688     'title':                do_title,
689     'capitalize':           do_capitalize,
690     'first':                do_first,
691     'last':                 do_last,
692     'random':               do_random,
693     'filesizeformat':       do_filesizeformat,
694     'pprint':               do_pprint,
695     'truncate':             do_truncate,
696     'wordwrap':             do_wordwrap,
697     'wordcount':            do_wordcount,
698     'int':                  do_int,
699     'float':                do_float,
700     'string':               soft_unicode,
701     'list':                 do_list,
702     'urlize':               do_urlize,
703     'format':               do_format,
704     'trim':                 do_trim,
705     'striptags':            do_striptags,
706     'slice':                do_slice,
707     'batch':                do_batch,
708     'sum':                  sum,
709     'abs':                  abs,
710     'round':                do_round,
711     'sort':                 do_sort,
712     'groupby':              do_groupby,
713     'safe':                 do_mark_safe,
714     'xmlattr':              do_xmlattr
715 }