summaryrefslogtreecommitdiffstats
path: root/third_party/python/pystache/pystache/renderer.py
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/python/pystache/pystache/renderer.py')
-rw-r--r--third_party/python/pystache/pystache/renderer.py460
1 files changed, 460 insertions, 0 deletions
diff --git a/third_party/python/pystache/pystache/renderer.py b/third_party/python/pystache/pystache/renderer.py
new file mode 100644
index 0000000000..ff6a90c64b
--- /dev/null
+++ b/third_party/python/pystache/pystache/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)