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 = '
Genshi error %s
' % 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 = 'Jinja2 error in \'%s\' on line %d
%s
'
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.
'''
lines = open(filename).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] = '%s' % lines[i]
else:
lines = [escape(l, True) for l in lines[:context]]
msg = '%s
'
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]