Unicode support for wordcount.
[jinja2.git] / jinja2 / filters.py
index 959b4daee480eb7cd3272aabaa5b5fe321a83c7b..676627f34d1c843961e54adb1d73e43603b33fae 100644 (file)
@@ -5,12 +5,11 @@
 
     Bundled jinja filters.
 
-    :copyright: 2008 by Armin Ronacher, Christoph Hack.
+    :copyright: (c) 2009 by the Jinja Team.
     :license: BSD, see LICENSE for more details.
 """
 import re
 import math
-import textwrap
 from random import choice
 from operator import itemgetter
 from itertools import imap, groupby
@@ -19,7 +18,7 @@ from jinja2.runtime import Undefined
 from jinja2.exceptions import FilterArgumentError, SecurityError
 
 
-_word_re = re.compile(r'\w+')
+_word_re = re.compile(r'\w+(?u)')
 
 
 def contextfilter(f):
@@ -138,7 +137,7 @@ def do_title(s):
 
 
 def do_dictsort(value, case_sensitive=False, by='key'):
-    """ Sort a dict and yield (key, value) pairs. Because python dicts are
+    """Sort a dict and yield (key, value) pairs. Because python dicts are
     unsorted you may want to use this function to order them by either
     key or value:
 
@@ -163,15 +162,34 @@ def do_dictsort(value, case_sensitive=False, by='key'):
                                   '"key" or "value"')
     def sort_func(item):
         value = item[pos]
-        if isinstance(value, basestring):
-            value = unicode(value)
-            if not case_sensitive:
-                value = value.lower()
+        if isinstance(value, basestring) and not case_sensitive:
+            value = value.lower()
         return value
 
     return sorted(value.items(), key=sort_func)
 
 
+def do_sort(value, case_sensitive=False):
+    """Sort an iterable.  If the iterable is made of strings the second
+    parameter can be used to control the case sensitiveness of the
+    comparison which is disabled by default.
+
+    .. sourcecode:: jinja
+
+        {% for item in iterable|sort %}
+            ...
+        {% endfor %}
+    """
+    if not case_sensitive:
+        def sort_func(item):
+            if isinstance(item, basestring):
+                item = item.lower()
+            return item
+    else:
+        sort_func = None
+    return sorted(seq, key=sort_func)
+
+
 def do_default(value, default_value=u'', boolean=False):
     """If the value is undefined it will return the passed default value,
     otherwise the value of the variable:
@@ -264,23 +282,22 @@ def do_random(environment, seq):
         return environment.undefined('No random item, sequence was empty.')
 
 
-def do_filesizeformat(value):
+def do_filesizeformat(value, binary=False):
     """Format the value like a 'human-readable' file size (i.e. 13 KB,
-    4.1 MB, 102 bytes, etc).
+    4.1 MB, 102 bytes, etc).  Per default decimal prefixes are used (mega,
+    giga, etc.), if the second parameter is set to `True` the binary
+    prefixes are used (mebi, gibi).
     """
-    # fail silently
-    try:
-        bytes = float(value)
-    except TypeError:
-        bytes = 0
-
-    if bytes < 1024:
+    bytes = float(value)
+    base = binary and 1024 or 1000
+    middle = binary and 'i' or ''
+    if bytes < base:
         return "%d Byte%s" % (bytes, bytes != 1 and 's' or '')
-    elif bytes < 1024 * 1024:
-        return "%.1f KB" % (bytes / 1024)
-    elif bytes < 1024 * 1024 * 1024:
-        return "%.1f MB" % (bytes / (1024 * 1024))
-    return "%.1f GB" % (bytes / (1024 * 1024 * 1024))
+    elif bytes < base * base:
+        return "%.1f K%sB" % (bytes / base, middle)
+    elif bytes < base * base * base:
+        return "%.1f M%sB" % (bytes / (base * base), middle)
+    return "%.1f G%sB" % (bytes / (base * base * base), middle)
 
 
 def do_pprint(value, verbose=False):
