better debugging information. compiler knows about name and filename now (the first...
authorArmin Ronacher <armin.ronacher@active-4.com>
Wed, 16 Apr 2008 21:10:49 +0000 (23:10 +0200)
committerArmin Ronacher <armin.ronacher@active-4.com>
Wed, 16 Apr 2008 21:10:49 +0000 (23:10 +0200)
--HG--
branch : trunk

jinja2/compiler.py
jinja2/debug.py
jinja2/environment.py
jinja2/loaders.py

index 06017f808b4b91a4c85d0f38cb6fc24e0660610b..d1025d4d126b8a998cabc0e3022aa2a4df605657 100644 (file)
@@ -37,9 +37,9 @@ else:
     have_condexpr = True
 
 
-def generate(node, environment, filename, stream=None):
+def generate(node, environment, name, filename, stream=None):
     """Generate the python source for a node tree."""
-    generator = CodeGenerator(environment, filename, stream)
+    generator = CodeGenerator(environment, name, filename, stream)
     generator.visit(node)
     if stream is None:
         return generator.stream.getvalue()
@@ -228,10 +228,11 @@ class CompilerExit(Exception):
 
 class CodeGenerator(NodeVisitor):
 
-    def __init__(self, environment, filename, stream=None):
+    def __init__(self, environment, name, filename, stream=None):
         if stream is None:
             stream = StringIO()
         self.environment = environment
+        self.name = name
         self.filename = filename
         self.stream = stream
 
@@ -248,10 +249,11 @@ class CodeGenerator(NodeVisitor):
         self.has_known_extends = False
 
         # the current line number
-        self.lineno = 1
+        self.code_lineno = 1
 
         # the debug information
         self.debug_info = []
+        self._write_debug_info = None
 
         # the number of new lines before the next write()
         self._new_lines = 0
@@ -306,7 +308,11 @@ class CodeGenerator(NodeVisitor):
         if self._new_lines:
             if not self._first_write:
                 self.stream.write('\n' * self._new_lines)
-                self.lineno += self._new_lines
+                self.code_lineno += self._new_lines
+                if self._write_debug_info is not None:
+                    self.debug_info.append((self._write_debug_info,
+                                            self.code_lineno))
+                    self._write_debug_info = None
             self._first_write = False
             self.stream.write('    ' * self._indentation)
             self._new_lines = 0
@@ -321,7 +327,8 @@ class CodeGenerator(NodeVisitor):
         """Add one or more newlines before the next write."""
         self._new_lines = max(self._new_lines, 1 + extra)
         if node is not None and node.lineno != self._last_line:
-            self.debug_info.append((node.lineno, self.lineno))
+            self._write_debug_info = node.lineno
+            self._last_line = node.lineno
 
     def signature(self, node, frame, have_comma=True, extra_kwargs=None):
         """Writes a function call to the stream for the current node.
@@ -444,7 +451,7 @@ class CodeGenerator(NodeVisitor):
     def visit_Template(self, node, frame=None):
         assert frame is None, 'no root frame allowed'
         self.writeline('from jinja2.runtime import *')
-        self.writeline('name = %r' % self.filename)
+        self.writeline('name = %r' % self.name)
 
         # do we have an extends tag at all?  If not, we can save some
         # overhead by just not processing any inheritance code.
@@ -463,7 +470,7 @@ class CodeGenerator(NodeVisitor):
                        ', standalone=False):', extra=1)
         self.indent()
         self.writeline('context = TemplateContext(environment, globals, %r, '
-                       'blocks, standalone)' % self.filename)
+                       'blocks, standalone)' % self.name)
         if have_extends:
             self.writeline('parent_root = None')
         self.outdent()
@@ -835,15 +842,17 @@ class CodeGenerator(NodeVisitor):
                 self.writeline('%s.append(' % frame.buffer)
             self.write(repr(u''.join(format)) + ' % (')
             idx = -1
-            for idx, argument in enumerate(arguments):
-                if idx:
-                    self.write(', ')
+            self.indent()
+            for argument in arguments:
+                self.newline(argument)
                 if have_finalizer:
                     self.write('(')
                 self.visit(argument, frame)
                 if have_finalizer:
                     self.write(')')
-            self.write(idx == 0 and ',)' or ')')
+                self.write(',')
+            self.outdent()
+            self.writeline(')')
             if frame.buffer is not None:
                 self.write(')')
 
index 909a8527ba9c9d9160d458caa1068711e926ac1e..02b6177089a01b294335eda55f1a7d5a82161e0e 100644 (file)
@@ -8,15 +8,33 @@
     :copyright: Copyright 2008 by Armin Ronacher.
     :license: BSD.
 """
-import re
 import sys
 from jinja2.exceptions import TemplateNotFound
 
 
-_line_re = re.compile(r'^\s*# line: (\d+)\s*$')
+def translate_exception(exc_info):
+    """If passed an exc_info it will automatically rewrite the exceptions
+    all the way down to the correct line numbers and frames.
+    """
+    result_tb = prev_tb = None
+    initial_tb = tb = exc_info[2]
+
+    while tb is not None:
+        template = tb.tb_frame.f_globals.get('__jinja_template__')
+        if template is not None:
+            lineno = template.get_corresponding_lineno(tb.tb_lineno)
+            tb = fake_exc_info(exc_info[:2] + (tb,), template.filename,
+                               lineno, prev_tb)[2]
+        if result_tb is None:
+            result_tb = tb
+        prev_tb = tb
+        tb = tb.tb_next
+
+    return exc_info[:2] + (result_tb or initial_tb,)
 
 
 def fake_exc_info(exc_info, filename, lineno, tb_back=None):
+    """Helper for `translate_exception`."""
     exc_type, exc_value, tb = exc_info
 
     # figure the real context out
@@ -50,25 +68,6 @@ def fake_exc_info(exc_info, filename, lineno, tb_back=None):
     return exc_info
 
 
-def translate_exception(exc_info):
-    result_tb = prev_tb = None
-    initial_tb = tb = exc_info[2]
-
-    while tb is not None:
-        template = tb.tb_frame.f_globals.get('__jinja_template__')
-        if template is not None:
-            # TODO: inject faked exception with correct line number
-            lineno = template.get_corresponding_lineno(tb.tb_lineno)
-            tb = fake_exc_info(exc_info[:2] + (tb,), template.filename,
-                               lineno, prev_tb)[2]
-        if result_tb is None:
-            result_tb = tb
-        prev_tb = tb
-        tb = tb.tb_next
-
-    return exc_info[:2] + (result_tb or initial_tb,)
-
-
 def _init_ugly_crap():
     """This function implements a few ugly things so that we can patch the
     traceback objects.  The function returned allows resetting `tb_next` on
@@ -112,11 +111,20 @@ def _init_ugly_crap():
     ]
 
     def tb_set_next(tb, next):
+        """Set the tb_next attribute of a traceback object."""
         if not (isinstance(tb, TracebackType) and
-                isinstance(next, TracebackType)):
+                (next is None or isinstance(next, TracebackType))):
             raise TypeError('tb_set_next arguments must be traceback objects')
         obj = _Traceback.from_address(id(tb))
-        obj.tb_next = ctypes.pointer(_Traceback.from_address(id(next)))
+        if tb.tb_next is not None:
+            old = _Traceback.from_address(id(tb.tb_next))
+            old.ob_refcnt -= 1
+        if next is None:
+            obj.tb_next = ctypes.POINTER(_Traceback)()
+        else:
+            next = _Traceback.from_address(id(next))
+            next.ob_refcnt += 1
+            obj.tb_next = ctypes.pointer(next)
 
     return tb_set_next
 
index 47d99e38ea85700896d4af1526d3ea830b63bacf..d0bbb1d4c98efb585b12cd14eb53e69450cb48bd 100644 (file)
@@ -134,13 +134,14 @@ class Environment(object):
         """
         return self.lexer.tokeniter(source, name)
 
-    def compile(self, source, filename=None, raw=False, globals=None):
+    def compile(self, source, name=None, filename=None, raw=False,
+                globals=None):
         """Compile a node or source."""
         if isinstance(source, basestring):
-            source = self.parse(source, filename)
+            source = self.parse(source, name)
         if self.optimized:
             node = optimize(source, self, globals or {})
-        source = generate(node, self, filename)
+        source = generate(node, self, name, filename)
         if raw:
             return source
         if filename is None:
@@ -164,10 +165,10 @@ class Environment(object):
         globals = self.make_globals(globals)
         return self.loader.load(self, name, globals)
 
-    def from_string(self, source, filename='<string>', globals=None):
+    def from_string(self, source, globals=None):
         """Load a template from a string."""
         globals = self.make_globals(globals)
-        return Template(self, self.compile(source, filename, globals=globals),
+        return Template(self, self.compile(source, globals=globals),
                         globals)
 
     def make_globals(self, d):
index 6c03ad37dcec92ded78ef106c4b8c360f63337da..37c34e17a3efa7afaf0405f6925ba4979382d20d 100644 (file)
@@ -21,7 +21,7 @@ class BaseLoader(object):
 
     def load(self, environment, name, globals=None):
         source, filename = self.get_source(environment, name)
-        code = environment.compile(source, filename, globals=globals)
+        code = environment.compile(source, name, filename, globals=globals)
         return Template(environment, code, globals or {})
 
 
@@ -43,7 +43,7 @@ class FileSystemLoader(BaseLoader):
             raise TemplateNotFound(template)
         f = file(filename)
         try:
-            return f.read().decode(self.encoding)
+            return f.read().decode(self.encoding), filename
         finally:
             f.close()