fixed one bug with blocks, one to go
authorArmin Ronacher <armin.ronacher@active-4.com>
Thu, 10 Apr 2008 18:43:43 +0000 (20:43 +0200)
committerArmin Ronacher <armin.ronacher@active-4.com>
Thu, 10 Apr 2008 18:43:43 +0000 (20:43 +0200)
--HG--
branch : trunk

jinja2/compiler.py
jinja2/environment.py
jinja2/filters.py
jinja2/optimizer.py
jinja2/parser.py
jinja2/runtime.py
jinja2/utils.py
test.py

index 44087f1609d5b5769f5c9a275f8cf824dbc4610a..48c9d9973087382ac9257b27b401384fa7cf2e5b 100644 (file)
@@ -257,11 +257,17 @@ class CodeGenerator(NodeVisitor):
         assert frame is None, 'no root frame allowed'
         self.writeline('from jinja2.runtime import *')
         self.writeline('filename = %r' % self.filename)
-        self.writeline('template_context = TemplateContext(global_context, '
-                       'filename)')
+
+        # find all blocks
+        for block in node.find_all(nodes.Block):
+            if block.name in self.blocks:
+                raise TemplateAssertionError('block %r defined twice' %
+                                             block.name, block.lineno,
+                                             self.filename)
+            self.blocks[block.name] = block
 
         # generate the root render function.
-        self.writeline('def root(context=template_context):', extra=1)
+        self.writeline('def root(context):', extra=1)
         self.indent()
         self.writeline('parent_root = None')
         self.outdent()
@@ -285,17 +291,13 @@ class CodeGenerator(NodeVisitor):
             block_frame = Frame()
             block_frame.inspect(block.body)
             block_frame.block = name
+            print block_frame.identifiers.__dict__
             self.writeline('def block_%s(context):' % name, block, 1)
             self.pull_locals(block_frame)
             self.blockvisit(block.body, block_frame, True)
 
     def visit_Block(self, node, frame):
         """Call a block and register it for the template."""
-        if node.name in self.blocks:
-            raise TemplateAssertionError("the block '%s' was already defined" %
-                                         node.name, node.lineno,
-                                         self.filename)
-        self.blocks[node.name] = node
         self.writeline('for event in block_%s(context):' % node.name)
         self.indent()
         self.writeline('yield event')
@@ -429,6 +431,11 @@ class CodeGenerator(NodeVisitor):
     def visit_Output(self, node, frame):
         self.newline(node)
 
+        if self.environment.finalize is unicode:
+            finalizer = 'unicode'
+        else:
+            finalizer = 'context.finalize'
+
         # try to evaluate as many chunks as possible into a static
         # string at compile time.
         body = []
@@ -450,7 +457,7 @@ class CodeGenerator(NodeVisitor):
                     self.writeline('yield %s' % repr(u''.join(item)))
                 else:
                     self.newline(item)
-                    self.write('yield unicode(')
+                    self.write('yield %s(' % finalizer)
                     self.visit(item, frame)
                     self.write(')')
 
@@ -469,7 +476,11 @@ class CodeGenerator(NodeVisitor):
             for idx, argument in enumerate(arguments):
                 if idx:
                     self.write(', ')
+                if finalizer != 'unicode':
+                    self.write('(')
                 self.visit(argument, frame)
+                if finalizer != 'unicode':
+                    self.write(')')
             self.write(idx == 0 and ',)' or ')')
 
     def visit_Assign(self, node, frame):
@@ -514,6 +525,24 @@ class CodeGenerator(NodeVisitor):
             self.visit(item, frame)
         self.write(idx == 0 and ',)' or ')')
 
+    def visit_List(self, node, frame):
+        self.write('[')
+        for idx, item in enumerate(node.items):
+            if idx:
+                self.write(', ')
+            self.visit(item, frame)
+        self.write(']')
+
+    def visit_Dict(self, node, frame):
+        self.write('{')
+        for idx, item in enumerate(node.items):
+            if idx:
+                self.write(', ')
+            self.visit(item.key, frame)
+            self.write(': ')
+            self.visit(item.value, frame)
+        self.write('}')
+
     def binop(operator):
         def visitor(self, node, frame):
             self.write('(')
