diff options
Diffstat (limited to 'pystache/renderer.py')
-rw-r--r-- | pystache/renderer.py | 219 |
1 files changed, 143 insertions, 76 deletions
diff --git a/pystache/renderer.py b/pystache/renderer.py index a3d4c57..20e4d48 100644 --- a/pystache/renderer.py +++ b/pystache/renderer.py @@ -8,25 +8,15 @@ This module provides a Renderer class to render templates. import sys from pystache import defaults -from pystache.common import TemplateNotFoundError -from pystache.context import ContextStack +from pystache.common import TemplateNotFoundError, MissingTags, is_string +from pystache.context import ContextStack, KeyNotFoundError from pystache.loader import Loader -from pystache.renderengine import RenderEngine +from pystache.parsed import ParsedTemplate +from pystache.renderengine import context_get, RenderEngine from pystache.specloader import SpecLoader from pystache.template_spec import TemplateSpec -# TODO: come up with a better solution for this. One of the issues here -# is that in Python 3 there is no common base class for unicode strings -# and byte strings, and 2to3 seems to convert all of "str", "unicode", -# and "basestring" to Python 3's "str". -if sys.version_info < (3, ): - _STRING_TYPES = basestring -else: - # The latter evaluates to "bytes" in Python 3 -- even after conversion by 2to3. - _STRING_TYPES = (unicode, type(u"a".encode('utf-8'))) - - class Renderer(object): """ @@ -49,12 +39,35 @@ class Renderer(object): def __init__(self, file_encoding=None, string_encoding=None, decode_errors=None, search_dirs=None, file_extension=None, - escape=None, partials=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 @@ -67,10 +80,6 @@ class Renderer(object): the file system -- using relevant instance attributes like search_dirs, file_encoding, etc. - decode_errors: the string to pass as the errors argument to the - built-in function unicode() when converting str strings to - unicode. Defaults to the package default. - 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 @@ -84,24 +93,9 @@ class Renderer(object): consider using markupsafe's escape function: markupsafe.escape(). This argument defaults to the package default. - file_encoding: the name of the default encoding to use when reading - template files. All templates are converted to unicode prior - to parsing. This encoding is used when reading template files - and converting them to unicode. 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. - - 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. - - string_encoding: the name of the encoding to use when converting - to unicode any strings of type str encountered during the - rendering process. The name will be passed as the encoding - argument to the built-in function unicode(). Defaults to the + 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. """ @@ -117,6 +111,9 @@ class Renderer(object): 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 @@ -131,6 +128,7 @@ class Renderer(object): 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 @@ -224,21 +222,21 @@ class Renderer(object): def _make_load_partial(self): """ - Return the load_partial function to pass to RenderEngine.__init__(). + Return a function that loads a partial by name. """ if self.partials is None: - load_template = self._make_load_template() - return load_template + return self._make_load_template() - # Otherwise, create a load_partial function from the custom partial - # loader that satisfies RenderEngine requirements (and that provides - # a nicer exception, etc). + # 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))) @@ -248,42 +246,78 @@ class Renderer(object): return load_partial - def _make_render_engine(self): + def _is_missing_tags_strict(self): """ - Return a RenderEngine instance for rendering. + 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() - engine = RenderEngine(load_partial=load_partial, - literal=self._to_unicode_hard, - escape=self._escape_to_unicode) - return engine + if self._is_missing_tags_strict(): + return load_partial + # Otherwise, ignore missing tags. - # TODO: add unit tests for this method. - def load_template(self, template_name): + def resolve_partial(name): + try: + return load_partial(name) + except TemplateNotFoundError: + return u'' + + return resolve_partial + + def _make_resolve_context(self): """ - Load a template by name from the file system. + Return the resolve_context function to pass to RenderEngine.__init__(). """ - load_template = self._make_load_template() - return load_template(template_name) + if self._is_missing_tags_strict(): + return context_get + # Otherwise, ignore missing tags. - def _render_string(self, template, *context, **kwargs): + def resolve_context(stack, name): + try: + return context_get(stack, name) + except KeyNotFoundError: + return u'' + + return resolve_context + + def _make_render_engine(self): """ - Render the given template string using the given context. + Return a RenderEngine instance for rendering. """ - # RenderEngine.render() requires that the template string be unicode. - template = self._to_unicode_hard(template) + resolve_context = self._make_resolve_context() + resolve_partial = self._make_resolve_partial() - context = ContextStack.create(*context, **kwargs) - self._context = context + engine = RenderEngine(literal=self._to_unicode_hard, + escape=self._escape_to_unicode, + resolve_context=resolve_context, + resolve_partial=resolve_partial) + return engine - engine = self._make_render_engine() - rendered = engine.render(template, context) + # TODO: add unit tests for this method. + def load_template(self, template_name): + """ + Load a template by name from the file system. - return unicode(rendered) + """ + load_template = self._make_load_template() + return load_template(template_name) def _render_object(self, obj, *context, **kwargs): """ @@ -319,24 +353,54 @@ class Renderer(object): 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 (or template object) using the given context. + Render the given template string, view template, or parsed template. - Returns the rendering as a unicode string. + Returns a unicode string. - Prior to rendering, templates of type str are converted to unicode - using the string_encoding and decode_errors attributes. See the - constructor docstring for more information. + 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 of type unicode or str, or an object - instance. If the argument is an object, 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. + 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 @@ -350,8 +414,11 @@ class Renderer(object): all items in the *context list. """ - if isinstance(template, _STRING_TYPES): + 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) |