OLD | NEW |
(Empty) | |
| 1 # coding: utf-8 |
| 2 |
| 3 """ |
| 4 This module provides a Renderer class to render templates. |
| 5 |
| 6 """ |
| 7 |
| 8 import sys |
| 9 |
| 10 from pystache import defaults |
| 11 from pystache.common import TemplateNotFoundError, MissingTags, is_string |
| 12 from pystache.context import ContextStack, KeyNotFoundError |
| 13 from pystache.loader import Loader |
| 14 from pystache.parsed import ParsedTemplate |
| 15 from pystache.renderengine import context_get, RenderEngine |
| 16 from pystache.specloader import SpecLoader |
| 17 from pystache.template_spec import TemplateSpec |
| 18 |
| 19 |
| 20 class Renderer(object): |
| 21 |
| 22 """ |
| 23 A class for rendering mustache templates. |
| 24 |
| 25 This class supports several rendering options which are described in |
| 26 the constructor's docstring. Other behavior can be customized by |
| 27 subclassing this class. |
| 28 |
| 29 For example, one can pass a string-string dictionary to the constructor |
| 30 to bypass loading partials from the file system: |
| 31 |
| 32 >>> partials = {'partial': 'Hello, {{thing}}!'} |
| 33 >>> renderer = Renderer(partials=partials) |
| 34 >>> # We apply print to make the test work in Python 3 after 2to3. |
| 35 >>> print renderer.render('{{>partial}}', {'thing': 'world'}) |
| 36 Hello, world! |
| 37 |
| 38 To customize string coercion (e.g. to render False values as ''), one can |
| 39 subclass this class. For example: |
| 40 |
| 41 class MyRenderer(Renderer): |
| 42 def str_coerce(self, val): |
| 43 if not val: |
| 44 return '' |
| 45 else: |
| 46 return str(val) |
| 47 |
| 48 """ |
| 49 |
| 50 def __init__(self, file_encoding=None, string_encoding=None, |
| 51 decode_errors=None, search_dirs=None, file_extension=None, |
| 52 escape=None, partials=None, missing_tags=None): |
| 53 """ |
| 54 Construct an instance. |
| 55 |
| 56 Arguments: |
| 57 |
| 58 file_encoding: the name of the encoding to use by default when |
| 59 reading template files. All templates are converted to unicode |
| 60 prior to parsing. Defaults to the package default. |
| 61 |
| 62 string_encoding: the name of the encoding to use when converting |
| 63 to unicode any byte strings (type str in Python 2) encountered |
| 64 during the rendering process. This name will be passed as the |
| 65 encoding argument to the built-in function unicode(). |
| 66 Defaults to the package default. |
| 67 |
| 68 decode_errors: the string to pass as the errors argument to the |
| 69 built-in function unicode() when converting byte strings to |
| 70 unicode. Defaults to the package default. |
| 71 |
| 72 search_dirs: the list of directories in which to search when |
| 73 loading a template by name or file name. If given a string, |
| 74 the method interprets the string as a single directory. |
| 75 Defaults to the package default. |
| 76 |
| 77 file_extension: the template file extension. Pass False for no |
| 78 extension (i.e. to use extensionless template files). |
| 79 Defaults to the package default. |
| 80 |
| 81 partials: an object (e.g. a dictionary) for custom partial loading |
| 82 during the rendering process. |
| 83 The object should have a get() method that accepts a string |
| 84 and returns the corresponding template as a string, preferably |
| 85 as a unicode string. If there is no template with that name, |
| 86 the get() method should either return None (as dict.get() does) |
| 87 or raise an exception. |
| 88 If this argument is None, the rendering process will use |
| 89 the normal procedure of locating and reading templates from |
| 90 the file system -- using relevant instance attributes like |
| 91 search_dirs, file_encoding, etc. |
| 92 |
| 93 escape: the function used to escape variable tag values when |
| 94 rendering a template. The function should accept a unicode |
| 95 string (or subclass of unicode) and return an escaped string |
| 96 that is again unicode (or a subclass of unicode). |
| 97 This function need not handle strings of type `str` because |
| 98 this class will only pass it unicode strings. The constructor |
| 99 assigns this function to the constructed instance's escape() |
| 100 method. |
| 101 To disable escaping entirely, one can pass `lambda u: u` |
| 102 as the escape function, for example. One may also wish to |
| 103 consider using markupsafe's escape function: markupsafe.escape(). |
| 104 This argument defaults to the package default. |
| 105 |
| 106 missing_tags: a string specifying how to handle missing tags. |
| 107 If 'strict', an error is raised on a missing tag. If 'ignore', |
| 108 the value of the tag is the empty string. Defaults to the |
| 109 package default. |
| 110 |
| 111 """ |
| 112 if decode_errors is None: |
| 113 decode_errors = defaults.DECODE_ERRORS |
| 114 |
| 115 if escape is None: |
| 116 escape = defaults.TAG_ESCAPE |
| 117 |
| 118 if file_encoding is None: |
| 119 file_encoding = defaults.FILE_ENCODING |
| 120 |
| 121 if file_extension is None: |
| 122 file_extension = defaults.TEMPLATE_EXTENSION |
| 123 |
| 124 if missing_tags is None: |
| 125 missing_tags = defaults.MISSING_TAGS |
| 126 |
| 127 if search_dirs is None: |
| 128 search_dirs = defaults.SEARCH_DIRS |
| 129 |
| 130 if string_encoding is None: |
| 131 string_encoding = defaults.STRING_ENCODING |
| 132 |
| 133 if isinstance(search_dirs, basestring): |
| 134 search_dirs = [search_dirs] |
| 135 |
| 136 self._context = None |
| 137 self.decode_errors = decode_errors |
| 138 self.escape = escape |
| 139 self.file_encoding = file_encoding |
| 140 self.file_extension = file_extension |
| 141 self.missing_tags = missing_tags |
| 142 self.partials = partials |
| 143 self.search_dirs = search_dirs |
| 144 self.string_encoding = string_encoding |
| 145 |
| 146 # This is an experimental way of giving views access to the current context. |
| 147 # TODO: consider another approach of not giving access via a property, |
| 148 # but instead letting the caller pass the initial context to the |
| 149 # main render() method by reference. This approach would probably |
| 150 # be less likely to be misused. |
| 151 @property |
| 152 def context(self): |
| 153 """ |
| 154 Return the current rendering context [experimental]. |
| 155 |
| 156 """ |
| 157 return self._context |
| 158 |
| 159 # We could not choose str() as the name because 2to3 renames the unicode() |
| 160 # method of this class to str(). |
| 161 def str_coerce(self, val): |
| 162 """ |
| 163 Coerce a non-string value to a string. |
| 164 |
| 165 This method is called whenever a non-string is encountered during the |
| 166 rendering process when a string is needed (e.g. if a context value |
| 167 for string interpolation is not a string). To customize string |
| 168 coercion, you can override this method. |
| 169 |
| 170 """ |
| 171 return str(val) |
| 172 |
| 173 def _to_unicode_soft(self, s): |
| 174 """ |
| 175 Convert a basestring to unicode, preserving any unicode subclass. |
| 176 |
| 177 """ |
| 178 # We type-check to avoid "TypeError: decoding Unicode is not supported". |
| 179 # We avoid the Python ternary operator for Python 2.4 support. |
| 180 if isinstance(s, unicode): |
| 181 return s |
| 182 return self.unicode(s) |
| 183 |
| 184 def _to_unicode_hard(self, s): |
| 185 """ |
| 186 Convert a basestring to a string with type unicode (not subclass). |
| 187 |
| 188 """ |
| 189 return unicode(self._to_unicode_soft(s)) |
| 190 |
| 191 def _escape_to_unicode(self, s): |
| 192 """ |
| 193 Convert a basestring to unicode (preserving any unicode subclass), and e
scape it. |
| 194 |
| 195 Returns a unicode string (not subclass). |
| 196 |
| 197 """ |
| 198 return unicode(self.escape(self._to_unicode_soft(s))) |
| 199 |
| 200 def unicode(self, b, encoding=None): |
| 201 """ |
| 202 Convert a byte string to unicode, using string_encoding and decode_error
s. |
| 203 |
| 204 Arguments: |
| 205 |
| 206 b: a byte string. |
| 207 |
| 208 encoding: the name of an encoding. Defaults to the string_encoding |
| 209 attribute for this instance. |
| 210 |
| 211 Raises: |
| 212 |
| 213 TypeError: Because this method calls Python's built-in unicode() |
| 214 function, this method raises the following exception if the |
| 215 given string is already unicode: |
| 216 |
| 217 TypeError: decoding Unicode is not supported |
| 218 |
| 219 """ |
| 220 if encoding is None: |
| 221 encoding = self.string_encoding |
| 222 |
| 223 # TODO: Wrap UnicodeDecodeErrors with a message about setting |
| 224 # the string_encoding and decode_errors attributes. |
| 225 return unicode(b, encoding, self.decode_errors) |
| 226 |
| 227 def _make_loader(self): |
| 228 """ |
| 229 Create a Loader instance using current attributes. |
| 230 |
| 231 """ |
| 232 return Loader(file_encoding=self.file_encoding, extension=self.file_exte
nsion, |
| 233 to_unicode=self.unicode, search_dirs=self.search_dirs) |
| 234 |
| 235 def _make_load_template(self): |
| 236 """ |
| 237 Return a function that loads a template by name. |
| 238 |
| 239 """ |
| 240 loader = self._make_loader() |
| 241 |
| 242 def load_template(template_name): |
| 243 return loader.load_name(template_name) |
| 244 |
| 245 return load_template |
| 246 |
| 247 def _make_load_partial(self): |
| 248 """ |
| 249 Return a function that loads a partial by name. |
| 250 |
| 251 """ |
| 252 if self.partials is None: |
| 253 return self._make_load_template() |
| 254 |
| 255 # Otherwise, create a function from the custom partial loader. |
| 256 partials = self.partials |
| 257 |
| 258 def load_partial(name): |
| 259 # TODO: consider using EAFP here instead. |
| 260 # http://docs.python.org/glossary.html#term-eafp |
| 261 # This would mean requiring that the custom partial loader |
| 262 # raise a KeyError on name not found. |
| 263 template = partials.get(name) |
| 264 if template is None: |
| 265 raise TemplateNotFoundError("Name %s not found in partials: %s"
% |
| 266 (repr(name), type(partials))) |
| 267 |
| 268 # RenderEngine requires that the return value be unicode. |
| 269 return self._to_unicode_hard(template) |
| 270 |
| 271 return load_partial |
| 272 |
| 273 def _is_missing_tags_strict(self): |
| 274 """ |
| 275 Return whether missing_tags is set to strict. |
| 276 |
| 277 """ |
| 278 val = self.missing_tags |
| 279 |
| 280 if val == MissingTags.strict: |
| 281 return True |
| 282 elif val == MissingTags.ignore: |
| 283 return False |
| 284 |
| 285 raise Exception("Unsupported 'missing_tags' value: %s" % repr(val)) |
| 286 |
| 287 def _make_resolve_partial(self): |
| 288 """ |
| 289 Return the resolve_partial function to pass to RenderEngine.__init__(). |
| 290 |
| 291 """ |
| 292 load_partial = self._make_load_partial() |
| 293 |
| 294 if self._is_missing_tags_strict(): |
| 295 return load_partial |
| 296 # Otherwise, ignore missing tags. |
| 297 |
| 298 def resolve_partial(name): |
| 299 try: |
| 300 return load_partial(name) |
| 301 except TemplateNotFoundError: |
| 302 return u'' |
| 303 |
| 304 return resolve_partial |
| 305 |
| 306 def _make_resolve_context(self): |
| 307 """ |
| 308 Return the resolve_context function to pass to RenderEngine.__init__(). |
| 309 |
| 310 """ |
| 311 if self._is_missing_tags_strict(): |
| 312 return context_get |
| 313 # Otherwise, ignore missing tags. |
| 314 |
| 315 def resolve_context(stack, name): |
| 316 try: |
| 317 return context_get(stack, name) |
| 318 except KeyNotFoundError: |
| 319 return u'' |
| 320 |
| 321 return resolve_context |
| 322 |
| 323 def _make_render_engine(self): |
| 324 """ |
| 325 Return a RenderEngine instance for rendering. |
| 326 |
| 327 """ |
| 328 resolve_context = self._make_resolve_context() |
| 329 resolve_partial = self._make_resolve_partial() |
| 330 |
| 331 engine = RenderEngine(literal=self._to_unicode_hard, |
| 332 escape=self._escape_to_unicode, |
| 333 resolve_context=resolve_context, |
| 334 resolve_partial=resolve_partial, |
| 335 to_str=self.str_coerce) |
| 336 return engine |
| 337 |
| 338 # TODO: add unit tests for this method. |
| 339 def load_template(self, template_name): |
| 340 """ |
| 341 Load a template by name from the file system. |
| 342 |
| 343 """ |
| 344 load_template = self._make_load_template() |
| 345 return load_template(template_name) |
| 346 |
| 347 def _render_object(self, obj, *context, **kwargs): |
| 348 """ |
| 349 Render the template associated with the given object. |
| 350 |
| 351 """ |
| 352 loader = self._make_loader() |
| 353 |
| 354 # TODO: consider an approach that does not require using an if |
| 355 # block here. For example, perhaps this class's loader can be |
| 356 # a SpecLoader in all cases, and the SpecLoader instance can |
| 357 # check the object's type. Or perhaps Loader and SpecLoader |
| 358 # can be refactored to implement the same interface. |
| 359 if isinstance(obj, TemplateSpec): |
| 360 loader = SpecLoader(loader) |
| 361 template = loader.load(obj) |
| 362 else: |
| 363 template = loader.load_object(obj) |
| 364 |
| 365 context = [obj] + list(context) |
| 366 |
| 367 return self._render_string(template, *context, **kwargs) |
| 368 |
| 369 def render_name(self, template_name, *context, **kwargs): |
| 370 """ |
| 371 Render the template with the given name using the given context. |
| 372 |
| 373 See the render() docstring for more information. |
| 374 |
| 375 """ |
| 376 loader = self._make_loader() |
| 377 template = loader.load_name(template_name) |
| 378 return self._render_string(template, *context, **kwargs) |
| 379 |
| 380 def render_path(self, template_path, *context, **kwargs): |
| 381 """ |
| 382 Render the template at the given path using the given context. |
| 383 |
| 384 Read the render() docstring for more information. |
| 385 |
| 386 """ |
| 387 loader = self._make_loader() |
| 388 template = loader.read(template_path) |
| 389 |
| 390 return self._render_string(template, *context, **kwargs) |
| 391 |
| 392 def _render_string(self, template, *context, **kwargs): |
| 393 """ |
| 394 Render the given template string using the given context. |
| 395 |
| 396 """ |
| 397 # RenderEngine.render() requires that the template string be unicode. |
| 398 template = self._to_unicode_hard(template) |
| 399 |
| 400 render_func = lambda engine, stack: engine.render(template, stack) |
| 401 |
| 402 return self._render_final(render_func, *context, **kwargs) |
| 403 |
| 404 # All calls to render() should end here because it prepares the |
| 405 # context stack correctly. |
| 406 def _render_final(self, render_func, *context, **kwargs): |
| 407 """ |
| 408 Arguments: |
| 409 |
| 410 render_func: a function that accepts a RenderEngine and ContextStack |
| 411 instance and returns a template rendering as a unicode string. |
| 412 |
| 413 """ |
| 414 stack = ContextStack.create(*context, **kwargs) |
| 415 self._context = stack |
| 416 |
| 417 engine = self._make_render_engine() |
| 418 |
| 419 return render_func(engine, stack) |
| 420 |
| 421 def render(self, template, *context, **kwargs): |
| 422 """ |
| 423 Render the given template string, view template, or parsed template. |
| 424 |
| 425 Returns a unicode string. |
| 426 |
| 427 Prior to rendering, this method will convert a template that is a |
| 428 byte string (type str in Python 2) to unicode using the string_encoding |
| 429 and decode_errors attributes. See the constructor docstring for |
| 430 more information. |
| 431 |
| 432 Arguments: |
| 433 |
| 434 template: a template string that is unicode or a byte string, |
| 435 a ParsedTemplate instance, or another object instance. In the |
| 436 final case, the function first looks for the template associated |
| 437 to the object by calling this class's get_associated_template() |
| 438 method. The rendering process also uses the passed object as |
| 439 the first element of the context stack when rendering. |
| 440 |
| 441 *context: zero or more dictionaries, ContextStack instances, or object
s |
| 442 with which to populate the initial context stack. None |
| 443 arguments are skipped. Items in the *context list are added to |
| 444 the context stack in order so that later items in the argument |
| 445 list take precedence over earlier items. |
| 446 |
| 447 **kwargs: additional key-value data to add to the context stack. |
| 448 As these arguments appear after all items in the *context list, |
| 449 in the case of key conflicts these values take precedence over |
| 450 all items in the *context list. |
| 451 |
| 452 """ |
| 453 if is_string(template): |
| 454 return self._render_string(template, *context, **kwargs) |
| 455 if isinstance(template, ParsedTemplate): |
| 456 render_func = lambda engine, stack: template.render(engine, stack) |
| 457 return self._render_final(render_func, *context, **kwargs) |
| 458 # Otherwise, we assume the template is an object. |
| 459 |
| 460 return self._render_object(template, *context, **kwargs) |
OLD | NEW |