Commit 3f3f2a96 by Chris Jerdonek

Merge branch 'issue-102-support-python3' into 'development'

Still more work to do: about 14 failing tests and still need to wire up
the spec tests and README doctests.
parents 844ebdbc 642cade9
...@@ -51,8 +51,8 @@ Use It ...@@ -51,8 +51,8 @@ Use It
:: ::
>>> import pystache >>> import pystache
>>> pystache.render('Hi {{person}}!', {'person': 'Mom'}) >>> print pystache.render('Hi {{person}}!', {'person': 'Mom'})
u'Hi Mom!' Hi Mom!
You can also create dedicated view classes to hold your view logic. You can also create dedicated view classes to hold your view logic.
...@@ -75,8 +75,8 @@ Then your template, say_hello.mustache:: ...@@ -75,8 +75,8 @@ Then your template, say_hello.mustache::
Pull it together:: Pull it together::
>>> renderer = pystache.Renderer() >>> renderer = pystache.Renderer()
>>> renderer.render(hello) >>> print renderer.render(hello)
u'Hello, Pizza!' Hello, Pizza!
Unicode Handling Unicode Handling
...@@ -161,8 +161,8 @@ Author ...@@ -161,8 +161,8 @@ Author
:: ::
>>> context = { 'author': 'Chris Wanstrath', 'email': 'chris@ozmm.org' } >>> context = { 'author': 'Chris Wanstrath', 'email': 'chris@ozmm.org' }
>>> pystache.render("{{author}} :: {{email}}", context) >>> print pystache.render("{{author}} :: {{email}}", context)
u'Chris Wanstrath :: chris@ozmm.org' Chris Wanstrath :: chris@ozmm.org
.. _ctemplate: http://code.google.com/p/google-ctemplate/ .. _ctemplate: http://code.google.com/p/google-ctemplate/
......
# coding: utf-8
"""
Exposes common functions.
"""
# This function was designed to be portable across Python versions -- both
# with older versions and with Python 3 after applying 2to3.
def read(path):
"""
Return the contents of a text file as a byte string.
"""
# Opening in binary mode is necessary for compatibility across Python
# 2 and 3. In both Python 2 and 3, open() defaults to opening files in
# text mode. However, in Python 2, open() returns file objects whose
# read() method returns byte strings (strings of type `str` in Python 2),
# whereas in Python 3, the file object returns unicode strings (strings
# of type `str` in Python 3).
f = open(path, 'rb')
# We avoid use of the with keyword for Python 2.4 support.
try:
return f.read()
finally:
f.close()
...@@ -5,11 +5,16 @@ Defines a Context class to represent mustache(5)'s notion of context. ...@@ -5,11 +5,16 @@ Defines a Context class to represent mustache(5)'s notion of context.
""" """
class NotFound(object): pass # This equals '__builtin__' in Python 2 and 'builtins' in Python 3.
_BUILTIN_MODULE = type(0).__module__
# We use this private global variable as a return value to represent a key # We use this private global variable as a return value to represent a key
# not being found on lookup. This lets us distinguish between the case # not being found on lookup. This lets us distinguish between the case
# of a key's value being None with the case of a key not being found -- # of a key's value being None with the case of a key not being found --
# without having to rely on exceptions (e.g. KeyError) for flow control. # without having to rely on exceptions (e.g. KeyError) for flow control.
class NotFound(object):
pass
_NOT_FOUND = NotFound() _NOT_FOUND = NotFound()
...@@ -34,7 +39,7 @@ def _get_value(item, key): ...@@ -34,7 +39,7 @@ def _get_value(item, key):
# (e.g. catching KeyError). # (e.g. catching KeyError).
if key in item: if key in item:
return item[key] return item[key]
elif type(item).__module__ != '__builtin__': elif type(item).__module__ != _BUILTIN_MODULE:
# Then we consider the argument an "object" for the purposes of # Then we consider the argument an "object" for the purposes of
# the spec. # the spec.
# #
......
...@@ -8,7 +8,12 @@ does not otherwise specify a value. ...@@ -8,7 +8,12 @@ does not otherwise specify a value.
""" """
import cgi try:
# Python 3.2 deprecates cgi.escape() and adds the html module as a replacement.
import html
except ImportError:
import cgi as html
import os import os
import sys import sys
...@@ -39,12 +44,14 @@ SEARCH_DIRS = [os.curdir] # i.e. ['.'] ...@@ -39,12 +44,14 @@ SEARCH_DIRS = [os.curdir] # i.e. ['.']
# rendering templates (e.g. for tags enclosed in double braces). # rendering templates (e.g. for tags enclosed in double braces).
# Only unicode strings will be passed to this function. # Only unicode strings will be passed to this function.
# #
# The quote=True argument causes double quotes to be escaped, # The quote=True argument causes double quotes to be escaped in Python 2,
# but not single quotes: # but not single quotes, and both double quotes and single quotes to be
# escaped in Python 3:
# #
# http://docs.python.org/dev/library/html.html#html.escape
# http://docs.python.org/library/cgi.html#cgi.escape # http://docs.python.org/library/cgi.html#cgi.escape
# #
TAG_ESCAPE = lambda u: cgi.escape(u, quote=True) TAG_ESCAPE = lambda u: html.escape(u, quote=True)
# The default template extension. # The default template extension.
TEMPLATE_EXTENSION = 'mustache' TEMPLATE_EXTENSION = 'mustache'
...@@ -8,6 +8,7 @@ This module provides a Loader class for locating and reading templates. ...@@ -8,6 +8,7 @@ This module provides a Loader class for locating and reading templates.
import os import os
import sys import sys
from pystache import common
from pystache import defaults from pystache import defaults
from pystache.locator import Locator from pystache.locator import Locator
...@@ -106,17 +107,12 @@ class Loader(object): ...@@ -106,17 +107,12 @@ class Loader(object):
Read the template at the given path, and return it as a unicode string. Read the template at the given path, and return it as a unicode string.
""" """
# We avoid use of the with keyword for Python 2.4 support. b = common.read(path)
f = open(path, 'r')
try:
text = f.read()
finally:
f.close()
if encoding is None: if encoding is None:
encoding = self.file_encoding encoding = self.file_encoding
return self.unicode(text, encoding) return self.unicode(b, encoding)
# TODO: unit-test this method. # TODO: unit-test this method.
def load_name(self, name): def load_name(self, name):
......
...@@ -131,7 +131,7 @@ class Parser(object): ...@@ -131,7 +131,7 @@ class Parser(object):
if tag_type == '/': if tag_type == '/':
if tag_key != section_key: if tag_key != section_key:
raise ParsingError("Section end tag mismatch: %s != %s" % (repr(tag_key), repr(section_key))) raise ParsingError("Section end tag mismatch: %s != %s" % (tag_key, section_key))
return ParsedTemplate(parse_tree), template[start_index:match_index], end_index return ParsedTemplate(parse_tree), template[start_index:match_index], end_index
......
...@@ -55,7 +55,7 @@ class RenderEngine(object): ...@@ -55,7 +55,7 @@ class RenderEngine(object):
this class will not pass tag values to literal prior to passing this class will not pass tag values to literal prior to passing
them to this function. This allows for more flexibility, them to this function. This allows for more flexibility,
for example using a custom escape function that handles for example using a custom escape function that handles
incoming strings of type markupssafe.Markup differently incoming strings of type markupsafe.Markup differently
from plain unicode strings. from plain unicode strings.
""" """
...@@ -167,9 +167,28 @@ class RenderEngine(object): ...@@ -167,9 +167,28 @@ class RenderEngine(object):
# TODO: should we check the arity? # TODO: should we check the arity?
template = data(template) template = data(template)
parsed_template = self._parse(template, delimiters=delims) parsed_template = self._parse(template, delimiters=delims)
data = [ data ] data = [data]
elif not hasattr(data, '__iter__') or isinstance(data, dict): else:
data = [ data ] # The cleanest, least brittle way of determining whether
# something supports iteration is by trying to call iter() on it:
#
# http://docs.python.org/library/functions.html#iter
#
# It is not sufficient, for example, to check whether the item
# implements __iter__ () (the iteration protocol). There is
# also __getitem__() (the sequence protocol). In Python 2,
# strings do not implement __iter__(), but in Python 3 they do.
try:
iter(data)
except TypeError:
# Then the value does not support iteration.
data = [data]
else:
# We treat the value as a list (but do not treat strings
# and dicts as lists).
if isinstance(data, (basestring, dict)):
data = [data]
# Otherwise, leave it alone.
parts = [] parts = []
for element in data: for element in data:
......
...@@ -9,7 +9,7 @@ from pystache import defaults ...@@ -9,7 +9,7 @@ from pystache import defaults
from pystache.context import Context from pystache.context import Context
from pystache.loader import Loader from pystache.loader import Loader
from pystache.renderengine import RenderEngine from pystache.renderengine import RenderEngine
from pystache.spec_loader import SpecLoader from pystache.specloader import SpecLoader
from pystache.template_spec import TemplateSpec from pystache.template_spec import TemplateSpec
...@@ -64,10 +64,10 @@ class Renderer(object): ...@@ -64,10 +64,10 @@ class Renderer(object):
this class will only pass it unicode strings. The constructor this class will only pass it unicode strings. The constructor
assigns this function to the constructed instance's escape() assigns this function to the constructed instance's escape()
method. method.
The argument defaults to `cgi.escape(s, quote=True)`. To To disable escaping entirely, one can pass `lambda u: u`
disable escaping entirely, one can pass `lambda u: u` as the as the escape function, for example. One may also wish to
escape function, for example. One may also wish to consider consider using markupsafe's escape function: markupsafe.escape().
using markupsafe's escape function: markupsafe.escape(). This argument defaults to the package default.
file_encoding: the name of the default encoding to use when reading file_encoding: the name of the default encoding to use when reading
template files. All templates are converted to unicode prior template files. All templates are converted to unicode prior
...@@ -160,9 +160,16 @@ class Renderer(object): ...@@ -160,9 +160,16 @@ class Renderer(object):
""" """
return unicode(self.escape(self._to_unicode_soft(s))) return unicode(self.escape(self._to_unicode_soft(s)))
def unicode(self, s, encoding=None): def unicode(self, b, encoding=None):
""" """
Convert a string to unicode, using string_encoding and decode_errors. Convert a byte string to unicode, using string_encoding and decode_errors.
Arguments:
b: a byte string.
encoding: the name of an encoding. Defaults to the string_encoding
attribute for this instance.
Raises: Raises:
...@@ -178,7 +185,7 @@ class Renderer(object): ...@@ -178,7 +185,7 @@ class Renderer(object):
# TODO: Wrap UnicodeDecodeErrors with a message about setting # TODO: Wrap UnicodeDecodeErrors with a message about setting
# the string_encoding and decode_errors attributes. # the string_encoding and decode_errors attributes.
return unicode(s, encoding, self.decode_errors) return unicode(b, encoding, self.decode_errors)
def _make_loader(self): def _make_loader(self):
""" """
......
...@@ -55,10 +55,10 @@ class AssertStringMixin: ...@@ -55,10 +55,10 @@ class AssertStringMixin:
description = details % reason description = details % reason
return format % description return format % description
self.assertEquals(actual, expected, make_message("different characters")) self.assertEqual(actual, expected, make_message("different characters"))
reason = "types different: %s != %s (actual)" % (repr(type(expected)), repr(type(actual))) reason = "types different: %s != %s (actual)" % (repr(type(expected)), repr(type(actual)))
self.assertEquals(type(expected), type(actual), make_message(reason)) self.assertEqual(type(expected), type(actual), make_message(reason))
class AssertIsMixin: class AssertIsMixin:
......
...@@ -39,7 +39,7 @@ class CommandsTestCase(unittest.TestCase): ...@@ -39,7 +39,7 @@ class CommandsTestCase(unittest.TestCase):
""" """
actual = self.callScript("Hi {{thing}}", '{"thing": "world"}') actual = self.callScript("Hi {{thing}}", '{"thing": "world"}')
self.assertEquals(actual, u"Hi world\n") self.assertEqual(actual, u"Hi world\n")
def tearDown(self): def tearDown(self):
sys.stdout = ORIGINAL_STDOUT sys.stdout = ORIGINAL_STDOUT
...@@ -95,7 +95,7 @@ Again, Welcome!""") ...@@ -95,7 +95,7 @@ Again, Welcome!""")
view.template = '''{{>partial_in_partial}}''' view.template = '''{{>partial_in_partial}}'''
actual = renderer.render(view, {'prop': 'derp'}) actual = renderer.render(view, {'prop': 'derp'})
self.assertEquals(actual, 'Hi derp!') self.assertEqual(actual, 'Hi derp!')
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()
...@@ -18,30 +18,30 @@ class LoaderTests(unittest.TestCase, AssertStringMixin): ...@@ -18,30 +18,30 @@ class LoaderTests(unittest.TestCase, AssertStringMixin):
def test_init__extension(self): def test_init__extension(self):
loader = Loader(extension='foo') loader = Loader(extension='foo')
self.assertEquals(loader.extension, 'foo') self.assertEqual(loader.extension, 'foo')
def test_init__extension__default(self): def test_init__extension__default(self):
# Test the default value. # Test the default value.
loader = Loader() loader = Loader()
self.assertEquals(loader.extension, 'mustache') self.assertEqual(loader.extension, 'mustache')
def test_init__file_encoding(self): def test_init__file_encoding(self):
loader = Loader(file_encoding='bar') loader = Loader(file_encoding='bar')
self.assertEquals(loader.file_encoding, 'bar') self.assertEqual(loader.file_encoding, 'bar')
def test_init__file_encoding__default(self): def test_init__file_encoding__default(self):
file_encoding = defaults.FILE_ENCODING file_encoding = defaults.FILE_ENCODING
try: try:
defaults.FILE_ENCODING = 'foo' defaults.FILE_ENCODING = 'foo'
loader = Loader() loader = Loader()
self.assertEquals(loader.file_encoding, 'foo') self.assertEqual(loader.file_encoding, 'foo')
finally: finally:
defaults.FILE_ENCODING = file_encoding defaults.FILE_ENCODING = file_encoding
def test_init__to_unicode(self): def test_init__to_unicode(self):
to_unicode = lambda x: x to_unicode = lambda x: x
loader = Loader(to_unicode=to_unicode) loader = Loader(to_unicode=to_unicode)
self.assertEquals(loader.to_unicode, to_unicode) self.assertEqual(loader.to_unicode, to_unicode)
def test_init__to_unicode__default(self): def test_init__to_unicode__default(self):
loader = Loader() loader = Loader()
......
...@@ -26,10 +26,10 @@ class LocatorTests(unittest.TestCase): ...@@ -26,10 +26,10 @@ class LocatorTests(unittest.TestCase):
def test_init__extension(self): def test_init__extension(self):
# Test the default value. # Test the default value.
locator = Locator() locator = Locator()
self.assertEquals(locator.template_extension, 'mustache') self.assertEqual(locator.template_extension, 'mustache')
locator = Locator(extension='txt') locator = Locator(extension='txt')
self.assertEquals(locator.template_extension, 'txt') self.assertEqual(locator.template_extension, 'txt')
locator = Locator(extension=False) locator = Locator(extension=False)
self.assertTrue(locator.template_extension is False) self.assertTrue(locator.template_extension is False)
...@@ -40,40 +40,40 @@ class LocatorTests(unittest.TestCase): ...@@ -40,40 +40,40 @@ class LocatorTests(unittest.TestCase):
obj = SayHello() obj = SayHello()
actual = locator.get_object_directory(obj) actual = locator.get_object_directory(obj)
self.assertEquals(actual, os.path.abspath(DATA_DIR)) self.assertEqual(actual, os.path.abspath(DATA_DIR))
def test_get_object_directory__not_hasattr_module(self): def test_get_object_directory__not_hasattr_module(self):
locator = Locator() locator = Locator()
obj = datetime(2000, 1, 1) obj = datetime(2000, 1, 1)
self.assertFalse(hasattr(obj, '__module__')) self.assertFalse(hasattr(obj, '__module__'))
self.assertEquals(locator.get_object_directory(obj), None) self.assertEqual(locator.get_object_directory(obj), None)
self.assertFalse(hasattr(None, '__module__')) self.assertFalse(hasattr(None, '__module__'))
self.assertEquals(locator.get_object_directory(None), None) self.assertEqual(locator.get_object_directory(None), None)
def test_make_file_name(self): def test_make_file_name(self):
locator = Locator() locator = Locator()
locator.template_extension = 'bar' locator.template_extension = 'bar'
self.assertEquals(locator.make_file_name('foo'), 'foo.bar') self.assertEqual(locator.make_file_name('foo'), 'foo.bar')
locator.template_extension = False locator.template_extension = False
self.assertEquals(locator.make_file_name('foo'), 'foo') self.assertEqual(locator.make_file_name('foo'), 'foo')
locator.template_extension = '' locator.template_extension = ''
self.assertEquals(locator.make_file_name('foo'), 'foo.') self.assertEqual(locator.make_file_name('foo'), 'foo.')
def test_make_file_name__template_extension_argument(self): def test_make_file_name__template_extension_argument(self):
locator = Locator() locator = Locator()
self.assertEquals(locator.make_file_name('foo', template_extension='bar'), 'foo.bar') self.assertEqual(locator.make_file_name('foo', template_extension='bar'), 'foo.bar')
def test_find_name(self): def test_find_name(self):
locator = Locator() locator = Locator()
path = locator.find_name(search_dirs=['examples'], template_name='simple') path = locator.find_name(search_dirs=['examples'], template_name='simple')
self.assertEquals(os.path.basename(path), 'simple.mustache') self.assertEqual(os.path.basename(path), 'simple.mustache')
def test_find_name__using_list_of_paths(self): def test_find_name__using_list_of_paths(self):
locator = Locator() locator = Locator()
...@@ -98,7 +98,7 @@ class LocatorTests(unittest.TestCase): ...@@ -98,7 +98,7 @@ class LocatorTests(unittest.TestCase):
dirpath = os.path.dirname(path) dirpath = os.path.dirname(path)
dirname = os.path.split(dirpath)[-1] dirname = os.path.split(dirpath)[-1]
self.assertEquals(dirname, 'locator') self.assertEqual(dirname, 'locator')
def test_find_name__non_existent_template_fails(self): def test_find_name__non_existent_template_fails(self):
locator = Locator() locator = Locator()
...@@ -113,7 +113,7 @@ class LocatorTests(unittest.TestCase): ...@@ -113,7 +113,7 @@ class LocatorTests(unittest.TestCase):
actual = locator.find_object(search_dirs=[], obj=obj, file_name='sample_view.mustache') actual = locator.find_object(search_dirs=[], obj=obj, file_name='sample_view.mustache')
expected = os.path.abspath(os.path.join(DATA_DIR, 'sample_view.mustache')) expected = os.path.abspath(os.path.join(DATA_DIR, 'sample_view.mustache'))
self.assertEquals(actual, expected) self.assertEqual(actual, expected)
def test_find_object__none_file_name(self): def test_find_object__none_file_name(self):
locator = Locator() locator = Locator()
...@@ -123,18 +123,18 @@ class LocatorTests(unittest.TestCase): ...@@ -123,18 +123,18 @@ class LocatorTests(unittest.TestCase):
actual = locator.find_object(search_dirs=[], obj=obj) actual = locator.find_object(search_dirs=[], obj=obj)
expected = os.path.abspath(os.path.join(DATA_DIR, 'say_hello.mustache')) expected = os.path.abspath(os.path.join(DATA_DIR, 'say_hello.mustache'))
self.assertEquals(actual, expected) self.assertEqual(actual, expected)
def test_find_object__none_object_directory(self): def test_find_object__none_object_directory(self):
locator = Locator() locator = Locator()
obj = None obj = None
self.assertEquals(None, locator.get_object_directory(obj)) self.assertEqual(None, locator.get_object_directory(obj))
actual = locator.find_object(search_dirs=[DATA_DIR], obj=obj, file_name='say_hello.mustache') actual = locator.find_object(search_dirs=[DATA_DIR], obj=obj, file_name='say_hello.mustache')
expected = os.path.join(DATA_DIR, 'say_hello.mustache') expected = os.path.join(DATA_DIR, 'say_hello.mustache')
self.assertEquals(actual, expected) self.assertEqual(actual, expected)
def test_make_template_name(self): def test_make_template_name(self):
""" """
...@@ -147,4 +147,4 @@ class LocatorTests(unittest.TestCase): ...@@ -147,4 +147,4 @@ class LocatorTests(unittest.TestCase):
pass pass
foo = FooBar() foo = FooBar()
self.assertEquals(locator.make_template_name(foo), 'foo_bar') self.assertEqual(locator.make_template_name(foo), 'foo_bar')
...@@ -9,6 +9,7 @@ Creates a unittest.TestCase for the tests defined in the mustache spec. ...@@ -9,6 +9,7 @@ Creates a unittest.TestCase for the tests defined in the mustache spec.
FILE_ENCODING = 'utf-8' # the encoding of the spec test files. FILE_ENCODING = 'utf-8' # the encoding of the spec test files.
yaml = None
try: try:
# We try yaml first since it is more convenient when adding and modifying # We try yaml first since it is more convenient when adding and modifying
...@@ -31,28 +32,28 @@ else: ...@@ -31,28 +32,28 @@ else:
parser = yaml parser = yaml
import codecs
import glob import glob
import os.path import os.path
import unittest import unittest
import pystache import pystache
from pystache import common
from pystache.renderer import Renderer from pystache.renderer import Renderer
from pystache.tests.common import AssertStringMixin, SPEC_TEST_DIR from pystache.tests.common import AssertStringMixin, SPEC_TEST_DIR
spec_paths = glob.glob(os.path.join(SPEC_TEST_DIR, '*.%s' % file_extension)) def parse(u):
def parse(u, file_extension):
""" """
Parse
Arguments: Arguments:
u: a unicode string. u: a unicode string.
""" """
# TODO: find a cleaner mechanism for choosing between the two. # TODO: find a cleaner mechanism for choosing between the two.
if file_extension[0] == 'j': if yaml is None:
# Then json. # Then use json.
# The only way to get the simplejson module to return unicode strings # The only way to get the simplejson module to return unicode strings
# is to pass it unicode. See, for example-- # is to pass it unicode. See, for example--
...@@ -156,20 +157,14 @@ def buildTest(testData, spec_filename, parser): ...@@ -156,20 +157,14 @@ def buildTest(testData, spec_filename, parser):
return test return test
for spec_path in spec_paths: spec_paths = glob.glob(os.path.join(SPEC_TEST_DIR, '*.%s' % file_extension))
for path in spec_paths:
file_name = os.path.basename(spec_path)
# We avoid use of the with keyword for Python 2.4 support. file_name = os.path.basename(path)
# TODO: share code here with pystache's open() code.
f = open(spec_path, 'r')
try:
s = f.read()
finally:
f.close()
u = s.decode(FILE_ENCODING) b = common.read(path)
spec_data = parse(u, file_extension) u = unicode(b, encoding=FILE_ENCODING)
spec_data = parse(u)
tests = spec_data['tests'] tests = spec_data['tests']
......
...@@ -9,15 +9,15 @@ class PystacheTests(unittest.TestCase): ...@@ -9,15 +9,15 @@ class PystacheTests(unittest.TestCase):
def _assert_rendered(self, expected, template, context): def _assert_rendered(self, expected, template, context):
actual = pystache.render(template, context) actual = pystache.render(template, context)
self.assertEquals(actual, expected) self.assertEqual(actual, expected)
def test_basic(self): def test_basic(self):
ret = pystache.render("Hi {{thing}}!", { 'thing': 'world' }) ret = pystache.render("Hi {{thing}}!", { 'thing': 'world' })
self.assertEquals(ret, "Hi world!") self.assertEqual(ret, "Hi world!")
def test_kwargs(self): def test_kwargs(self):
ret = pystache.render("Hi {{thing}}!", thing='world') ret = pystache.render("Hi {{thing}}!", thing='world')
self.assertEquals(ret, "Hi world!") self.assertEqual(ret, "Hi world!")
def test_less_basic(self): def test_less_basic(self):
template = "It's a nice day for {{beverage}}, right {{person}}?" template = "It's a nice day for {{beverage}}, right {{person}}?"
...@@ -42,7 +42,7 @@ class PystacheTests(unittest.TestCase): ...@@ -42,7 +42,7 @@ class PystacheTests(unittest.TestCase):
def test_comments(self): def test_comments(self):
template = "What {{! the }} what?" template = "What {{! the }} what?"
actual = pystache.render(template) actual = pystache.render(template)
self.assertEquals("What what?", actual) self.assertEqual("What what?", actual)
def test_false_sections_are_hidden(self): def test_false_sections_are_hidden(self):
template = "Ready {{#set}}set {{/set}}go!" template = "Ready {{#set}}set {{/set}}go!"
......
...@@ -5,10 +5,10 @@ Unit tests of renderengine.py. ...@@ -5,10 +5,10 @@ Unit tests of renderengine.py.
""" """
import cgi
import unittest import unittest
from pystache.context import Context from pystache.context import Context
from pystache import defaults
from pystache.parser import ParsingError from pystache.parser import ParsingError
from pystache.renderengine import RenderEngine from pystache.renderengine import RenderEngine
from pystache.tests.common import AssertStringMixin from pystache.tests.common import AssertStringMixin
...@@ -26,9 +26,9 @@ class RenderEngineTestCase(unittest.TestCase): ...@@ -26,9 +26,9 @@ class RenderEngineTestCase(unittest.TestCase):
# In real-life, these arguments would be functions # In real-life, these arguments would be functions
engine = RenderEngine(load_partial="foo", literal="literal", escape="escape") engine = RenderEngine(load_partial="foo", literal="literal", escape="escape")
self.assertEquals(engine.escape, "escape") self.assertEqual(engine.escape, "escape")
self.assertEquals(engine.literal, "literal") self.assertEqual(engine.literal, "literal")
self.assertEquals(engine.load_partial, "foo") self.assertEqual(engine.load_partial, "foo")
class RenderTests(unittest.TestCase, AssertStringMixin): class RenderTests(unittest.TestCase, AssertStringMixin):
...@@ -47,7 +47,7 @@ class RenderTests(unittest.TestCase, AssertStringMixin): ...@@ -47,7 +47,7 @@ class RenderTests(unittest.TestCase, AssertStringMixin):
Create and return a default RenderEngine for testing. Create and return a default RenderEngine for testing.
""" """
escape = lambda s: unicode(cgi.escape(s)) escape = defaults.TAG_ESCAPE
engine = RenderEngine(literal=unicode, escape=escape, load_partial=None) engine = RenderEngine(literal=unicode, escape=escape, load_partial=None)
return engine return engine
...@@ -230,7 +230,7 @@ class RenderTests(unittest.TestCase, AssertStringMixin): ...@@ -230,7 +230,7 @@ class RenderTests(unittest.TestCase, AssertStringMixin):
# #
# we need to resort to built-in attributes (double-underscored) on # we need to resort to built-in attributes (double-underscored) on
# the integer type. # the integer type.
self._assert_builtin_type(15, '__hex__', '0xf', u'999') self._assert_builtin_type(15, '__neg__', -15, u'999')
def test_interpolation__built_in_type__list(self): def test_interpolation__built_in_type__list(self):
""" """
...@@ -315,7 +315,7 @@ class RenderTests(unittest.TestCase, AssertStringMixin): ...@@ -315,7 +315,7 @@ class RenderTests(unittest.TestCase, AssertStringMixin):
try: try:
self._assert_render(None, template) self._assert_render(None, template)
except ParsingError, err: except ParsingError, err:
self.assertEquals(str(err), "Section end tag mismatch: u'section' != None") self.assertEqual(str(err), "Section end tag mismatch: section != None")
def test_section__end_tag_mismatch(self): def test_section__end_tag_mismatch(self):
""" """
...@@ -326,7 +326,7 @@ class RenderTests(unittest.TestCase, AssertStringMixin): ...@@ -326,7 +326,7 @@ class RenderTests(unittest.TestCase, AssertStringMixin):
try: try:
self._assert_render(None, template) self._assert_render(None, template)
except ParsingError, err: except ParsingError, err:
self.assertEquals(str(err), "Section end tag mismatch: u'section_end' != u'section_start'") self.assertEqual(str(err), "Section end tag mismatch: section_end != section_start")
def test_section__context_values(self): def test_section__context_values(self):
""" """
......
...@@ -28,11 +28,11 @@ class TestSimple(unittest.TestCase, AssertStringMixin): ...@@ -28,11 +28,11 @@ class TestSimple(unittest.TestCase, AssertStringMixin):
renderer = Renderer() renderer = Renderer()
actual = renderer.render(template, context) actual = renderer.render(template, context)
self.assertEquals(actual, "Colors: red Colors: green Colors: blue ") self.assertEqual(actual, "Colors: red Colors: green Colors: blue ")
def test_empty_context(self): def test_empty_context(self):
template = '{{#empty_list}}Shouldnt see me {{/empty_list}}{{^empty_list}}Should see me{{/empty_list}}' template = '{{#empty_list}}Shouldnt see me {{/empty_list}}{{^empty_list}}Should see me{{/empty_list}}'
self.assertEquals(pystache.Renderer().render(template), "Should see me") self.assertEqual(pystache.Renderer().render(template), "Should see me")
def test_callables(self): def test_callables(self):
view = Lambdas() view = Lambdas()
...@@ -58,7 +58,7 @@ class TestSimple(unittest.TestCase, AssertStringMixin): ...@@ -58,7 +58,7 @@ class TestSimple(unittest.TestCase, AssertStringMixin):
def test_non_existent_value_renders_blank(self): def test_non_existent_value_renders_blank(self):
view = Simple() view = Simple()
template = '{{not_set}} {{blank}}' template = '{{not_set}} {{blank}}'
self.assertEquals(pystache.Renderer().render(template), ' ') self.assertEqual(pystache.Renderer().render(template), ' ')
def test_template_partial_extension(self): def test_template_partial_extension(self):
......
...@@ -18,7 +18,7 @@ from pystache import Renderer ...@@ -18,7 +18,7 @@ from pystache import Renderer
from pystache import TemplateSpec from pystache import TemplateSpec
from pystache.locator import Locator from pystache.locator import Locator
from pystache.loader import Loader from pystache.loader import Loader
from pystache.spec_loader import SpecLoader from pystache.specloader import SpecLoader
from pystache.tests.common import DATA_DIR from pystache.tests.common import DATA_DIR
from pystache.tests.common import EXAMPLES_DIR from pystache.tests.common import EXAMPLES_DIR
from pystache.tests.common import AssertIsMixin from pystache.tests.common import AssertIsMixin
...@@ -49,7 +49,7 @@ class ViewTestCase(unittest.TestCase, AssertStringMixin): ...@@ -49,7 +49,7 @@ class ViewTestCase(unittest.TestCase, AssertStringMixin):
# TODO: change this test to remove the following brittle line. # TODO: change this test to remove the following brittle line.
view.template_rel_directory = "../../examples" view.template_rel_directory = "../../examples"
actual = renderer.render(view) actual = renderer.render(view)
self.assertEquals(actual, "No tags...") self.assertEqual(actual, "No tags...")
def test_template_path_for_partials(self): def test_template_path_for_partials(self):
""" """
...@@ -65,7 +65,7 @@ class ViewTestCase(unittest.TestCase, AssertStringMixin): ...@@ -65,7 +65,7 @@ class ViewTestCase(unittest.TestCase, AssertStringMixin):
self.assertRaises(IOError, renderer1.render, spec) self.assertRaises(IOError, renderer1.render, spec)
actual = renderer2.render(spec) actual = renderer2.render(spec)
self.assertEquals(actual, "Partial: No tags...") self.assertEqual(actual, "Partial: No tags...")
def test_basic_method_calls(self): def test_basic_method_calls(self):
renderer = Renderer() renderer = Renderer()
...@@ -79,7 +79,7 @@ class ViewTestCase(unittest.TestCase, AssertStringMixin): ...@@ -79,7 +79,7 @@ class ViewTestCase(unittest.TestCase, AssertStringMixin):
renderer = Renderer() renderer = Renderer()
actual = renderer.render(view) actual = renderer.render(view)
self.assertEquals(actual, "Hi Chris!") self.assertEqual(actual, "Hi Chris!")
def test_complex(self): def test_complex(self):
renderer = Renderer() renderer = Renderer()
...@@ -95,7 +95,7 @@ class ViewTestCase(unittest.TestCase, AssertStringMixin): ...@@ -95,7 +95,7 @@ class ViewTestCase(unittest.TestCase, AssertStringMixin):
def test_higher_order_replace(self): def test_higher_order_replace(self):
renderer = Renderer() renderer = Renderer()
actual = renderer.render(Lambdas()) actual = renderer.render(Lambdas())
self.assertEquals(actual, 'bar != bar. oh, it does!') self.assertEqual(actual, 'bar != bar. oh, it does!')
def test_higher_order_rot13(self): def test_higher_order_rot13(self):
view = Lambdas() view = Lambdas()
...@@ -119,7 +119,7 @@ class ViewTestCase(unittest.TestCase, AssertStringMixin): ...@@ -119,7 +119,7 @@ class ViewTestCase(unittest.TestCase, AssertStringMixin):
renderer = Renderer(search_dirs=EXAMPLES_DIR) renderer = Renderer(search_dirs=EXAMPLES_DIR)
actual = renderer.render(view) actual = renderer.render(view)
self.assertEquals(actual, u'nopqrstuvwxyz') self.assertEqual(actual, u'nopqrstuvwxyz')
def test_hierarchical_partials_with_lambdas(self): def test_hierarchical_partials_with_lambdas(self):
view = Lambdas() view = Lambdas()
...@@ -152,6 +152,28 @@ class ViewTestCase(unittest.TestCase, AssertStringMixin): ...@@ -152,6 +152,28 @@ class ViewTestCase(unittest.TestCase, AssertStringMixin):
self.assertString(actual, u"""one, two, three, empty list""") self.assertString(actual, u"""one, two, three, empty list""")
def _make_specloader():
"""
Return a default SpecLoader instance for testing purposes.
"""
# Python 2 and 3 have different default encodings. Thus, to have
# consistent test results across both versions, we need to specify
# the string and file encodings explicitly rather than relying on
# the defaults.
def to_unicode(s, encoding=None):
"""
Raises a TypeError exception if the given string is already unicode.
"""
if encoding is None:
encoding = 'ascii'
return unicode(s, encoding, 'strict')
loader = Loader(file_encoding='ascii', to_unicode=to_unicode)
return SpecLoader(loader=loader)
class SpecLoaderTests(unittest.TestCase, AssertIsMixin, AssertStringMixin): class SpecLoaderTests(unittest.TestCase, AssertIsMixin, AssertStringMixin):
""" """
...@@ -159,13 +181,16 @@ class SpecLoaderTests(unittest.TestCase, AssertIsMixin, AssertStringMixin): ...@@ -159,13 +181,16 @@ class SpecLoaderTests(unittest.TestCase, AssertIsMixin, AssertStringMixin):
""" """
def _make_specloader(self):
return _make_specloader()
def test_init__defaults(self): def test_init__defaults(self):
custom = SpecLoader() spec_loader = SpecLoader()
# Check the loader attribute. # Check the loader attribute.
loader = custom.loader loader = spec_loader.loader
self.assertEquals(loader.extension, 'mustache') self.assertEqual(loader.extension, 'mustache')
self.assertEquals(loader.file_encoding, sys.getdefaultencoding()) self.assertEqual(loader.file_encoding, sys.getdefaultencoding())
# TODO: finish testing the other Loader attributes. # TODO: finish testing the other Loader attributes.
to_unicode = loader.to_unicode to_unicode = loader.to_unicode
...@@ -187,7 +212,8 @@ class SpecLoaderTests(unittest.TestCase, AssertIsMixin, AssertStringMixin): ...@@ -187,7 +212,8 @@ class SpecLoaderTests(unittest.TestCase, AssertIsMixin, AssertStringMixin):
custom = TemplateSpec() custom = TemplateSpec()
custom.template = "abc" custom.template = "abc"
self._assert_template(SpecLoader(), custom, u"abc") spec_loader = self._make_specloader()
self._assert_template(spec_loader, custom, u"abc")
def test_load__template__type_unicode(self): def test_load__template__type_unicode(self):
""" """
...@@ -197,7 +223,8 @@ class SpecLoaderTests(unittest.TestCase, AssertIsMixin, AssertStringMixin): ...@@ -197,7 +223,8 @@ class SpecLoaderTests(unittest.TestCase, AssertIsMixin, AssertStringMixin):
custom = TemplateSpec() custom = TemplateSpec()
custom.template = u"abc" custom.template = u"abc"
self._assert_template(SpecLoader(), custom, u"abc") spec_loader = self._make_specloader()
self._assert_template(spec_loader, custom, u"abc")
def test_load__template__unicode_non_ascii(self): def test_load__template__unicode_non_ascii(self):
""" """
...@@ -207,7 +234,8 @@ class SpecLoaderTests(unittest.TestCase, AssertIsMixin, AssertStringMixin): ...@@ -207,7 +234,8 @@ class SpecLoaderTests(unittest.TestCase, AssertIsMixin, AssertStringMixin):
custom = TemplateSpec() custom = TemplateSpec()
custom.template = u"é" custom.template = u"é"
self._assert_template(SpecLoader(), custom, u"é") spec_loader = self._make_specloader()
self._assert_template(spec_loader, custom, u"é")
def test_load__template__with_template_encoding(self): def test_load__template__with_template_encoding(self):
""" """
...@@ -217,10 +245,12 @@ class SpecLoaderTests(unittest.TestCase, AssertIsMixin, AssertStringMixin): ...@@ -217,10 +245,12 @@ class SpecLoaderTests(unittest.TestCase, AssertIsMixin, AssertStringMixin):
custom = TemplateSpec() custom = TemplateSpec()
custom.template = u'é'.encode('utf-8') custom.template = u'é'.encode('utf-8')
self.assertRaises(UnicodeDecodeError, self._assert_template, SpecLoader(), custom, u'é') spec_loader = self._make_specloader()
self.assertRaises(UnicodeDecodeError, self._assert_template, spec_loader, custom, u'é')
custom.template_encoding = 'utf-8' custom.template_encoding = 'utf-8'
self._assert_template(SpecLoader(), custom, u'é') self._assert_template(spec_loader, custom, u'é')
# TODO: make this test complete. # TODO: make this test complete.
def test_load__template__correct_loader(self): def test_load__template__correct_loader(self):
...@@ -255,8 +285,8 @@ class SpecLoaderTests(unittest.TestCase, AssertIsMixin, AssertStringMixin): ...@@ -255,8 +285,8 @@ class SpecLoaderTests(unittest.TestCase, AssertIsMixin, AssertStringMixin):
# Check that our unicode() above was called. # Check that our unicode() above was called.
self._assert_template(custom_loader, view, u'foo') self._assert_template(custom_loader, view, u'foo')
self.assertEquals(loader.s, "template-foo") self.assertEqual(loader.s, "template-foo")
self.assertEquals(loader.encoding, "encoding-foo") self.assertEqual(loader.encoding, "encoding-foo")
# TODO: migrate these tests into the SpecLoaderTests class. # TODO: migrate these tests into the SpecLoaderTests class.
...@@ -266,14 +296,13 @@ class SpecLoaderTests(unittest.TestCase, AssertIsMixin, AssertStringMixin): ...@@ -266,14 +296,13 @@ class SpecLoaderTests(unittest.TestCase, AssertIsMixin, AssertStringMixin):
# TemplateSpec attributes or something). # TemplateSpec attributes or something).
class TemplateSpecTests(unittest.TestCase): class TemplateSpecTests(unittest.TestCase):
# TODO: rename this method to _make_loader(). def _make_loader(self):
def _make_locator(self): return _make_specloader()
return SpecLoader()
def _assert_template_location(self, view, expected): def _assert_template_location(self, view, expected):
locator = self._make_locator() loader = self._make_loader()
actual = locator._find_relative(view) actual = loader._find_relative(view)
self.assertEquals(actual, expected) self.assertEqual(actual, expected)
def test_find_relative(self): def test_find_relative(self):
""" """
...@@ -334,38 +363,38 @@ class TemplateSpecTests(unittest.TestCase): ...@@ -334,38 +363,38 @@ class TemplateSpecTests(unittest.TestCase):
Test _find() with a view that has a directory specified. Test _find() with a view that has a directory specified.
""" """
locator = self._make_locator() loader = self._make_loader()
view = SampleView() view = SampleView()
view.template_rel_path = 'foo/bar.txt' view.template_rel_path = 'foo/bar.txt'
self.assertTrue(locator._find_relative(view)[0] is not None) self.assertTrue(loader._find_relative(view)[0] is not None)
actual = locator._find(view) actual = loader._find(view)
expected = os.path.abspath(os.path.join(DATA_DIR, 'foo/bar.txt')) expected = os.path.abspath(os.path.join(DATA_DIR, 'foo/bar.txt'))
self.assertEquals(actual, expected) self.assertEqual(actual, expected)
def test_find__without_directory(self): def test_find__without_directory(self):
""" """
Test _find() with a view that doesn't have a directory specified. Test _find() with a view that doesn't have a directory specified.
""" """
locator = self._make_locator() loader = self._make_loader()
view = SampleView() view = SampleView()
self.assertTrue(locator._find_relative(view)[0] is None) self.assertTrue(loader._find_relative(view)[0] is None)
actual = locator._find(view) actual = loader._find(view)
expected = os.path.abspath(os.path.join(DATA_DIR, 'sample_view.mustache')) expected = os.path.abspath(os.path.join(DATA_DIR, 'sample_view.mustache'))
self.assertEquals(actual, expected) self.assertEqual(actual, expected)
def _assert_get_template(self, custom, expected): def _assert_get_template(self, custom, expected):
locator = self._make_locator() loader = self._make_loader()
actual = locator.load(custom) actual = loader.load(custom)
self.assertEquals(type(actual), unicode) self.assertEqual(type(actual), unicode)
self.assertEquals(actual, expected) self.assertEqual(actual, expected)
def test_get_template(self): def test_get_template(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