:copyright: 2007 by Armin Ronacher.
:license: BSD, see LICENSE for more details.
"""
+import sys
from compiler import ast
from jinja import nodes
from jinja.nodes import get_nodes
from jinja.parser import Parser
from jinja.exceptions import TemplateSyntaxError
from jinja.translators import Translator
+from jinja.utils import translate_exception
def _to_tuple(args):
Represents a finished template.
"""
- def __init__(self, environment, code):
+ def __init__(self, environment, code, translated_source=None):
self.environment = environment
self.code = code
+ self.translated_source = translated_source
self.generate_func = None
+ def source(self):
+ """The original sourcecode for this template."""
+ return self.environment.loader.get_source(self.code.co_filename)
+ source = property(source, doc=source.__doc__)
+
def dump(self, stream=None):
"""Dump the template into python bytecode."""
if stream is not None:
from marshal import dump
- dump(self.code, stream)
+ dump((self.code, self.translated_source), stream)
else:
from marshal import dumps
- return dumps(self.code)
+ return dumps((self.code, self.translated_source))
def load(environment, data):
"""Load the template from python bytecode."""
if isinstance(data, basestring):
from marshal import loads
- code = loads(data)
+ code, src = loads(data)
else:
from marshal import load
- code = load(data)
- return Template(environment, code)
+ code, src = load(data)
+ return Template(environment, code, src)
load = staticmethod(load)
def render(self, *args, **kwargs):
"""Render a template."""
if self.generate_func is None:
- ns = {}
+ ns = {'environment': self.environment}
exec self.code in ns
self.generate_func = ns['generate']
ctx = self.environment.context_class(self.environment, *args, **kwargs)
- return u''.join(self.generate_func(ctx))
+ try:
+ return u''.join(self.generate_func(ctx))
+ except:
+ exc_type, exc_value, traceback = sys.exc_info()
+ traceback = translate_exception(self, exc_type,
+ exc_value, traceback.tb_next,
+ ctx)
+ raise exc_type, exc_value, traceback
class PythonTranslator(Translator):
def process(environment, node):
translator = PythonTranslator(environment, node)
filename = node.filename or '<template>'
+ source = translator.translate()
return Template(environment,
- compile(translator.translate(), filename, 'exec'))
+ compile(source, filename, 'exec'),
+ source)
process = staticmethod(process)
# -- private helper methods
"""
return (' ' * (self.indention * 4)) + text
+ def nodeinfo(self, node):
+ """
+ Return a comment that helds the node informations.
+ """
+ return '# DEBUG(filename=%r, lineno=%s)' % (
+ node.filename,
+ node.lineno
+ )
+
def filter(self, s, filter_nodes):
"""
Apply a filter on an object that already is a python expression.
requirements_todo.append(node)
# load the template we inherit from and add not known blocks
- # to the block registry, make this template the new root.
parent = self.environment.loader.parse(node.extends.template,
node.filename)
-
+ # look up all block nodes and let them override each other
overwrites = {}
for n in get_nodes(nodes.Block, node):
overwrites[n.name] = n
for n in get_nodes(nodes.Block, parent):
if n.name in overwrites:
n.replace(overwrites[n.name])
-
+ # make the parent node the new node
node = parent
# look up requirements
lines = [
'from __future__ import division\n'
'from jinja.datastructure import Undefined, LoopContext, CycleContext\n'
- 'from jinja.utils import buffereater\n\n'
+ 'from jinja.utils import buffereater\n'
+ '__name__ = %r\n\n'
'def generate(context):\n'
- ' # BOOTSTRAPPING CODE\n'
- ' environment = context.environment\n'
+ ' assert environment is context.environment\n'
' get_attribute = environment.get_attribute\n'
' perform_test = environment.perform_test\n'
' apply_filters = environment.apply_filters\n'
' call_function_simple = environment.call_function_simple\n'
' finish_var = environment.finish_var\n'
' ctx_push = context.push\n'
- ' ctx_pop = context.pop\n'
+ ' ctx_pop = context.pop\n' % node.filename
]
# we have requirements? add them here.
if requirements:
- lines.append(self.indent('# REQUIREMENTS'))
for n in requirements:
lines.append(self.handle_node(n))
- lines.append(self.indent('# END OF REQUIREMENTS'))
# the template body
rv = self.handle_node_list(node)
"""
Handle data around nodes.
"""
- return self.indent('yield %r' % node.text)
+ return self.indent(self.nodeinfo(node)) + '\n' + \
+ self.indent('yield %r' % node.text)
def handle_node_list(self, node):
"""
In some situations we might have a node list. It's just
a collection of multiple statements.
"""
- buf = []
- for n in node:
- buf.append(self.handle_node(n))
- return '\n'.join(buf)
+ buf = [self.handle_node(n) for n in node]
+ if buf:
+ return '\n'.join([self.indent(self.nodeinfo(node))] + buf)
+ return ''
def handle_for_loop(self, node):
"""
"""
buf = []
write = lambda x: buf.append(self.indent(x))
+ write(self.nodeinfo(node))
write('ctx_push()')
# recursive loops
# handle real loop code
self.indention += 1
+ buf.append(self.indent(self.nodeinfo(node.body)))
buf.append(self.handle_node(node.body))
self.indention -= 1
if node.else_:
write('if not context[\'loop\'].iterated:')
self.indention += 1
+ buf.append(self.indent(self.nodeinfo(node.else_)))
buf.append(self.handle_node(node.else_))
self.indention -= 1
"""
buf = []
write = lambda x: buf.append(self.indent(x))
+ write(self.nodeinfo(node))
for idx, (test, body) in enumerate(node.tests):
write('%sif %s:' % (
idx and 'el' or '',
self.handle_node(test)
))
self.indention += 1
+ write(self.nodeinfo(node))
buf.append(self.handle_node(body))
self.indention -= 1
if node.else_ is not None:
write('else:')
self.indention += 1
+ write(self.nodeinfo(node))
buf.append(self.handle_node(node.else_))
self.indention -= 1
return '\n'.join(buf)
write('if not %r in context.current:' % name)
self.indention += 1
+ write(self.nodeinfo(node))
if node.seq.__class__ in (ast.Tuple, ast.List):
write('context.current[%r] = CycleContext(%s)' % (
name,
"""
Handle a print statement.
"""
- return self.indent('yield finish_var(%s)' % self.handle_node(node.variable))
+ return self.indent(self.nodeinfo(node)) + '\n' + \
+ self.indent('yield finish_var(%s)' %
+ self.handle_node(node.variable))
def handle_macro(self, node):
"""
write('def macro(*args):')
self.indention += 1
+ write(self.nodeinfo(node))
if node.arguments:
write('argcount = len(args)')
else:
write('ctx_push()')
+ write(self.nodeinfo(node.body))
buf.append(self.handle_node(node.body))
write('ctx_pop()')
write('if False:')
"""
Handle variable assignments.
"""
- return self.indent('context[%r] = %s' % (
+ return self.indent(self.nodeinfo(node)) + '\n' + \
+ self.indent('context[%r] = %s' % (
node.name,
self.handle_node(node.expr)
))
write = lambda x: buf.append(self.indent(x))
write('def filtered():')
self.indention += 1
+ write(self.nodeinfo(node))
write('ctx_push()')
+ write(self.nodeinfo(node.body))
buf.append(self.handle_node(node.body))
write('ctx_pop()')
write('if False:')
"""
rv = self.handle_node(node.body)
if not rv:
- return self.indent('# EMPTY BLOCK "%s" FROM %r, LINE %s' % (
- node.name,
- node.filename or '?',
- node.lineno
- ))
+ return
buf = []
write = lambda x: buf.append(self.indent(x))
- write('# BLOCK "%s" FROM %r, LINE %s' % (
- node.name,
- node.filename or '?',
- node.lineno
- ))
write('ctx_push()')
+ write(self.nodeinfo(node.body))
buf.append(self.handle_node(node.body))
write('ctx_pop()')
- buf.append(self.indent('# END OF BLOCK'))
return '\n'.join(buf)
def handle_include(self, node):
"""
Include another template at the current position.
"""
- buf = [self.indent('# INCLUDED TEMPLATE %r' % node.filename)]
tmpl = self.environment.loader.parse(node.template,
node.filename)
- buf.append(self.handle_node_list(tmpl))
- buf.append(self.indent('# END OF INCLUSION'))
- return '\n'.join(buf)
+ return self.handle_node_list(tmpl)
def handle_trans(self, node):
"""
replacements = '{%s}' % ', '.join(replacements)
else:
replacements = 'None'
- return self.indent('yield translate(%r, %r, %r, %s)' % (
+ return self.indent(self.nodeinfo(node)) + '\n' + \
+ self.indent('yield translate(%r, %r, %r, %s)' % (
node.singular,
node.plural,
node.indicator,
:license: BSD, see LICENSE for more details.
"""
import re
+import sys
from types import MethodType, FunctionType
from jinja.nodes import Trans
from jinja.datastructure import Markup
except ImportError:
deque = None
+_debug_info_re = re.compile(r'^\s*\# DEBUG\(filename=(.*?), lineno=(.*?)\)$')
_escape_pairs = {
'&': '&',
return wrapped
+def raise_template_exception(template, exception, filename, lineno, context):
+ """
+ Raise an exception "in a template". Return a traceback
+ object.
+ """
+ offset = '\n'.join([''] * lineno)
+ code = compile(offset + 'raise __exception_to_raise__', filename, 'exec')
+ namespace = context.to_dict()
+ globals = {
+ '__name__': filename,
+ '__file__': filename,
+ '__loader__': TracebackLoader(template),
+ '__exception_to_raise__': exception
+ }
+ try:
+ exec code in globals, namespace
+ except:
+ traceback = sys.exc_info()[2]
+ return traceback
+
+
+def translate_exception(template, exc_type, exc_value, traceback, context):
+ """
+ Translate an exception and return the new traceback.
+ """
+ sourcelines = template.translated_source.splitlines()
+ startpos = traceback.tb_lineno - 1
+ args = None
+ # looks like we loaded the template from string. we cannot
+ # do anything here.
+ if startpos > len(sourcelines):
+ print startpos, len(sourcelines)
+ return traceback
+
+ while startpos > 0:
+ m = _debug_info_re.search(sourcelines[startpos])
+ if m is not None:
+ args = m.groups()
+ break
+ startpos -= 1
+
+ # no traceback information found, reraise unchanged
+ if args is None:
+ return traceback
+ return raise_template_exception(template, exc_value, args[0],
+ int(args[1] or 0), context)
+
+
+class TracebackLoader(object):
+ """
+ Fake importer that just returns the source of a template.
+ """
+
+ def __init__(self, template):
+ self.template = template
+
+ def get_source(self, impname):
+ return self.template.source
+
+
class CacheDict(object):
"""
A dict like object that stores a limited number of items and forgets