| # 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) |