[svn] added jinja doctests and fixed problem with i18n strings in requirements
authorArmin Ronacher <armin.ronacher@active-4.com>
Sun, 18 Mar 2007 19:47:50 +0000 (20:47 +0100)
committerArmin Ronacher <armin.ronacher@active-4.com>
Sun, 18 Mar 2007 19:47:50 +0000 (20:47 +0100)
--HG--
branch : trunk

Makefile [new file with mode: 0644]
jinja/datastructure.py
jinja/filters.py
jinja/tests.py
jinja/translators/python.py
tests/conftest.py [new file with mode: 0644]
tests/test_filters.py [new file with mode: 0644]
tests/test_forloop.py [new file with mode: 0644]
tests/test_ifcondition.py [new file with mode: 0644]
tests/test_tests.py [new file with mode: 0644]

diff --git a/Makefile b/Makefile
new file mode 100644 (file)
index 0000000..64f448a
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,12 @@
+#
+# Jinja Makefile
+# ~~~~~~~~~~~~~~
+#
+# Shortcuts for various tasks.
+#
+# :copyright: 2007 by Armin Ronacher.
+# :license: BSD, see LICENSE for more details.
+#
+
+test:
+       @(cd tests; py.test $(TESTS))
index 39625c3c7f633bf40d85c28980540738a942d5ff..7da8813e166d97e312face677b9e054ab1c7a175 100644 (file)
@@ -244,13 +244,16 @@ class LoopContext(object):
     iterated = property(lambda s: s._stack[-1]['index'] > -1)
     index0 = property(lambda s: s._stack[-1]['index'])
     index = property(lambda s: s._stack[-1]['index'] + 1)
-    revindex0 = property(lambda s: s._stack[-1]['length'] - s._stack[-1]['index'] - 1)
-    revindex = property(lambda s: s._stack[-1]['length'] - s._stack[-1]['index'])
+    revindex0 = property(lambda s: s._stack[-1]['length'] -
+                                   s._stack[-1]['index'] - 1)
+    revindex = property(lambda s: s._stack[-1]['length'] -
+                                  s._stack[-1]['index'])
     length = property(lambda s: s._stack[-1]['length'])
-    even = property(lambda s: s._stack[-1]['index'] % 2 == 0)
-    odd = property(lambda s: s._stack[-1]['index'] % 2 == 1)
+    even = property(lambda s: s._stack[-1]['index'] % 2 == 1)
+    odd = property(lambda s: s._stack[-1]['index'] % 2 == 0)
     first = property(lambda s: s._stack[-1]['index'] == 0)
-    last = property(lambda s: s._stack[-1]['index'] == s._stack[-1]['length'] - 1)
+    last = property(lambda s: s._stack[-1]['index'] ==
+                              s._stack[-1]['length'] - 1)
 
     def __iter__(self):
         s = self._stack[-1]
index 93939803cb0cd7f07dac07dad5f65f158e9b44c1..dde0be6a5ec9dff2f903b7977b19ef46e79dc082 100644 (file)
@@ -109,16 +109,6 @@ def do_escape(s, attribute=False):
 do_escape = stringfilter(do_escape)
 
 
-def do_addslashes(s):
-    """
-    Add backslashes in front of special characters to s. This method
-    might be useful if you try to fill javascript strings. Also have
-    a look at the `jsonencode` filter.
-    """
-    return s.encode('utf-8').encode('string-escape').decode('utf-8')
-do_addslashes = stringfilter(do_addslashes)
-
-
 def do_capitalize(s):
     """
     Capitalize a value. The first character will be uppercase, all others
@@ -196,11 +186,10 @@ def do_default(default_value=u'', boolean=False):
         {{ ''|default('the string was empty', true) }}
     """
     def wrapped(env, context, value):
-        if (boolean and not v) or v in (Undefined, None):
+        if (boolean and not value) or value in (Undefined, None):
             return default_value
-        return v
+        return value
     return wrapped
-do_default = stringfilter(do_default)
 
 
 def do_join(d=u''):
