fixed more unittests
authorArmin Ronacher <armin.ronacher@active-4.com>
Tue, 22 Apr 2008 08:40:26 +0000 (10:40 +0200)
committerArmin Ronacher <armin.ronacher@active-4.com>
Tue, 22 Apr 2008 08:40:26 +0000 (10:40 +0200)
--HG--
branch : trunk

15 files changed:
jinja2/__init__.py
jinja2/defaults.py
jinja2/ext.py
jinja2/i18n.py
jinja2/lexer.py
jinja2/nodes.py
jinja2/optimizer.py
jinja2/parser.py
jinja2/runtime.py
jinja2/utils.py
jinja2/visitor.py
tests/test_i18n.py
tests/test_parser.py
tests/test_security.py
tests/test_various.py

index 12c147e7794709b6d9a0d711eea0f920934bff28..7dbe329c6342922c0974fef8446cd3a691dfa05c 100644 (file)
@@ -61,4 +61,4 @@ from jinja2.loaders import BaseLoader, FileSystemLoader, PackageLoader, \
      DictLoader
 from jinja2.runtime import Undefined, DebugUndefined, StrictUndefined
 from jinja2.filters import environmentfilter, contextfilter
-from jinja2.utils import Markup, escape
+from jinja2.utils import Markup, escape, contextfunction
index 6462f32ef80d4667412cec9a67e7377edd761295..773ad80c241f24a4a3ada2ab4e1e2d92f86b1550 100644 (file)
 """
 from jinja2.filters import FILTERS as DEFAULT_FILTERS
 from jinja2.tests import TESTS as DEFAULT_TESTS
+from jinja2.utils import generate_lorem_ipsum
 
 
 DEFAULT_NAMESPACE = {
-    'range':        xrange
+    'range':        xrange,
+    'lipsum':       generate_lorem_ipsum
 }
index 230c022fbed6a3d3ed9b3043d5a2634ebbdc6598..947150d30009c245c01d1a69cec9310edf20b6f7 100644 (file)
@@ -40,10 +40,11 @@ class CacheExtension(Extension):
     def parse(self, parser):
         lineno = parser.stream.next().lineno
         args = [parser.parse_expression()]
-        if self.stream.current.type is 'comma':
+        if parser.stream.current.type is 'comma':
+            parser.stream.next()
             args.append(parser.parse_expression())
         body = parser.parse_statements(('name:endcache',), drop_needle=True)
         return nodes.CallBlock(
-            nodes.Call(nodes.Name('cache_support'), args, [], None, None),
+            nodes.Call(nodes.Name('cache_support', 'load'), args, [], None, None),
             [], [], body
         )
index f6ec7914b793d110b6a3ec6f07c6c2eb8ce3fe48..9125ee9b96c08ec1f4e05b68926d0628ee5a4072 100644 (file)
@@ -34,7 +34,7 @@ def extract_from_ast(node, gettext_functions=GETTEXT_FUNCTIONS):
     *  ``message`` is the string itself (a ``unicode`` object, or a tuple
        of ``unicode`` objects for functions with multiple string arguments).
     """
-    for call in node.find_all(nodes.Call):
+    for node in node.find_all(nodes.Call):
         if not isinstance(node.node, nodes.Name) or \
            node.node.name not in gettext_functions:
             continue
@@ -43,7 +43,7 @@ def extract_from_ast(node, gettext_functions=GETTEXT_FUNCTIONS):
         for arg in node.args:
             if isinstance(arg, nodes.Const) and \
                isinstance(arg.value, basestring):
-                strings.append(arg)
+                strings.append(arg.value)
             else:
                 strings.append(None)
 