@@ -305,7 +322,7 @@ def do_urlize(environment, value, trim_url_limit=None, nofollow=False):
         {{ mytext|urlize(40, true) }}
             links are shortened to 40 chars and defined with rel="nofollow"
     """
-    rv = urlize(soft_unicode(value), trim_url_limit, nofollow)
+    rv = urlize(value, trim_url_limit, nofollow)
     if environment.autoescape:
         rv = Markup(rv)
     return rv
@@ -322,10 +339,11 @@ def do_indent(s, width=4, indentfirst=False):
         {{ mytext|indent(2, true) }}
             indent by two spaces and indent the first line too.
     """
-    indention = ' ' * width
+    indention = u' ' * width
+    rv = (u'\n' + indention).join(s.splitlines())
     if indentfirst:
-        return u'\n'.join(indention + line for line in s.splitlines())
-    return s.replace('\n', '\n' + indention)
+        rv = indention + rv
+    return rv
 
 
 def do_truncate(s, length=255, killwords=False, end='...'):
@@ -366,6 +384,7 @@ def do_wordwrap(s, width=79, break_long_words=True):
     parameter.  If you set the second parameter to `false` Jinja will not
     split words apart if they are longer than `width`.
     """
+    import textwrap
     return u'\n'.join(textwrap.wrap(s, width=width, expand_tabs=False,
                                    replace_whitespace=False,
                                    break_long_words=break_long_words))
@@ -433,7 +452,7 @@ def do_striptags(value):
 def do_slice(value, slices, fill_with=None):
     """Slice an iterator and return a list of lists containing
     those items. Useful if you want to create a div containing
-    three div tags that represent columns:
+    three ul tags that represent columns:
 
     .. sourcecode:: html+jinja
 
@@ -479,7 +498,7 @@ def do_batch(value, linecount, fill_with=None):
         {%- for row in items|batch(3, '&nbsp;') %}
           <tr>
           {%- for column in row %}
-            <tr>{{ column }}</td>
+            <td>{{ column }}</td>
           {%- endfor %}
           </tr>
         {%- endfor %}
@@ -572,7 +591,7 @@ def do_groupby(environment, value, attribute):
     attribute and the `list` contains all the objects that have this grouper
     in common.
     """
-    expr = lambda x: environment.subscribe(x, attribute)
+    expr = lambda x: environment.getitem(x, attribute)
     return sorted(map(_GroupTuple, groupby(sorted(value, key=expr), expr)))
 
 
@@ -624,22 +643,26 @@ def do_reverse(value):
 @environmentfilter
 def do_attr(environment, obj, name):
     """Get an attribute of an object.  ``foo|attr("bar")`` works like
-    ``foo["bar"]`` just that always an attribute is returned.  This is useful
-    if data structures are passed to the template that have an item that hides
-    an attribute with the same name.  For example a dict ``{'items': []}``
-    that obviously hides the item method of a dict.
+    ``foo["bar"]`` just that always an attribute is returned and items are not
+    looked up.
+
+    See :ref:`Notes on subscriptions <notes-on-subscriptions>` for more details.
     """
     try:
-        value = getattr(obj, name)
-    except AttributeError:
-        return environment.undefined(obj=obj, name=name)
-    if environment.sandboxed and not \
-       environment.is_safe_attribute(obj, name, value):
-        return environment.undefined('access to attribute %r of %r '
-                                     'object is unsafe.' % (
-            name, obj.__class__.__name__
-        ), name=name, obj=obj, exc=SecurityError)
-    return value
+        name = str(name)
+    except UnicodeError:
+        pass
+    else:
+        try:
+            value = getattr(obj, name)
+        except AttributeError:
+            pass
+        else:
+            if environment.sandboxed and not \
+               environment.is_safe_attribute(obj, name, value):
+                return environment.unsafe_undefined(obj, name)
+            return value
+    return environment.undefined(obj=obj, name=name)
 
 
 FILTERS = {
@@ -657,6 +680,7 @@ FILTERS = {
     'join':                 do_join,
     'count':                len,
     'dictsort':             do_dictsort,
+    'sort':                 do_sort,
     'length':               len,
     'reverse':              do_reverse,
     'center':               do_center,