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