291 lines
8.2 KiB
Python
291 lines
8.2 KiB
Python
from .compat import escape
|
|
from .jsonify import encode
|
|
|
|
_builtin_renderers = {}
|
|
error_formatters = []
|
|
|
|
#
|
|
# JSON rendering engine
|
|
#
|
|
|
|
|
|
class JsonRenderer(object):
|
|
'''
|
|
Defines the builtin ``JSON`` renderer.
|
|
'''
|
|
def __init__(self, path, extra_vars):
|
|
pass
|
|
|
|
def render(self, template_path, namespace):
|
|
'''
|
|
Implements ``JSON`` rendering.
|
|
'''
|
|
return encode(namespace)
|
|
|
|
# TODO: add error formatter for json (pass it through json lint?)
|
|
|
|
_builtin_renderers['json'] = JsonRenderer
|
|
|
|
#
|
|
# Genshi rendering engine
|
|
#
|
|
|
|
try:
|
|
from genshi.template import (TemplateLoader,
|
|
TemplateError as gTemplateError)
|
|
|
|
class GenshiRenderer(object):
|
|
'''
|
|
Defines the builtin ``Genshi`` renderer.
|
|
'''
|
|
def __init__(self, path, extra_vars):
|
|
self.loader = TemplateLoader([path], auto_reload=True)
|
|
self.extra_vars = extra_vars
|
|
|
|
def render(self, template_path, namespace):
|
|
'''
|
|
Implements ``Genshi`` rendering.
|
|
'''
|
|
tmpl = self.loader.load(template_path)
|
|
stream = tmpl.generate(**self.extra_vars.make_ns(namespace))
|
|
return stream.render('html')
|
|
|
|
_builtin_renderers['genshi'] = GenshiRenderer
|
|
|
|
def format_genshi_error(exc_value):
|
|
'''
|
|
Implements ``Genshi`` renderer error formatting.
|
|
'''
|
|
if isinstance(exc_value, (gTemplateError)):
|
|
retval = '<h4>Genshi error %s</h4>' % escape(
|
|
exc_value.args[0],
|
|
True
|
|
)
|
|
retval += format_line_context(exc_value.filename, exc_value.lineno)
|
|
return retval
|
|
error_formatters.append(format_genshi_error)
|
|
except ImportError: # pragma no cover
|
|
pass
|
|
|
|
|
|
#
|
|
# Mako rendering engine
|
|
#
|
|
|
|
try:
|
|
from mako.lookup import TemplateLookup
|
|
from mako.exceptions import (CompileException, SyntaxException,
|
|
html_error_template)
|
|
|
|
class MakoRenderer(object):
|
|
'''
|
|
Defines the builtin ``Mako`` renderer.
|
|
'''
|
|
def __init__(self, path, extra_vars):
|
|
self.loader = TemplateLookup(
|
|
directories=[path],
|
|
output_encoding='utf-8'
|
|
)
|
|
self.extra_vars = extra_vars
|
|
|
|
def render(self, template_path, namespace):
|
|
'''
|
|
Implements ``Mako`` rendering.
|
|
'''
|
|
tmpl = self.loader.get_template(template_path)
|
|
return tmpl.render(**self.extra_vars.make_ns(namespace))
|
|
|
|
_builtin_renderers['mako'] = MakoRenderer
|
|
|
|
def format_mako_error(exc_value):
|
|
'''
|
|
Implements ``Mako`` renderer error formatting.
|
|
'''
|
|
if isinstance(exc_value, (CompileException, SyntaxException)):
|
|
return html_error_template().render(full=False, css=False)
|
|
|
|
error_formatters.append(format_mako_error)
|
|
except ImportError: # pragma no cover
|
|
pass
|
|
|
|
|
|
#
|
|
# Kajiki rendering engine
|
|
#
|
|
|
|
try:
|
|
from kajiki.loader import FileLoader
|
|
|
|
class KajikiRenderer(object):
|
|
'''
|
|
Defines the builtin ``Kajiki`` renderer.
|
|
'''
|
|
def __init__(self, path, extra_vars):
|
|
self.loader = FileLoader(path, reload=True)
|
|
self.extra_vars = extra_vars
|
|
|
|
def render(self, template_path, namespace):
|
|
'''
|
|
Implements ``Kajiki`` rendering.
|
|
'''
|
|
Template = self.loader.import_(template_path)
|
|
stream = Template(self.extra_vars.make_ns(namespace))
|
|
return stream.render()
|
|
_builtin_renderers['kajiki'] = KajikiRenderer
|
|
# TODO: add error formatter for kajiki
|
|
except ImportError: # pragma no cover
|
|
pass
|
|
|
|
#
|
|
# Jinja2 rendering engine
|
|
#
|
|
try:
|
|
from jinja2 import Environment, FileSystemLoader
|
|
from jinja2.exceptions import TemplateSyntaxError as jTemplateSyntaxError
|
|
|
|
class JinjaRenderer(object):
|
|
'''
|
|
Defines the builtin ``Jinja`` renderer.
|
|
'''
|
|
def __init__(self, path, extra_vars):
|
|
self.env = Environment(loader=FileSystemLoader(path))
|
|
self.extra_vars = extra_vars
|
|
|
|
def render(self, template_path, namespace):
|
|
'''
|
|
Implements ``Jinja`` rendering.
|
|
'''
|
|
template = self.env.get_template(template_path)
|
|
return template.render(self.extra_vars.make_ns(namespace))
|
|
_builtin_renderers['jinja'] = JinjaRenderer
|
|
|
|
def format_jinja_error(exc_value):
|
|
'''
|
|
Implements ``Jinja`` renderer error formatting.
|
|
'''
|
|
retval = '<h4>Jinja2 error in \'%s\' on line %d</h4><div>%s</div>'
|
|
if isinstance(exc_value, (jTemplateSyntaxError)):
|
|
retval = retval % (
|
|
exc_value.name,
|
|
exc_value.lineno,
|
|
exc_value.message
|
|
)
|
|
retval += format_line_context(exc_value.filename, exc_value.lineno)
|
|
return retval
|
|
error_formatters.append(format_jinja_error)
|
|
except ImportError: # pragma no cover
|
|
pass
|
|
|
|
|
|
#
|
|
# format helper function
|
|
#
|
|
def format_line_context(filename, lineno, context=10):
|
|
'''
|
|
Formats the the line context for error rendering.
|
|
|
|
:param filename: the location of the file, within which the error occurred
|
|
:param lineno: the offending line number
|
|
:param context: number of lines of code to display before and after the
|
|
offending line.
|
|
'''
|
|
with open(filename) as f:
|
|
lines = f.readlines()
|
|
|
|
lineno = lineno - 1 # files are indexed by 1 not 0
|
|
if lineno > 0:
|
|
start_lineno = max(lineno - context, 0)
|
|
end_lineno = lineno + context
|
|
|
|
lines = [escape(l, True) for l in lines[start_lineno:end_lineno]]
|
|
i = lineno - start_lineno
|
|
lines[i] = '<strong>%s</strong>' % lines[i]
|
|
|
|
else:
|
|
lines = [escape(l, True) for l in lines[:context]]
|
|
msg = '<pre style="background-color:#ccc;padding:2em;">%s</pre>'
|
|
return msg % ''.join(lines)
|
|
|
|
|
|
#
|
|
# Extra Vars Rendering
|
|
#
|
|
class ExtraNamespace(object):
|
|
'''
|
|
Extra variables for the template namespace to pass to the renderer as named
|
|
parameters.
|
|
|
|
:param extras: dictionary of extra parameters. Defaults to an empty dict.
|
|
'''
|
|
def __init__(self, extras={}):
|
|
self.namespace = dict(extras)
|
|
|
|
def update(self, d):
|
|
'''
|
|
Updates the extra variable dictionary for the namespace.
|
|
'''
|
|
self.namespace.update(d)
|
|
|
|
def make_ns(self, ns):
|
|
'''
|
|
Returns the `lazily` created template namespace.
|
|
'''
|
|
if self.namespace:
|
|
val = {}
|
|
val.update(self.namespace)
|
|
val.update(ns)
|
|
return val
|
|
else:
|
|
return ns
|
|
|
|
|
|
#
|
|
# Rendering Factory
|
|
#
|
|
class RendererFactory(object):
|
|
'''
|
|
Manufactures known Renderer objects.
|
|
|
|
:param custom_renderers: custom-defined renderers to manufacture
|
|
:param extra_vars: extra vars for the template namespace
|
|
'''
|
|
def __init__(self, custom_renderers={}, extra_vars={}):
|
|
self._renderers = {}
|
|
self._renderer_classes = dict(_builtin_renderers)
|
|
self.add_renderers(custom_renderers)
|
|
self.extra_vars = ExtraNamespace(extra_vars)
|
|
|
|
def add_renderers(self, custom_dict):
|
|
'''
|
|
Adds a custom renderer.
|
|
|
|
:param custom_dict: a dictionary of custom renderers to add
|
|
'''
|
|
self._renderer_classes.update(custom_dict)
|
|
|
|
def available(self, name):
|
|
'''
|
|
Returns true if queried renderer class is available.
|
|
|
|
:param name: renderer name
|
|
'''
|
|
return name in self._renderer_classes
|
|
|
|
def get(self, name, template_path):
|
|
'''
|
|
Returns the renderer object.
|
|
|
|
:param name: name of the requested renderer
|
|
:param template_path: path to the template
|
|
'''
|
|
if name not in self._renderers:
|
|
cls = self._renderer_classes.get(name)
|
|
if cls is None:
|
|
return None
|
|
else:
|
|
self._renderers[name] = cls(template_path, self.extra_vars)
|
|
return self._renderers[name]
|
|
|
|
def keys(self, *args, **kwargs):
|
|
return self._renderer_classes.keys(*args, **kwargs)
|