Commit 98e4f43f by Chris Jerdonek

The View class now does all its loading through a Renderer instance.

This will help with future refactoring and code maintenance by not
having the same logic duplicated in more than one part of the code.
It also takes more responsibility away from the View class, which
is what we want.
parent 4720fa7f
......@@ -6,9 +6,11 @@ This module provides a Renderer class to render templates.
"""
import cgi
import os
import sys
from .context import Context
from .loader import DEFAULT_EXTENSION
from .loader import Loader
from .reader import Reader
from .renderengine import RenderEngine
......@@ -34,7 +36,8 @@ class Renderer(object):
"""
def __init__(self, loader=None, file_encoding=None, default_encoding=None,
decode_errors='strict', escape=None):
decode_errors='strict', search_dirs=None, file_extension=None,
escape=None):
"""
Construct an instance.
......@@ -80,28 +83,54 @@ class Renderer(object):
strings of type str encountered during the rendering process.
Defaults to "strict".
search_dirs: the list of directories in which to search for
templates when loading a template by name. Defaults to the
current working directory. If given a string, the string is
interpreted as a single directory.
file_extension: the template file extension. Defaults to "mustache".
Pass False for no extension (i.e. for extensionless files).
"""
if default_encoding is None:
default_encoding = sys.getdefaultencoding()
if file_encoding is None:
file_encoding = default_encoding
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)
# This needs to be after we set the default default_encoding.
if file_encoding is None:
file_encoding = default_encoding
if file_extension is None:
file_extension = DEFAULT_EXTENSION
if search_dirs is None:
search_dirs = os.curdir # i.e. "."
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)
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.search_dirs = search_dirs
def _to_unicode_soft(self, s):
"""
......
......@@ -23,6 +23,7 @@ class View(object):
template_extension = None
_loader = None
_renderer = None
def __init__(self, template=None, context=None, loader=None, **kwargs):
"""
......@@ -52,22 +53,20 @@ class View(object):
self.context = _context
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.
reader = Reader(encoding=self.template_encoding)
loader = Loader(search_dirs=self.template_path, reader=reader,
extension=self.template_extension)
self._loader = loader
return self._loader
def load_template(self, template_name):
loader = self.get_loader()
return loader.get(template_name)
def _get_renderer(self):
if self._renderer is None:
# We delay setting self._renderer until now (instead of, say,
# setting it in the constructor) in case the user changes after
# 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,
file_encoding=self.template_encoding,
search_dirs=self.template_path,
file_extension=self.template_extension)
self._renderer = renderer
return self._renderer
def get_template(self):
"""
......@@ -76,7 +75,8 @@ class View(object):
"""
if not self.template:
template_name = self._get_template_name()
self.template = self.load_template(template_name)
renderer = self._get_renderer()
self.template = renderer.loader.get(template_name)
return self.template
......@@ -102,19 +102,13 @@ class View(object):
return re.sub('[A-Z]', repl, template_name)[1:]
# TODO: the View class should probably have some sort of template renderer
# associated with it to encapsulate all of the render-specific behavior
# and options like encoding, escape, etc. This would probably be better
# than passing all of these options to render(), especially as the list
# of possible options grows.
def render(self, escape=None):
def render(self):
"""
Return the view rendered using the current context.
"""
loader = self.get_loader()
template = self.get_template()
renderer = Renderer(escape=escape, loader=loader)
renderer = self._get_renderer()
return renderer.render(template, self.context)
def get(self, key, default=None):
......
......@@ -31,10 +31,6 @@ class TestView(unittest.TestCase):
def test_escaping(self):
self.assertEquals(Escaped().render(), "<h1>Bear &gt; Shark</h1>")
def test_escaping__custom(self):
escape = lambda s: s.upper()
self.assertEquals(Escaped().render(escape=escape), "<h1>BEAR > SHARK</h1>")
def test_literal(self):
self.assertEquals(Unescaped().render(), "<h1>Bear > Shark</h1>")
......
......@@ -6,6 +6,7 @@ Unit tests of template.py.
"""
import codecs
import os
import sys
import unittest
......@@ -66,6 +67,26 @@ class RendererInitTestCase(unittest.TestCase):
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.
"""
renderer = Renderer(search_dirs='foo')
loader = renderer.loader
self.assertEquals(loader.search_dirs, ['foo'])
def test_escape__default(self):
escape = Renderer().escape
......@@ -113,7 +134,7 @@ class RendererInitTestCase(unittest.TestCase):
def test_file_encoding__default(self):
"""
Check that file_encoding defaults to default_encoding.
Check the file_encoding default.
"""
renderer = Renderer()
......@@ -127,6 +148,46 @@ class RendererInitTestCase(unittest.TestCase):
renderer = Renderer(file_encoding='foo')
self.assertEquals(renderer.file_encoding, 'foo')
def test_file_extension__default(self):
"""
Check the file_extension default.
"""
renderer = Renderer()
self.assertEquals(renderer.file_extension, 'mustache')
def test_file_extension(self):
"""
Check that the file_encoding attribute is set correctly.
"""
renderer = Renderer(file_extension='foo')
self.assertEquals(renderer.file_extension, 'foo')
def test_search_dirs__default(self):
"""
Check the search_dirs default.
"""
renderer = Renderer()
self.assertEquals(renderer.search_dirs, [os.curdir])
def test_search_dirs__string(self):
"""
Check that the search_dirs attribute is set correctly when a string.
"""
renderer = Renderer(search_dirs='foo')
self.assertEquals(renderer.search_dirs, ['foo'])
def test_search_dirs__list(self):
"""
Check that the search_dirs attribute is set correctly when a list.
"""
renderer = Renderer(search_dirs=['foo'])
self.assertEquals(renderer.search_dirs, ['foo'])
class RendererTestCase(unittest.TestCase):
......
......@@ -46,29 +46,16 @@ class ViewTestCase(unittest.TestCase):
view = Simple(thing='world')
self.assertEquals(view.render(), "Hi world!")
def test_load_template(self):
"""
Test View.load_template().
"""
template = Simple().load_template("escaped")
self.assertEquals(template, "<h1>{{title}}</h1>")
def test_load_template__extensionless_file(self):
view = Simple()
view.template_extension = False
template = view.load_template('extensionless')
self.assertEquals(template, "No file extension: {{foo}}")
def test_load_template__custom_loader(self):
"""
Test passing a custom loader to View.__init__().
"""
template = "{{>partial}}"
partials = {"partial": "Loaded from dictionary"}
view = Simple(loader=partials)
view = Simple(template=template, loader=partials)
actual = view.render()
actual = view.load_template("partial")
self.assertEquals(actual, "Loaded from dictionary")
def test_template_path(self):
......
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