summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--pystache/custom_template.py3
-rw-r--r--pystache/defaults.py27
-rw-r--r--pystache/loader.py87
-rw-r--r--pystache/renderer.py27
-rw-r--r--tests/test_custom_template.py7
-rw-r--r--tests/test_loader.py119
-rw-r--r--tests/test_renderer.py10
7 files changed, 185 insertions, 95 deletions
diff --git a/pystache/custom_template.py b/pystache/custom_template.py
index c3f232d..5f6a79b 100644
--- a/pystache/custom_template.py
+++ b/pystache/custom_template.py
@@ -120,6 +120,9 @@ class View(CustomizedTemplate):
return renderer.render(template, self.context)
+# TODO: finalize this class's name.
+# TODO: get this class fully working with test cases, and then refactor
+# and replace the View class.
class CustomLoader(object):
"""
diff --git a/pystache/defaults.py b/pystache/defaults.py
index b9156d6..69c4995 100644
--- a/pystache/defaults.py
+++ b/pystache/defaults.py
@@ -3,8 +3,15 @@
"""
This module provides a central location for defining default behavior.
+Throughout the package, these defaults take effect only when the user
+does not otherwise specify a value.
+
"""
+import cgi
+import sys
+
+
# How to handle encoding errors when decoding strings from str to unicode.
#
# This value is passed as the "errors" argument to Python's built-in
@@ -14,5 +21,25 @@ This module provides a central location for defining default behavior.
#
DECODE_ERRORS = 'strict'
+# The name of the encoding to use when converting to unicode any strings of
+# type str encountered during the rendering process.
+STRING_ENCODING = sys.getdefaultencoding()
+
+# The name of the encoding to use when converting file contents to unicode.
+# This default takes precedence over the STRING_ENCODING default for
+# strings that arise from files.
+FILE_ENCODING = sys.getdefaultencoding()
+
+# The escape function to apply to strings that require escaping when
+# rendering templates (e.g. for tags enclosed in double braces).
+# Only unicode strings will be passed to this function.
+#
+# The quote=True argument causes double quotes to be escaped,
+# but not single quotes:
+#
+# http://docs.python.org/library/cgi.html#cgi.escape
+#
+TAG_ESCAPE = lambda u: cgi.escape(u, quote=True)
+
# The default template extension.
TEMPLATE_EXTENSION = 'mustache'
diff --git a/pystache/loader.py b/pystache/loader.py
index fb30857..d7cdca1 100644
--- a/pystache/loader.py
+++ b/pystache/loader.py
@@ -1,7 +1,7 @@
# coding: utf-8
"""
-This module provides a Reader class to read a template given a path.
+This module provides a Loader class for locating and reading templates.
"""
@@ -14,62 +14,82 @@ from . import defaults
from .locator import Locator
+def _to_unicode(s, encoding=None):
+ """
+ Raises a TypeError exception if the given string is already unicode.
+
+ """
+ if encoding is None:
+ encoding = defaults.STRING_ENCODING
+ return unicode(s, encoding, defaults.DECODE_ERRORS)
+
+
class Loader(object):
- def __init__(self, encoding=None, decode_errors=None, extension=None):
- """
- Construct a template reader.
+ """
+ Loads the template associated to a name or user-defined object.
- Arguments:
+ """
- decode_errors: the string to pass as the errors argument to the
- built-in function unicode() when converting str strings to
- unicode. Defaults to the package default.
+ def __init__(self, file_encoding=None, extension=None, to_unicode=None):
+ """
+ Construct a template loader instance.
- 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().
+ Arguments:
extension: the template file extension. Pass False for no
extension (i.e. to use extensionless template files).
Defaults to the package default.
- """
- if decode_errors is None:
- decode_errors = defaults.DECODE_ERRORS
+ file_encoding: the name of the encoding to use when converting file
+ contents to unicode. Defaults to the package default.
- if encoding is None:
- encoding = sys.getdefaultencoding()
+ to_unicode: the function to use when converting strings of type
+ str to unicode. The function should have the signature:
+
+ to_unicode(s, encoding=None)
+
+ It should accept a string of type str and an optional encoding
+ name and return a string of type unicode. Defaults to calling
+ Python's built-in function unicode() using the package encoding
+ and decode-errors defaults.
+ """
if extension is None:
extension = defaults.TEMPLATE_EXTENSION
- self.decode_errors = decode_errors
- self.encoding = encoding
+ if file_encoding is None:
+ file_encoding = defaults.FILE_ENCODING
+
+ if to_unicode is None:
+ to_unicode = _to_unicode
+
self.extension = extension
+ self.file_encoding = file_encoding
+ self.to_unicode = to_unicode
- # TODO: eliminate redundancy with the Renderer class's unicode code.
def unicode(self, s, encoding=None):
"""
- Call Python's built-in function unicode(), and return the result.
+ Convert a string to unicode using the given encoding, and return it.
+
+ This function uses the underlying to_unicode attribute.
+
+ Arguments:
- For unicode strings (or unicode subclasses), this function calls
- Python's unicode() without the encoding and errors arguments.
- Thus, unlike Python's built-in unicode(), it is okay to pass unicode
- strings to this function. (Passing a unicode string to Python's
- unicode() with the encoding argument throws the following
- error: "TypeError: decoding Unicode is not supported.")
+ s: a basestring instance to convert to unicode. Unlike Python's
+ built-in unicode() function, it is okay to pass unicode strings
+ to this function. (Passing a unicode string to Python's unicode()
+ with the encoding argument throws the error, "TypeError: decoding
+ Unicode is not supported.")
+
+ encoding: the encoding to pass to the to_unicode attribute.
+ Defaults to None.
"""
if isinstance(s, unicode):
return unicode(s)
- if encoding is None:
- encoding = self.encoding
-
- return unicode(s, encoding, self.decode_errors)
+ return self.to_unicode(s, encoding)
def read(self, path, encoding=None):
"""
@@ -79,6 +99,9 @@ class Loader(object):
with open(path, 'r') as f:
text = f.read()
+ if encoding is None:
+ encoding = self.file_encoding
+
return self.unicode(text, encoding)
# TODO: unit-test this method.
diff --git a/pystache/renderer.py b/pystache/renderer.py
index ffc3194..9ed0949 100644
--- a/pystache/renderer.py
+++ b/pystache/renderer.py
@@ -5,9 +5,7 @@ This module provides a Renderer class to render templates.
"""
-import cgi
import os
-import sys
from . import defaults
from .context import Context
@@ -15,12 +13,6 @@ from .loader import Loader
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):
"""
@@ -40,6 +32,7 @@ class Renderer(object):
"""
+ # TODO: rename default_encoding to string_encoding.
def __init__(self, file_encoding=None, default_encoding=None,
decode_errors=None, search_dirs=None, file_extension=None,
escape=None, partials=None):
@@ -82,7 +75,7 @@ class Renderer(object):
to unicode any strings of type str encountered during the
rendering process. The name will be passed as the encoding
argument to the built-in function unicode(). Defaults to the
- encoding name returned by sys.getdefaultencoding().
+ package default.
decode_errors: the string to pass as the errors argument to the
built-in function unicode() when converting str strings to
@@ -102,10 +95,10 @@ class Renderer(object):
decode_errors = defaults.DECODE_ERRORS
if default_encoding is None:
- default_encoding = sys.getdefaultencoding()
+ default_encoding = defaults.STRING_ENCODING
if escape is None:
- escape = DEFAULT_ESCAPE
+ escape = defaults.TAG_ESCAPE
# This needs to be after we set the default default_encoding.
if file_encoding is None:
@@ -121,6 +114,7 @@ class Renderer(object):
search_dirs = [search_dirs]
self.decode_errors = decode_errors
+ # TODO: rename this attribute to string_encoding.
self.default_encoding = default_encoding
self.escape = escape
self.file_encoding = file_encoding
@@ -152,7 +146,7 @@ class Renderer(object):
"""
return unicode(self.escape(self._to_unicode_soft(s)))
- def unicode(self, s):
+ def unicode(self, s, encoding=None):
"""
Convert a string to unicode, using default_encoding and decode_errors.
@@ -165,17 +159,20 @@ class Renderer(object):
TypeError: decoding Unicode is not supported
"""
+ if encoding is None:
+ encoding = self.default_encoding
+
# TODO: Wrap UnicodeDecodeErrors with a message about setting
# the default_encoding and decode_errors attributes.
- return unicode(s, self.default_encoding, self.decode_errors)
+ return unicode(s, encoding, self.decode_errors)
def _make_loader(self):
"""
Create a Loader instance using current attributes.
"""
- return Loader(encoding=self.file_encoding, decode_errors=self.decode_errors,
- extension=self.file_extension)
+ return Loader(file_encoding=self.file_encoding, extension=self.file_extension,
+ to_unicode=self.unicode)
def _make_load_template(self):
"""
diff --git a/tests/test_custom_template.py b/tests/test_custom_template.py
index d46b5bf..eedf431 100644
--- a/tests/test_custom_template.py
+++ b/tests/test_custom_template.py
@@ -146,10 +146,11 @@ class CustomLoaderTests(unittest.TestCase, AssertIsMixin, AssertStringMixin):
def test_init__defaults(self):
custom = CustomLoader()
- # Check the reader attribute.
+ # Check the loader attribute.
loader = custom.loader
- self.assertEquals(loader.decode_errors, 'strict')
- self.assertEquals(loader.encoding, sys.getdefaultencoding())
+ self.assertEquals(loader.extension, 'mustache')
+ self.assertEquals(loader.file_encoding, sys.getdefaultencoding())
+ to_unicode = loader.to_unicode
# Check search_dirs.
self.assertEquals(custom.search_dirs, [])
diff --git a/tests/test_loader.py b/tests/test_loader.py
index 1836466..a285e68 100644
--- a/tests/test_loader.py
+++ b/tests/test_loader.py
@@ -10,40 +10,71 @@ import sys
import unittest
from .common import AssertStringMixin
+from pystache import defaults
from pystache.loader import Loader
DATA_DIR = 'tests/data'
-class LoaderTestCase(unittest.TestCase, AssertStringMixin):
-
- def _get_path(self, filename):
- return os.path.join(DATA_DIR, filename)
-
- def test_init__decode_errors(self):
- # Test the default value.
- reader = Loader()
- self.assertEquals(reader.decode_errors, 'strict')
-
- reader = Loader(decode_errors='replace')
- self.assertEquals(reader.decode_errors, 'replace')
-
- def test_init__encoding(self):
- # Test the default value.
- reader = Loader()
- self.assertEquals(reader.encoding, sys.getdefaultencoding())
-
- reader = Loader(encoding='foo')
- self.assertEquals(reader.encoding, 'foo')
+class LoaderTests(unittest.TestCase, AssertStringMixin):
def test_init__extension(self):
+ loader = Loader(extension='foo')
+ self.assertEquals(loader.extension, 'foo')
+
+ def test_init__extension__default(self):
# Test the default value.
- reader = Loader()
- self.assertEquals(reader.extension, 'mustache')
+ loader = Loader()
+ self.assertEquals(loader.extension, 'mustache')
+
+ def test_init__file_encoding(self):
+ loader = Loader(file_encoding='bar')
+ self.assertEquals(loader.file_encoding, 'bar')
+
+ def test_init__file_encoding__default(self):
+ file_encoding = defaults.FILE_ENCODING
+ try:
+ defaults.FILE_ENCODING = 'foo'
+ loader = Loader()
+ self.assertEquals(loader.file_encoding, 'foo')
+ finally:
+ defaults.FILE_ENCODING = file_encoding
+
+ def test_init__to_unicode(self):
+ to_unicode = lambda x: x
+ loader = Loader(to_unicode=to_unicode)
+ self.assertEquals(loader.to_unicode, to_unicode)
+
+ def test_init__to_unicode__default(self):
+ loader = Loader()
+ self.assertRaises(TypeError, loader.to_unicode, u"abc")
+
+ decode_errors = defaults.DECODE_ERRORS
+ string_encoding = defaults.STRING_ENCODING
+
+ nonascii = 'abcdé'
+
+ try:
+ defaults.DECODE_ERRORS = 'strict'
+ defaults.STRING_ENCODING = 'ascii'
+ loader = Loader()
+ self.assertRaises(UnicodeDecodeError, loader.to_unicode, nonascii)
+
+ defaults.DECODE_ERRORS = 'ignore'
+ loader = Loader()
+ self.assertString(loader.to_unicode(nonascii), u'abcd')
+
+ defaults.STRING_ENCODING = 'utf-8'
+ loader = Loader()
+ self.assertString(loader.to_unicode(nonascii), u'abcdé')
+
+ finally:
+ defaults.DECODE_ERRORS = decode_errors
+ defaults.STRING_ENCODING = string_encoding
- reader = Loader(extension='foo')
- self.assertEquals(reader.extension, 'foo')
+ def _get_path(self, filename):
+ return os.path.join(DATA_DIR, filename)
def test_unicode__basic__input_str(self):
"""
@@ -80,19 +111,24 @@ class LoaderTestCase(unittest.TestCase, AssertStringMixin):
self.assertString(actual, u"foo")
- def test_unicode__encoding_attribute(self):
+ def test_unicode__to_unicode__attribute(self):
"""
Test unicode(): encoding attribute.
"""
reader = Loader()
- non_ascii = u'é'.encode('utf-8')
+ non_ascii = u'abcdé'.encode('utf-8')
self.assertRaises(UnicodeDecodeError, reader.unicode, non_ascii)
- reader.encoding = 'utf-8'
- self.assertEquals(reader.unicode(non_ascii), u"é")
+ def to_unicode(s, encoding=None):
+ if encoding is None:
+ encoding = 'utf-8'
+ return unicode(s, encoding)
+
+ reader.to_unicode = to_unicode
+ self.assertString(reader.unicode(non_ascii), u"abcdé")
def test_unicode__encoding_argument(self):
"""
@@ -101,13 +137,14 @@ class LoaderTestCase(unittest.TestCase, AssertStringMixin):
"""
reader = Loader()
- non_ascii = u'é'.encode('utf-8')
+ non_ascii = u'abcdé'.encode('utf-8')
self.assertRaises(UnicodeDecodeError, reader.unicode, non_ascii)
actual = reader.unicode(non_ascii, encoding='utf-8')
- self.assertEquals(actual, u'é')
+ self.assertString(actual, u'abcdé')
+ # TODO: check the read() unit tests.
def test_read(self):
"""
Test read().
@@ -118,18 +155,18 @@ class LoaderTestCase(unittest.TestCase, AssertStringMixin):
actual = reader.read(path)
self.assertString(actual, u'ascii: abc')
- def test_read__encoding__attribute(self):
+ def test_read__file_encoding__attribute(self):
"""
- Test read(): encoding attribute respected.
+ Test read(): file_encoding attribute respected.
"""
- reader = Loader()
+ loader = Loader()
path = self._get_path('non_ascii.mustache')
- self.assertRaises(UnicodeDecodeError, reader.read, path)
+ self.assertRaises(UnicodeDecodeError, loader.read, path)
- reader.encoding = 'utf-8'
- actual = reader.read(path)
+ loader.file_encoding = 'utf-8'
+ actual = loader.read(path)
self.assertString(actual, u'non-ascii: é')
def test_read__encoding__argument(self):
@@ -145,9 +182,9 @@ class LoaderTestCase(unittest.TestCase, AssertStringMixin):
actual = reader.read(path, encoding='utf-8')
self.assertString(actual, u'non-ascii: é')
- def test_get__decode_errors(self):
+ def test_reader__to_unicode__attribute(self):
"""
- Test get(): decode_errors attribute.
+ Test read(): to_unicode attribute respected.
"""
reader = Loader()
@@ -155,7 +192,7 @@ class LoaderTestCase(unittest.TestCase, AssertStringMixin):
self.assertRaises(UnicodeDecodeError, reader.read, path)
- reader.decode_errors = 'ignore'
- actual = reader.read(path)
- self.assertString(actual, u'non-ascii: ')
+ #reader.decode_errors = 'ignore'
+ #actual = reader.read(path)
+ #self.assertString(actual, u'non-ascii: ')
diff --git a/tests/test_renderer.py b/tests/test_renderer.py
index 5c702d3..49f3999 100644
--- a/tests/test_renderer.py
+++ b/tests/test_renderer.py
@@ -196,19 +196,21 @@ class RendererTestCase(unittest.TestCase):
def test__make_loader__attributes(self):
"""
- Test that _make_locator() sets all attributes correctly..
+ Test that _make_loader() sets all attributes correctly..
"""
+ unicode_ = lambda x: x
+
renderer = Renderer()
- renderer.decode_errors = 'dec'
renderer.file_encoding = 'enc'
renderer.file_extension = 'ext'
+ renderer.unicode = unicode_
loader = renderer._make_loader()
- self.assertEquals(loader.decode_errors, 'dec')
- self.assertEquals(loader.encoding, 'enc')
self.assertEquals(loader.extension, 'ext')
+ self.assertEquals(loader.file_encoding, 'enc')
+ self.assertEquals(loader.to_unicode, unicode_)
## Test the render() method.