Skip to content
Permalink
Browse files

core: templating: jinja2: visualize tracebacks

Signed-off-by: Florian Scherf <f.scherf@pengutronix.de>
  • Loading branch information...
fscherf committed Oct 3, 2019
1 parent 9aaa49b commit a96dd25ac43caff9b33161874f8d0e2cdd249d6f
@@ -219,6 +219,9 @@ def run_plugin_hook(self, name, *args, **kwargs):
def render(self, content, template_name=''):
template_name = template_name or content['template']

self.logger.debug('rendering %s using %s', content['path'] or content,
template_name)

if not template_name:
content['template_context'] = {}

@@ -236,8 +239,15 @@ def render(self, content, template_name=''):
}

if self.settings.PRE_RENDER_CONTENT:
content['content_body'] = self.templating_engine.render_string(
content['content_body'], template_context)
self.logger.debug('pre rendering %s', content['path'] or content)

exitcode = self.templating_engine.pre_render_content(
content, template_context)

if not exitcode:
content['template_context'] = template_context

return content['content_body']

output = self.templating_engine.render(template_name, template_context)
content['template_context'] = template_context
@@ -44,9 +44,13 @@
THEME_PATHS = []

DEFAULT_TEMPLATE = 'page.html'

DEFAULT_PAGINATION = 25

JINJA2_TRACEBACKS = True
JINJA2_TRACEBACKS_CONTEXT_LINES = 3
JINJA2_TRACEBACKS_FILTER_PYTHON_CODE = True
JINJA2_TRACEBACKS_PYGMENTS = True

# build
SKIP_FILE_OPERATIONS = False

@@ -1,9 +1,41 @@
import traceback
import datetime
import tempfile
import hashlib
import logging
import code
import html
import os

from jinja2 import Environment, FileSystemLoader, contextfunction
from jinja2 import TemplateNotFound, TemplateSyntaxError

from flamingo.core.templating.base import TemplatingEngine
from flamingo.core.errors import ObjectDoesNotExist
from .base import TemplatingEngine
from flamingo import THEME_ROOT

try:
from pygments.formatters import HtmlFormatter
from pygments.lexers import get_lexer_by_name, get_lexer_for_filename
from pygments.util import ClassNotFound
from pygments import highlight as pygments_highlight

PYGMENTS = True

except ImportError:
PYGMENTS = False

try:
import IPython

IPYTHON = True

except ImportError:
IPYTHON = False

ERROR_TEMPLATE = os.path.join(os.path.dirname(__file__), 'jinja2_error.html')

logger = logging.getLogger('flamingo.core.templating.Jinja2')


def silent_none(value):
@@ -13,6 +45,22 @@ def silent_none(value):
return value


def html_escape(obj):
if not isinstance(obj, str):
obj = str(obj)

return html.escape(obj)


@contextfunction
def _shell(context):
if IPYTHON:
IPython.embed()

else:
code.interact(local=globals())


@contextfunction
def link(context, path, name='', lang=''):
i18n = 'flamingo.plugins.I18N' in context['context'].settings.PLUGINS
@@ -65,22 +113,179 @@ class Jinja2(TemplatingEngine):
def __init__(self, context):
super().__init__(context)

self.contents = {}

# setup tempdir
self.tempdir = tempfile.TemporaryDirectory()

# setup env
template_dirs = [
os.path.join(i, 'templates') for i in self.theme_paths
] + [self.tempdir.name]

self.env = FlamingoEnvironment(
context,
loader=FileSystemLoader(
[os.path.join(i, 'templates') for i in self.theme_paths]),
loader=FileSystemLoader(template_dirs),
finalize=silent_none,
)

self.env.globals['link'] = link
self.env.globals['_shell'] = _shell

# setup error env
if(self.context.settings. LIVE_SERVER_RUNNING and
self.context.settings.JINJA2_TRACEBACKS):

autoescape = not self.context.settings.JINJA2_TRACEBACKS_PYGMENTS

