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