"""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."""
'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()
"""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."""
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):
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
: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
'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."""
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>' % (
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
__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')
class DebugUndefined(Undefined):
"""An undefined that returns the debug info when printed."""
+ __slots__ = ()
def __unicode__(self):
if self._undefined_hint is None:
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__
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):
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
}
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 }}'''
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'