Jinja2模板编译信息
Jinja2 template compilation information
在mako
template engine there is a handy ModuleInfo
class中:
class ModuleInfo(object):
"""Stores information about a module currently loaded into
memory, provides reverse lookups of template source, module
source code based on a module's identifier.
"""
基本上,它提供了一个模板源代码映射,可以帮助测量模板覆盖率,参见coverage-mako-plugin .
对于 Jinja2,最接近 ModuleInfo
的替代品是什么?
仅供参考:github issue reference。
我问的原因是我正在研究 "coverage-jinja2-plugin",这基本上是起点之一。
Jinja2 在节点本身中提供了所有这些信息,每个节点都知道它所在的行号、它所在的文件等等。我写了一篇关于在 Django 中调试 Jinja2 的博文,它展示了一些示例:http://w.wol.ph/2013/07/28/mixing-django-with-jinja2-without-losing-template-debugging/
我用来获取包含文件和代码的完整堆栈跟踪的代码:
def _generate_django_exception(e, source=None):
'''Generate a Django exception from a Jinja source'''
from django.views.debug import linebreak_iter
if source:
exception = DjangoTemplateSyntaxError(e.message)
exception_dict = e.__dict__
del exception_dict['source']
'''Fetch the entire template in a string'''
template_string = source[0].reload()
'''Get the line number from the error message, if available'''
match = re.match('.* at (\d+)$', e.message)
start_index = 0
stop_index = 0
if match:
'''Convert the position found in the stacktrace to a position
the Django template debug system can use'''
position = int(match.group(1)) + source[1][0] + 1
for index in linebreak_iter(template_string):
if index >= position:
stop_index = min(index, position + 3)
start_index = min(index, position - 2)
break
start_index = index
else:
'''So there wasn't a matching error message, in that case we
simply have to highlight the entire line instead of the specific
words'''
ignore_lines = 0
for i, index in enumerate(linebreak_iter(template_string)):
if source[1][0] > index:
ignore_lines += 1
if i - ignore_lines == e.lineno:
stop_index = index
break
start_index = index
'''Convert the positions to a source that is compatible with the
Django template debugger'''
source = source[0], (
start_index,
stop_index,
)
else:
'''No source available so we let Django fetch it for us'''
lineno = e.lineno - 1
template_string, source = django_loader.find_template_source(e.name)
exception = DjangoTemplateSyntaxError(e.message)
'''Find the positions by the line number given in the exception'''
start_index = 0
for i in range(lineno):
start_index = template_string.index('\n', start_index + 1)
source = source, (
start_index + 1,
template_string.index('\n', start_index + 1) + 1,
)
exception.source = source
return exception
实际模板标签:
class Template(_Jinja2Template):
"""Fixes the incompabilites between Jinja2's template class and
Django's.
The end result should be a class that renders Jinja2 templates but
is compatible with the interface specfied by Django.
This includes flattening a ``Context`` instance passed to render
and making sure that this class will automatically use the global
coffin environment.
"""
def __new__(cls, template_string, origin=None, name=None, source=None):
# We accept the "origin" and "name" arguments, but discard them
# right away - Jinja's Template class (apparently) stores no
# equivalent information.
from coffin.common import env
try:
return env.from_string(template_string, template_class=cls)
except JinjaTemplateSyntaxError, e:
raise _generate_django_exception(e, source)
def __iter__(self):
# TODO: Django allows iterating over the templates nodes. Should
# be parse ourself and iterate over the AST?
raise NotImplementedError()
def render(self, context=None):
"""Differs from Django's own render() slightly in that makes the
``context`` parameter optional. We try to strike a middle ground
here between implementing Django's interface while still supporting
Jinja's own call syntax as well.
"""
if not context:
context = {}
else:
context = dict_from_django_context(context)
try:
return super(Template, self).render(context)
except JinjaTemplateSyntaxError, e:
raise _generate_django_exception(e)
def dict_from_django_context(context):
"""Flattens a Django :class:`django.template.context.Context` object.
"""
if isinstance(context, DjangoContext):
dict_ = {}
# Newest dicts are up front, so update from oldest to newest.
for subcontext in reversed(list(context)):
dict_.update(dict_from_django_context(subcontext))
return dict_
else:
return context
在mako
template engine there is a handy ModuleInfo
class中:
class ModuleInfo(object):
"""Stores information about a module currently loaded into
memory, provides reverse lookups of template source, module
source code based on a module's identifier.
"""
基本上,它提供了一个模板源代码映射,可以帮助测量模板覆盖率,参见coverage-mako-plugin .
对于 Jinja2,最接近 ModuleInfo
的替代品是什么?
仅供参考:github issue reference。
我问的原因是我正在研究 "coverage-jinja2-plugin",这基本上是起点之一。
Jinja2 在节点本身中提供了所有这些信息,每个节点都知道它所在的行号、它所在的文件等等。我写了一篇关于在 Django 中调试 Jinja2 的博文,它展示了一些示例:http://w.wol.ph/2013/07/28/mixing-django-with-jinja2-without-losing-template-debugging/
我用来获取包含文件和代码的完整堆栈跟踪的代码:
def _generate_django_exception(e, source=None):
'''Generate a Django exception from a Jinja source'''
from django.views.debug import linebreak_iter
if source:
exception = DjangoTemplateSyntaxError(e.message)
exception_dict = e.__dict__
del exception_dict['source']
'''Fetch the entire template in a string'''
template_string = source[0].reload()
'''Get the line number from the error message, if available'''
match = re.match('.* at (\d+)$', e.message)
start_index = 0
stop_index = 0
if match:
'''Convert the position found in the stacktrace to a position
the Django template debug system can use'''
position = int(match.group(1)) + source[1][0] + 1
for index in linebreak_iter(template_string):
if index >= position:
stop_index = min(index, position + 3)
start_index = min(index, position - 2)
break
start_index = index
else:
'''So there wasn't a matching error message, in that case we
simply have to highlight the entire line instead of the specific
words'''
ignore_lines = 0
for i, index in enumerate(linebreak_iter(template_string)):
if source[1][0] > index:
ignore_lines += 1
if i - ignore_lines == e.lineno:
stop_index = index
break
start_index = index
'''Convert the positions to a source that is compatible with the
Django template debugger'''
source = source[0], (
start_index,
stop_index,
)
else:
'''No source available so we let Django fetch it for us'''
lineno = e.lineno - 1
template_string, source = django_loader.find_template_source(e.name)
exception = DjangoTemplateSyntaxError(e.message)
'''Find the positions by the line number given in the exception'''
start_index = 0
for i in range(lineno):
start_index = template_string.index('\n', start_index + 1)
source = source, (
start_index + 1,
template_string.index('\n', start_index + 1) + 1,
)
exception.source = source
return exception
实际模板标签:
class Template(_Jinja2Template):
"""Fixes the incompabilites between Jinja2's template class and
Django's.
The end result should be a class that renders Jinja2 templates but
is compatible with the interface specfied by Django.
This includes flattening a ``Context`` instance passed to render
and making sure that this class will automatically use the global
coffin environment.
"""
def __new__(cls, template_string, origin=None, name=None, source=None):
# We accept the "origin" and "name" arguments, but discard them
# right away - Jinja's Template class (apparently) stores no
# equivalent information.
from coffin.common import env
try:
return env.from_string(template_string, template_class=cls)
except JinjaTemplateSyntaxError, e:
raise _generate_django_exception(e, source)
def __iter__(self):
# TODO: Django allows iterating over the templates nodes. Should
# be parse ourself and iterate over the AST?
raise NotImplementedError()
def render(self, context=None):
"""Differs from Django's own render() slightly in that makes the
``context`` parameter optional. We try to strike a middle ground
here between implementing Django's interface while still supporting
Jinja's own call syntax as well.
"""
if not context:
context = {}
else:
context = dict_from_django_context(context)
try:
return super(Template, self).render(context)
except JinjaTemplateSyntaxError, e:
raise _generate_django_exception(e)
def dict_from_django_context(context):
"""Flattens a Django :class:`django.template.context.Context` object.
"""
if isinstance(context, DjangoContext):
dict_ = {}
# Newest dicts are up front, so update from oldest to newest.
for subcontext in reversed(list(context)):
dict_.update(dict_from_django_context(subcontext))
return dict_
else:
return context