be32113d201ec80e99c329807aed51c1cebecede
[jinja2.git] / jinja / filters.py
1 # -*- coding: utf-8 -*-
2 """
3     jinja.filters
4     ~~~~~~~~~~~~~
5
6     Bundled jinja filters.
7
8     :copyright: 2007 by Armin Ronacher.
9     :license: BSD, see LICENSE for more details.
10 """
11 from random import choice
12 from urllib import urlencode, quote
13 from jinja.utils import escape, urlize
14 from jinja.datastructure import Undefined
15 from jinja.exceptions import FilterArgumentError
16
17
18 try:
19     _reversed = reversed
20 except NameError:
21     # python2.3 compatibility hack for the do_reverse function
22     def _reversed(seq):
23         try:
24             return seq[::-1]
25         except:
26             try:
27                 return list(seq)[::-1]
28             except:
29                 raise TypeError('argument to _reversed must '
30                                 'be a sequence')
31
32
33 def stringfilter(f):
34     """
35     Decorator for filters that just work on unicode objects.
36     """
37     def decorator(*args):
38         def wrapped(env, context, value):
39             nargs = list(args)
40             for idx, var in enumerate(nargs):
41                 if isinstance(var, str):
42                     nargs[idx] = env.to_unicode(var)
43             return f(env.to_unicode(value), *nargs)
44         return wrapped
45     try:
46         decorator.__doc__ = f.__doc__
47         decorator.__name__ = f.__name__
48     except:
49         pass
50     return decorator
51
52
53 def do_replace(s, old, new, count=None):
54     """
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 not isinstance(old, basestring) or \
70        not isinstance(new, basestring):
71         raise FilterArgumentException('the replace filter requires '
72                                       'string replacement arguments')
73     elif not isinstance(count, (int, long)):
74         raise FilterArgumentException('the count parameter of the '
75                                       'replace filter requires '
76                                       'an integer')
77     if count is None:
78         return s.replace(old, new)
79     return s.replace(old, new, count)
80 do_replace = stringfilter(do_replace)
81
82
83 def do_upper(s):
84     """
85     Convert a value to uppercase.
86     """
87     return s.upper()
88 do_upper = stringfilter(do_upper)
89
90
91 def do_lower(s):
92     """
93     Convert a value to lowercase.
94     """
95     return s.lower()
96 do_lower = stringfilter(do_lower)
97
98
99 def do_escape(s, attribute=False):
100     """
101     XML escape ``&``, ``<``, and ``>`` in a string of data. If the
102     optional parameter is `true` this filter will also convert
103     ``"`` to ``&quot;``. This filter is just used if the environment
104     was configured with disabled `auto_escape`.
105
106     This method will have no effect it the value is already escaped.
107     """
108     return escape(s, attribute)
109 do_escape = stringfilter(do_escape)
110
111
112 def do_capitalize(s):
113     """
114     Capitalize a value. The first character will be uppercase, all others
115     lowercase.
116     """
117     return s.capitalize()
118 do_capitalize = stringfilter(do_capitalize)
119
120
121 def do_title(s):
122     """
123     Return a titlecased version of the value. I.e. words will start with
124     uppercase letters, all remaining characters are lowercase.
125     """
126     return s.title()
127 do_title = stringfilter(do_title)
128
129
130 def do_dictsort(case_sensitive=False, by='key'):
131     """
132     Sort a dict and yield (key, value) pairs. Because python dicts are
133     unsorted you may want to use this function to order them by either
134     key or value:
135
136     .. sourcecode:: jinja
137
138         {% for item in mydict|dictsort %}
139             sort the dict by key, case insensitive
140
141         {% for item in mydict|dicsort(true) %}
142             sort the dict by key, case sensitive
143
144         {% for item in mydict|dictsort(false, 'value') %}
145             sort the dict by key, case insensitive, sorted
146             normally and ordered by value.
147     """
148     if by == 'key':
149         pos = 0
150     elif by == 'value':
151         pos = 1
152     else:
153         raise FilterArgumentError('You can only sort by either '
154                                   '"key" or "value"')
155     def sort_func(value, env):
156         if isinstance(value, basestring):
157             value = env.to_unicode(value)
158             if not case_sensitive:
159                 value = value.lower()
160         return value
161
162     def wrapped(env, context, value):
163         items = value.items()
164         items.sort(lambda a, b: cmp(sort_func(a[pos], env),
165                                     sort_func(b[pos], env)))
166         return items
167     return wrapped
168
169
170 def do_default(default_value=u'', boolean=False):
171     """
172     If the value is undefined it will return the passed default value,
173     otherwise the value of the variable:
174
175     .. sourcecode:: jinja
176
177         {{ my_variable|default('my_variable is not defined') }}
178
179     This will output the value of ``my_variable`` if the variable was
180     defined, otherwise ``'my_variable is not defined'``. If you want
181     to use default with variables that evaluate to false you have to
182     set the second parameter to `true`:
183
184     .. sourcecode:: jinja
185
186         {{ ''|default('the string was empty', true) }}
187     """
188     def wrapped(env, context, value):
189         if (boolean and not value) or value in (Undefined, None):
190             return default_value
191         return value
192     return wrapped
193
194
195 def do_join(d=u''):
196     """
197     Return a string which is the concatenation of the strings in the
198     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
208     """
209     def wrapped(env, context, value):
210         return env.to_unicode(d).join([env.to_unicode(x) for x in value])
211     return wrapped
212
213
214 def do_count():
215     """
216     Return the length of the value. In case if getting an integer or float
217     it will convert it into a string an return the length of the new
218     string. If the object has no length it will of corse return 0.
219     """
220     def wrapped(env, context, value):
221         try:
222             if type(value) in (int, float, long):
223                 return len(str(value))
224             return len(value)
225         except TypeError:
226             return 0
227     return wrapped
228
229
230 def do_reverse():
231     """
232     Return a reversed list of the sequence filtered. You can use this
233     for example for reverse iteration:
234
235     .. sourcecode:: jinja
236
237         {% for item in seq|reverse %}
238             {{ item|e }}
239         {% endfor %}
240     """
241     def wrapped(env, context, value):
242         try:
243             return value[::-1]
244         except:
245             l = list(value)
246             l.reverse()
247             return l
248     return wrapped
249
250
251 def do_center(value, width=80):
252     """
253     Centers the value in a field of a given width.
254     """
255     return value.center(width)
256 do_center = stringfilter(do_center)
257
258
259 def do_first():
260     """
261     Return the frist item of a sequence.
262     """
263     def wrapped(env, context, seq):
264         try:
265             return iter(seq).next()
266         except StopIteration:
267             return Undefined
268     return wrapped
269
270
271 def do_last():
272     """
273     Return the last item of a sequence.
274     """
275     def wrapped(env, context, seq):
276         try:
277             return iter(_reversed(seq)).next()
278         except (TypeError, StopIteration):
279             return Undefined
280     return wrapped
281
282
283 def do_random():
284     """
285     Return a random item from the sequence.
286     """
287     def wrapped(env, context, seq):
288         try:
289             return choice(seq)
290         except:
291             return Undefined
292     return wrapped
293
294
295 def do_urlencode():
296     """
297     urlencode a string or directory.
298
299     .. sourcecode:: jinja
300
301         {{ {'foo': 'bar', 'blub': 'blah'}|urlencode }}
302             -> foo=bar&blub=blah
303
304         {{ 'Hello World' }}
305             -> Hello%20World
306     """
307     def wrapped(env, context, value):
308         if isinstance(value, dict):
309             tmp = {}
310             for key, value in value.iteritems():
311                 tmp[env.to_unicode(key)] = env.to_unicode(value)
312             return urlencode(tmp)
313         else:
314             return quote(env.to_unicode(value))
315     return wrapped
316
317
318 def do_jsonencode():
319     """
320     JSON dump a variable. just works if simplejson is installed.
321
322     .. sourcecode:: jinja
323
324         {{ 'Hello World'|jsonencode }}
325             -> "Hello World"
326     """
327     global simplejson
328     try:
329         simplejson
330     except NameError:
331         import simplejson
332     return lambda e, c, v: simplejson.dumps(v)
333
334
335 def do_filesizeformat():
336     """
337     Format the value like a 'human-readable' file size (i.e. 13 KB, 4.1 MB, 102
338     bytes, etc).
339     """
340     def wrapped(env, context, value):
341         # fail silently
342         try:
343             bytes = float(value)
344         except TypeError:
345             bytes = 0
346
347         if bytes < 1024:
348             return "%d Byte%s" % (bytes, bytes != 1 and 's' or '')
349         elif bytes < 1024 * 1024:
350             return "%.1f KB" % (bytes / 1024)
351         elif bytes < 1024 * 1024 * 1024:
352             return "%.1f MB" % (bytes / (1024 * 1024))
353         return "%.1f GB" % (bytes / (1024 * 1024 * 1024))
354     return wrapped
355
356
357 def do_pprint():
358     """
359     Pretty print a variable. Useful for debugging.
360     """
361     def wrapped(env, context, value):
362         from pprint import pformat
363         return pformat(value)
364     return wrapped
365
366
367 def do_urlize(value, trim_url_limit=None, nofollow=False):
368     """
369     Converts URLs in plain text into clickable links.
370
371     If you pass the filter an additional integer it will shorten the urls
372     to that number. Also a third argument exists that makes the urls
373     "nofollow":
374
375     .. sourcecode:: jinja
376
377         {{ mytext|urlize(40, True) }}
378             links are shortened to 40 chars and defined with rel="nofollow"
379     """
380     return urlize(value, trim_url_limit, nofollow)
381 do_urlize = stringfilter(do_urlize)
382
383
384 def do_indent(s, width=4, indentfirst=False):
385     """
386     {{ s|indent[ width[ indentfirst[ usetab]]] }}
387
388     Return a copy of the passed string, each line indented by
389     4 spaces. The first line is not indented. If you want to
390     change the number of spaces or indent the first line too
391     you can pass additional parameters to the filter:
392
393     .. sourcecode:: jinja
394
395         {{ mytext|indent(2, True) }}
396             indent by two spaces and indent the first line too.
397     """
398     indention = ' ' * width
399     if indentfirst:
400         return u'\n'.join([indention + line for line in s.splitlines()])
401     return s.replace('\n', '\n' + indention)
402 do_indent = stringfilter(do_indent)
403
404
405 def do_truncate(s, length=255, killwords=False, end='...'):
406     """
407     Return a truncated copy of the string. The length is specified
408     with the first parameter which defaults to ``255``. If the second
409     parameter is ``true`` the filter will cut the text at length. Otherwise
410     it will try to save the last word. If the text was in fact
411     truncated it will append an ellipsis sign (``"..."``). If you want a
412     different ellipsis sign than ``"..."`` you can specify it using the
413     third parameter.
414
415     .. sourcecode jinja::
416
417         {{ mytext|truncate(300, false, '&raquo;') }}
418             truncate mytext to 300 chars, don't split up words, use a
419             right pointing double arrow as ellipsis sign.
420     """
421     if len(s) <= length:
422         return s
423     elif killwords:
424         return s[:length] + end
425     words = s.split(' ')
426     result = []
427     m = 0
428     for word in words:
429         m += len(word) + 1
430         if m > length:
431             break
432         result.append(word)
433     result.append(end)
434     return u' '.join(result)
435 do_truncate = stringfilter(do_truncate)
436
437
438 def do_wordwrap(s, pos=79, hard=False):
439     """
440     Return a copy of the string passed to the filter wrapped after
441     ``79`` characters. You can override this default using the first
442     parameter. If you set the second parameter to `true` Jinja will
443     also split words apart (usually a bad idea because it makes
444     reading hard).
445     """
446     if len(s) < pos:
447         return s
448     if hard:
449         return u'\n'.join([s[idx:idx + pos] for idx in
450                           xrange(0, len(s), pos)])
451     # code from http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/148061
452     return reduce(lambda line, word, pos=pos: u'%s%s%s' %
453                   (line, u' \n'[(len(line)-line.rfind('\n') - 1 +
454                                 len(word.split('\n', 1)[0]) >= pos)],
455                    word), s.split(' '))
456 do_wordwrap = stringfilter(do_wordwrap)
457
458
459 def do_wordcount(s):
460     """
461     Count the words in that string.
462     """
463     return len([x for x in s.split() if x])
464 do_wordcount = stringfilter(do_wordcount)
465
466
467 def do_textile(s):
468     """
469     Prase the string using textile.
470
471     requires the `PyTextile`_ library.
472
473     .. _PyTextile: http://dealmeida.net/projects/textile/
474     """
475     from textile import textile
476     return textile(s)
477 do_textile = stringfilter(do_textile)
478
479
480 def do_markdown(s):
481     """
482     Parse the string using markdown.
483
484     requires the `Python-markdown`_ library.
485
486     .. _Python-markdown: http://www.freewisdom.org/projects/python-markdown/
487     """
488     from markdown import markdown
489     return markdown(s)
490 do_markdown = stringfilter(do_markdown)
491
492
493 def do_rst(s):
494     """
495     Parse the string using the reStructuredText parser from the
496     docutils package.
497
498     requires `docutils`_.
499
500     .. _docutils: from http://docutils.sourceforge.net/
501     """
502     try:
503         from docutils.core import publish_parts
504         parts = publish_parts(source=s, writer_name='html4css1')
505         return parts['fragment']
506     except:
507         return s
508 do_rst = stringfilter(do_rst)
509
510
511 def do_int(default=0):
512     """
513     Convert the value into an integer. If the
514     conversion doesn't work it will return ``0``. You can
515     override this default using the first parameter.
516     """
517     def wrapped(env, context, value):
518         try:
519             return int(value)
520         except (TypeError, ValueError):
521             try:
522                 return int(float(value))
523             except (TypeError, ValueError):
524                 return default
525     return wrapped
526
527
528 def do_float(default=0.0):
529     """
530     Convert the value into a floating point number. If the
531     conversion doesn't work it will return ``0.0``. You can
532     override this default using the first parameter.
533     """
534     def wrapped(env, context, value):
535         try:
536             return float(value)
537         except (TypeError, ValueError):
538             return default
539     return wrapped
540
541
542 def do_string():
543     """
544     Convert the value into an string.
545     """
546     return lambda e, c, v: e.to_unicode(v)
547
548
549 def do_format(*args):
550     """
551     Apply python string formatting on an object:
552
553     .. sourcecode:: jinja
554
555         {{ "%s - %s"|format("Hello?", "Foo!") }}
556             -> Hello? - Foo!
557
558     Note that you cannot use the mapping syntax (``%(name)s``)
559     like in python.
560     """
561     def wrapped(env, context, value):
562         return env.to_unicode(value) % args
563     return wrapped
564
565
566 def do_trim(value):
567     """
568     Strip leading and trailing whitespace.
569     """
570     return value.strip()
571 do_trim = stringfilter(do_trim)
572
573
574 def do_capture(name='captured', clean=False):
575     """
576     Store the value in a variable called ``captured`` or a variable
577     with the name provided. Useful for filter blocks:
578
579     .. sourcecode:: jinja
580
581         {% filter capture('foo') %}
582             ...
583         {% endfilter %}
584         {{ foo }}
585
586     This will output "..." two times. One time from the filter block
587     and one time from the variable. If you don't want the filter to
588     output something you can use it in `clean` mode:
589
590     .. sourcecode:: jinja
591
592         {% filter capture('foo', True) %}
593             ...
594         {% endfilter %}
595         {{ foo }}
596     """
597     if not isinstance(name, unicode):
598         raise FilterArgumentError('You can only capture into variables')
599     def wrapped(env, context, value):
600         context[name] = value
601         if clean:
602             return Undefined
603         return value
604     return wrapped
605
606
607 FILTERS = {
608     'replace':              do_replace,
609     'upper':                do_upper,
610     'lower':                do_lower,
611     'escape':               do_escape,
612     'e':                    do_escape,
613     'capitalize':           do_capitalize,
614     'title':                do_title,
615     'default':              do_default,
616     'join':                 do_join,
617     'count':                do_count,
618     'dictsort':             do_dictsort,
619     'length':               do_count,
620     'reverse':              do_reverse,
621     'center':               do_center,
622     'title':                do_title,
623     'capitalize':           do_capitalize,
624     'first':                do_first,
625     'last':                 do_last,
626     'random':               do_random,
627     'urlencode':            do_urlencode,
628     'jsonencode':           do_jsonencode,
629     'filesizeformat':       do_filesizeformat,
630     'pprint':               do_pprint,
631     'indent':               do_indent,
632     'truncate':             do_truncate,
633     'wordwrap':             do_wordwrap,
634     'wordcount':            do_wordcount,
635     'textile':              do_textile,
636     'markdown':             do_markdown,
637     'rst':                  do_rst,
638     'int':                  do_int,
639     'float':                do_float,
640     'string':               do_string,
641     'urlize':               do_urlize,
642     'format':               do_format,
643     'capture':              do_capture,
644     'trim':                 do_trim
645 }