worked on the tests and made undefined fail on comparisons now
authorArmin Ronacher <armin.ronacher@active-4.com>
Sat, 26 Apr 2008 16:30:19 +0000 (18:30 +0200)
committerArmin Ronacher <armin.ronacher@active-4.com>
Sat, 26 Apr 2008 16:30:19 +0000 (18:30 +0200)
--HG--
branch : trunk

jinja2/compiler.py
jinja2/environment.py
jinja2/exceptions.py
jinja2/runtime.py
jinja2/tests.py
tests/test_tests.py

index 8541cba7640a65a6763ed2879d46854c3221cd19..8d58fbe04740d1fbd35d0bb404dfb212cdc84355 100644 (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()
index f61c740181aef96fcd2d188560601bcef3c30271..3a212b86145cda93fa1fd6048f1881fe8631cce6 100644 (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):
index b4c87da5e6d4a950fda056d0ad94ff9d6e60d4a2..b742959726bff513c865b10e7633dd6370dc9ea1 100644 (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
index 11924cf20f60e468ed74a39dd3c0f1079cfc8260..6d63d47f25b65a8817c5aa7eb06c6badbf385163 100644 (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__
index d62ffeb223a6cea2fbff3e3838fec9897c2dfd65..59493ac37a016befe1e90c443c8096aaaa0be6ec 100644 (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
 }
index fa665fb5b441bf1041805b582aba223ab298d726..3943ab451573d3ba70b947447c5e09b80ca0b6ed 100644 (file)
 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'