Commit 761ccf44 by Chris Jerdonek

Changed the Loader class to a Locator class.

Now the class no longer has an indirect dependency on the Reader class.
parent c8ee84f9
......@@ -19,7 +19,6 @@ import sys
#
# ValueError: Attempted relative import in non-package
#
from pystache.loader import Loader
from pystache.renderer import Renderer
......@@ -54,8 +53,10 @@ def main(sys_argv):
if template.endswith('.mustache'):
template = template[:-9]
renderer = Renderer()
try:
template = Loader().get(template)
template = renderer.load_template(template)
except IOError:
pass
......@@ -64,8 +65,6 @@ def main(sys_argv):
except IOError:
context = json.loads(context)
renderer = Renderer()
rendered = renderer.render(template, context)
print rendered
......
......@@ -7,10 +7,9 @@ This module contains the initialization logic called by __init__.py.
from .renderer import Renderer
from .view import View
from .loader import Loader
__all__ = ['render', 'Loader', 'Renderer', 'View']
__all__ = ['render', 'Renderer', 'View']
def render(template, context=None, **kwargs):
......
# coding: utf-8
"""
This module provides a Loader class.
This module provides a Locator class.
"""
......@@ -9,8 +9,6 @@ import os
import re
import sys
from .reader import Reader
DEFAULT_EXTENSION = 'mustache'
......@@ -38,11 +36,11 @@ def make_template_name(obj):
return re.sub('[A-Z]', repl, template_name)[1:]
class Loader(object):
class Locator(object):
def __init__(self, search_dirs=None, extension=None, reader=None):
def __init__(self, search_dirs=None, extension=None):
"""
Construct a template loader.
Construct a template locator.
Arguments:
......@@ -54,14 +52,7 @@ class Loader(object):
extension: the template file extension. Defaults to "mustache".
Pass False for no extension (i.e. extensionless template files).
reader: the Reader instance to use to read file contents and
return them as unicode strings. Defaults to constructing
the default Reader with no constructor arguments.
"""
if reader is None:
reader = Reader()
if extension is None:
extension = DEFAULT_EXTENSION
......@@ -71,17 +62,9 @@ class Loader(object):
if isinstance(search_dirs, basestring):
search_dirs = [search_dirs]
self.reader = reader
self.search_dirs = search_dirs
self.template_extension = extension
def _read(self, path):
"""
Read and return a template as a unicode string.
"""
return self.reader.read(path)
def make_file_name(self, template_name):
file_name = template_name
if self.template_extension is not False:
......@@ -89,9 +72,9 @@ class Loader(object):
return file_name
def get(self, template_name):
def locate_path(self, template_name):
"""
Find and load the given template, and return it as a string.
Find and return the path to the template with the given name.
Raises an IOError if the template cannot be found.
......@@ -103,7 +86,7 @@ class Loader(object):
for dir_path in search_dirs:
file_path = os.path.join(dir_path, file_name)
if os.path.exists(file_path):
return self._read(file_path)
return file_path
# TODO: we should probably raise an exception of our own type.
raise IOError('"%s" not found in "%s"' % (template_name, ':'.join(search_dirs),))
......
......@@ -11,7 +11,7 @@ import sys
from .context import Context
from .loader import DEFAULT_EXTENSION
from .loader import Loader
from .loader import Locator
from .reader import Reader
from .renderengine import RenderEngine
......@@ -32,7 +32,7 @@ class Renderer(object):
passing a custom partial loader.
Here is an example of rendering a template using a custom partial loader
that loads partials loaded from a string-string dictionary.
that loads partials from a string-string dictionary.
>>> partials = {'partial': 'Hello, {{thing}}!'}
>>> renderer = Renderer(partials=partials)
......@@ -49,8 +49,8 @@ class Renderer(object):
Arguments:
partials: an object (e.g. pystache.Loader or dictionary) for
custom partial loading during the rendering process.
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,
......@@ -58,8 +58,8 @@ class Renderer(object):
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.
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
......@@ -174,15 +174,26 @@ class Renderer(object):
"""
return Reader(encoding=self.file_encoding, decode_errors=self.decode_errors)
def _make_loader(self):
def _make_locator(self):
"""
Create a Loader instance using current attributes.
Create a Locator instance using current attributes.
"""
return Locator(search_dirs=self.search_dirs, extension=self.file_extension)
def _make_load_template(self):
"""
Return a function that loads a template by name.
"""
reader = self._make_reader()
loader = Loader(reader=reader, search_dirs=self.search_dirs, extension=self.file_extension)
locator = self._make_locator()
def load_template(template_name):
path = locator.locate_path(template_name)
return reader.read(path)
return loader
return load_template
def _make_load_partial(self):
"""
......@@ -190,20 +201,20 @@ class Renderer(object):
"""
if self.partials is None:
loader = self._make_loader()
return loader.get
load_template = self._make_load_template()
return load_template
# 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
# Otherwise, create a load_partial function from the custom partial
# loader that satisfies RenderEngine requirements (and that provides
# a nicer exception, etc).
partials = self.partials
def load_partial(name):
template = get_partial(name)
template = partials.get(name)
if template is None:
# TODO: make a TemplateNotFoundException type that provides
# the original loader as an attribute.
# the original partials as an attribute.
raise Exception("Partial not found with name: %s" % repr(name))
# RenderEngine requires that the return value be unicode.
......@@ -241,8 +252,8 @@ class Renderer(object):
Load a template by name from the file system.
"""
loader = self._make_loader()
return loader.get(template_name)
load_template = self._make_load_template()
return load_template(template_name)
def render_path(self, template_path, *context, **kwargs):
"""
......
......@@ -18,7 +18,6 @@ class View(object):
template_encoding = None
template_extension = None
_loader = None
_renderer = None
def __init__(self, template=None, context=None, partials=None, **kwargs):
......
# encoding: utf-8
"""
Contains loader.py unit tests.
"""
import os
import sys
import unittest
from pystache.loader import make_template_name
from pystache.loader import Loader
from pystache.loader import Locator
from pystache.reader import Reader
from .common import DATA_DIR
......@@ -25,93 +30,80 @@ class MakeTemplateNameTests(unittest.TestCase):
self.assertEquals(make_template_name(foo), 'foo_bar')
class LoaderTestCase(unittest.TestCase):
class LocatorTests(unittest.TestCase):
search_dirs = 'examples'
def _loader(self):
return Loader(search_dirs=DATA_DIR)
def _locator(self):
return Locator(search_dirs=DATA_DIR)
def test_init__search_dirs(self):
# Test the default value.
loader = Loader()
self.assertEquals(loader.search_dirs, [os.curdir])
locator = Locator()
self.assertEquals(locator.search_dirs, [os.curdir])
loader = Loader(search_dirs=['foo'])
self.assertEquals(loader.search_dirs, ['foo'])
locator = Locator(search_dirs=['foo'])
self.assertEquals(locator.search_dirs, ['foo'])
def test_init__extension(self):
# Test the default value.
loader = Loader()
self.assertEquals(loader.template_extension, 'mustache')
loader = Loader(extension='txt')
self.assertEquals(loader.template_extension, 'txt')
locator = Locator()
self.assertEquals(locator.template_extension, 'mustache')
loader = Loader(extension=False)
self.assertTrue(loader.template_extension is False)
locator = Locator(extension='txt')
self.assertEquals(locator.template_extension, 'txt')
def test_init__reader(self):
# Test the default value.
loader = Loader()
reader = loader.reader
self.assertEquals(reader.encoding, sys.getdefaultencoding())
self.assertEquals(reader.decode_errors, 'strict')
reader = Reader()
loader = Loader(reader=reader)
self.assertTrue(loader.reader is reader)
locator = Locator(extension=False)
self.assertTrue(locator.template_extension is False)
def test_make_file_name(self):
loader = Loader()
locator = Locator()
loader.template_extension = 'bar'
self.assertEquals(loader.make_file_name('foo'), 'foo.bar')
locator.template_extension = 'bar'
self.assertEquals(locator.make_file_name('foo'), 'foo.bar')
loader.template_extension = False
self.assertEquals(loader.make_file_name('foo'), 'foo')
locator.template_extension = False
self.assertEquals(locator.make_file_name('foo'), 'foo')
loader.template_extension = ''
self.assertEquals(loader.make_file_name('foo'), 'foo.')
locator.template_extension = ''
self.assertEquals(locator.make_file_name('foo'), 'foo.')
def test_get__template_is_loaded(self):
loader = Loader(search_dirs='examples')
template = loader.get('simple')
def test_locate_path(self):
locator = Locator(search_dirs='examples')
path = locator.locate_path('simple')
self.assertEqual(template, 'Hi {{thing}}!{{blank}}')
self.assertEquals(os.path.basename(path), 'simple.mustache')
def test_get__using_list_of_paths(self):
loader = Loader(search_dirs=['doesnt_exist', 'examples'])
template = loader.get('simple')
def test_locate_path__using_list_of_paths(self):
locator = Locator(search_dirs=['doesnt_exist', 'examples'])
path = locator.locate_path('simple')
self.assertEqual(template, 'Hi {{thing}}!{{blank}}')
self.assertTrue(path)
def test_get__non_existent_template_fails(self):
loader = Loader()
def test_locate_path__precedence(self):
"""
Test the order in which locate_path() searches directories.
self.assertRaises(IOError, loader.get, 'doesnt_exist')
"""
locator = Locator()
def test_get__extensionless_file(self):
loader = Loader(search_dirs=self.search_dirs)
self.assertRaises(IOError, loader.get, 'extensionless')
dir1 = DATA_DIR
dir2 = os.path.join(DATA_DIR, 'locator')
loader.template_extension = False
self.assertEquals(loader.get('extensionless'), "No file extension: {{foo}}")
locator.search_dirs = [dir1]
self.assertTrue(locator.locate_path('duplicate'))
locator.search_dirs = [dir2]
self.assertTrue(locator.locate_path('duplicate'))
def test_get(self):
"""
Test get().
locator.search_dirs = [dir2, dir1]
path = locator.locate_path('duplicate')
dirpath = os.path.dirname(path)
dirname = os.path.split(dirpath)[-1]
"""
loader = self._loader()
self.assertEquals(loader.get('ascii'), 'ascii: abc')
self.assertEquals(dirname, 'locator')
def test_get__unicode_return_value(self):
"""
Test that get() returns unicode strings.
def test_locate_path__non_existent_template_fails(self):
locator = Locator()
"""
loader = self._loader()
actual = loader.get('ascii')
self.assertEqual(type(actual), unicode)
self.assertRaises(IOError, locator.locate_path, 'doesnt_exist')
......@@ -12,7 +12,7 @@ import unittest
from pystache import renderer
from pystache.renderer import Renderer
from pystache.loader import Loader
from pystache.loader import Locator
from .common import get_data_path
......@@ -25,7 +25,7 @@ class RendererInitTestCase(unittest.TestCase):
def test_partials__default(self):
"""
Test that the default loader is constructed correctly.
Test the default value.
"""
renderer = Renderer()
......@@ -33,7 +33,7 @@ class RendererInitTestCase(unittest.TestCase):
def test_partials(self):
"""
Test that the loader attribute is set correctly.
Test that the attribute is set correctly.
"""
renderer = Renderer(partials={'foo': 'bar'})
......@@ -216,78 +216,53 @@ class RendererTestCase(unittest.TestCase):
actual = self._read(renderer, filename)
self.assertEquals(actual, 'non-ascii: ')
## Test the _make_loader() method.
## Test the _make_locator() method.
def test__make_loader__return_type(self):
def test__make_locator__return_type(self):
"""
Test that _make_loader() returns a Loader.
Test that _make_locator() returns a Locator.
"""
renderer = Renderer()
loader = renderer._make_loader()
locator = renderer._make_locator()
self.assertEquals(type(loader), Loader)
self.assertEquals(type(locator), Locator)
def test__make_loader__file_encoding(self):
def test__make_locator__file_extension(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.
Test that _make_locator() respects the file_extension attribute.
"""
renderer = Renderer()
renderer.file_extension = 'foo'
loader = renderer._make_loader()
locator = renderer._make_locator()
self.assertEquals(loader.template_extension, 'foo')
self.assertEquals(locator.template_extension, 'foo')
def test__make_loader__search_dirs(self):
def test__make_locator__search_dirs(self):
"""
Test that _make_loader() respects the search_dirs attribute.
Test that _make_locator() respects the search_dirs attribute.
"""
renderer = Renderer()
renderer.search_dirs = ['foo']
loader = renderer._make_loader()
locator = renderer._make_locator()
self.assertEquals(loader.search_dirs, ['foo'])
self.assertEquals(locator.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):
def test__make_locator__default(self):
renderer = Renderer()
actual = renderer._make_loader()
actual = renderer._make_locator()
expected = Loader()
expected = Locator()
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.
......
......@@ -42,13 +42,13 @@ class ViewTestCase(unittest.TestCase):
view = Simple("Hi {{thing}}!", thing='world')
self.assertEquals(view.render(), "Hi world!")
def test_template_load(self):
def test_render(self):
view = Simple(thing='world')
self.assertEquals(view.render(), "Hi world!")
def test_load_template__custom_loader(self):
def test_render__partials(self):
"""
Test passing a custom loader to View.__init__().
Test passing partials to View.__init__().
"""
template = "{{>partial}}"
......
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