1from .compat import escape 2from .jsonify import encode 3 4_builtin_renderers = {} 5error_formatters = [] 6 7# 8# JSON rendering engine 9# 10 11 12class JsonRenderer(object): 13 ''' 14 Defines the builtin ``JSON`` renderer. 15 ''' 16 def __init__(self, path, extra_vars): 17 pass 18 19 def render(self, template_path, namespace): 20 ''' 21 Implements ``JSON`` rendering. 22 ''' 23 return encode(namespace) 24 25 # TODO: add error formatter for json (pass it through json lint?) 26 27_builtin_renderers['json'] = JsonRenderer 28 29# 30# Genshi rendering engine 31# 32 33try: 34 from genshi.template import (TemplateLoader, 35 TemplateError as gTemplateError) 36 37 class GenshiRenderer(object): 38 ''' 39 Defines the builtin ``Genshi`` renderer. 40 ''' 41 def __init__(self, path, extra_vars): 42 self.loader = TemplateLoader([path], auto_reload=True) 43 self.extra_vars = extra_vars 44 45 def render(self, template_path, namespace): 46 ''' 47 Implements ``Genshi`` rendering. 48 ''' 49 tmpl = self.loader.load(template_path) 50 stream = tmpl.generate(**self.extra_vars.make_ns(namespace)) 51 return stream.render('html') 52 53 _builtin_renderers['genshi'] = GenshiRenderer 54 55 def format_genshi_error(exc_value): 56 ''' 57 Implements ``Genshi`` renderer error formatting. 58 ''' 59 if isinstance(exc_value, (gTemplateError)): 60 retval = '<h4>Genshi error %s</h4>' % escape( 61 exc_value.args[0], 62 True 63 ) 64 retval += format_line_context(exc_value.filename, exc_value.lineno) 65 return retval 66 error_formatters.append(format_genshi_error) 67except ImportError: # pragma no cover 68 pass 69 70 71# 72# Mako rendering engine 73# 74 75try: 76 from mako.lookup import TemplateLookup 77 from mako.exceptions import (CompileException, SyntaxException, 78 html_error_template) 79 80 class MakoRenderer(object): 81 ''' 82 Defines the builtin ``Mako`` renderer. 83 ''' 84 def __init__(self, path, extra_vars): 85 self.loader = TemplateLookup( 86 directories=[path], 87 output_encoding='utf-8' 88 ) 89 self.extra_vars = extra_vars 90 91 def render(self, template_path, namespace): 92 ''' 93 Implements ``Mako`` rendering. 94 ''' 95 tmpl = self.loader.get_template(template_path) 96 return tmpl.render(**self.extra_vars.make_ns(namespace)) 97 98 _builtin_renderers['mako'] = MakoRenderer 99 100 def format_mako_error(exc_value): 101 ''' 102 Implements ``Mako`` renderer error formatting. 103 ''' 104 if isinstance(exc_value, (CompileException, SyntaxException)): 105 return html_error_template().render(full=False, css=False) 106 107 error_formatters.append(format_mako_error) 108except ImportError: # pragma no cover 109 pass 110 111 112# 113# Kajiki rendering engine 114# 115 116try: 117 from kajiki.loader import FileLoader 118 119 class KajikiRenderer(object): 120 ''' 121 Defines the builtin ``Kajiki`` renderer. 122 ''' 123 def __init__(self, path, extra_vars): 124 self.loader = FileLoader(path, reload=True) 125 self.extra_vars = extra_vars 126 127 def render(self, template_path, namespace): 128 ''' 129 Implements ``Kajiki`` rendering. 130 ''' 131 Template = self.loader.import_(template_path) 132 stream = Template(self.extra_vars.make_ns(namespace)) 133 return stream.render() 134 _builtin_renderers['kajiki'] = KajikiRenderer 135 # TODO: add error formatter for kajiki 136except ImportError: # pragma no cover 137 pass 138 139# 140# Jinja2 rendering engine 141# 142try: 143 from jinja2 import Environment, FileSystemLoader 144 from jinja2.exceptions import TemplateSyntaxError as jTemplateSyntaxError 145 146 class JinjaRenderer(object): 147 ''' 148 Defines the builtin ``Jinja`` renderer. 149 ''' 150 def __init__(self, path, extra_vars): 151 self.env = Environment(loader=FileSystemLoader(path)) 152 self.extra_vars = extra_vars 153 154 def render(self, template_path, namespace): 155 ''' 156 Implements ``Jinja`` rendering. 157 ''' 158 template = self.env.get_template(template_path) 159 return template.render(self.extra_vars.make_ns(namespace)) 160 _builtin_renderers['jinja'] = JinjaRenderer 161 162 def format_jinja_error(exc_value): 163 ''' 164 Implements ``Jinja`` renderer error formatting. 165 ''' 166 retval = '<h4>Jinja2 error in \'%s\' on line %d</h4><div>%s</div>' 167 if isinstance(exc_value, (jTemplateSyntaxError)): 168 retval = retval % ( 169 exc_value.name, 170 exc_value.lineno, 171 exc_value.message 172 ) 173 retval += format_line_context(exc_value.filename, exc_value.lineno) 174 return retval 175 error_formatters.append(format_jinja_error) 176except ImportError: # pragma no cover 177 pass 178 179 180# 181# format helper function 182# 183def format_line_context(filename, lineno, context=10): 184 ''' 185 Formats the the line context for error rendering. 186 187 :param filename: the location of the file, within which the error occurred 188 :param lineno: the offending line number 189 :param context: number of lines of code to display before and after the 190 offending line. 191 ''' 192 with open(filename) as f: 193 lines = f.readlines() 194 195 lineno = lineno - 1 # files are indexed by 1 not 0 196 if lineno > 0: 197 start_lineno = max(lineno - context, 0) 198 end_lineno = lineno + context 199 200 lines = [escape(l, True) for l in lines[start_lineno:end_lineno]] 201 i = lineno - start_lineno 202 lines[i] = '<strong>%s</strong>' % lines[i] 203 204 else: 205 lines = [escape(l, True) for l in lines[:context]] 206 msg = '<pre style="background-color:#ccc;padding:2em;">%s</pre>' 207 return msg % ''.join(lines) 208 209 210# 211# Extra Vars Rendering 212# 213class ExtraNamespace(object): 214 ''' 215 Extra variables for the template namespace to pass to the renderer as named 216 parameters. 217 218 :param extras: dictionary of extra parameters. Defaults to an empty dict. 219 ''' 220 def __init__(self, extras={}): 221 self.namespace = dict(extras) 222 223 def update(self, d): 224 ''' 225 Updates the extra variable dictionary for the namespace. 226 ''' 227 self.namespace.update(d) 228 229 def make_ns(self, ns): 230 ''' 231 Returns the `lazily` created template namespace. 232 ''' 233 if self.namespace: 234 val = {} 235 val.update(self.namespace) 236 val.update(ns) 237 return val 238 else: 239 return ns 240 241 242# 243# Rendering Factory 244# 245class RendererFactory(object): 246 ''' 247 Manufactures known Renderer objects. 248 249 :param custom_renderers: custom-defined renderers to manufacture 250 :param extra_vars: extra vars for the template namespace 251 ''' 252 def __init__(self, custom_renderers={}, extra_vars={}): 253 self._renderers = {} 254 self._renderer_classes = dict(_builtin_renderers) 255 self.add_renderers(custom_renderers) 256 self.extra_vars = ExtraNamespace(extra_vars) 257 258 def add_renderers(self, custom_dict): 259 ''' 260 Adds a custom renderer. 261 262 :param custom_dict: a dictionary of custom renderers to add 263 ''' 264 self._renderer_classes.update(custom_dict) 265 266 def available(self, name): 267 ''' 268 Returns true if queried renderer class is available. 269 270 :param name: renderer name 271 ''' 272 return name in self._renderer_classes 273 274 def get(self, name, template_path): 275 ''' 276 Returns the renderer object. 277 278 :param name: name of the requested renderer 279 :param template_path: path to the template 280 ''' 281 if name not in self._renderers: 282 cls = self._renderer_classes.get(name) 283 if cls is None: 284 return None 285 else: 286 self._renderers[name] = cls(template_path, self.extra_vars) 287 return self._renderers[name] 288 289 def keys(self, *args, **kwargs): 290 return self._renderer_classes.keys(*args, **kwargs) 291