@@ -521,21 +510,34 @@ def do_rst(s):
 do_rst = stringfilter(do_rst)
 
 
-def do_int():
+def do_int(default=0):
     """
-    Convert the value into an integer.
+    Convert the value into an integer. If the
+    conversion doesn't work it will return ``0``. You can
+    override this default using the first parameter.
     """
     def wrapped(env, context, value):
-        return int(value)
+        try:
+            return int(value)
+        except (TypeError, ValueError):
+            try:
+                return int(float(value))
+            except (TypeError, ValueError):
+                return default
     return wrapped
 
 
-def do_float():
+def do_float(default=0.0):
     """
-    Convert the value into a floating point number.
+    Convert the value into a floating point number. If the
+    conversion doesn't work it will return ``0.0``. You can
+    override this default using the first parameter.
     """
     def wrapped(env, context, value):
-        return float(value)
+        try:
+            return float(value)
+        except (TypeError, ValueError):
+            return default
     return wrapped
 
 
@@ -552,7 +554,6 @@ FILTERS = {
     'lower':                do_lower,
     'escape':               do_escape,
     'e':                    do_escape,
-    'addslashes':           do_addslashes,
     'capitalize':           do_capitalize,
     'title':                do_title,
     'default':              do_default,
index c73235897a7bdd47c07de7fac10bde032172300b..ae293d447ba26c143f20b0f36eb67f6122516426 100644 (file)
@@ -12,7 +12,7 @@ import re
 from jinja.datastructure import Undefined
 
 
-number_re = re.compile(r'^-?\d+(\.\d+)$')
+number_re = re.compile(r'^-?\d+(\.\d+)?$')
 
 regex_type = type(number_re)
 
@@ -87,31 +87,29 @@ def test_sequence():
 
 
 def test_matching(regex):
-    """
-    Test if the variable matches the regular expression
-    given. If the regular expression is a string additional
-    slashes are automatically added, if it's a compiled regex
-    it's used without any modifications:
+    r"""
+    Test if the variable matches the regular expression given. Note that
+    you have to escape special chars using *two* backslashes, these are
+    *not* raw strings.
 
     .. sourcecode:: jinja
 
-        {% if var is matching('\d+$') %}
+        {% if var is matching('^\\d+$') %}
             var looks like a number
         {% else %}
             var doesn't really look like a number
         {% endif %}
     """
     if isinstance(regex, unicode):
-        regex = re.compile(regex.encode('unicode-escape'), re.U)
-    elif isinstance(regex, unicode):
-        regex = re.compile(regex.encode('string-escape'))
+        regex = re.compile(regex, re.U)
+    elif isinstance(regex, str):
+        regex = re.compile(regex)
     elif type(regex) is not regex_type:
         regex = None
     def wrapped(environment, context, value):
         if regex is None:
             return False
-        else:
-            return regex.match(value)
+        return regex.search(value) is not None
     return wrapped
 
 TESTS = {
index b341852d5c7f5c47540726725f440792d3eb5021..6d5b924803247e3c88e7c84096ead4fd7d772bb1 100644 (file)
@@ -304,12 +304,13 @@ class PythonTranslator(Translator):
         ]
 
         # we have requirements? add them here.
+        body_lines = []
         if requirements:
             for n in requirements:
-                lines.append(self.handle_node(n))
+                body_lines.append(self.handle_node(n))
 
         # the template body
-        body_lines = [self.handle_node(n) for n in node]
+        body_lines.extend([self.handle_node(n) for n in node])
 
         # add translation helpers if required
         if self.require_translations:
@@ -379,7 +380,7 @@ class PythonTranslator(Translator):
         nodeinfo = self.nodeinfo(node.body)
         if nodeinfo:
             write(nodeinfo)
-        buf.append(self.handle_node(node.body))
+        buf.append(self.handle_node(node.body) or self.indent('pass'))
         self.indention -= 1
 
         # else part of loop
@@ -389,7 +390,7 @@ class PythonTranslator(Translator):
             nodeinfo = self.nodeinfo(node.else_)
             if nodeinfo:
                 write(nodeinfo)
-            buf.append(self.handle_node(node.else_))
+            buf.append(self.handle_node(node.else_) or self.indent('pass'))
             self.indention -= 1
 
         # call recursive for loop!
diff --git a/tests/conftest.py b/tests/conftest.py
new file mode 100644 (file)
index 0000000..8ec3737
--- /dev/null
@@ -0,0 +1,55 @@
+# -*- coding: utf-8 -*-
+"""
+    conftest
+    ~~~~~~~~
+
+    Configure py.test for support stuff.
+
+    :copyright: 2007 by Armin Ronacher, Alexander Schremmer.
+    :license: BSD, see LICENSE for more details.
+"""
+
+import os
+import sys
+sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
+
+import py
+from inspect import isclass
+from jinja import Environment
+
+
+simple_env = Environment(trim_blocks=True)
+
+
+class Module(py.test.collect.Module):
+
+    def __init__(self, *args, **kwargs):
+        self.env = simple_env
+        super(Module, self).__init__(*args, **kwargs)
+
+    def join(self, name):
+        obj = getattr(self.obj, name)
+        if isclass(obj):
+            return JinjaClassCollector(name, parent=self)
+        elif hasattr(obj, 'func_code'):
+            return JinjaTestFunction(name, parent=self)
+
+
+class JinjaTestFunction(py.test.collect.Function):
+
+    def execute(self, target, *args):
+        co = target.func_code
+        if 'env' in co.co_varnames[:co.co_argcount]:
+            target(self.parent.env, *args)
+        else:
+            target(*args)
+
+
+class JinjaClassCollector(py.test.collect.Class):
+
+    Function = JinjaTestFunction
+
+    def setup(self):
+        cls = self.obj
+        cls.env = self.parent.env
+        super(JinjaClassCollector, self).setup()
diff --git a/tests/test_filters.py b/tests/test_filters.py
new file mode 100644 (file)
index 0000000..2d728b0
--- /dev/null
@@ -0,0 +1,191 @@
+# -*- coding: utf-8 -*-
+"""
+    unit test for the filters
+    ~~~~~~~~~~~~~~~~~~~~~~~~~
+
+    Missing tests:
+
+    -   wordcount
+    -   rst
+    -   markdown
+    -   textile
+
+    :copyright: 2007 by Armin Ronacher.
+    :license: BSD, see LICENSE for more details.
+"""
+
+CAPITALIZE = '''{{ "foo bar"|capitalize }}'''
+CENTER = '''{{ "foo"|center(9) }}'''
+DEFAULT = '''{{ missing|default("no") }}|{{ false|default('no') }}|\
+{{ false|default('no', true) }}|{{ given|default("no") }}'''
+DICTSORT = '''{{ foo|dictsort }}|\
+{{ foo|dictsort(true) }}|\
+{{ foo|dictsort(false, 'value') }}'''
+ESCAPE = '''{{ '<">&'|escape }}|{{ '<">&'|escape(true) }}'''
+FILESIZEFORMAT = '{{ 100|filesizeformat }}|\
+{{ 1000|filesizeformat }}|\
+{{ 1000000|filesizeformat }}|\
+{{ 1000000000|filesizeformat }}|\
+{{ 1000000000000|filesizeformat }}'
+FIRST = '''{{ foo|first }}'''
+FLOAT = '''{{ "42"|float }}|{{ "ajsghasjgd"|float }}|{{ "32.32"|float }}'''
+INDENT = '''{{ foo|indent(2) }}|{{ foo|indent(2, true) }}'''
+INT = '''{{ "42"|int }}|{{ "ajsghasjgd"|int }}|{{ "32.32"|int }}'''
+JOIN = '''{{ [1, 2, 3]|join("|") }}'''
+LAST = '''{{ foo|last }}'''
+LENGTH = '''{{ "hello world"|length }}'''
+LOWER = '''{{ "FOO"|lower }}'''
+PPRINT = '''{{ data|pprint }}'''
+RANDOM = '''{{ seq|random }}'''
+REVERSE = '''{{ "foobar"|reverse }}|{{ [1, 2, 3]|reverse }}'''
+STRING = '''{{ range(10)|string }}'''
+TITLE = '''{{ "foo bar"|title }}'''
+TRUNCATE = '''{{ data|truncate(15, true, ">>>") }}|\
+{{ data|truncate(15, false, ">>>") }}|\
+{{ smalldata|truncate(15) }}'''
+UPPER = '''{{ "foo"|upper }}'''
+URLENCODE = '''{{ "f#b"|urlencode }}'''
+URLIZE = '''{{ "foo http://www.example.com/ bar"|urlize }}'''
+WORDCOUNT = '''{{ "foo bar baz"|wordcount }}'''
+
+
+def test_capitalize(env):
+    tmpl = env.from_string(CAPITALIZE)
+    assert tmpl.render() == 'Foo bar'
+
+
+def test_center(env):
+    tmpl = env.from_string(CENTER)
+    assert tmpl.render() == '   foo   '
+
+
+def test_default(env):
+    tmpl = env.from_string(DEFAULT)
+    assert tmpl.render(given='yes') == 'no|False|no|yes'
+
+
+def test_dictsort(env):
+    tmpl = env.from_string(DICTSORT)
+    out = tmpl.render(foo={"a": 0, "b": 1, "c": 2, "A": 3})
+    assert out == ("[('a', 0), ('A', 3), ('b', 1), ('c', 2)]|"
+                   "[('A', 3), ('a', 0), ('b', 1), ('c', 2)]|"
+                   "[('a', 0), ('b', 1), ('c', 2), ('A', 3)]")
+
+
+def test_escape(env):
+    tmpl = env.from_string(ESCAPE)
+    out = tmpl.render()
+    assert out == '&lt;"&gt;&amp;|&lt;&quot;&gt;&amp;'
+
+
+def test_filesizeformat(env):
+    tmpl = env.from_string(FILESIZEFORMAT)
+    out = tmpl.render()
+    assert out == '100 Bytes|1000 Bytes|976.6 KB|953.7 MB|931.3 GB'
+
+
+def test_first(env):
+    tmpl = env.from_string(FIRST)
+    out = tmpl.render(foo=range(10))
+    assert out == '0'
+
+
+def test_float(env):
+    tmpl = env.from_string(FLOAT)
+    out = tmpl.render()
+    assert out == '42.0|0.0|32.32'
+
+
+def test_indent(env):
+    tmpl = env.from_string(INDENT)
+    text = '\n'.join([' '.join(['foo', 'bar'] * 2)] * 2)
+    out = tmpl.render(foo=text)
+    assert out == 'foo bar foo bar\n  foo bar foo bar|  ' \
+                  'foo bar foo bar\n  foo bar foo bar'
+
+
+def test_int(env):
+    tmpl = env.from_string(INT)
+    out = tmpl.render()
+    assert out == '42|0|32'
+
+
+def test_join(env):
+    tmpl = env.from_string(JOIN)
+    out = tmpl.render()
+    assert out == '1|2|3'
+
+
+def test_last(env):
+    tmpl = env.from_string(LAST)
+    out = tmpl.render(foo=range(10))
+    assert out == '9'
+
+
+def test_length(env):
+    tmpl = env.from_string(LENGTH)
+    out = tmpl.render()
+    assert out == '11'
+
+
+def test_lower(env):
+    tmpl = env.from_string(LOWER)
+    out = tmpl.render()
+    assert out == 'foo'
+
+
+def test_pprint(env):
+    from pprint import pformat
+    tmpl = env.from_string(PPRINT)
+    data = range(10000)
+    assert tmpl.render(data=data) == pformat(data)
+
+
+def test_random(env):
+    tmpl = env.from_string(RANDOM)
+    seq = range(100)
+    for _ in range(10):
+        assert int(tmpl.render(seq=seq)) in seq
+
+
+def test_reverse(env):
+    tmpl = env.from_string(REVERSE)
+    assert tmpl.render() == 'raboof|[3, 2, 1]'
+
+
+def test_string(env):
+    tmpl = env.from_string(STRING)
+    assert tmpl.render(foo=range(10)) == str(range(10))
+
+
+def test_title(env):
+    tmpl = env.from_string(TITLE)
+    assert tmpl.render() == "Foo Bar"
+
+
+def test_truncate(env):
+    tmpl = env.from_string(TRUNCATE)
+    out = tmpl.render(data='foobar baz bar' * 1000,
+                      smalldata='foobar baz bar')
+    assert out == 'foobar baz barf>>>|foobar baz >>>|foobar baz bar'
+
+
+def test_upper(env):
+    tmpl = env.from_string(UPPER)
+    assert tmpl.render() == 'FOO'
+
+
+def test_urlencode(env):
+    tmpl = env.from_string(URLENCODE)
+    assert tmpl.render() == 'f%23b'
+
+
+def test_urlize(env):
+    tmpl = env.from_string(URLIZE)
+    assert tmpl.render() == 'foo <a href="http://www.example.com/">'\
+                            'http://www.example.com/</a> bar'
+
+
+def test_wordcount(env):
+    tmpl = env.from_string(WORDCOUNT)
+    assert tmpl.render() == '3'
diff --git a/tests/test_forloop.py b/tests/test_forloop.py
new file mode 100644 (file)
index 0000000..af8afce
--- /dev/null
@@ -0,0 +1,65 @@
+# -*- coding: utf-8 -*-
+"""
+    unit test for loop functions
+    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+    :copyright: 2007 by Armin Ronacher.
+    :license: BSD, see LICENSE for more details.
+"""
+
+SIMPLE = '''{% for item in seq %}{{ item }}{% endfor %}'''
+ELSE = '''{% for item in seq %}XXX{% else %}...{% endfor %}'''
+EMPTYBLOCKS = '''<{% for item in seq %}{% else %}{% endfor %}>'''
+CONTEXTVARS = '''{% for item in seq %}\
+{{ loop.index }}|{{ loop.index0 }}|{{ loop.revindex }}|{{
+   loop.revindex0 }}|{{ loop.first }}|{{ loop.last }}|{{
+   loop.even }}|{{ loop.odd }}|{{ loop.length }}###{% endfor %}'''
+CYCLING = '''{% for item in seq %}{% cycle '<1>', '<2>' %}{% endfor %}\
+{% for item in seq %}{% cycle through %}{% endfor %}'''
+SCOPE = '''{% for item in seq %}{% endfor %}{{ item }}'''
+
+
+def test_simple(env):
+    tmpl = env.from_string(SIMPLE)
+    assert tmpl.render(seq=range(10)) == '0123456789'
+
+
+def test_else(env):
+    tmpl = env.from_string(ELSE)
+    assert tmpl.render() == '...'
+
+
+def test_empty_blocks(env):
+    tmpl = env.from_string(EMPTYBLOCKS)
+    assert tmpl.render() == '<>'
+
+
+def test_context_vars(env):
+    tmpl = env.from_string(CONTEXTVARS)
+    one, two, _ = tmpl.render(seq=[0, 1]).split('###')
+    (one_index, one_index0, one_revindex, one_revindex0, one_first,
+     one_last, one_even, one_odd, one_length) = one.split('|')
+    (two_index, two_index0, two_revindex, two_revindex0, two_first,
+     two_last, two_even, two_odd, two_length) = two.split('|')
+
+    assert int(one_index) == 1 and int(two_index) == 2
+    assert int(one_index0) == 0 and int(two_index0) == 1
+    assert int(one_revindex) == 2 and int(two_revindex) == 1
+    assert int(one_revindex0) == 1 and int(two_revindex0) == 0
+    assert one_first == 'True' and two_first == 'False'
+    assert one_last == 'False' and two_last == 'True'
+    assert one_even == 'False' and two_even == 'True'
+    assert one_odd == 'True' and two_odd == 'False'
+    assert one_length == two_length == '2'
+
+
+def test_cycling(env):
+    tmpl = env.from_string(CYCLING)
+    output = tmpl.render(seq=range(4), through=('<1>', '<2>'))
+    assert output == '<1><2>' * 4
+
+
+def test_scope(env):
+    tmpl = env.from_string(SCOPE)
+    output = tmpl.render(seq=range(10))
+    assert not output
diff --git a/tests/test_ifcondition.py b/tests/test_ifcondition.py
new file mode 100644 (file)
index 0000000..21072fc
--- /dev/null
@@ -0,0 +1,27 @@
+# -*- coding: utf-8 -*-
+"""
+    unit test for if conditions
+    ~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+    :copyright: 2007 by Armin Ronacher.
+    :license: BSD, see LICENSE for more details.
+"""
+
+SIMPLE = '''{% if true %}...{% endif %}'''
+ELIF = '''{% if false %}XXX{% elif true %}...{% else %}XXX{% endif %}'''
+ELSE = '''{% if false %}XXX{% else %}...{% endif %}'''
+
+
+def test_simple(env):
+    tmpl = env.from_string(SIMPLE)
+    assert tmpl.render() == '...'
+
+
+def test_elif(env):
+    tmpl = env.from_string(ELIF)
+    assert tmpl.render() == '...'
+
+
+def test_else(env):
+    tmpl = env.from_string(ELSE)
+    assert tmpl.render() == '...'
diff --git a/tests/test_tests.py b/tests/test_tests.py
new file mode 100644 (file)
index 0000000..22bdec4
--- /dev/null
@@ -0,0 +1,62 @@
+# -*- coding: utf-8 -*-
+"""
+    unit test for the test functions
+    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+    :copyright: 2007 by Armin Ronacher.
+    :license: BSD, see LICENSE for more details.
+"""
+
+DEFINED = '''{{ missing is defined }}|{{ true is defined }}'''
+EVEN = '''{{ 1 is even }}|{{ 2 is even }}'''
+LOWER = '''{{ "foo" is lower }}|{{ "FOO" is lower }}'''
+MATCHING = '''{{ "42" is matching('^\\d+$') }}|\
+{{ "foo" is matching('^\\d+$') }}'''
+NUMERIC = '''{{ "43" is numeric }}|{{ "foo" is numeric }}|\
+{{ 42 is numeric }}'''
+ODD = '''{{ 1 is odd }}|{{ 2 is odd }}'''
+SEQUENCE = '''{{ [1, 2, 3] is sequence }}|\
+{{ "foo" is sequence }}|\
+{{ 42 is sequence }}'''
+UPPER = '''{{ "FOO" is upper }}|{{ "foo" is upper }}'''
+
+
+def test_defined(env):
+    tmpl = env.from_string(DEFINED)
+    assert tmpl.render() == 'False|True'
+
+
+def test_even(env):
+    tmpl = env.from_string(EVEN)
+    assert tmpl.render() == 'False|True'
+
+
+def test_lower(env):
+    tmpl = env.from_string(LOWER)
+    assert tmpl.render() == 'True|False'
+
+
+def test_matching(env):
+    tmpl = env.from_string(MATCHING)
+    assert tmpl.render() == 'True|False'
+
+
+def test_numeric(env):
+    tmpl = env.from_string(NUMERIC)
+    assert tmpl.render() == 'True|False|True'
+
+
+def test_odd(env):
+    tmpl = env.from_string(ODD)
+    assert tmpl.render() == 'True|False'
+
+
+def test_sequence(env):
+    tmpl = env.from_string(SEQUENCE)
+    assert tmpl.render() == 'True|True|False'
+
+
+def test_upper(env):
+    tmpl = env.from_string(UPPER)
+    assert tmpl.render() == 'True|False'
+