]> git.ipfire.org Git - thirdparty/jinja.git/commitdiff
Improved Jinja's debugging support by introducing "@internalcode" which marks code...
authorArmin Ronacher <armin.ronacher@active-4.com>
Tue, 24 Feb 2009 21:58:00 +0000 (22:58 +0100)
committerArmin Ronacher <armin.ronacher@active-4.com>
Tue, 24 Feb 2009 21:58:00 +0000 (22:58 +0100)
--HG--
branch : trunk

examples/basic/debugger.py
examples/basic/templates/broken.html
examples/basic/templates/subbroken.html [new file with mode: 0644]
jinja2/debug.py
jinja2/environment.py
jinja2/exceptions.py
jinja2/loaders.py
jinja2/runtime.py
jinja2/utils.py
tests/test_debug.py

index 52a8be2f187cd8a17af8de4cc971097148a2f8af..4291ff7ac4a9ff480520ca174dae7dd87adc3508 100644 (file)
@@ -4,4 +4,4 @@ from jinja2.loaders import FileSystemLoader
 env = Environment(loader=FileSystemLoader('templates'))
 
 tmpl = env.get_template('broken.html')
-print tmpl.render(seq=range(10))
+print tmpl.render(seq=[3, 2, 4, 5, 3, 2, 0, 2, 1])
index bbd5bf44913e4130fd28cba86e61dca4aa340c6f..294d5c99894d9fca10a044baf0c33f32ac80d32a 100644 (file)
@@ -1,5 +1,6 @@
+{% from 'subbroken.html' import may_break %}
 <ul>
 {% for item in seq %}
-  <li>{{ item / 0 }}</li>
+  <li>{{ may_break(item) }}</li>
 {% endfor %}
 </ul>
diff --git a/examples/basic/templates/subbroken.html b/examples/basic/templates/subbroken.html
new file mode 100644 (file)
index 0000000..245eb7e
--- /dev/null
@@ -0,0 +1,3 @@
+{% macro may_break(item) -%}
+  [{{ item / 0 }}]
+{%- endmacro %}
index bfd00f1037c34b6516b73782a6a5e28be0b11025..d2c5a116468236eb6b4e8cb310a593f627a4e144 100644 (file)
     :license: BSD.
 """
 import sys
-from jinja2.utils import CodeType, missing
+from jinja2.utils import CodeType, missing, internal_code
+
+
+def translate_syntax_error(error, source=None):
+    """Rewrites a syntax error to please traceback systems."""
+    error.source = source
+    error.translated = True
+    exc_info = (type(error), error, None)
+    filename = error.filename
+    if filename is None:
+        filename = '<unknown>'
+    return fake_exc_info(exc_info, filename, error.lineno)
 
 
 def translate_exception(exc_info):
@@ -22,6 +33,14 @@ def translate_exception(exc_info):
     initial_tb = tb = exc_info[2].tb_next
 
     while tb is not None:
+        # skip frames decorated with @internalcode.  These are internal
+        # calls we can't avoid and that are useless in template debugging
+        # output.
+        if tb_set_next is not None and tb.tb_frame.f_code in internal_code:
+            tb_set_next(prev_tb, tb.tb_next)
+            tb = tb.tb_next
+            continue
+
         template = tb.tb_frame.f_globals.get('__jinja_template__')
         if template is not None:
             lineno = template.get_corresponding_lineno(tb.tb_lineno)
@@ -40,19 +59,22 @@ def fake_exc_info(exc_info, filename, lineno, tb_back=None):
     exc_type, exc_value, tb = exc_info
 
     # figure the real context out
-    real_locals = tb.tb_frame.f_locals.copy()
-    ctx = real_locals.get('context')
-    if ctx:
-        locals = ctx.get_all()
+    if tb is not None:
+        real_locals = tb.tb_frame.f_locals.copy()
+        ctx = real_locals.get('context')
+        if ctx:
+            locals = ctx.get_all()
+        else:
+            locals = {}
+        for name, value in real_locals.iteritems():
+            if name.startswith('l_') and value is not missing:
+                locals[name[2:]] = value
+
+        # if there is a local called __jinja_exception__, we get
+        # rid of it to not break the debug functionality.
+        locals.pop('__jinja_exception__', None)
     else:
         locals = {}
-    for name, value in real_locals.iteritems():
-        if name.startswith('l_') and value is not missing:
-            locals[name[2:]] = value
-
-    # if there is a local called __jinja_exception__, we get
-    # rid of it to not break the debug functionality.
-    locals.pop('__jinja_exception__', None)
 
     # assamble fake globals we need
     globals = {
@@ -68,13 +90,16 @@ def fake_exc_info(exc_info, filename, lineno, tb_back=None):
     # if it's possible, change the name of the code.  This won't work
     # on some python environments such as google appengine
     try:
-        function = tb.tb_frame.f_code.co_name
-        if function == 'root':
-            location = 'top-level template code'
-        elif function.startswith('block_'):
-            location = 'block "%s"' % function[6:]
-        else:
+        if tb is None:
             location = 'template'
+        else:
+            function = tb.tb_frame.f_code.co_name
+            if function == 'root':
+                location = 'top-level template code'
+            elif function.startswith('block_'):
+                location = 'block "%s"' % function[6:]
+            else:
+                location = 'template'
         code = CodeType(0, code.co_nlocals, code.co_stacksize,
                         code.co_flags, code.co_code, code.co_consts,
                         code.co_names, code.co_varnames, filename,
index fcc11d2fba6c7e225f7d448ee5564d7f5572611a..c7f311ef97ea4766ca6c5ef8b386a5710cc8dca1 100644 (file)
@@ -18,7 +18,7 @@ from jinja2.compiler import generate
 from jinja2.runtime import Undefined, new_context
 from jinja2.exceptions import TemplateSyntaxError
 from jinja2.utils import import_string, LRUCache, Markup, missing, \
-     concat, consume
+     concat, consume, internalcode
 
 
 # for direct template usage we have up to ten living environments
@@ -339,6 +339,7 @@ class Environment(object):
         except (TypeError, LookupError, AttributeError):
             return self.undefined(obj=obj, name=attribute)
 
+    @internalcode
     def parse(self, source, name=None, filename=None):
         """Parse the sourcecode and return the abstract syntax tree.  This
         tree of nodes is used by the compiler to convert the template into