index 77e0d03453699cd66b218458015a1314a1e4d1c8..8ba4fce111f8b527e9975d89327ac81ad8085074 100644 (file)
 """
 from jinja2.lexer import Lexer
 from jinja2.parser import Parser
-from jinja2.runtime import Undefined
 from jinja2.defaults import DEFAULT_FILTERS, DEFAULT_TESTS, DEFAULT_NAMESPACE
 
 
 class Environment(object):
-    """
-    The Jinja environment.
+    """The Jinja environment.
 
     The core component of Jinja is the `Environment`. It contains
     important shared variables like configuration, filters, tests,
@@ -32,8 +30,7 @@ class Environment(object):
                  comment_end_string='#}',
                  trim_blocks=False,
                  template_charset='utf-8'):
-        """
-        Here the possible initialization parameters:
+        """Here the possible initialization parameters:
 
         ========================= ============================================
         `block_start_string`      the string marking the begin of a block.
@@ -68,26 +65,25 @@ class Environment(object):
         self.tests = DEFAULT_TESTS.copy()
         self.globals = DEFAULT_NAMESPACE.copy()
 
-        # the factory that creates the undefined object
-        self.undefined_factory = Undefined
+        # if no finalize function/method exists we default to unicode.  The
+        # compiler check if the finalize attribute *is* unicode, if yes no
+        # finalizaion is written where it can be avoided.
+        if not hasattr(self, 'finalize'):
+            self.finalize = unicode
 
         # create lexer
         self.lexer = Lexer(self)
 
     def parse(self, source, filename=None):
-        """
-        Parse the sourcecode and return the abstract syntax tree. This tree
-        of nodes is used by the `translators`_ to convert the template into
+        """Parse the sourcecode and return the abstract syntax tree. This tree
+        of nodes is used by the compiler to convert the template into
         executable source- or bytecode.
-
-        .. _translators: translators.txt
         """
         parser = Parser(self, source, filename)
         return parser.parse()
 
     def lex(self, source, filename=None):
-        """
-        Lex the given sourcecode and return a generator that yields tokens.
+        """Lex the given sourcecode and return a generator that yields tokens.
         The stream returned is not usable for Jinja but can be used if
         Jinja templates should be processed by other tools (for example
         syntax highlighting etc)
index 8b66ce807958f2ce5ff9c5ccc3d8029a005c75bb..1c3ffcba27785f029628e6e407bdf62752a113d2 100644 (file)
@@ -15,15 +15,14 @@ try:
 except ImportError:
     itemgetter = lambda a: lambda b: b[a]
 from urllib import urlencode, quote
-from jinja.utils import escape
+from jinja2.utils import escape
 
 
 _striptags_re = re.compile(r'(<!--.*?-->|<[^>]*>)')
 
 
 def contextfilter(f):
-    """
-    Decorator for marking context dependent filters. The current context
+    """Decorator for marking context dependent filters. The current context
     argument will be passed as first argument.
     """
     f.contextfilter = True
@@ -60,17 +59,13 @@ def do_replace(s, old, new, count=None):
 
 
 def do_upper(s):
-    """
-    Convert a value to uppercase.
-    """
-    return s.upper()
+    """Convert a value to uppercase."""
+    return unicode(s).upper()
 
 
 def do_lower(s):
-    """
-    Convert a value to lowercase.
-    """
-    return s.lower()
+    """Convert a value to lowercase."""
+    return unicode(s).lower()
 
 
 def do_escape(s, attribute=False):
@@ -82,11 +77,6 @@ def do_escape(s, attribute=False):
 
     This method will have no effect it the value is already escaped.
     """
-    # XXX: Does this still exists?
-    #if isinstance(s, TemplateData):
-    #    return s
-    if hasattr(s, '__html__'):
-        return s.__html__()
     return escape(unicode(s), attribute)
 
 
index e16961f737cf02db3a8314634baee6e3bfacf690..167f6eba3796aba6cbf931ff4af0448b0b42b8c6 100644 (file)
@@ -26,6 +26,7 @@ from jinja2.runtime import subscribe, LoopContext
 
 class ContextStack(object):
     """Simple compile time context implementation."""
+    undefined = object()
 
     def __init__(self, initial=None):
         self.stack = [{}]
@@ -44,10 +45,24 @@ class ContextStack(object):
         except KeyError:
             return default
 
+    def undef(self, name):
+        if name in self:
+            self[name] = self.undefined
+
+    def __contains__(self, key):
+        try:
+            self[key]
+        except KeyError:
+            return False
+        return True
+
     def __getitem__(self, key):
         for level in reversed(self.stack):
             if key in level:
-                return level[key]
+                rv = level[key]
+                if rv is self.undefined:
+                    raise KeyError(key)
+                return rv
         raise KeyError(key)
 
     def __setitem__(self, key, value):
@@ -66,6 +81,13 @@ class Optimizer(NodeTransformer):
     def visit_Block(self, node, context):
         return self.generic_visit(node, context.blank())
 
+    def visit_Macro(self, node, context):
+        context.push()
+        try:
+            return self.generic_visit(node, context)
+        finally:
+            context.pop()
+
     def visit_For(self, node, context):
         """Loop unrolling for iterable constant values."""
         try:
