--- /dev/null
+#
+# Jinja Makefile
+# ~~~~~~~~~~~~~~
+#
+# Shortcuts for various tasks.
+#
+# :copyright: 2007 by Armin Ronacher.
+# :license: BSD, see LICENSE for more details.
+#
+
+test:
+ @(cd tests; py.test $(TESTS))
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]
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
{{ ''|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''):
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
'lower': do_lower,
'escape': do_escape,
'e': do_escape,
- 'addslashes': do_addslashes,
'capitalize': do_capitalize,
'title': do_title,
'default': do_default,
from jinja.datastructure import Undefined
-number_re = re.compile(r'^-?\d+(\.\d+)$')
+number_re = re.compile(r'^-?\d+(\.\d+)?$')
regex_type = type(number_re)
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 = {
]
# 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:
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
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!
--- /dev/null
+# -*- 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()
--- /dev/null
+# -*- 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 == '<">&|<">&'
+
+
+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'
--- /dev/null
+# -*- 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
--- /dev/null
+# -*- 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() == '...'
--- /dev/null
+# -*- 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'
+