@@ -353,8 +354,9 @@ class Environment(object):
         try:
             return Parser(self, source, name, filename).parse()
         except TemplateSyntaxError, e:
-            e.source = source
-            raise e
+            from jinja2.debug import translate_syntax_error
+            exc_type, exc_value, tb = translate_syntax_error(e, source)
+            raise exc_type, exc_value, tb
 
     def lex(self, source, name=None, filename=None):
         """Lex the given sourcecode and return a generator that yields
@@ -370,8 +372,9 @@ class Environment(object):
         try:
             return self.lexer.tokeniter(source, name, filename)
         except TemplateSyntaxError, e:
-            e.source = source
-            raise e
+            from jinja2.debug import translate_syntax_error
+            exc_type, exc_value, tb = translate_syntax_error(e, source)
+            raise exc_type, exc_value, tb
 
     def preprocess(self, source, name=None, filename=None):
         """Preprocesses the source with all extensions.  This is automatically
@@ -393,6 +396,7 @@ class Environment(object):
                 stream = TokenStream(stream, name, filename)
         return stream
 
+    @internalcode
     def compile(self, source, name=None, filename=None, raw=False):
         """Compile a node or template source code.  The `name` parameter is
         the load name of the template after it was joined using
@@ -473,6 +477,7 @@ class Environment(object):
         """
         return template
 
+    @internalcode
     def get_template(self, name, parent=None, globals=None):
         """Load a template from the loader.  If a loader is configured this
         method ask the loader for the template and returns a :class:`Template`.
index 8311cf3dddf63dc5d054b245b8b0142f8a539408..182c0619ddc8ce71358d237d179e1cb83414a5a4 100644 (file)
@@ -44,7 +44,16 @@ class TemplateSyntaxError(TemplateError):
         self.filename = filename
         self.source = None
 
+        # this is set to True if the debug.translate_syntax_error
+        # function translated the syntax error into a new traceback
+        self.translated = False
+
     def __unicode__(self):
+        # for translated errors we only return the message
+        if self.translated:
+            return self.message.encode('utf-8')
+
+        # otherwise attach some stuff
         location = 'line %d' % self.lineno
         name = self.filename or self.name
         if name:
index b5817c7514e4a8141d9331af63e0574af4b82bf5..feb4ff7602db8bdde413c789aa174dac54eae8c2 100644 (file)
@@ -14,7 +14,7 @@ try:
 except ImportError:
     from sha import new as sha1
 from jinja2.exceptions import TemplateNotFound
-from jinja2.utils import LRUCache, open_if_exists
+from jinja2.utils import LRUCache, open_if_exists, internalcode
 
 
 def split_template_path(template):
@@ -79,6 +79,7 @@ class BaseLoader(object):
         """
         raise TemplateNotFound(template)
 
