worked on the tests and made undefined fail on comparisons now

--HG--
branch : trunk
This commit is contained in:
Armin Ronacher 2008-04-26 18:30:19 +02:00
parent 2feed1d5e2
commit 5304229478
6 changed files with 91 additions and 52 deletions

View File

@ -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()

View File

@ -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 '<from string>'
)
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):

View File

@ -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

View File

@ -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__

View File

@ -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
}

View File

@ -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'