@@ -67,6 +67,7 @@ def babel_extract(fileobj, keywords, comment_tags, options):
              (comments will be empty currently)
     """
     encoding = options.get('encoding', 'utf-8')
+    extensions = [x.strip() for x in options.get('extensions', '').split(',')]
     environment = Environment(
         options.get('block_start_string', '{%'),
         options.get('block_end_string', '%}'),
@@ -76,9 +77,18 @@ def babel_extract(fileobj, keywords, comment_tags, options):
         options.get('comment_end_string', '#}'),
         options.get('line_statement_prefix') or None,
         options.get('trim_blocks', '').lower() in ('1', 'on', 'yes', 'true'),
-        extensions=[x.strip() for x in options.get('extensions', '')
-                     .split(',')] + [TransExtension]
+        extensions=[x for x in extensions if x]
     )
+
+    # add the i18n extension only if it's not yet in the list.  Some people
+    # might use a script to sync the babel ini with the Jinja configuration
+    # so we want to avoid having the trans extension twice in the list.
+    for extension in environment.extensions:
+        if isinstance(extension, TransExtension):
+            break
+    else:
+        environment.extensions.append(TransExtension(environment))
+
     node = environment.parse(fileobj.read().decode(encoding))
     for lineno, func, message in extract_from_ast(node, keywords):
         yield lineno, func, message, []
@@ -163,10 +173,10 @@ class TransExtension(Extension):
                 plural = plural.replace('%%', '%')
 
         if not have_plural:
-            if plural_expr is None:
-                raise TemplateAssertionError('pluralize without variables',
-                                             lineno, parser.filename)
             plural_expr = None
+        elif plural_expr is None:
+            raise TemplateAssertionError('pluralize without variables',
+                                         lineno, parser.filename)
 
         if variables:
             variables = nodes.Dict([nodes.Pair(nodes.Const(x, lineno=lineno), y)
index eab6e88ad7ff5f48419c2f5be965faca2727fd32..beb9866696593d352605359a863a906f0472d40e 100644 (file)
@@ -38,7 +38,7 @@ float_re = re.compile(r'\d+\.\d+')
 # set of used keywords
 keywords = set(['and', 'block', 'elif', 'else', 'endblock', 'print',
                 'endfilter', 'endfor', 'endif', 'endmacro', 'endraw',
-                'extends', 'filter', 'for', 'if', 'in', 'include'
+                'extends', 'filter', 'for', 'if', 'in', 'include',
                 'is', 'macro', 'not', 'or', 'raw', 'call', 'endcall'])
 
 # bind operators to token types
@@ -246,7 +246,7 @@ class Lexer(object):
             ('block',       environment.block_start_string),
             ('variable',    environment.variable_start_string)
         ]
-        root_tag_rules.sort(key=lambda x: len(x[1]))
+        root_tag_rules.sort(key=lambda x: -len(x[1]))
 
         # now escape the rules.  This is done here so that the escape
         # signs don't count for the lengths of the tags.
@@ -320,7 +320,7 @@ class Lexer(object):
 
     def tokenize(self, source, filename=None):
         """Works like `tokeniter` but returns a tokenstream of tokens and not
-        a generator or token tuples. Additionally all token values are already
+        a generator or token tuples.  Additionally all token values are already
         converted into types and postprocessed. For example keywords are
         already keyword tokens, not named tokens, comments are removed,
         integers and floats converted, strings unescaped etc.
@@ -334,6 +334,9 @@ class Lexer(object):
                     token = 'block_begin'
                 elif token == 'linestatement_end':
                     token = 'block_end'
+                # we are not interested in those tokens in the parser
+                elif token in ('raw_begin', 'raw_end'):
+                    continue
                 elif token == 'data':
                     try:
                         value = str(value)
index 992752a1b197e59b630f314876d3bfc042f658af..7688f6284d1f1f3c931fecd246ccdf23af060bdf 100644 (file)
@@ -9,10 +9,11 @@
     `get_nodes` used by the parser and translator in order to normalize
     python and jinja nodes.
 