+    @internalcode
     def load(self, environment, name, globals=None):
         """Loads a template.  This method looks up the template in the cache
         or loads one by calling :meth:`get_source`.  Subclasses should not
index c2e0aa378df0180643a0418a5d900098a60676fa..669ff2100faf9c8a8b64584c656eef03f5501f6e 100644 (file)
@@ -11,7 +11,7 @@
 import sys
 from itertools import chain, imap
 from jinja2.utils import Markup, partial, soft_unicode, escape, missing, \
-     concat, MethodType, FunctionType
+     concat, MethodType, FunctionType, internalcode
 from jinja2.exceptions import UndefinedError, TemplateRuntimeError, \
      TemplateNotFound
 
@@ -156,6 +156,7 @@ class Context(object):
         """
         return dict(self.parent, **self.vars)
 
+    @internalcode
     def call(__self, __obj, *args, **kwargs):
         """Call the callable with the arguments and keyword arguments
         provided but inject the active context or environment as first
@@ -239,6 +240,7 @@ class BlockReference(object):
         return BlockReference(self.name, self._context, self._stack,
                               self._depth + 1)
 
+    @internalcode
     def __call__(self):
         rv = concat(self._stack[self._depth](self._context))
         if self._context.environment.autoescape:
@@ -281,6 +283,7 @@ class LoopContext(object):
     def __iter__(self):
         return LoopContextIterator(self)
 
+    @internalcode
     def loop(self, iterable):
         if self._recurse is None:
             raise TypeError('Tried to call non recursive loop.  Maybe you '
@@ -342,6 +345,7 @@ class Macro(object):
         self.catch_varargs = catch_varargs
         self.caller = caller
 
+    @internalcode
     def __call__(self, *args, **kwargs):
         arguments = []
         for idx, name in enumerate(self.arguments):
@@ -409,6 +413,7 @@ class Undefined(object):
         self._undefined_name = name
         self._undefined_exception = exc
 
+    @internalcode
     def _fail_with_undefined_error(self, *args, **kwargs):
         """Regular callback function for undefined objects that raises an
         `UndefinedError` on call.
index 1ae38e04a1f43f5d5b97fdc403ae7a1772284fbf..6c3805a001bb3e7d5a820c41fd4cbb72e1db7aa2 100644 (file)
@@ -35,6 +35,9 @@ _digits = '0123456789'
 # special singleton representing missing values for the runtime
 missing = type('MissingType', (), {'__repr__': lambda x: 'missing'})()
 
+# internal code
+internal_code = set()
+
 
 # concatenate a list of strings and convert them to unicode.
 # unfortunately there is a bug in python 2.4 and lower that causes
@@ -120,6 +123,12 @@ def environmentfunction(f):
     return f
 
 
+def internalcode(f):
+    """Marks the function as internally used"""
+    internal_code.add(f.func_code)
+    return f
+
+
 def is_undefined(obj):
     """Check if the object passed is undefined.  This does nothing more than
     performing an instance check against :class:`Undefined` but looks nicer.
index d9c5f7a9c63998c5852954db629eda3c24ebdbeb..aadb9a4c8de9b6f11bf8f207482d4841fc581295 100644 (file)
@@ -31,7 +31,19 @@ test_syntax_error = '''
 >>> tmpl = MODULE.env.get_template('syntaxerror.html')
 Traceback (most recent call last):
   ...
-TemplateSyntaxError: unknown tag 'endif'
   File "loaderres/templates/syntaxerror.html", line 4
     {% endif %}
+TemplateSyntaxError: unknown tag 'endif'
+'''
+
+
+test_regular_syntax_error = '''
+>>> from jinja2.exceptions import TemplateSyntaxError
+>>> raise TemplateSyntaxError('wtf', 42)
+Traceback (most recent call last):
+  ...
+  File "<doctest test_regular_syntax_error[1]>", line 1, in <module>
+    raise TemplateSyntaxError('wtf', 42)
+TemplateSyntaxError: wtf
+  line 42
 '''