From 4f7d2d56ab996050c9094f9426969028db0c8aa6 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Tue, 22 Apr 2008 10:40:26 +0200 Subject: [PATCH] fixed more unittests --HG-- branch : trunk --- jinja2/__init__.py | 2 +- jinja2/defaults.py | 4 ++- jinja2/ext.py | 5 +-- jinja2/i18n.py | 24 +++++++++---- jinja2/lexer.py | 9 +++-- jinja2/nodes.py | 13 +++++-- jinja2/optimizer.py | 10 +++--- jinja2/parser.py | 8 ++--- jinja2/runtime.py | 9 ++++- jinja2/utils.py | 76 +++++++++++++++++++++++++++++++++++++-- jinja2/visitor.py | 9 +++++ tests/test_i18n.py | 80 +++++++++++++++++++----------------------- tests/test_parser.py | 40 ++++++--------------- tests/test_security.py | 55 +++++++++++++++-------------- tests/test_various.py | 73 ++++++-------------------------------- 15 files changed, 228 insertions(+), 189 deletions(-) diff --git a/jinja2/__init__.py b/jinja2/__init__.py index 12c147e..7dbe329 100644 --- a/jinja2/__init__.py +++ b/jinja2/__init__.py @@ -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 diff --git a/jinja2/defaults.py b/jinja2/defaults.py index 6462f32..773ad80 100644 --- a/jinja2/defaults.py +++ b/jinja2/defaults.py @@ -10,8 +10,10 @@ """ 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 } diff --git a/jinja2/ext.py b/jinja2/ext.py index 230c022..947150d 100644 --- a/jinja2/ext.py +++ b/jinja2/ext.py @@ -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 ) diff --git a/jinja2/i18n.py b/jinja2/i18n.py index f6ec791..9125ee9 100644 --- a/jinja2/i18n.py +++ b/jinja2/i18n.py @@ -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) diff --git a/jinja2/lexer.py b/jinja2/lexer.py index eab6e88..beb9866 100644 --- a/jinja2/lexer.py +++ b/jinja2/lexer.py @@ -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) diff --git a/jinja2/nodes.py b/jinja2/nodes.py index 992752a..7688f62 100644 --- a/jinja2/nodes.py +++ b/jinja2/nodes.py @@ -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): diff --git a/jinja2/optimizer.py b/jinja2/optimizer.py index 6b13aec..3aa46f3 100644 --- a/jinja2/optimizer.py +++ b/jinja2/optimizer.py @@ -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: diff --git a/jinja2/parser.py b/jinja2/parser.py index 54aa06e..5658ca9 100644 --- a/jinja2/parser.py +++ b/jinja2/parser.py @@ -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) diff --git a/jinja2/runtime.py b/jinja2/runtime.py index d7de80e..8cc1b2f 100644 --- a/jinja2/runtime.py +++ b/jinja2/runtime.py @@ -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 diff --git a/jinja2/utils.py b/jinja2/utils.py index 195a942..6e9dbc0 100644 --- a/jinja2/utils.py +++ b/jinja2/utils.py @@ -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'

%s

' % 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) diff --git a/jinja2/visitor.py b/jinja2/visitor.py index a4dc3d1..895aa75 100644 --- a/jinja2/visitor.py +++ b/jinja2/visitor.py @@ -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 diff --git a/tests/test_i18n.py b/tests/test_i18n.py index afae0cd..0d00973 100644 --- a/tests/test_i18n.py +++ b/tests/test_i18n.py @@ -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': '{{ page_title|default(_("missing")) }}' '{% 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), []) + ] diff --git a/tests/test_parser.py b/tests/test_parser.py index 35d9c3e..80ac497 100644 --- a/tests/test_parser.py +++ b/tests/test_parser.py @@ -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 = '''\ \ @@ -34,11 +27,10 @@ COMMENT_SYNTAX = '''\ ${item} ''' -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) diff --git a/tests/test_security.py b/tests/test_security.py index cb470f8..331e8b9 100644 --- a/tests/test_security.py +++ b/tests/test_security.py @@ -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: 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 diff --git a/tests/test_various.py b/tests/test_various.py index b180239..37082ba 100644 --- a/tests/test_various.py +++ b/tests/test_various.py @@ -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' -- 2.26.2