self.error_env = Environment(
loader=FileSystemLoader(
[os.path.join(THEME_ROOT, 'templates/jinja2')]),
autoescape=autoescape,
finalize=silent_none,
)

self.error_env.globals['gen_snippet'] = self.gen_snippet
self.error_env.globals['html_escape'] = html_escape
self.error_env.globals['_shell'] = _shell

def gen_snippet(self, path, lineno, show_linenos=True):
context_lines = self.context.settings.JINJA2_TRACEBACKS_CONTEXT_LINES
index = lineno - 1

lines = open(path, 'r').read().splitlines()

context_lines_top = lines[index-context_lines:index]
context_lines_bottom = lines[index+1:index+context_lines+1]

for line in context_lines_top[::]:
if line:
break

context_lines_top.remove(line)

for line in context_lines_bottom[::-1]:
if line:
break

context_lines_bottom.pop()

snippet = '\n'.join([
*context_lines_top,
lines[index],
*context_lines_bottom,
])

try:
lexer = get_lexer_for_filename(path)

except ClassNotFound:
lexer = get_lexer_by_name('text')

if show_linenos:
formatter = HtmlFormatter(
linenos='inline',
hl_lines=[len(context_lines_top) + 1],
linenostart=lineno-len(context_lines_top),
)

else:
formatter = HtmlFormatter(
hl_lines=[len(context_lines_top) + 1],
)

return pygments_highlight(snippet, lexer, formatter)

def _render_exception(self, exception):
stack = []

for frame in traceback.extract_tb(exception.__traceback__)[::-1]:
filename = frame.filename
content_path = ''

if filename in self.contents:
content_path = self.contents[filename]['path']

stack.append(
(filename, frame.lineno, content_path, )
)

if isinstance(exception, (TemplateSyntaxError, TemplateNotFound, )):
stack = [
i for i in stack
if os.path.splitext(i[0])[1] != '.py'
]

if isinstance(exception, TemplateSyntaxError):
# in case of a TemplateSyntaxError jinja2 adds the current
# template to the traceback

if len(stack) > 1 and stack[0] == stack[-1]:
stack = stack[:-1]

template = self.error_env.get_template('error.html')

return template.render(exception=exception, stack=stack)

def _render(self, template_name, template_context):
if(not self.context.settings.LIVE_SERVER_RUNNING or
not self.context.settings.JINJA2_TRACEBACKS):

return True, self.env.get_template(template_name).render(
**template_context)

exception = None

try:
return True, self.env.get_template(template_name).render(
**template_context)

except Exception as e:
exception = e

try:
return False, self._render_exception(exception)

except Exception as e:
logger.error('Exception occurred while rendering %s',
e, exc_info=True)

raise exception

def render(self, template_name, template_context):
return self.env.get_template(template_name).render(**template_context)
return self._render(template_name, template_context)[1]

def pre_render_content(self, content, template_context):
if not content['content_body'] or '{' not in content['content_body']:
return True, content['content_body']

if(not self.context.settings.LIVE_SERVER_RUNNING or
not self.context.settings.JINJA2_TRACEBACKS):

template = self.env.from_string(content['content_body'])
content['content_body'] = template.render(**template_context)

return True

path = ''

try:
name = '{}{}'.format(
hashlib.md5(str(datetime.datetime.now()).encode()).hexdigest(),
os.path.splitext(content['path'])[1],
)

path = os.path.join(self.tempdir.name, name)
self.contents[path] = content

with open(path, 'w+') as f:
f.write(content['content_body'])

def render_string(self, string, template_context):
if not string or '{' not in string:
return string
exit_code, output = self._render(name, template_context)
content['content_body'] = output

template = self.env.from_string(string)
return exit_code

return template.render(**template_context)
finally:
if path and path in self.contents:
self.contents.pop(path)

0 comments on commit a96dd25

Please sign in to comment.
You can’t perform that action at this time.