Commit d4181510 by Chris Jerdonek

Finished issue #64: "Change Renderer to accept a loader implementing get()"

parent abe5700e
......@@ -22,13 +22,41 @@ except ImportError:
class Renderer(object):
def __init__(self, output_encoding=None, escape=None,
default_encoding=None, decode_errors='strict', load_template=None):
"""
A class for rendering mustache templates.
This class supports several rendering options which are described in
the constructor's docstring. Among these, the constructor supports
passing a custom template loader.
Here is an example of passing a custom template loader to render a
template using partials loaded from a string-string dictionary.
>>> partials = {'partial': 'Hello, {{thing}}!'}
>>> renderer = Renderer(loader=partials)
>>> renderer.render('{{>partial}}', {'thing': 'world'})
u'Hello, world!'
"""
def __init__(self, loader=None, default_encoding=None, decode_errors='strict',
output_encoding=None, escape=None):
"""
Construct an instance.
Arguments:
loader: the object (e.g. pystache.Loader or dictionary) that will
load templates during the rendering process, for example when
loading a partial.
The loader 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 method should either return None (as dict.get() does) or
raise an exception.
Defaults to constructing a Loader instance with
default_encoding passed as the encoding argument.
output_encoding: the encoding to use when rendering to a string.
The argument should be the name of an encoding as a string, for
example "utf-8". See the render() method's documentation for
......@@ -57,11 +85,6 @@ class Renderer(object):
strings of type `str` encountered during the rendering process.
Defaults to "strict".
load_template: a function for loading templates by name, for
example when loading partials. The function should accept a
single template_name parameter and return a template as a string.
Defaults to the default Loader's get() method.
"""
if default_encoding is None:
default_encoding = sys.getdefaultencoding()
......@@ -69,9 +92,8 @@ class Renderer(object):
if escape is None:
escape = markupsafe.escape if markupsafe else cgi.escape
if load_template is None:
loader = Loader()
load_template = loader.get
if loader is None:
loader = Loader(encoding=default_encoding)
literal = markupsafe.Markup if markupsafe else unicode
......@@ -80,7 +102,7 @@ class Renderer(object):
self.decode_errors = decode_errors
self.default_encoding = default_encoding
self.escape = escape
self.load_template = load_template
self.loader = loader
self.output_encoding = output_encoding
def _unicode_and_escape(self, s):
......@@ -139,11 +161,11 @@ class Renderer(object):
"""
def load_partial(name):
template = self.load_template(name)
# Make sure the return value of load_template is unicode since
# RenderEngine requires it. Also, check that the string is not
# already unicode to avoid "double-decoding". Otherwise, we
# would get the following error:
template = self.loader.get(name)
# Make sure the return value is unicode since RenderEngine requires
# it. Also, check that the string is not already unicode to
# avoid "double-decoding". Otherwise, we would get the following
# error:
# TypeError: decoding Unicode is not supported
if not isinstance(template, unicode):
template = self.unicode(template)
......
......@@ -21,16 +21,24 @@ class View(object):
template_encoding = None
template_extension = None
# A function that accepts a single template_name parameter.
_load_template = None
_loader = None
def __init__(self, template=None, context=None, load_template=None, **kwargs):
def __init__(self, template=None, context=None, loader=None, **kwargs):
"""
Construct a View instance.
Arguments:
loader: the object (e.g. pystache.Loader or dictionary) responsible
for loading templates during the rendering process, for example
when loading partials. The object should have a get() method
that accepts a string and returns the corresponding template
as a string, preferably as a unicode string. The method should
return None if there is no template with that name.
"""
if load_template is not None:
self._load_template = load_template
if loader is not None:
self._loader = loader
if template is not None:
self.template = template
......@@ -43,17 +51,21 @@ class View(object):
self.context = _context
def load_template(self, template_name):
if self._load_template is None:
# We delay setting self._load_template until now (in the case
# that the user did not supply a load_template to the constructor)
def get_loader(self):
if self._loader is None:
# We delay setting self._loader until now (in the case that the
# user did not supply a load_template to the constructor)
# to let users set the template_extension attribute, etc. after
# View.__init__() has already been called.
loader = Loader(search_dirs=self.template_path, encoding=self.template_encoding,
extension=self.template_extension)
self._load_template = loader.get
self._loader = loader
return self._load_template(template_name)
return self._loader
def load_template(self, template_name):
loader = self.get_loader()
return loader.get(template_name)
def get_template(self):
"""
......@@ -98,9 +110,9 @@ class View(object):
Return the view rendered using the current context.
"""
loader = self.get_loader()
template = self.get_template()
renderer = Renderer(output_encoding=encoding, escape=escape,
load_template=self.load_template)
renderer = Renderer(output_encoding=encoding, escape=escape, loader=loader)
return renderer.render(template, self.context)
def get(self, key, default=None):
......
......@@ -11,6 +11,37 @@ import unittest
from pystache import renderer
from pystache.renderer import Renderer
from pystache.loader import Loader
class RendererInitTestCase(unittest.TestCase):
"""A class to test the Renderer.__init__() method."""
def test_loader(self):
"""Test that the loader attribute is set correctly."""
loader = {'foo': 'bar'}
r = Renderer(loader=loader)
self.assertEquals(r.loader, {'foo': 'bar'})
def test_loader__default(self):
"""Test that the default loader is constructed correctly."""
r = Renderer()
actual = r.loader
expected = Loader()
self.assertEquals(type(actual), type(expected))
self.assertEquals(actual.__dict__, expected.__dict__)
def test_loader__default__default_encoding(self):
"""Test that the default loader inherits the default_encoding."""
r = Renderer(default_encoding='foo')
actual = r.loader
expected = Loader(encoding='foo')
self.assertEquals(actual.template_encoding, expected.template_encoding)
# Check all attributes for good measure.
self.assertEquals(actual.__dict__, expected.__dict__)
class RendererTestCase(unittest.TestCase):
......@@ -240,19 +271,37 @@ class RendererTestCase(unittest.TestCase):
renderer.default_encoding = 'utf_8'
self.assertEquals(renderer.render(template), "déf")
def test_make_load_partial(self):
"""
Test the _make_load_partial() method.
"""
partials = {'foo': 'bar'}
renderer = Renderer(loader=partials)
load_partial = renderer._make_load_partial()
actual = load_partial('foo')
self.assertEquals(actual, 'bar')
self.assertEquals(type(actual), unicode, "RenderEngine requires that "
"load_partial return unicode strings.")
def test_make_load_partial__unicode(self):
"""
Test that the generated load_partial does not "double-decode" Unicode.
Test _make_load_partial(): that load_partial doesn't "double-decode" Unicode.
"""
renderer = Renderer()
# In real-life, the partial would be different with each name.
renderer.load_template = lambda name: u"partial"
renderer.loader = {'partial': 'foo'}
load_partial = renderer._make_load_partial()
self.assertEquals(load_partial("partial"), "foo")
# This would raise a TypeError exception if we tried to double-decode.
self.assertEquals(load_partial("test"), "partial")
# Now with a value that is already unicode.
renderer.loader = {'partial': u'foo'}
load_partial = renderer._make_load_partial()
# If the next line failed, we would get the following error:
# TypeError: decoding Unicode is not supported
self.assertEquals(load_partial("partial"), "foo")
# By testing that Renderer.render() constructs the RenderEngine instance
# correctly, we no longer need to test the rendering code paths through
......@@ -263,14 +312,13 @@ class RendererTestCase(unittest.TestCase):
Test that _make_render_engine() constructs and passes load_partial correctly.
"""
renderer = Renderer()
partials = {'partial': 'foo'}
renderer = Renderer(loader=partials)
renderer.unicode = lambda s: s.upper() # a test version.
# In real-life, the partial would be different with each name.
renderer.load_template = lambda name: "partial"
engine = renderer._make_render_engine()
# Make sure it calls unicode.
self.assertEquals(engine.load_partial('name'), "PARTIAL")
self.assertEquals(engine.load_partial('partial'), "FOO")
def test_make_render_engine__literal(self):
"""
......
......@@ -60,15 +60,13 @@ class ViewTestCase(unittest.TestCase):
template = view.load_template('extensionless')
self.assertEquals(template, "No file extension: {{foo}}")
def test_custom_load_template(self):
def test_load_template__custom_loader(self):
"""
Test passing a custom load_template to View.__init__().
Test passing a custom loader to View.__init__().
"""
partials_dict = {"partial": "Loaded from dictionary"}
load_template = lambda template_name: partials_dict[template_name]
view = Simple(load_template=load_template)
partials = {"partial": "Loaded from dictionary"}
view = Simple(loader=partials)
actual = view.load_template("partial")
self.assertEquals(actual, "Loaded from dictionary")
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment