Commit 58494244 by Chris Jerdonek

Merge 'issue_72' into development: closing issue #73 (Reader class)

Created a Reader class to encapsulate reading files with the encoding
and decode_errors attributes.
parents b2f59598 d9769c86
...@@ -8,13 +8,15 @@ This module provides a Loader class. ...@@ -8,13 +8,15 @@ This module provides a Loader class.
import os import os
import sys import sys
DEFAULT_DECODE_ERRORS = 'strict' from .reader import Reader
DEFAULT_EXTENSION = 'mustache' DEFAULT_EXTENSION = 'mustache'
class Loader(object): class Loader(object):
def __init__(self, search_dirs=None, extension=None, encoding=None, decode_errors=None): def __init__(self, search_dirs=None, extension=None, reader=None):
""" """
Construct a template loader. Construct a template loader.
...@@ -28,21 +30,13 @@ class Loader(object): ...@@ -28,21 +30,13 @@ 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).
encoding: the name of the encoding to use when converting file reader: the Reader instance to use to read file contents and
contents to unicode. This name will be passed as the encoding return them as unicode strings. Defaults to constructing
argument to the built-in function unicode(). Defaults to the the default Reader with no constructor arguments.
encoding name returned by sys.getdefaultencoding().
decode_errors: the string to pass as the "errors" argument to the
built-in function unicode() when converting file contents to
unicode. Defaults to "strict".
""" """
if decode_errors is None: if reader is None:
decode_errors = DEFAULT_DECODE_ERRORS reader = Reader()
if encoding is None:
encoding = sys.getdefaultencoding()
if extension is None: if extension is None:
extension = DEFAULT_EXTENSION extension = DEFAULT_EXTENSION
...@@ -53,11 +47,17 @@ class Loader(object): ...@@ -53,11 +47,17 @@ class Loader(object):
if isinstance(search_dirs, basestring): if isinstance(search_dirs, basestring):
search_dirs = [search_dirs] search_dirs = [search_dirs]
self.decode_errors = decode_errors self.reader = reader
self.search_dirs = search_dirs self.search_dirs = search_dirs
self.template_encoding = encoding
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:
...@@ -79,23 +79,8 @@ class Loader(object): ...@@ -79,23 +79,8 @@ 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._load_template_file(file_path) return self._read(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),))
def _load_template_file(self, file_path):
"""
Read a template file, and return it as a string.
"""
f = open(file_path, 'r')
try:
template = f.read()
finally:
f.close()
template = unicode(template, self.template_encoding, self.decode_errors)
return template
# coding: utf-8
"""
This module provides a Reader class to read a template given a path.
"""
from __future__ import with_statement
import os
import sys
DEFAULT_DECODE_ERRORS = 'strict'
class Reader(object):
def __init__(self, encoding=None, decode_errors=None):
"""
Construct a template reader.
Arguments:
encoding: the file encoding. This is the name of the encoding to
use when converting file contents to unicode. This name is
passed as the encoding argument to Python's built-in function
unicode(). Defaults to the encoding name returned by
sys.getdefaultencoding().
decode_errors: the string to pass as the errors argument to the
built-in function unicode() when converting file contents to
unicode. Defaults to "strict".
"""
if decode_errors is None:
decode_errors = DEFAULT_DECODE_ERRORS
if encoding is None:
encoding = sys.getdefaultencoding()
self.decode_errors = decode_errors
self.encoding = encoding
def read(self, path):
"""
Read the template at the given path, and return it as a unicode string.
"""
with open(path, 'r') as f:
text = f.read()
text = unicode(text, self.encoding, self.decode_errors)
return text
...@@ -10,6 +10,7 @@ import sys ...@@ -10,6 +10,7 @@ import sys
from .context import Context from .context import Context
from .loader import Loader from .loader import Loader
from .reader import Reader
from .renderengine import RenderEngine from .renderengine import RenderEngine
...@@ -47,9 +48,8 @@ class Renderer(object): ...@@ -47,9 +48,8 @@ class Renderer(object):
as a unicode string. If there is no template with that name, as a unicode string. If there is no template with that name,
the method should either return None (as dict.get() does) or the method should either return None (as dict.get() does) or
raise an exception. raise an exception.
Defaults to constructing a Loader instance with Defaults to constructing a default Loader, but using the
default_encoding and decode_errors passed as the encoding and default_encoding and decode_errors arguments.
decode_errors arguments, respectively.
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
...@@ -86,7 +86,8 @@ class Renderer(object): ...@@ -86,7 +86,8 @@ class Renderer(object):
escape = lambda s: cgi.escape(s, quote=True) escape = lambda s: cgi.escape(s, quote=True)
if loader is None: if loader is None:
loader = Loader(encoding=default_encoding, decode_errors=decode_errors) reader = Reader(encoding=default_encoding, decode_errors=decode_errors)
loader = Loader(reader=reader)
self.decode_errors = decode_errors self.decode_errors = decode_errors
self.default_encoding = default_encoding self.default_encoding = default_encoding
......
...@@ -10,6 +10,7 @@ from types import UnboundMethodType ...@@ -10,6 +10,7 @@ from types import UnboundMethodType
from .context import Context from .context import Context
from .loader import Loader from .loader import Loader
from .reader import Reader
from .renderer import Renderer from .renderer import Renderer
...@@ -57,7 +58,8 @@ class View(object): ...@@ -57,7 +58,8 @@ class View(object):
# user did not supply a load_template to the constructor) # user did not supply a load_template to the constructor)
# to let users set the template_extension attribute, etc. after # to let users set the template_extension attribute, etc. after
# View.__init__() has already been called. # View.__init__() has already been called.
loader = Loader(search_dirs=self.template_path, encoding=self.template_encoding, reader = Reader(encoding=self.template_encoding)
loader = Loader(search_dirs=self.template_path, reader=reader,
extension=self.template_extension) extension=self.template_extension)
self._loader = loader self._loader = loader
......
...@@ -5,6 +5,7 @@ import sys ...@@ -5,6 +5,7 @@ import sys
import unittest import unittest
from pystache.loader import Loader from pystache.loader import Loader
from pystache.reader import Reader
DATA_DIR = 'tests/data' DATA_DIR = 'tests/data'
...@@ -23,22 +24,6 @@ class LoaderTestCase(unittest.TestCase): ...@@ -23,22 +24,6 @@ class LoaderTestCase(unittest.TestCase):
loader = Loader(search_dirs=['foo']) loader = Loader(search_dirs=['foo'])
self.assertEquals(loader.search_dirs, ['foo']) self.assertEquals(loader.search_dirs, ['foo'])
def test_init__decode_errors(self):
# Test the default value.
loader = Loader()
self.assertEquals(loader.decode_errors, 'strict')
loader = Loader(decode_errors='replace')
self.assertEquals(loader.decode_errors, 'replace')
def test_init__encoding(self):
# Test the default value.
loader = Loader()
self.assertEquals(loader.template_encoding, sys.getdefaultencoding())
loader = Loader(encoding='foo')
self.assertEquals(loader.template_encoding, 'foo')
def test_init__extension(self): def test_init__extension(self):
# Test the default value. # Test the default value.
loader = Loader() loader = Loader()
...@@ -50,6 +35,17 @@ class LoaderTestCase(unittest.TestCase): ...@@ -50,6 +35,17 @@ class LoaderTestCase(unittest.TestCase):
loader = Loader(extension=False) loader = Loader(extension=False)
self.assertTrue(loader.template_extension is False) self.assertTrue(loader.template_extension is False)
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)
def test_make_file_name(self): def test_make_file_name(self):
loader = Loader() loader = Loader()
...@@ -103,25 +99,3 @@ class LoaderTestCase(unittest.TestCase): ...@@ -103,25 +99,3 @@ class LoaderTestCase(unittest.TestCase):
actual = loader.get('ascii') actual = loader.get('ascii')
self.assertEqual(type(actual), unicode) self.assertEqual(type(actual), unicode)
def test_get__encoding(self):
"""
Test get(): encoding attribute respected.
"""
loader = self._loader()
self.assertRaises(UnicodeDecodeError, loader.get, 'nonascii')
loader.template_encoding = 'utf-8'
self.assertEquals(loader.get('nonascii'), u'non-ascii: é')
def test_get__decode_errors(self):
"""
Test get(): decode_errors attribute.
"""
loader = self._loader()
self.assertRaises(UnicodeDecodeError, loader.get, 'nonascii')
loader.decode_errors = 'replace'
self.assertEquals(loader.get('nonascii'), u'non-ascii: \ufffd\ufffd')
# encoding: utf-8
"""
Unit tests of reader.py.
"""
import os
import sys
import unittest
from pystache.reader import Reader
DATA_DIR = 'tests/data'
class ReaderTestCase(unittest.TestCase):
def _get_path(self, filename):
return os.path.join(DATA_DIR, filename)
def test_init__decode_errors(self):
# Test the default value.
reader = Reader()
self.assertEquals(reader.decode_errors, 'strict')
reader = Reader(decode_errors='replace')
self.assertEquals(reader.decode_errors, 'replace')
def test_init__encoding(self):
# Test the default value.
reader = Reader()
self.assertEquals(reader.encoding, sys.getdefaultencoding())
reader = Reader(encoding='foo')
self.assertEquals(reader.encoding, 'foo')
def test_read(self):
"""
Test read().
"""
reader = Reader()
path = self._get_path('ascii.mustache')
self.assertEquals(reader.read(path), 'ascii: abc')
def test_read__returns_unicode(self):
"""
Test that read() returns unicode strings.
"""
reader = Reader()
path = self._get_path('ascii.mustache')
contents = reader.read(path)
self.assertEqual(type(contents), unicode)
def test_read__encoding(self):
"""
Test read(): encoding attribute respected.
"""
reader = Reader()
path = self._get_path('nonascii.mustache')
self.assertRaises(UnicodeDecodeError, reader.read, path)
reader.encoding = 'utf-8'
self.assertEquals(reader.read(path), u'non-ascii: é')
def test_get__decode_errors(self):
"""
Test get(): decode_errors attribute.
"""
reader = Reader()
path = self._get_path('nonascii.mustache')
self.assertRaises(UnicodeDecodeError, reader.read, path)
reader.decode_errors = 'replace'
self.assertEquals(reader.read(path), u'non-ascii: \ufffd\ufffd')
...@@ -35,39 +35,35 @@ class RendererInitTestCase(unittest.TestCase): ...@@ -35,39 +35,35 @@ class RendererInitTestCase(unittest.TestCase):
Test that the default loader is constructed correctly. Test that the default loader is constructed correctly.
""" """
r = Renderer() renderer = Renderer()
actual = r.loader actual = renderer.loader
expected = Loader() expected = Loader()
self.assertEquals(type(actual), type(expected)) self.assertEquals(type(actual), type(expected))
self.assertEquals(actual.__dict__, expected.__dict__) 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__default_encoding(self): def test_loader__default__default_encoding(self):
""" """
Test that the default loader inherits the default_encoding. Test that the default loader inherits default_encoding.
""" """
r = Renderer(default_encoding='foo') renderer = Renderer(default_encoding='foo')
actual = r.loader reader = renderer.loader.reader
expected = Loader(encoding='foo') self.assertEquals(reader.encoding, 'foo')
self.assertEquals(actual.template_encoding, expected.template_encoding)
# Check all attributes for good measure.
self.assertEquals(actual.__dict__, expected.__dict__)
def test_loader__default__decode_errors(self): def test_loader__default__decode_errors(self):
""" """
Test that the default loader inherits the decode_errors. Test that the default loader inherits decode_errors.
""" """
r = Renderer(decode_errors='foo') renderer = Renderer(decode_errors='foo')
actual = r.loader reader = renderer.loader.reader
expected = Loader(decode_errors='foo') self.assertEquals(reader.decode_errors, 'foo')
self.assertEquals(actual.decode_errors, expected.decode_errors)
# Check all attributes for good measure.
self.assertEquals(actual.__dict__, expected.__dict__)
def test_escape__default(self): def test_escape__default(self):
escape = Renderer().escape escape = Renderer().escape
......
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