Jinja Changelog
===============
+Version 1.2
+-----------
+(codename to be selected, release date unknown)
+
+- environments now have a `translator_factory` parameter that allows
+ to change the translator without subclassing the environment.
+
+- fixed bug in buffet plugin regarding the package loader
+
+- once again improved debugger.
+
+
Version 1.1
-----------
(codename: sinka, released Jun 1, 2007)
tmpl = env.get_template('index.html')
tmpl.render(LANGUAGE='de_DE')
-This example assumes that you use gettext and have a gettext
-`Translations` object which is returned by the `get_translator`
-function.
+This example assumes that you use gettext and have a gettext `Translations`
+object which is returned by the `get_translator` function. But you don't
+have to use gettext. The only thing Jinja requires is an object with to
+functions/methods on it that return and accept unicode strings:
+``gettext(string)`` that takes a string, translates and returns it, a
+``ngettext(singular, plural, count)`` function that returns the correct plural
+form for `count` items. Because some languages have no or multiple plural
+forms this is necessary.
+
+
+Translator Factory
+==================
+
+With Jinja 1.2 onwards it's possible to use a translator factory
+instead of an enviornment subclass to create a translator for a context.
+A translator factory is passed a context and has to return a translator.
+Because of the way classes work you can also assign a translator class
+that takes a context object as only argument as factory.
+
+Example:
+
+.. sourcecode:: python
+
+ from jinja import Environment
+ from myapplication import get_translator
+
+ def translator_factory(context):
+ return get_translator(context['LANGUAGE'])
+
+ env = ApplicationEnvironment(translator_factory=translator_factory)
+ tmpl = env.get_template('index.html')
+ tmpl.render(LANGUAGE='de_DE')
+
+This example assumes that the translator returned by `get_translator`
+already has a gettext and ngettext function that returns unicode strings.
Collecting Translations
.. sourcecode:: pycon
>>> env.get_translations('index.html')
- [(1, u'foo', None), (2, u'Foo', None), (3, u'%d Foo', u'%d Foos')]
+ [(1, u'foo', None), (2, u'Foo', None), (3, u'%(count)s Foo', u'%(count)s Foos')]
The first item in the tuple is the linenumer, the second one is the
singular form and the third is the plural form if given.
--- /dev/null
+/**
+ * Jinja Extended Debugger
+ * ~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * this module allows the jinja debugger to set the tb_next flag
+ * on traceback objects. This is required to inject a traceback into
+ * another one.
+ *
+ * :copyright: 2007 by Armin Ronacher.
+ * :license: BSD, see LICENSE for more details.
+ */
+
+#include <Python.h>
+
+
+/**
+ * set the tb_next attribute of a traceback object
+ */
+PyObject*
+tb_set_next(PyObject *self, PyObject *args)
+{
+ PyObject *tb, *next;
+
+ if (!PyArg_ParseTuple(args, "OO", &tb, &next))
+ return NULL;
+ if (!(PyTraceBack_Check(tb) && (PyTraceBack_Check(next) || next == Py_None))) {
+ PyErr_SetString(PyExc_TypeError, "traceback object required.");
+ return NULL;
+ }
+
+ ((PyTracebackObject*)tb)->tb_next = next;
+
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+
+static PyMethodDef module_methods[] = {
+ {"tb_set_next", (PyCFunction)tb_set_next, METH_VARARGS,
+ "Set the tb_next member of a traceback object."},
+ {NULL, NULL, 0, NULL} /* Sentinel */
+};
+
+
+#ifndef PyMODINIT_FUNC /* declarations for DLL import/export */
+#define PyMODINIT_FUNC void
+#endif
+PyMODINIT_FUNC
+init_debugger(void)
+{
+ PyObject *module = Py_InitModule3("_debugger", module_methods, "");
+ if (!module)
+ return;
+}
--- /dev/null
+# -*- coding: utf-8 -*-
+"""
+ jinja.debugger
+ ~~~~~~~~~~~~~~
+
+ The debugger module of awesomeness.
+
+ :copyright: 2007 by Armin Ronacher.
+ :license: BSD, see LICENSE for more details.
+"""
+
+import sys
+from random import randrange
+from opcode import opmap
+from types import CodeType
+
+# if we have extended debugger support we should really use it
+try:
+ from jinja._debugger import *
+ has_extended_debugger = True
+except ImportError:
+ has_extended_debugger = False
+
+# we need the RUNTIME_EXCEPTION_OFFSET to skip the not used frames
+from jinja.utils import RUNTIME_EXCEPTION_OFFSET
+
+
+def fake_template_exception(exc_type, exc_value, traceback, filename, lineno,
+ source, context_or_env):
+ """
+ Raise an exception "in a template". Return a traceback
+ object. This is used for runtime debugging, not compile time.
+ """
+ # some traceback systems allow to skip frames
+ __traceback_hide__ = True
+
+ # create the namespace which will be the local namespace
+ # of the new frame then. Some debuggers show local variables
+ # so we better inject the context and not the evaluation loop context.
+ from jinja.datastructure import Context
+ if isinstance(context_or_env, Context):
+ env = context_or_env.environment
+ namespace = context_or_env.to_dict()
+ else:
+ env = context_or_env
+ namespace = {}
+
+ # no unicode for filenames
+ if isinstance(filename, unicode):
+ filename = filename.encode('utf-8')
+
+ # generate an jinja unique filename used so that linecache
+ # gets data that doesn't interferes with other modules
+ if filename is None:
+ vfilename = 'jinja://~%d' % randrange(0, 10000)
+ filename = '<string>'
+ else:
+ vfilename = 'jinja://%s' % filename
+
+ # now create the used loaded and update the linecache
+ loader = TracebackLoader(env, source, filename)
+ loader.update_linecache(vfilename)
+ globals = {
+ '__name__': vfilename,
+ '__file__': vfilename,
+ '__loader__': loader
+ }
+
+ # use the simple debugger to reraise the exception in the
+ # line where the error originally occoured
+ globals['__exception_to_raise__'] = (exc_type, exc_value)
+ offset = '\n' * (lineno - 1)
+ code = compile(offset + 'raise __exception_to_raise__[0], '
+ '__exception_to_raise__[1]',
+ vfilename or '<template>', 'exec')
+ try:
+ exec code in globals, namespace
+ except:
+ exc_info = sys.exc_info()
+
+ # if we have an extended debugger we set the tb_next flag
+ if has_extended_debugger and traceback is not None:
+ tb_set_next(exc_info[2].tb_next, traceback.tb_next)
+
+ # otherwise just return the exc_info from the simple debugger
+ return exc_info
+
+
+def translate_exception(template, context, exc_type, exc_value, traceback):
+ """
+ Translate an exception and return the new traceback.
+ """
+ # depending on the python version we have to skip some frames to
+ # step to get the frame of the current template. The frames before
+ # are the toolchain used to render that thing.
+ for x in xrange(RUNTIME_EXCEPTION_OFFSET):
+ traceback = traceback.tb_next
+
+ # the next thing we do is matching the current error line against the
+ # debugging table to get the correct source line. If we can't find the
+ # filename and line number we return the traceback object unaltered.
+ error_line = traceback.tb_lineno
+ for code_line, tmpl_filename, tmpl_line in template._debug_info[::-1]:
+ if code_line <= error_line:
+ break
+ else:
+ return traceback
+
+ return fake_template_exception(exc_type, exc_value, traceback,
+ tmpl_filename, tmpl_line,
+ template._source, context)
+
+
+def raise_syntax_error(exception, env, source=None):
+ """
+ This method raises an exception that includes more debugging
+ informations so that debugging works better. Unlike
+ `translate_exception` this method raises the exception with
+ the traceback.
+ """
+ exc_info = fake_template_exception(exception, None, None,
+ exception.filename,
+ exception.lineno, source, env)
+ raise exc_info[0], exc_info[1], exc_info[2]
+
+
+class TracebackLoader(object):
+ """
+ Fake importer that just returns the source of a template.
+ """
+
+ def __init__(self, environment, source, filename):
+ self.loader = environment.loader
+ self.source = source
+ self.filename = filename
+
+ def update_linecache(self, virtual_filename):
+ """
+ Hacky way to let traceback systems know about the
+ Jinja template sourcecode. Very hackish indeed.
+ """
+ # check for linecache, not every implementation of python
+ # might have such an module.
+ try:
+ from linecache import cache
+ except ImportError:
+ return
+ data = self.get_source(None)
+ cache[virtual_filename] = (
+ len(data),
+ None,
+ data.splitlines(True),
+ virtual_filename
+ )
+
+ def get_source(self, impname):
+ source = ''
+ if self.source is not None:
+ source = self.source
+ elif self.loader is not None:
+ try:
+ source = self.loader.get_source(self.filename)
+ except TemplateNotFound:
+ pass
+ if isinstance(source, unicode):
+ source = source.encode('utf-8')
+ return source
tests=None,
context_class=Context,
undefined_singleton=SilentUndefined,
- friendly_traceback=True):
+ friendly_traceback=True,
+ translator_factory=None):
"""
Here the possible initialization parameters:
This however can be annoying when debugging
broken functions that are called from the
template. *new in Jinja 1.1*
+ `translator_factory` A callback function that is called with
+ the context as first argument to get the
+ translator for the current instance.
+ *new in Jinja 1.2*
========================= ============================================
All of these variables except those marked with a star (*) are
self.default_filters.append(('escape', (True,)))
self.globals['Markup'] = Markup
+ # and here the translator factory
+ self.translator_factory = translator_factory
+
# create lexer
self.lexer = Lexer(self)
# on syntax errors rewrite the traceback if wanted
if not self.friendly_traceback:
raise
- from jinja.utils import raise_syntax_error
+ from jinja.debugger import raise_syntax_error
__traceback_hide__ = True
raise_syntax_error(e, self, source)
else:
``gettext(string)`` and ``ngettext(singular, plural, n)``. Note
that both of them have to return unicode!
"""
+ if self.translator_factory is not None:
+ return self.translator_factory(context)
return FakeTranslator()
def get_translations(self, name):
"""
Apply a list of filters on the variable.
"""
+ # some traceback systems allow to skip frames. but allow
+ # disabling that via -O to not make things slow
+ if __debug__:
+ __traceback_hide__ = True
+
cache = context.cache
for key in filters:
if key in cache:
"""
Perform a test on a variable.
"""
+ # some traceback systems allow to skip frames. but allow
+ # disabling that via -O to not make things slow
+ if __debug__:
+ __traceback_hide__ = True
+
key = (testname, args)
if key in context.cache:
func = context.cache[key]
"""
Get one attribute from an object.
"""
+ # some traceback systems allow to skip frames. but allow
+ # disabling that via -O to not make things slow
+ if __debug__:
+ __traceback_hide__ = True
+
try:
return obj[name]
except (TypeError, KeyError, IndexError, AttributeError):
Function call helper. Called for all functions that are passed
any arguments.
"""
+ # some traceback systems allow to skip frames. but allow
+ # disabling that via -O to not make things slow
+ if __debug__:
+ __traceback_hide__ = True
+
if dyn_args is not None:
args += tuple(dyn_args)
if dyn_kwargs is not None:
Function call without arguments. Because of the smaller signature and
fewer logic here we have a bit of redundant code.
"""
+ # some traceback systems allow to skip frames. but allow
+ # disabling that via -O to not make things slow
+ if __debug__:
+ __traceback_hide__ = True
+
if _getattr(f, 'jinja_unsafe_call', False) or \
_getattr(f, 'alters_data', False):
return self.undefined_singleton
evaluator the source generated by the python translator will
call this function for all variables.
"""
+ # some traceback systems allow to skip frames. but allow
+ # disabling that via -O to not make things slow
+ if __debug__:
+ __traceback_hide__ = True
+
if value is None:
return u''
elif value is self.undefined_singleton:
from jinja.parser import Parser
from jinja.translators.python import PythonTranslator, Template
from jinja.exceptions import TemplateNotFound, TemplateSyntaxError
-from jinja.utils import CacheDict, raise_syntax_error
+from jinja.utils import CacheDict
#: when updating this, update the listing in the jinja package too
if not self.environment.friendly_traceback:
raise
__traceback_hide__ = True
+ from jinja.debugger import raise_syntax_error
raise_syntax_error(e, self.environment)
def _loader_missing(self, *args, **kwargs):
from jinja.exceptions import TemplateSyntaxError
from jinja.translators import Translator
from jinja.datastructure import TemplateStream
-from jinja.utils import set, translate_exception, capture_generator, \
- RUNTIME_EXCEPTION_OFFSET
+from jinja.utils import set, capture_generator
#: regular expression for the debug symbols
def _debug(self, ctx, exc_type, exc_value, traceback):
"""Debugging Helper"""
# just modify traceback if we have that feature enabled
+ from traceback import print_exception
+ print_exception(exc_type, exc_value, traceback)
+
if self.environment.friendly_traceback:
- # debugging system:
- # on any exception we first skip the internal frames (currently
- # either one (python2.5) or two (python2.4 and lower)). After that
- # we call a function that creates a new traceback that is easier
- # to debug.
- for _ in xrange(RUNTIME_EXCEPTION_OFFSET):
- traceback = traceback.tb_next
- traceback = translate_exception(self, exc_type, exc_value,
- traceback, ctx)
+ # hook the debugger in
+ from jinja.debugger import translate_exception
+ exc_type, exc_value, traceback = translate_exception(
+ self, ctx, exc_type, exc_value, traceback)
+ print_exception(exc_type, exc_value, traceback)
+
raise exc_type, exc_value, traceback
rv.reverse()
return rv
+# if we have extended debugger support we should really use it
+try:
+ from jinja._tbtools import *
+ has_extended_debugger = True
+except ImportError:
+ has_extended_debugger = False
+
#: function types
callable_types = (FunctionType, MethodType)
if 0: yield None
-def fake_template_exception(exception, filename, lineno, source,
- context_or_env):
- """
- Raise an exception "in a template". Return a traceback
- object. This is used for runtime debugging, not compile time.
- """
- # some traceback systems allow to skip frames
- __traceback_hide__ = True
-
- from jinja.datastructure import Context
- if isinstance(context_or_env, Context):
- env = context_or_env.environment
- namespace = context_or_env.to_dict()
- else:
- env = context_or_env
- namespace = {}
-
- # generate an jinja unique filename used so that linecache
- # gets data that doesn't interferes with other modules
- if filename is None:
- from random import randrange
- vfilename = 'jinja://~%d' % randrange(0, 10000)
- filename = '<string>'
- else:
- vfilename = 'jinja://%s' % filename
-
- offset = '\n' * (lineno - 1)
- code = compile(offset + 'raise __exception_to_raise__',
- vfilename or '<template>', 'exec')
-
- loader = TracebackLoader(env, source, filename)
- loader.update_linecache(vfilename)
- globals = {
- '__name__': vfilename,
- '__file__': vfilename,
- '__loader__': loader,
- '__exception_to_raise__': exception
- }
- try:
- exec code in globals, namespace
- except:
- return sys.exc_info()
-
-
-def translate_exception(template, exc_type, exc_value, traceback, context):
- """
- Translate an exception and return the new traceback.
- """
- error_line = traceback.tb_lineno
- for code_line, tmpl_filename, tmpl_line in template._debug_info[::-1]:
- if code_line <= error_line:
- break
- else:
- # no debug symbol found. give up
- return traceback
-
- return fake_template_exception(exc_value, tmpl_filename, tmpl_line,
- template._source, context)[2]
-
-
-def raise_syntax_error(exception, env, source=None):
- """
- This method raises an exception that includes more debugging
- informations so that debugging works better. Unlike
- `translate_exception` this method raises the exception with
- the traceback.
- """
- exc_info = fake_template_exception(exception, exception.filename,
- exception.lineno, source, env)
- raise exc_info[0], exc_info[1], exc_info[2]
-
-
def collect_translations(ast):
"""
Collect all translatable strings for the given ast. The
elif node.__class__ is CallFunc and \
node.node.__class__ is Name and \
node.node.name == '_':
- if len(node.args) in (1, 3):
- args = []
- for arg in node.args:
- if not arg.__class__ is Const:
- break
- args.append(arg.value)
- else:
- if len(args) == 1:
- singular = args[0]
- plural = None
- else:
- singular, plural, _ = args
- result.append((node.lineno, singular, plural))
+ if len(node.args) == 1 and node.args[0].__class__ is Const:
+ result.append((node.lineno, node.args[0].value, None))
todo.extend(node.getChildNodes())
result.sort(lambda a, b: cmp(a[0], b[0]))
return result
debug_helper = object.__new__(DebugHelper)
-class TracebackLoader(object):
- """
- Fake importer that just returns the source of a template.
- """
-
- def __init__(self, environment, source, filename):
- self.loader = environment.loader
- self.source = source
- self.filename = filename
-
- def update_linecache(self, virtual_filename):
- """
- Hacky way to let traceback systems know about the
- Jinja template sourcecode. Very hackish indeed.
- """
- # check for linecache, not every implementation of python
- # might have such an module.
- try:
- from linecache import cache
- except ImportError:
- return
- data = self.get_source(None)
- cache[virtual_filename] = (
- len(data),
- None,
- data.splitlines(True),
- virtual_filename
- )
-
- def get_source(self, impname):
- source = ''
- if self.source is not None:
- source = self.source
- elif self.loader is not None:
- try:
- source = self.loader.get_source(self.filename)
- except TemplateNotFound:
- pass
- if isinstance(source, unicode):
- source = source.encode('utf-8')
- return source
-
-
class CacheDict(object):
"""
A dict like object that stores a limited number of items and forgets
jinja = jinja.plugin:BuffetPlugin
''',
extras_require = {'plugin': ['setuptools>=0.6a2']},
- features = {'speedups': Feature(
- 'optional C-speed enhancements',
- standard = True,
- ext_modules = [
- Extension('jinja._speedups', ['jinja/_speedups.c'])
- ]
- )},
+ features = {
+ 'speedups': Feature(
+ 'optional C-speed enhancements',
+ standard = True,
+ ext_modules = [
+ Extension('jinja._speedups', ['jinja/_speedups.c'])
+ ]
+ ),
+ 'extended-debugger': Feature(
+ 'extended debugger',
+ standard = True,
+ ext_modules = [
+ Extension('jinja._debugger', ['jinja/_debugger.c'])
+ ]
+ )
+ },
cmdclass = {'build_ext': optional_build_ext}
)
<li><a href="runtime_error">runtime error</a></li>
<li><a href="nested_syntax_error">nested syntax error</a></li>
<li><a href="nested_runtime_error">nested runtime error</a></li>
+ <li><a href="code_runtime_error">runtime error in code</a></li>
<li><a href="syntax_from_string">a syntax error from string</a></li>
<li><a href="runtime_from_string">runtime error from a string</a></li>
</ul>
{% include 'syntax_broken' %}
''',
+ '/code_runtime_error': u'''
+{{ broken() }}
+''',
+
'runtime_broken': '''\
This is an included template
{% set a = 1 / 0 %}''',
BROKEN_STRING_TEMPLATE = '{% if foo %}...{% endfor %}'
+def broken():
+ raise RuntimeError("I'm broken")
+
+
def test(environ, start_response):
path = environ.get('PATH_INFO' or '/')
try:
start_response('404 NOT FOUND', [('Content-Type', 'text/plain')])
return ['NOT FOUND']
start_response('200 OK', [('Content-Type', 'text/html; charset=utf-8')])
- return [tmpl.render().encode('utf-8')]
+ return [tmpl.render(broken=broken).encode('utf-8')]
+
if __name__ == '__main__':
- from colubrid.debug import DebuggedApplication
+ from werkzeug.debug import DebuggedApplication
app = DebuggedApplication(test)
make_server("localhost", 7000, app).serve_forever()
i18n_env = I18NEnvironment()
+def test_factory():
+ def factory(context):
+ return SimpleTranslator(context['LANGUAGE'] or 'en')
+ env = Environment(translator_factory=factory)
+ tmpl = env.from_string('{% trans "watch out" %}')
+ assert tmpl.render(LANGUAGE='de') == 'pass auf'
+
+
def test_get_translations():
trans = list(i18n_env.get_translations('child.html'))
assert len(trans) == 1