-    :copyright: 2007 by Armin Ronacher.
+    :copyright: 2008 by Armin Ronacher.
     :license: BSD, see LICENSE for more details.
 """
 import operator
+from types import FunctionType
 from itertools import chain, izip
 from collections import deque
 from copy import copy
@@ -42,7 +43,7 @@ _cmpop_to_func = {
     'lt':       operator.lt,
     'lteq':     operator.le,
     'in':       operator.contains,
-    'notin':    lambda a, b: not operator.contains(a, b)
+    'notin':    lambda a, b: b not in a
 }
 
 
@@ -449,6 +450,12 @@ class Call(Expr):
 
     def as_const(self):
         obj = self.node.as_const()
+
+        # don't evaluate context functions
+        if type(obj) is FunctionType and \
+           getattr(obj, 'contextfunction', False):
+            raise Impossible()
+
         args = [x.as_const() for x in self.args]
         kwargs = dict(x.as_const() for x in self.kwargs)
         if self.dyn_args is not None:
@@ -480,7 +487,7 @@ class Subscript(Expr):
             raise Impossible()
 
     def can_assign(self):
-        return True
+        return False
 
 
 class Slice(Expr):
index 6b13aec25347accf7c9889860a46b70573f075ad..3aa46f3dd0bf2f5971ec905e237c8f7d06ce82a4 100644 (file)
@@ -32,6 +32,8 @@ from jinja2.runtime import LoopContext
 #   - multiple Output() nodes should be concatenated into one node.
 #     for example the i18n system could output such nodes:
 #     "foo{% trans %}bar{% endtrans %}blah"
+#   - when unrolling loops local sets become global sets :-/
+#     see also failing test case `test_localset` in test_various
 
 
 def optimize(node, environment, context_hint=None):
@@ -183,12 +185,12 @@ class Optimizer(NodeTransformer):
                 for item, loop in LoopContext(iterable, True):
                     context['loop'] = loop.make_static()
                     assign(node.target, item)
-                    result.extend(self.visit(n.copy(), context)
-                                  for n in node.body)
+                    for n in node.body:
+                        result.extend(self.visit_list(n.copy(), context))
                     iterated = True
                 if not iterated and node.else_:
-                    result.extend(self.visit(n.copy(), context)
-                                  for n in node.else_)
+                    for n in node.else_:
+                        result.extend(self.visit_list(n.copy(), context))
             except nodes.Impossible:
                 return node
         finally:
index 54aa06e01494339a1d8f34559bc3a017e97c999e..5658ca9a10c633fe59eab7b650c7dbc8e26207bd 100644 (file)
@@ -64,8 +64,8 @@ class Parser(object):
         lineno = self.stream.expect('assign').lineno
         if not target.can_assign():
             raise TemplateSyntaxError("can't assign to '%s'" %
-                                      target, target.lineno,
-                                      self.filename)
+                                      target.__class__.__name__.lower(),
+                                      target.lineno, self.filename)
         expr = self.parse_tuple()
         target.set_ctx('store')
         return nodes.Assign(target, expr, lineno=lineno)
@@ -94,8 +94,8 @@ class Parser(object):
         target = self.parse_tuple(simplified=True)
         if not target.can_assign():
             raise TemplateSyntaxError("can't assign to '%s'" %
-                                      target, target.lineno,
-                                      self.filename)
+                                      target.__class__.__name__.lower(),
+                                      target.lineno, self.filename)
         target.set_ctx('store')
         self.stream.expect('in')
         iter = self.parse_tuple(no_condexpr=True)
index d7de80e05aea6501062c1bb962c8537074bbc837..8cc1b2ffa1f8948b3b178be49deb5b5475d8870d 100644 (file)
@@ -12,7 +12,8 @@ try:
     from collections import defaultdict
 except ImportError:
     defaultdict = None
-from jinja2.utils import Markup
+from types import FunctionType
+from jinja2.utils import Markup, partial
 from jinja2.exceptions import UndefinedError
 
 
@@ -34,6 +35,12 @@ class TemplateContext(dict):
         self.name = name
         self.blocks = dict((k, [v]) for k, v in blocks.iteritems())
 
+        # give all context functions the context as first argument
+        for key, value in self.iteritems():
+            if type(value) is FunctionType and \
+               getattr(value, 'contextfunction', False):
+                dict.__setitem__(self, key, partial(value, self))
+
         # if the template is in standalone mode we don't copy the blocks over.
         # this is used for includes for example but otherwise, if the globals
         # are a template context, this template is participating in a template
index 195a9420c27f79dc74afad9a354dad7b355095bc..6e9dbc00f0e18b9190b5be444c0d5ffb7fbb2157 100644 (file)
@@ -12,7 +12,6 @@ import re
 import string
 from collections import deque
 from copy import deepcopy
-from functools import update_wrapper
 from itertools import imap
 
 
@@ -26,6 +25,14 @@ _punctuation_re = re.compile(
 _simple_email_re = re.compile(r'^\S+@[a-zA-Z0-9._-]+\.[a-zA-Z0-9._-]+$')
 
 
+def contextfunction(f):
+    """Mark a callable as context callable.  A context callable is passed
+    the active context as first argument.
+    """
+    f.contextfunction = True
+    return f
+
+
 def import_string(import_name, silent=False):
     """Imports an object based on a string.  This use useful if you want to
     use import paths as endpoints or something similar.  An import path can