@@ -73,6 +95,9 @@ class Optimizer(NodeTransformer):
             # we only unroll them if they have a length and are iterable
             iter(iterable)
             len(iterable)
+            # we also don't want unrolling if macros are defined in it
+            if node.find(nodes.Macro) is not None:
+                raise TypeError()
         except (nodes.Impossible, TypeError):
             return self.generic_visit(node, context)
 
@@ -124,6 +149,9 @@ class Optimizer(NodeTransformer):
 
     def visit_Name(self, node, context):
         if node.ctx != 'load':
+            # something overwrote the variable, we can no longer use
+            # the constant from the context
+            context.undef(node.name)
             return node
         try:
             return nodes.Const.from_untrusted(context[node.name],
index a7f0e980afda4b1129ef068b86d1891a27448609..74cc421cddb773b13cc410a75cfd243291ee6abf 100644 (file)
@@ -55,7 +55,6 @@ class Parser(object):
         if token_type in _statement_keywords:
             return getattr(self, 'parse_' + token_type)()
         elif token_type is 'call':
-            self.stream.next()
             return self.parse_call_block()
         lineno = self.stream.current
         expr = self.parse_tuple()
@@ -179,7 +178,10 @@ class Parser(object):
 
     def parse_call_block(self):
         node = nodes.CallBlock(lineno=self.stream.expect('call').lineno)
-        node.call = self.parse_call()
+        node.call = self.parse_expression()
+        if not isinstance(node.call, nodes.Call):
+            raise TemplateSyntaxError('expected call', node.lineno,
+                                      self.filename)
         node.body = self.parse_statements(('endcall',), drop_needle=True)
         return node
 
index 5101d0f648d9dac9df177b804cc5c6d11cf60216..b80a4880b152615125591ff9bb60502ffa9e606b 100644 (file)
@@ -33,9 +33,17 @@ def subscribe(obj, argument):
             return Undefined(obj, argument)
 
 
+class TemplateData(unicode):
+    """Marks data as "coming from the template".  This is used to let the
+    system know that this data is already processed if a finalization is
+    used."""
+
+    def __html__(self):
+        return self
+
+
 class TemplateContext(dict):
-    """
-    Holds the variables of the local template or of the global one.  It's
+    """Holds the variables of the local template or of the global one.  It's
     not save to use this class outside of the compiled code.  For example
     update and other methods will not work as they seem (they don't update
     the exported variables for example).
@@ -177,7 +185,7 @@ class Macro(object):
             arguments['l_' + name] = value
         if self.catch_all:
             arguments['l_arguments'] = kwargs
-        return u''.join(self.func(**arguments))
+        return TemplateData(u''.join(self.func(**arguments)))
 
 
 class Undefined(object):
index 3f6395e2e9e0e659aa4d24de5336321143553ba7..23a3b152525d5842c57955f4a650cbf0b679bcb5 100644 (file)
@@ -8,3 +8,16 @@
     :copyright: 2008 by Armin Ronacher.
     :license: BSD, see LICENSE for more details.
 """
+
+
+def escape(obj, attribute=False):
+    """HTML escape an object."""
+    if hasattr(obj, '__html__'):
+        return obj.__html__()
+    s = unicode(obj) \
+        .replace('&', '&amp;') \
+        .replace('>', '&gt;') \
+        .replace('<', '&lt;')
+    if attribute:
+        s = s.replace('"', '&quot;')
+    return s
diff --git a/test.py b/test.py
index 7ab5b96dda5980ce38e8ff96f3c81df4a9179a4e..0629c1415fb386b3534985557445753e3f6ef71d 100644 (file)
--- a/test.py
+++ b/test.py
@@ -5,24 +5,17 @@ from jinja2.compiler import generate
 
 env = Environment()
 ast = env.parse("""
-{% block body %}
-    {% b = 23 %}
-    {% macro foo(a) %}[{{ a }}|{{ b }}|{{ c }}]{% endmacro %}
-    {% for item in seq %}
-      {{ foo(item) }}
-    {%- endfor %}
+hallo
+{% block root %}
+  inhalt
+  {% x = 3 %}
+  {% block inner %}
+    {% x = x + 2 %}
+    {{ x }}
+  {% endblock %}
+  {{ x }}
 {% endblock %}
+ende
 """)
-print ast
-print
 source = generate(ast, env, "foo.html")
 print source
-print
-
-# execute the template
-code = compile(source, 'jinja://foo.html', 'exec')
-context = {'seq': range(5), 'c': 'foobar'}
-namespace = {'global_context': context}
-exec code in namespace
-for event in namespace['root'](context):
-    sys.stdout.write(event)