Commit 22d5f044 by Chris Jerdonek

Merge branch into development: more refactoring around partial-loading.

parents 66eca808 9d2da6ae
......@@ -16,6 +16,12 @@ from .reader import Reader
from .renderengine import RenderEngine
# The quote=True argument causes double quotes to be escaped,
# but not single quotes:
# http://docs.python.org/library/cgi.html#cgi.escape
DEFAULT_ESCAPE = lambda s: cgi.escape(s, quote=True)
class Renderer(object):
"""
......@@ -23,36 +29,37 @@ class Renderer(object):
This class supports several rendering options which are described in
the constructor's docstring. Among these, the constructor supports
passing a custom template loader.
passing a custom partial loader.
Here is an example of passing a custom template loader to render a
template using partials loaded from a string-string dictionary.
Here is an example of rendering a template using a custom partial loader
that loads partials loaded from a string-string dictionary.
>>> partials = {'partial': 'Hello, {{thing}}!'}
>>> renderer = Renderer(loader=partials)
>>> renderer = Renderer(partials=partials)
>>> renderer.render('{{>partial}}', {'thing': 'world'})
u'Hello, world!'
"""
def __init__(self, loader=None, file_encoding=None, default_encoding=None,
def __init__(self, file_encoding=None, default_encoding=None,
decode_errors='strict', search_dirs=None, file_extension=None,
escape=None):
escape=None, partials=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
partials: an object (e.g. pystache.Loader or 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 method should either return None (as dict.get() does) or
raise an exception.
Defaults to constructing a default Loader, but using the
file_encoding and decode_errors arguments.
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 the Loader-related 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
......@@ -96,10 +103,7 @@ class Renderer(object):
default_encoding = sys.getdefaultencoding()
if escape is None:
# The quote=True argument causes double quotes to be escaped,
# but not single quotes:
# http://docs.python.org/library/cgi.html#cgi.escape
escape = lambda s: cgi.escape(s, quote=True)
escape = DEFAULT_ESCAPE
# This needs to be after we set the default default_encoding.
if file_encoding is None:
......@@ -114,22 +118,12 @@ class Renderer(object):
if isinstance(search_dirs, basestring):
search_dirs = [search_dirs]
# This needs to be after we set some of the defaults above.
if loader is None:
reader = Reader(encoding=file_encoding, decode_errors=decode_errors)
loader = Loader(reader=reader, search_dirs=search_dirs, extension=file_extension)
self.decode_errors = decode_errors
self.default_encoding = default_encoding
self.escape = escape
self.file_encoding = file_encoding
self.file_extension = file_extension
# TODO: we should not store a loader attribute because the loader
# would no longer reflect the current attributes if, say, someone
# changed the search_dirs attribute after instantiation. Instead,
# we should construct the Loader instance each time on the fly,
# as we do with the Reader in the read() method.
self.loader = loader
self.partials = partials
self.search_dirs = search_dirs
def _to_unicode_soft(self, s):
......@@ -191,9 +185,39 @@ class Renderer(object):
return context
def _make_reader(self):
"""
Create a Reader instance using current attributes.
"""
return Reader(encoding=self.file_encoding, decode_errors=self.decode_errors)
def _make_loader(self):
"""
Create a Loader instance using current attributes.
"""
reader = self._make_reader()
loader = Loader(reader=reader, search_dirs=self.search_dirs, extension=self.file_extension)
return loader
def _make_load_partial(self):
"""
Return the load_partial function to pass to RenderEngine.__init__().
"""
if self.partials is None:
loader = self._make_loader()
return loader.get
# Otherwise, create a load_partial function from the custom loader
# that satisfies RenderEngine requirements (and that provides a
# nicer exception, etc).
get_partial = self.partials.get
def load_partial(name):
template = self.loader.get(name)
template = get_partial(name)
if template is None:
# TODO: make a TemplateNotFoundException type that provides
......@@ -226,9 +250,18 @@ class Renderer(object):
attributes.
"""
reader = Reader(encoding=self.file_encoding, decode_errors=self.decode_errors)
reader = self._make_reader()
return reader.read(path)
# TODO: add unit tests for this method.
def load_template(self, template_name):
"""
Load a template by name from the file system.
"""
loader = self._make_loader()
return loader.get(template_name)
def render_path(self, template_path, context=None, **kwargs):
"""
Render the template at the given path using the given context.
......
......@@ -25,23 +25,20 @@ class View(object):
_loader = None
_renderer = None
def __init__(self, template=None, context=None, loader=None, **kwargs):
def __init__(self, template=None, context=None, partials=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.
partials: the object (e.g. pystache.Loader or dictionary)
responsible for loading partials 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. The method should return None if there is no
template with that name, or raise an exception.
"""
if loader is not None:
self._loader = loader
if template is not None:
self.template = template
......@@ -51,6 +48,8 @@ class View(object):
if kwargs:
_context.push(kwargs)
self._partials = partials
self.context = _context
def _get_renderer(self):
......@@ -60,7 +59,7 @@ class View(object):
# instantiation some of the attributes on which the Renderer
# depends. This lets users set the template_extension attribute,
# etc. after View.__init__() has already been called.
renderer = Renderer(loader=self._loader,
renderer = Renderer(partials=self._partials,
file_encoding=self.template_encoding,
search_dirs=self.template_path,
file_extension=self.template_extension)
......@@ -76,7 +75,7 @@ class View(object):
if not self.template:
template_name = self._get_template_name()
renderer = self._get_renderer()
self.template = renderer.loader.get(template_name)
self.template = renderer.load_template(template_name)
return self.template
......
......@@ -23,69 +23,21 @@ class RendererInitTestCase(unittest.TestCase):
"""
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):
def test_partials__default(self):
"""
Test that the default loader is constructed correctly.
"""
renderer = Renderer()
actual = renderer.loader
self.assertTrue(renderer.partials is None)
expected = Loader()
self.assertEquals(type(actual), type(expected))
self.assertEquals(actual.template_extension, expected.template_extension)
self.assertEquals(actual.search_dirs, expected.search_dirs)
self.assertEquals(actual.reader.__dict__, expected.reader.__dict__)
def test_loader__default__encoding(self):
"""
Test that the default loader inherits the correct encoding.
"""
renderer = Renderer(file_encoding='foo')
reader = renderer.loader.reader
self.assertEquals(reader.encoding, 'foo')
def test_loader__default__decode_errors(self):
def test_partials(self):
"""
Test that the default loader inherits decode_errors.
"""
renderer = Renderer(decode_errors='foo')
reader = renderer.loader.reader
self.assertEquals(reader.decode_errors, 'foo')
def test_loader__default__file_extension(self):
"""
Test that the default loader inherits file_extension.
"""
renderer = Renderer(file_extension='foo')
loader = renderer.loader
self.assertEquals(loader.template_extension, 'foo')
def test_loader__default__search_dirs(self):
"""
Test that the default loader inherits search_dirs.
Test that the loader attribute is set correctly.
"""
renderer = Renderer(search_dirs='foo')
loader = renderer.loader
self.assertEquals(loader.search_dirs, ['foo'])
renderer = Renderer(partials={'foo': 'bar'})
self.assertEquals(renderer.partials, {'foo': 'bar'})
def test_escape__default(self):
escape = Renderer().escape
......@@ -264,6 +216,79 @@ class RendererTestCase(unittest.TestCase):
actual = self._read(renderer, filename)
self.assertEquals(actual, 'non-ascii: ')
## Test the _make_loader() method.
def test__make_loader__return_type(self):
"""
Test that _make_loader() returns a Loader.
"""
renderer = Renderer()
loader = renderer._make_loader()
self.assertEquals(type(loader), Loader)
def test__make_loader__file_encoding(self):
"""
Test that _make_loader() respects the file_encoding attribute.
"""
renderer = Renderer()
renderer.file_encoding = 'foo'
loader = renderer._make_loader()
self.assertEquals(loader.reader.encoding, 'foo')
def test__make_loader__decode_errors(self):
"""
Test that _make_loader() respects the decode_errors attribute.
"""
renderer = Renderer()
renderer.decode_errors = 'foo'
loader = renderer._make_loader()
self.assertEquals(loader.reader.decode_errors, 'foo')
def test__make_loader__file_extension(self):
"""
Test that _make_loader() respects the file_extension attribute.
"""
renderer = Renderer()
renderer.file_extension = 'foo'
loader = renderer._make_loader()
self.assertEquals(loader.template_extension, 'foo')
def test__make_loader__search_dirs(self):
"""
Test that _make_loader() respects the search_dirs attribute.
"""
renderer = Renderer()
renderer.search_dirs = ['foo']
loader = renderer._make_loader()
self.assertEquals(loader.search_dirs, ['foo'])
# This test is a sanity check. Strictly speaking, it shouldn't
# be necessary based on our tests above.
def test__make_loader__default(self):
renderer = Renderer()
actual = renderer._make_loader()
expected = Loader()
self.assertEquals(type(actual), type(expected))
self.assertEquals(actual.template_extension, expected.template_extension)
self.assertEquals(actual.search_dirs, expected.search_dirs)
self.assertEquals(actual.reader.__dict__, expected.reader.__dict__)
## Test the render() method.
def test_render__return_type(self):
......@@ -354,8 +379,8 @@ class RendererTestCase(unittest.TestCase):
Test the _make_load_partial() method.
"""
partials = {'foo': 'bar'}
renderer = Renderer(loader=partials)
renderer = Renderer()
renderer.partials = {'foo': 'bar'}
load_partial = renderer._make_load_partial()
actual = load_partial('foo')
......@@ -370,12 +395,12 @@ class RendererTestCase(unittest.TestCase):
"""
renderer = Renderer()
renderer.loader = {'partial': 'foo'}
renderer.partials = {'partial': 'foo'}
load_partial = renderer._make_load_partial()
self.assertEquals(load_partial("partial"), "foo")
# Now with a value that is already unicode.
renderer.loader = {'partial': u'foo'}
renderer.partials = {'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
......@@ -415,7 +440,7 @@ class Renderer_MakeRenderEngineTests(unittest.TestCase):
renderer = Renderer()
renderer.default_encoding = 'ascii'
renderer.loader = {'str': 'foo', 'subclass': MyUnicode('abc')}
renderer.partials = {'str': 'foo', 'subclass': MyUnicode('abc')}
engine = renderer._make_render_engine()
......@@ -434,7 +459,7 @@ class Renderer_MakeRenderEngineTests(unittest.TestCase):
"""
renderer = Renderer()
renderer.loader = {}
renderer.partials = {}
engine = renderer._make_render_engine()
load_partial = engine.load_partial
......
......@@ -53,7 +53,7 @@ class ViewTestCase(unittest.TestCase):
"""
template = "{{>partial}}"
partials = {"partial": "Loaded from dictionary"}
view = Simple(template=template, loader=partials)
view = Simple(template=template, partials=partials)
actual = view.render()
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