From 5304229478b34fa7190f0cfa93cce13ed8a1273b Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Sat, 26 Apr 2008 18:30:19 +0200 Subject: [PATCH] worked on the tests and made undefined fail on comparisons now --HG-- branch : trunk --- jinja2/compiler.py | 7 +++++-- jinja2/environment.py | 31 ++++++++++++++++------------ jinja2/exceptions.py | 6 +----- jinja2/runtime.py | 47 ++++++++++++++++++++++++++++--------------- jinja2/tests.py | 43 +++++++++++++++++++++++++++++++-------- tests/test_tests.py | 9 +-------- 6 files changed, 91 insertions(+), 52 deletions(-) diff --git a/jinja2/compiler.py b/jinja2/compiler.py index 8541cba..8d58fbe 100644 --- a/jinja2/compiler.py +++ b/jinja2/compiler.py @@ -678,10 +678,12 @@ class CodeGenerator(NodeVisitor): """Visit regular imports.""" self.writeline('l_%s = ' % node.target, node) if frame.toplevel: - self.write('context[%r] = ' % node.target) + self.write('context.vars[%r] = ' % node.target) self.write('environment.get_template(') self.visit(node.template, frame) self.write(', %r).include(context)' % self.name) + if frame.toplevel: + self.writeline('context.exported_vars.discard(%r)' % node.target) def visit_FromImport(self, node, frame): """Visit named imports.""" @@ -704,7 +706,8 @@ class CodeGenerator(NodeVisitor): 'the requested name ' + repr(name))) self.outdent() if frame.toplevel: - self.writeline('context[%r] = l_%s' % (alias, alias)) + self.writeline('context.vars[%r] = l_%s' % (alias, alias)) + self.writeline('context.exported_vars.discard(%r)' % alias) def visit_For(self, node, frame): loop_frame = frame.inner() diff --git a/jinja2/environment.py b/jinja2/environment.py index f61c740..3a212b8 100644 --- a/jinja2/environment.py +++ b/jinja2/environment.py @@ -238,7 +238,7 @@ class Environment(object): """Load a template from a string.""" globals = self.make_globals(globals) return template_from_code(self, self.compile(source, globals=globals), - globals, template_class) + globals, None, template_class) def make_globals(self, d): """Return a dict for the globals.""" @@ -385,31 +385,36 @@ class Template(object): self._debug_info.split('&')] def __repr__(self): - return '<%s %r>' % ( - self.__class__.__name__, - self.name or '' - ) + if self.name is None: + name = 'memory:%x' % id(self) + else: + name = repr(self.name) + return '<%s %s>' % (self.__class__.__name__, name) class IncludedTemplate(object): - """Represents an included template.""" + """Represents an included template. All the exported names of the + template are available as attributes on this object. Additionally + converting it into an unicode- or bytestrings renders the contents. + """ def __init__(self, template, context): - self._body_stream = tuple(template.root_render_func(context)) + self.__body_stream = tuple(template.root_render_func(context)) self.__dict__.update(context.get_exported()) self.__name__ = template.name - __html__ = lambda x: Markup(concat(x._body_stream)) - __unicode__ = lambda x: unicode(concat(x._body_stream)) + __html__ = lambda x: Markup(concat(x.__body_stream)) + __unicode__ = lambda x: unicode(concat(x.__body_stream)) def __str__(self): return unicode(self).encode('utf-8') def __repr__(self): - return '<%s %r>' % ( - self.__class__.__name__, - self.__name__ - ) + if self.__name__ is None: + name = 'memory:%x' % id(self) + else: + name = repr(self.name) + return '<%s %s>' % (self.__class__.__name__, name) class TemplateStream(object): diff --git a/jinja2/exceptions.py b/jinja2/exceptions.py index b4c87da..b742959 100644 --- a/jinja2/exceptions.py +++ b/jinja2/exceptions.py @@ -36,16 +36,12 @@ class TemplateSyntaxError(TemplateError): self.name = name -class TemplateAssertionError(AssertionError, TemplateSyntaxError): +class TemplateAssertionError(TemplateSyntaxError): """Like a template syntax error, but covers cases where something in the template caused an error at compile time that wasn't necessarily caused by a syntax error. """ - def __init__(self, message, lineno, name): - AssertionError.__init__(self, message) - TemplateSyntaxError.__init__(self, message, lineno, name) - class TemplateRuntimeError(TemplateError): """Raised by the template engine if a tag encountered an error when diff --git a/jinja2/runtime.py b/jinja2/runtime.py index 11924cf..6d63d47 100644 --- a/jinja2/runtime.py +++ b/jinja2/runtime.py @@ -9,14 +9,13 @@ :license: GNU GPL. """ from types import FunctionType -from itertools import izip from jinja2.utils import Markup, partial from jinja2.exceptions import UndefinedError # these variables are exported to the template runtime __all__ = ['LoopContext', 'StaticLoopContext', 'TemplateContext', - 'Macro', 'Markup', 'missing', 'concat', 'izip'] + 'Macro', 'Markup', 'missing', 'concat'] # special singleton representing missing values for the runtime @@ -66,16 +65,24 @@ class TemplateContext(object): 'called %r.' % block) return SuperBlock(block, self, last) - def get(self, name, default=None): + def get(self, key, default=None): """For dict compatibility""" - try: - return self[name] - except KeyError: - return default + if key in self.vars: + return self.vars[key] + if key in self.parent: + return self.parent[key] + return default - def update(self, mapping): + def setdefault(self, key, default=None): + """For dict compatibility""" + self.exported_vars.add(key) + return self.vars.setdefault(key, default) + + def update(self, *args, **kwargs): """Update vars from a mapping but don't export them.""" - self.vars.update(mapping) + d = dict(*args, **kwargs) + self.vars.update(d) + self.exported_vars.update(d) def get_exported(self): """Get a new dict with the exported variables.""" @@ -100,10 +107,9 @@ class TemplateContext(object): def __getitem__(self, key): if key in self.vars: return self.vars[key] - try: + if key in self.parent: return self.parent[key] - except KeyError: - return self.environment.undefined(name=key) + return self.environment.undefined(name=key) def __repr__(self): return '<%s %s of %r>' % ( @@ -302,6 +308,7 @@ class Undefined(object): can be printed and iterated over, but every other access will raise a `NameError`. Custom undefined classes must subclass this. """ + __slots__ = ('_undefined_hint', '_undefined_obj', '_undefined_name') def __init__(self, hint=None, obj=None, name=None): self._undefined_hint = hint @@ -311,7 +318,8 @@ class Undefined(object): __add__ = __radd__ = __mul__ = __rmul__ = __div__ = __rdiv__ = \ __realdiv__ = __rrealdiv__ = __floordiv__ = __rfloordiv__ = \ __mod__ = __rmod__ = __pos__ = __neg__ = __call__ = \ - __getattr__ = __getitem__ = fail_with_undefined_error + __getattr__ = __getitem__ = __lt__ = __le__ = __gt__ = __ge__ = \ + fail_with_undefined_error def __str__(self): return self.__unicode__().encode('utf-8') @@ -335,6 +343,7 @@ class Undefined(object): class DebugUndefined(Undefined): """An undefined that returns the debug info when printed.""" + __slots__ = () def __unicode__(self): if self._undefined_hint is None: @@ -349,8 +358,14 @@ class DebugUndefined(Undefined): class StrictUndefined(Undefined): """An undefined that barks on print and iteration as well as boolean - tests. In other words: you can do nothing with it except checking if it's - defined using the `defined` test. + tests and all kinds of comparisons. In other words: you can do nothing + with it except checking if it's defined using the `defined` test. """ + __slots__ = () + __iter__ = __unicode__ = __len__ = __nonzero__ = __eq__ = __ne__ = \ + fail_with_undefined_error + - __iter__ = __unicode__ = __len__ = __nonzero__ = fail_with_undefined_error +# remove remaining slots attributes, after the metaclass did the magic they +# are unneeded and irritating as they contain wrong data for the subclasses. +del Undefined.__slots__, DebugUndefined.__slots__, StrictUndefined.__slots__ diff --git a/jinja2/tests.py b/jinja2/tests.py index d62ffeb..59493ac 100644 --- a/jinja2/tests.py +++ b/jinja2/tests.py @@ -48,21 +48,34 @@ def test_defined(value): return not isinstance(value, Undefined) +def test_undefined(value): + """Like `defined` but the other way round.""" + return isinstance(value, Undefined) + + +def test_none(value): + """Return true if the variable is none.""" + return value is None + + def test_lower(value): - """Return true if the variable is lowercase.""" + """Return true if the variable is lowercased.""" return unicode(value).islower() def test_upper(value): - """Return true if the variable is uppercase.""" + """Return true if the variable is uppercased.""" return unicode(value).isupper() -def test_numeric(value): - """Return true if the variable is numeric.""" - return isinstance(value, (int, long, float)) or ( - isinstance(value, basestring) and - number_re.match(value) is not None) +def test_string(value): + """Return true if the object is a string.""" + return isinstance(value, basestring) + + +def test_number(value): + """Return true if the variable is a number.""" + return isinstance(value, (int, long, float, complex)) def test_sequence(value): @@ -90,14 +103,28 @@ def test_sameas(value, other): return value is other +def test_iterable(value): + """Check if it's possible to iterate over an object.""" + try: + iter(value) + except TypeError: + return False + return True + + TESTS = { 'odd': test_odd, 'even': test_even, 'divisibleby': test_divisibleby, 'defined': test_defined, + 'undefined': test_undefined, + 'none': test_none, 'lower': test_lower, 'upper': test_upper, - 'numeric': test_numeric, + 'string': test_string, + 'number': test_number, 'sequence': test_sequence, + 'iterable': test_iterable, + 'callable': callable, 'sameas': test_sameas } diff --git a/tests/test_tests.py b/tests/test_tests.py index fa665fb..3943ab4 100644 --- a/tests/test_tests.py +++ b/tests/test_tests.py @@ -10,14 +10,12 @@ DEFINED = '''{{ missing is defined }}|{{ true is defined }}''' EVEN = '''{{ 1 is even }}|{{ 2 is even }}''' LOWER = '''{{ "foo" is lower }}|{{ "FOO" is lower }}''' -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 }}''' -SAMEAS = '''{{ foo is sameas(false) }}|{{ 0 is sameas(false) }}''' +SAMEAS = '''{{ foo is sameas false }}|{{ 0 is sameas false }}''' NOPARENFORARG1 = '''{{ foo is sameas none }}''' @@ -36,11 +34,6 @@ def test_lower(env): 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' -- 2.26.2