@@ -108,6 +115,55 @@ def urlize(text, trim_url_limit=None, nofollow=False):
     return u''.join(words)
 
 
+def generate_lorem_ipsum(n=5, html=True, min=20, max=100):
+    """Generate some lorem impsum for the template."""
+    from jinja2.constants import LOREM_IPSUM_WORDS
+    from random import choice, random, randrange
+    words = LOREM_IPSUM_WORDS.split()
+    result = []
+
+    for _ in xrange(n):
+        next_capitalized = True
+        last_comma = last_fullstop = 0
+        word = None
+        last = None
+        p = []
+
+        # each paragraph contains out of 20 to 100 words.
+        for idx, _ in enumerate(xrange(randrange(min, max))):
+            while True:
+                word = choice(words)
+                if word != last:
+                    last = word
+                    break
+            if next_capitalized:
+                word = word.capitalize()
+                next_capitalized = False
+            # add commas
+            if idx - randrange(3, 8) > last_comma:
+                last_comma = idx
+                last_fullstop += 2
+                word += ','
+            # add end of sentences
+            if idx - randrange(10, 20) > last_fullstop:
+                last_comma = last_fullstop = idx
+                word += '.'
+                next_capitalized = True
+            p.append(word)
+
+        # ensure that the paragraph ends with a dot.
+        p = u' '.join(p)
+        if p.endswith(','):
+            p = p[:-1] + '.'
+        elif not p.endswith('.'):
+            p += '.'
+        result.append(p)
+
+    if not html:
+        return u'\n\n'.join(result)
+    return Markup(u'\n'.join(u'<p>%s</p>' % escape(x) for x in result))
+
+
 class Markup(unicode):
     """Marks a string as being safe for inclusion in HTML/XML output without
     needing to be escaped.  This implements the `__html__` interface a couple
@@ -178,7 +234,9 @@ class Markup(unicode):
                 if hasattr(arg, '__html__') or isinstance(arg, basestring):
                     kwargs[name] = escape(arg)
             return self.__class__(orig(self, *args, **kwargs))
-        return update_wrapper(func, orig, ('__name__', '__doc__'))
+        func.__name__ = orig.__name__
+        func.__doc__ = orig.__doc__
+        return func
     for method in '__getitem__', '__getslice__', 'capitalize', \
                   'title', 'lower', 'upper', 'replace', 'ljust', \
                   'rjust', 'lstrip', 'rstrip', 'partition', 'center', \
@@ -339,3 +397,17 @@ except ImportError:
         if not isinstance(s, unicode):
             s = unicode(s)
         return s
+
+
+# partials
+try:
+    from functools import partial
+except ImportError:
+    class partial(object):
+        def __init__(self, _func, *args, **kwargs):
+            self._func = func
+            self._args = args
+            self._kwargs = kwargs
+        def __call__(self, *args, **kwargs):
+            kwargs.update(self._kwargs)
+            return self._func(*(self._args + args), **kwargs)
index a4dc3d14f9f8caf0ed6606ba66a88803c61e70de..895aa758446a81693c3b87a6fcc550d487b47588 100644 (file)
@@ -77,3 +77,12 @@ class NodeTransformer(NodeVisitor):
                 else:
                     setattr(node, field, new_node)
         return node
+
+    def visit_list(self, node, *args, **kwargs):
+        """As transformers may return lists in some places this method
+        can be used to enforce a list as return value.
+        """
+        rv = self.visit(node, *args, **kwargs)
+        if not isinstance(rv, list):
+            rv = [rv]
+        return rv
index afae0cdfe19b86a9b795346d068b35627b5ed0e0..0d00973550282e4678e5ece7c032334f43815b6f 100644 (file)
@@ -6,13 +6,13 @@
     :copyright: 2007 by Armin Ronacher.
     :license: BSD, see LICENSE for more details.
 """
