Index: third_party/pystache/src/renderer.py |
diff --git a/third_party/pystache/src/renderer.py b/third_party/pystache/src/renderer.py |
new file mode 100644 |
index 0000000000000000000000000000000000000000..ff6a90c64b4dc41303dd99923d33d7c2c13fb963 |
--- /dev/null |
+++ b/third_party/pystache/src/renderer.py |
@@ -0,0 +1,460 @@ |
+# coding: utf-8 |
+ |
+""" |
+This module provides a Renderer class to render templates. |
+ |
+""" |
+ |
+import sys |
+ |
+from pystache import defaults |
+from pystache.common import TemplateNotFoundError, MissingTags, is_string |
+from pystache.context import ContextStack, KeyNotFoundError |
+from pystache.loader import Loader |
+from pystache.parsed import ParsedTemplate |
+from pystache.renderengine import context_get, RenderEngine |
+from pystache.specloader import SpecLoader |
+from pystache.template_spec import TemplateSpec |
+ |
+ |
+class Renderer(object): |
+ |
+ """ |
+ A class for rendering mustache templates. |
+ |
+ This class supports several rendering options which are described in |
+ the constructor's docstring. Other behavior can be customized by |
+ subclassing this class. |
+ |
+ For example, one can pass a string-string dictionary to the constructor |
+ to bypass loading partials from the file system: |
+ |
+ >>> partials = {'partial': 'Hello, {{thing}}!'} |
+ >>> renderer = Renderer(partials=partials) |
+ >>> # We apply print to make the test work in Python 3 after 2to3. |
+ >>> print renderer.render('{{>partial}}', {'thing': 'world'}) |
+ Hello, world! |
+ |
+ To customize string coercion (e.g. to render False values as ''), one can |
+ subclass this class. For example: |
+ |
+ class MyRenderer(Renderer): |
+ def str_coerce(self, val): |
+ if not val: |
+ return '' |
+ else: |
+ return str(val) |
+ |
+ """ |
+ |
+ def __init__(self, file_encoding=None, string_encoding=None, |
+ decode_errors=None, search_dirs=None, file_extension=None, |
+ escape=None, partials=None, missing_tags=None): |
+ """ |
+ Construct an instance. |
+ |
+ Arguments: |
+ |
+ file_encoding: the name of the encoding to use by default when |
+ reading template files. All templates are converted to unicode |
+ prior to parsing. Defaults to the package default. |
+ |
+ string_encoding: the name of the encoding to use when converting |
+ to unicode any byte strings (type str in Python 2) encountered |
+ during the rendering process. This name will be passed as the |
+ encoding argument to the built-in function unicode(). |
+ Defaults to the package default. |
+ |
+ decode_errors: the string to pass as the errors argument to the |
+ built-in function unicode() when converting byte strings to |
+ unicode. Defaults to the package default. |
+ |
+ search_dirs: the list of directories in which to search when |
+ loading a template by name or file name. If given a string, |
+ the method interprets the string as a single directory. |
+ Defaults to the package default. |
+ |
+ file_extension: the template file extension. Pass False for no |
+ extension (i.e. to use extensionless template files). |
+ Defaults to the package default. |
+ |
+ partials: an object (e.g. a dictionary) for custom partial loading |
+ during the rendering process. |
+ The object should have a get() method that accepts a string |
+ and returns the corresponding template as a string, preferably |
+ as a unicode string. If there is no template with that name, |
+ the get() method should either return None (as dict.get() does) |
+ or raise an exception. |
+ If this argument is None, the rendering process will use |
+ the normal procedure of locating and reading templates from |
+ the file system -- using relevant instance attributes like |
+ search_dirs, file_encoding, etc. |
+ |
+ escape: the function used to escape variable tag values when |
+ rendering a template. The function should accept a unicode |
+ string (or subclass of unicode) and return an escaped string |
+ that is again unicode (or a subclass of unicode). |
+ This function need not handle strings of type `str` because |
+ this class will only pass it unicode strings. The constructor |
+ assigns this function to the constructed instance's escape() |
+ method. |
+ To disable escaping entirely, one can pass `lambda u: u` |
+ as the escape function, for example. One may also wish to |
+ consider using markupsafe's escape function: markupsafe.escape(). |
+ This argument defaults to the package default. |
+ |
+ missing_tags: a string specifying how to handle missing tags. |
+ If 'strict', an error is raised on a missing tag. If 'ignore', |
+ the value of the tag is the empty string. Defaults to the |
+ package default. |
+ |
+ """ |
+ if decode_errors is None: |
+ decode_errors = defaults.DECODE_ERRORS |
+ |
+ if escape is None: |
+ escape = defaults.TAG_ESCAPE |
+ |
+ if file_encoding is None: |
+ file_encoding = defaults.FILE_ENCODING |
+ |
+ if file_extension is None: |
+ file_extension = defaults.TEMPLATE_EXTENSION |
+ |
+ if missing_tags is None: |
+ missing_tags = defaults.MISSING_TAGS |
+ |
+ if search_dirs is None: |
+ search_dirs = defaults.SEARCH_DIRS |
+ |
+ if string_encoding is None: |
+ string_encoding = defaults.STRING_ENCODING |
+ |
+ if isinstance(search_dirs, basestring): |
+ search_dirs = [search_dirs] |
+ |
+ self._context = None |
+ self.decode_errors = decode_errors |
+ self.escape = escape |
+ self.file_encoding = file_encoding |
+ self.file_extension = file_extension |
+ self.missing_tags = missing_tags |
+ self.partials = partials |
+ self.search_dirs = search_dirs |
+ self.string_encoding = string_encoding |
+ |
+ # This is an experimental way of giving views access to the current context. |
+ # TODO: consider another approach of not giving access via a property, |
+ # but instead letting the caller pass the initial context to the |
+ # main render() method by reference. This approach would probably |
+ # be less likely to be misused. |
+ @property |
+ def context(self): |
+ """ |
+ Return the current rendering context [experimental]. |
+ |
+ """ |
+ return self._context |
+ |
+ # We could not choose str() as the name because 2to3 renames the unicode() |
+ # method of this class to str(). |
+ def str_coerce(self, val): |
+ """ |
+ Coerce a non-string value to a string. |
+ |
+ This method is called whenever a non-string is encountered during the |
+ rendering process when a string is needed (e.g. if a context value |
+ for string interpolation is not a string). To customize string |
+ coercion, you can override this method. |
+ |
+ """ |
+ return str(val) |
+ |
+ def _to_unicode_soft(self, s): |
+ """ |
+ Convert a basestring to unicode, preserving any unicode subclass. |
+ |
+ """ |
+ # We type-check to avoid "TypeError: decoding Unicode is not supported". |
+ # We avoid the Python ternary operator for Python 2.4 support. |
+ if isinstance(s, unicode): |
+ return s |
+ return self.unicode(s) |
+ |
+ def _to_unicode_hard(self, s): |
+ """ |
+ Convert a basestring to a string with type unicode (not subclass). |
+ |
+ """ |
+ return unicode(self._to_unicode_soft(s)) |
+ |
+ def _escape_to_unicode(self, s): |
+ """ |
+ Convert a basestring to unicode (preserving any unicode subclass), and escape it. |
+ |
+ Returns a unicode string (not subclass). |
+ |
+ """ |
+ return unicode(self.escape(self._to_unicode_soft(s))) |
+ |
+ def unicode(self, b, encoding=None): |
+ """ |
+ Convert a byte string to unicode, using string_encoding and decode_errors. |
+ |
+ Arguments: |
+ |
+ b: a byte string. |
+ |
+ encoding: the name of an encoding. Defaults to the string_encoding |
+ attribute for this instance. |
+ |
+ Raises: |
+ |
+ TypeError: Because this method calls Python's built-in unicode() |
+ function, this method raises the following exception if the |
+ given string is already unicode: |
+ |
+ TypeError: decoding Unicode is not supported |
+ |
+ """ |
+ if encoding is None: |
+ encoding = self.string_encoding |
+ |
+ # TODO: Wrap UnicodeDecodeErrors with a message about setting |
+ # the string_encoding and decode_errors attributes. |
+ return unicode(b, encoding, self.decode_errors) |
+ |
+ def _make_loader(self): |
+ """ |
+ Create a Loader instance using current attributes. |
+ |
+ """ |
+ return Loader(file_encoding=self.file_encoding, extension=self.file_extension, |
+ to_unicode=self.unicode, search_dirs=self.search_dirs) |
+ |
+ def _make_load_template(self): |
+ """ |
+ Return a function that loads a template by name. |
+ |
+ """ |
+ loader = self._make_loader() |
+ |
+ def load_template(template_name): |
+ return loader.load_name(template_name) |
+ |
+ return load_template |
+ |
+ def _make_load_partial(self): |
+ """ |
+ Return a function that loads a partial by name. |
+ |
+ """ |
+ if self.partials is None: |
+ return self._make_load_template() |
+ |
+ # Otherwise, create a function from the custom partial loader. |
+ partials = self.partials |
+ |
+ def load_partial(name): |
+ # TODO: consider using EAFP here instead. |
+ # http://docs.python.org/glossary.html#term-eafp |
+ # This would mean requiring that the custom partial loader |
+ # raise a KeyError on name not found. |
+ template = partials.get(name) |
+ if template is None: |
+ raise TemplateNotFoundError("Name %s not found in partials: %s" % |
+ (repr(name), type(partials))) |
+ |
+ # RenderEngine requires that the return value be unicode. |
+ return self._to_unicode_hard(template) |
+ |
+ return load_partial |
+ |
+ def _is_missing_tags_strict(self): |
+ """ |
+ Return whether missing_tags is set to strict. |
+ |
+ """ |
+ val = self.missing_tags |
+ |
+ if val == MissingTags.strict: |
+ return True |
+ elif val == MissingTags.ignore: |
+ return False |
+ |
+ raise Exception("Unsupported 'missing_tags' value: %s" % repr(val)) |
+ |
+ def _make_resolve_partial(self): |
+ """ |
+ Return the resolve_partial function to pass to RenderEngine.__init__(). |
+ |
+ """ |
+ load_partial = self._make_load_partial() |
+ |
+ if self._is_missing_tags_strict(): |
+ return load_partial |
+ # Otherwise, ignore missing tags. |
+ |
+ def resolve_partial(name): |
+ try: |
+ return load_partial(name) |
+ except TemplateNotFoundError: |
+ return u'' |
+ |
+ return resolve_partial |
+ |
+ def _make_resolve_context(self): |
+ """ |
+ Return the resolve_context function to pass to RenderEngine.__init__(). |
+ |
+ """ |
+ if self._is_missing_tags_strict(): |
+ return context_get |
+ # Otherwise, ignore missing tags. |
+ |
+ def resolve_context(stack, name): |
+ try: |
+ return context_get(stack, name) |
+ except KeyNotFoundError: |
+ return u'' |
+ |
+ return resolve_context |
+ |
+ def _make_render_engine(self): |
+ """ |
+ Return a RenderEngine instance for rendering. |
+ |
+ """ |
+ resolve_context = self._make_resolve_context() |
+ resolve_partial = self._make_resolve_partial() |
+ |
+ engine = RenderEngine(literal=self._to_unicode_hard, |
+ escape=self._escape_to_unicode, |
+ resolve_context=resolve_context, |
+ resolve_partial=resolve_partial, |
+ to_str=self.str_coerce) |
+ return engine |
+ |
+ # TODO: add unit tests for this method. |
+ def load_template(self, template_name): |
+ """ |
+ Load a template by name from the file system. |
+ |
+ """ |
+ load_template = self._make_load_template() |
+ return load_template(template_name) |
+ |
+ def _render_object(self, obj, *context, **kwargs): |
+ """ |
+ Render the template associated with the given object. |
+ |
+ """ |
+ loader = self._make_loader() |
+ |
+ # TODO: consider an approach that does not require using an if |
+ # block here. For example, perhaps this class's loader can be |
+ # a SpecLoader in all cases, and the SpecLoader instance can |
+ # check the object's type. Or perhaps Loader and SpecLoader |
+ # can be refactored to implement the same interface. |
+ if isinstance(obj, TemplateSpec): |
+ loader = SpecLoader(loader) |
+ template = loader.load(obj) |
+ else: |
+ template = loader.load_object(obj) |
+ |
+ context = [obj] + list(context) |
+ |
+ return self._render_string(template, *context, **kwargs) |
+ |
+ def render_name(self, template_name, *context, **kwargs): |
+ """ |
+ Render the template with the given name using the given context. |
+ |
+ See the render() docstring for more information. |
+ |
+ """ |
+ loader = self._make_loader() |
+ template = loader.load_name(template_name) |
+ return self._render_string(template, *context, **kwargs) |
+ |
+ def render_path(self, template_path, *context, **kwargs): |
+ """ |
+ Render the template at the given path using the given context. |
+ |
+ Read the render() docstring for more information. |
+ |
+ """ |
+ loader = self._make_loader() |
+ template = loader.read(template_path) |
+ |
+ return self._render_string(template, *context, **kwargs) |
+ |
+ def _render_string(self, template, *context, **kwargs): |
+ """ |
+ Render the given template string using the given context. |
+ |
+ """ |
+ # RenderEngine.render() requires that the template string be unicode. |
+ template = self._to_unicode_hard(template) |
+ |
+ render_func = lambda engine, stack: engine.render(template, stack) |
+ |
+ return self._render_final(render_func, *context, **kwargs) |
+ |
+ # All calls to render() should end here because it prepares the |
+ # context stack correctly. |
+ def _render_final(self, render_func, *context, **kwargs): |
+ """ |
+ Arguments: |
+ |
+ render_func: a function that accepts a RenderEngine and ContextStack |
+ instance and returns a template rendering as a unicode string. |
+ |
+ """ |
+ stack = ContextStack.create(*context, **kwargs) |
+ self._context = stack |
+ |
+ engine = self._make_render_engine() |
+ |
+ return render_func(engine, stack) |
+ |
+ def render(self, template, *context, **kwargs): |
+ """ |
+ Render the given template string, view template, or parsed template. |
+ |
+ Returns a unicode string. |
+ |
+ Prior to rendering, this method will convert a template that is a |
+ byte string (type str in Python 2) to unicode using the string_encoding |
+ and decode_errors attributes. See the constructor docstring for |
+ more information. |
+ |
+ Arguments: |
+ |
+ template: a template string that is unicode or a byte string, |
+ a ParsedTemplate instance, or another object instance. In the |
+ final case, the function first looks for the template associated |
+ to the object by calling this class's get_associated_template() |
+ method. The rendering process also uses the passed object as |
+ the first element of the context stack when rendering. |
+ |
+ *context: zero or more dictionaries, ContextStack instances, or objects |
+ with which to populate the initial context stack. None |
+ arguments are skipped. Items in the *context list are added to |
+ the context stack in order so that later items in the argument |
+ list take precedence over earlier items. |
+ |
+ **kwargs: additional key-value data to add to the context stack. |
+ As these arguments appear after all items in the *context list, |
+ in the case of key conflicts these values take precedence over |
+ all items in the *context list. |
+ |
+ """ |
+ if is_string(template): |
+ return self._render_string(template, *context, **kwargs) |
+ if isinstance(template, ParsedTemplate): |
+ render_func = lambda engine, stack: template.render(engine, stack) |
+ return self._render_final(render_func, *context, **kwargs) |
+ # Otherwise, we assume the template is an object. |
+ |
+ return self._render_object(template, *context, **kwargs) |