-from jinja2 import Environment, DictLoader
+from jinja2 import Environment, DictLoader, contextfunction
 
 templates = {
     'master.html': '<title>{{ page_title|default(_("missing")) }}</title>'
                    '{% block body %}{% endblock %}',
     'child.html': '{% extends "master.html" %}{% block body %}'
-                  '{% trans "watch out" %}{% endblock %}',
+                  '{% trans %}watch out{% endtrans %}{% endblock %}',
     'plural.html': '{% trans user_count %}One user online{% pluralize %}'
                    '{{ user_count }} users online{% endtrans %}',
     'stringformat.html': '{{ _("User: %d")|format(user_count) }}'
@@ -30,52 +30,29 @@ languages = {
 }
 
 
-class SimpleTranslator(object):
-    """Yes i know it's only suitable for english and german but
-    that's a stupid unittest..."""
+@contextfunction
+def gettext(context, string):
+    language = context.get('LANGUAGE', 'en')
+    return languages.get(language, {}).get(string, string)
 
-    def __init__(self, language):
-        self.strings = languages.get(language, {})
 
-    def gettext(self, string):
-        return self.strings.get(string, string)
+@contextfunction
+def ngettext(context, s, p, n):
+    language = context.get('LANGUAGE', 'en')
+    if n != 1:
+        return languages.get(language, {}).get(p, p)
+    return languages.get(language, {}).get(s, s)
 
-    def ngettext(self, s, p, n):
-        if n != 1:
-            return self.strings.get(p, p)
-        return self.strings.get(s, s)
 
-
-class I18NEnvironment(Environment):
-
-    def __init__(self):
-        super(I18NEnvironment, self).__init__(loader=DictLoader(templates))
-
-    def get_translator(self, context):
-        return SimpleTranslator(context['LANGUAGE'] or 'en')
-
-
-i18n_env = I18NEnvironment()
-
-
-def test_factory():
-    def factory(context):
-        return SimpleTranslator(context['LANGUAGE'] or 'en')
-    env = Environment(translator_factory=factory)
-    tmpl = env.from_string('{% trans "watch out" %}')
-    assert tmpl.render(LANGUAGE='de') == 'pass auf'
-
-
-def test_get_translations():
-    trans = list(i18n_env.get_translations('child.html'))
-    assert len(trans) == 1
-    assert trans[0] == (1, u'watch out', None)
-
-
-def test_get_translations_for_string():
-    trans = list(i18n_env.get_translations('master.html'))
-    assert len(trans) == 1
-    assert trans[0] == (1, u'missing', None)
+i18n_env = Environment(
+    loader=DictLoader(templates),
+    extensions=['jinja2.i18n.TransExtension']
+)
+i18n_env.globals.update({
+    '_':            gettext,
+    'gettext':      gettext,
+    'ngettext':     ngettext
+})
 
 
 def test_trans():
@@ -92,3 +69,18 @@ def test_trans_plural():
 def test_trans_stringformatting():
     tmpl = i18n_env.get_template('stringformat.html')
     assert tmpl.render(LANGUAGE='de', user_count=5) == 'Benutzer: 5'
+
+
+def test_extract():
+    from jinja2.i18n import babel_extract
+    from StringIO import StringIO
+    source = StringIO('''
+    {{ gettext('Hello World') }}
+    {% trans %}Hello World{% endtrans %}
+    {% trans %}{{ users }} user{% pluralize %}{{ users }} users{% endtrans %}
+    ''')
+    assert list(babel_extract(source, ('gettext', 'ngettext', '_'), [], {})) == [
+        (2, 'gettext', 'Hello World', []),
+        (3, 'gettext', u'Hello World', []),
+        (4, 'ngettext', (u'%(users)s user', u'%(users)s users', None), [])
+    ]
index 35d9c3e32802a2613c0b220b293f57f2cc541765..80ac497883dd7a5d0b349d3fd6b48656b13702d5 100644 (file)
@@ -6,15 +6,8 @@
     :copyright: 2007 by Armin Ronacher.
     :license: BSD, see LICENSE for more details.
 """
-
 from jinja2 import Environment
 
-NO_VARIABLE_BLOCK = '''\
-{# i'm a freaking comment #}\
-{% if foo %}{% foo %}{% endif %}
-{% for item in seq %}{% item %}{% endfor %}
-{% trans foo %}foo is {% foo %}{% endtrans %}
-{% trans foo %}one foo{% pluralize %}{% foo %} foos{% endtrans %}'''
 
 PHP_SYNTAX = '''\
 <!-- I'm a comment, I'm not interesting -->\
@@ -34,11 +27,10 @@ COMMENT_SYNTAX = '''\
     ${item}
 <!--- endfor -->'''
 
-SMARTY_SYNTAX = '''\
-{* I'm a comment, I'm not interesting *}\
-{for item in seq-}
-    {item}
-{-endfor}'''
+MAKO_SYNTAX = '''\
+% for item in seq:
+    ${item}
+% endfor'''
 
 BALANCING = '''{{{'foo':'bar'}.foo}}'''
 
@@ -48,17 +40,6 @@ and bar comment #}
 {{ blub() }}'''
 
 
-def test_no_variable_block():
-    env = Environment('{%', '%}', None, None)
-    tmpl = env.from_string(NO_VARIABLE_BLOCK)
-    assert tmpl.render(foo=42, seq=range(2)).splitlines() == [
-        '42',
-        '01',
-        'foo is 42',
-        '42 foos'
-    ]
-
-
 def test_php_syntax():
     env = Environment('<?', '?>', '<?=', '?>', '<!--', '-->')
     tmpl = env.from_string(PHP_SYNTAX)
@@ -77,12 +58,6 @@ def test_comment_syntax():
     assert tmpl.render(seq=range(5)) == '01234'
 
 
-def test_smarty_syntax():
-    env = Environment('{', '}', '{', '}', '{*', '*}')
-    tmpl = env.from_string(SMARTY_SYNTAX)
-    assert tmpl.render(seq=range(5)) == '01234'
-
-
 def test_balancing(env):
     tmpl = env.from_string(BALANCING)
     assert tmpl.render() == 'bar'
@@ -91,3 +66,10 @@ def test_balancing(env):
 def test_start_comment(env):
     tmpl = env.from_string(STARTCOMMENT)
     assert tmpl.render().strip() == 'foo'
+
+
+def test_line_syntax():
+    env = Environment('<%', '%>', '${', '}', '<%#', '%>', '%')
+    tmpl = env.from_string(MAKO_SYNTAX)
+    assert [int(x.strip()) for x in tmpl.render(seq=range(5)).split()] == \
+           range(5)
index cb470f8e479388de7024c1a1ee3ba02ef706e96f..331e8b97dc58efb8b72b0d2bb97ea73a3c9cf1bf 100644 (file)
@@ -6,62 +6,65 @@
     :copyright: 2007 by Armin Ronacher.
     :license: BSD, see LICENSE for more details.
 """
-from jinja2 import Environment
+from jinja2.sandbox import SandboxedEnvironment, unsafe
 
 
-NONLOCALSET = '''\
-{% for item in range(10) %}
-    {%- set outer = item! -%}
-{% endfor -%}
-{{ outer }}'''
+class PrivateStuff(object):
 
+    def bar(self):
+        return 23
 
-class PrivateStuff(object):
-    bar = lambda self: 23
-    foo = lambda self: 42
-    foo.jinja_unsafe_call = True
+    @unsafe
+    def foo(self):
+        return 42
+
+    def __repr__(self):
+        return 'PrivateStuff'
 
 
 class PublicStuff(object):
-    jinja_allowed_attributes = ['bar']
     bar = lambda self: 23
-    foo = lambda self: 42
+    _foo = lambda self: 42
+
+    def __repr__(self):
+        return 'PublicStuff'
 
 
 test_unsafe = '''
+>>> env = MODULE.SandboxedEnvironment()
 >>> env.from_string("{{ foo.foo() }}").render(foo=MODULE.PrivateStuff())
-u''
+Traceback (most recent call last):
+    ...
+TypeError: <bound method PrivateStuff.foo of PrivateStuff> is not safely callable
 >>> env.from_string("{{ foo.bar() }}").render(foo=MODULE.PrivateStuff())
 u'23'
 
->>> env.from_string("{{ foo.foo() }}").render(foo=MODULE.PublicStuff())
-u''
+>>> env.from_string("{{ foo._foo() }}").render(foo=MODULE.PublicStuff())
+Traceback (most recent call last):
+    ...
+UndefinedError: access to attribute '_foo' of 'PublicStuff' object is unsafe.
 >>> env.from_string("{{ foo.bar() }}").render(foo=MODULE.PublicStuff())
 u'23'
 
 >>> env.from_string("{{ foo.__class__ }}").render(foo=42)
 u''
-
 >>> env.from_string("{{ foo.func_code }}").render(foo=lambda:None)
 u''
+>>> env.from_string("{{ foo.__class__.__subclasses__() }}").render(foo=42)
+Traceback (most recent call last):
+    ...
+UndefinedError: access to attribute '__class__' of 'int' object is unsafe.
 '''
 
 
 test_restricted = '''
+>>> env = MODULE.SandboxedEnvironment()
 >>> env.from_string("{% for item.attribute in seq %}...{% endfor %}")
 Traceback (most recent call last):
     ...
-TemplateSyntaxError: cannot assign to expression (line 1)
+TemplateSyntaxError: can't assign to 'subscript' (line 1)
 >>> env.from_string("{% for foo, bar.baz in seq %}...{% endfor %}")
 Traceback (most recent call last):
     ...
-TemplateSyntaxError: cannot assign to expression (line 1)
+TemplateSyntaxError: can't assign to 'tuple' (line 1)
 '''
-
-
-def test_nonlocal_set():
-    env = Environment()
-    env.globals['outer'] = 42
-    tmpl = env.from_string(NONLOCALSET)
-    assert tmpl.render() == '9'
-    assert env.globals['outer'] == 42
index b1802392e7db9e7a444be66eb7be2eedbb50cd59..37082ba2f03e786708725243b7b0cf871b9d1a19 100644 (file)
@@ -8,43 +8,16 @@
 """
 from jinja2.exceptions import TemplateSyntaxError
 
-KEYWORDS = '''\
-{{ with }}
-{{ as }}
-{{ import }}
-{{ from }}
-{{ class }}
-{{ def }}
-{{ try }}
-{{ except }}
-{{ exec }}
-{{ global }}
-{{ assert }}
-{{ break }}
-{{ continue }}
-{{ lambda }}
-{{ return }}
-{{ raise }}
-{{ yield }}
-{{ while }}
-{{ pass }}
-{{ finally }}'''
+
 UNPACKING = '''{% for a, b, c in [[1, 2, 3]] %}{{ a }}|{{ b }}|{{ c }}{% endfor %}'''
 RAW = '''{% raw %}{{ FOO }} and {% BAR %}{% endraw %}'''
-CONST = '''{{ true }}|{{ false }}|{{ none }}|{{ undefined }}|\
-{{ none is defined }}|{{ undefined is defined }}'''
-LOCALSET = '''{% set foo = 0 %}\
-{% for item in [1, 2] %}{% set foo = 1 %}{% endfor %}\
-{{ foo }}'''
-NONLOCALSET = '''{% set foo = 0 %}\
-{% for item in [1, 2] %}{% set foo = 1! %}{% endfor %}\
+CONST = '''{{ true }}|{{ false }}|{{ none }}|\
+{{ none is defined }}|{{ missing is defined }}'''
+LOCALSET = '''{% foo = 0 %}\
+{% for item in [1, 2] %}{% foo = 1 %}{% endfor %}\
 {{ foo }}'''
-CONSTASS1 = '''{% set true = 42 %}'''
-CONSTASS2 = '''{% for undefined in seq %}{% endfor %}'''
-
-
-def test_keywords(env):
-    env.from_string(KEYWORDS)
+CONSTASS1 = '''{% true = 42 %}'''
+CONSTASS2 = '''{% for none in seq %}{% endfor %}'''
 
 
 def test_unpacking(env):
@@ -57,16 +30,9 @@ def test_raw(env):
     assert tmpl.render() == '{{ FOO }} and {% BAR %}'
 
 
-def test_crazy_raw():
-    from jinja2 import Environment
-    env = Environment('{', '}', '{', '}')
-    tmpl = env.from_string('{raw}{broken foo}{endraw}')
-    assert tmpl.render() == '{broken foo}'
-
-
-def test_cache_dict():
-    from jinja2.utils import CacheDict
-    d = CacheDict(3)
+def test_lru_cache():
+    from jinja2.utils import LRUCache
+    d = LRUCache(3)
     d["a"] = 1
     d["b"] = 2
     d["c"] = 3
@@ -76,21 +42,9 @@ def test_cache_dict():
     assert 'a' in d and 'c' in d and 'd' in d and 'b' not in d
 
 
-def test_stringfilter(env):
-    from jinja2.filters import stringfilter
-    f = stringfilter(lambda f, x: f + x)
-    assert f('42')(env, None, 23) == '2342'
-
-
-def test_simplefilter(env):
-    from jinja2.filters import simplefilter
-    f = simplefilter(lambda f, x: f + x)
-    assert f(42)(env, None, 23) == 65
-
-
 def test_const(env):
     tmpl = env.from_string(CONST)
-    assert tmpl.render() == 'True|False|||True|False'
+    assert tmpl.render() == 'True|False|None|True|False'
 
 
 def test_const_assign(env):
@@ -106,8 +60,3 @@ def test_const_assign(env):
 def test_localset(env):
     tmpl = env.from_string(LOCALSET)
     assert tmpl.render() == '0'
-
-
-def test_nonlocalset(env):
-    tmpl = env.from_string(NONLOCALSET)
-    assert tmpl.render() == '1'