Commit 5a94c93b by Chris Jerdonek

Addressed issue #115: "Match spec expectation for partials not found"

parent 27f2ae76
...@@ -5,6 +5,7 @@ History ...@@ -5,6 +5,7 @@ History
----------- -----------
* Added support for dot notation and version 1.1.2 of the spec (issue #99). [rbp] * Added support for dot notation and version 1.1.2 of the spec (issue #99). [rbp]
* Missing partials now render as empty string per latest version of spec (issue #115).
* Bugfix: falsey values now coerced to strings using str(). * Bugfix: falsey values now coerced to strings using str().
* Bugfix: lambda return values for sections no longer pushed onto context stack (issue #113). * Bugfix: lambda return values for sections no longer pushed onto context stack (issue #113).
* Bugfix: lists of lambdas for sections were not rendered (issue #114). * Bugfix: lists of lambdas for sections were not rendered (issue #114).
......
Subproject commit bf6288ed6bd0ce8ccea6f1dac070b3d779132c3b Subproject commit 9b1bc7ad19247e9671304af02078f2ce30132665
...@@ -35,6 +35,7 @@ import sys ...@@ -35,6 +35,7 @@ import sys
# #
# ValueError: Attempted relative import in non-package # ValueError: Attempted relative import in non-package
# #
from pystache.common import TemplateNotFoundError
from pystache.renderer import Renderer from pystache.renderer import Renderer
...@@ -78,7 +79,7 @@ def main(sys_argv=sys.argv): ...@@ -78,7 +79,7 @@ def main(sys_argv=sys.argv):
try: try:
template = renderer.load_template(template) template = renderer.load_template(template)
except IOError: except TemplateNotFoundError:
pass pass
try: try:
......
# coding: utf-8 # coding: utf-8
""" """
Exposes common functions. Exposes functionality needed throughout the project.
""" """
...@@ -24,3 +24,13 @@ def read(path): ...@@ -24,3 +24,13 @@ def read(path):
return f.read() return f.read()
finally: finally:
f.close() f.close()
class PystacheError(Exception):
"""Base class for Pystache exceptions."""
pass
class TemplateNotFoundError(PystacheError):
"""An exception raised when a template is not found."""
pass
...@@ -9,6 +9,7 @@ import os ...@@ -9,6 +9,7 @@ import os
import re import re
import sys import sys
from pystache.common import TemplateNotFoundError
from pystache import defaults from pystache import defaults
...@@ -117,8 +118,7 @@ class Locator(object): ...@@ -117,8 +118,7 @@ class Locator(object):
path = self._find_path(search_dirs, file_name) path = self._find_path(search_dirs, file_name)
if path is None: if path is None:
# TODO: we should probably raise an exception of our own type. raise TemplateNotFoundError('File %s not found in dirs: %s' %
raise IOError('Template file %s not found in directories: %s' %
(repr(file_name), repr(search_dirs))) (repr(file_name), repr(search_dirs)))
return path return path
......
...@@ -32,6 +32,9 @@ class ParsedTemplate(object): ...@@ -32,6 +32,9 @@ class ParsedTemplate(object):
""" """
self._parse_tree = parse_tree self._parse_tree = parse_tree
def __repr__(self):
return "[%s]" % (", ".join([repr(part) for part in self._parse_tree]))
def render(self, context): def render(self, context):
""" """
Returns: a string of type unicode. Returns: a string of type unicode.
......
...@@ -9,12 +9,13 @@ This module is only meant for internal use by the renderengine module. ...@@ -9,12 +9,13 @@ This module is only meant for internal use by the renderengine module.
import re import re
from pystache.common import TemplateNotFoundError
from pystache.parsed import ParsedTemplate from pystache.parsed import ParsedTemplate
DEFAULT_DELIMITERS = ('{{', '}}') DEFAULT_DELIMITERS = (u'{{', u'}}')
END_OF_LINE_CHARACTERS = ['\r', '\n'] END_OF_LINE_CHARACTERS = [u'\r', u'\n']
NON_BLANK_RE = re.compile(r'^(.)', re.M) NON_BLANK_RE = re.compile(ur'^(.)', re.M)
def _compile_template_re(delimiters=None): def _compile_template_re(delimiters=None):
...@@ -215,10 +216,14 @@ class Parser(object): ...@@ -215,10 +216,14 @@ class Parser(object):
elif tag_type == '>': elif tag_type == '>':
try:
# TODO: make engine.load() and test it separately.
template = engine.load_partial(tag_key) template = engine.load_partial(tag_key)
except TemplateNotFoundError:
template = u''
# Indent before rendering. # Indent before rendering.
template = re.sub(NON_BLANK_RE, leading_whitespace + r'\1', template) template = re.sub(NON_BLANK_RE, leading_whitespace + ur'\1', template)
func = engine._make_get_partial(template) func = engine._make_get_partial(template)
......
...@@ -35,7 +35,8 @@ class RenderEngine(object): ...@@ -35,7 +35,8 @@ class RenderEngine(object):
load_partial: the function to call when loading a partial. The load_partial: the function to call when loading a partial. The
function should accept a string template name and return a function should accept a string template name and return a
template string of type unicode (not a subclass). template string of type unicode (not a subclass). If the
template is not found, it should raise a TemplateNotFoundError.
literal: the function used to convert unescaped variable tag literal: the function used to convert unescaped variable tag
values to unicode, e.g. the value corresponding to a tag values to unicode, e.g. the value corresponding to a tag
......
...@@ -8,6 +8,7 @@ This module provides a Renderer class to render templates. ...@@ -8,6 +8,7 @@ This module provides a Renderer class to render templates.
import sys import sys
from pystache import defaults from pystache import defaults
from pystache.common import TemplateNotFoundError
from pystache.context import ContextStack from pystache.context import ContextStack
from pystache.loader import Loader from pystache.loader import Loader
from pystache.renderengine import RenderEngine from pystache.renderengine import RenderEngine
...@@ -239,9 +240,8 @@ class Renderer(object): ...@@ -239,9 +240,8 @@ class Renderer(object):
template = partials.get(name) template = partials.get(name)
if template is None: if template is None:
# TODO: make a TemplateNotFoundException type that provides raise TemplateNotFoundError("Name %s not found in partials: %s" %
# the original partials as an attribute. (repr(name), type(partials)))
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.
return self._to_unicode_hard(template) return self._to_unicode_hard(template)
......
...@@ -168,6 +168,20 @@ class AssertIsMixin: ...@@ -168,6 +168,20 @@ class AssertIsMixin:
self.assertTrue(first is second, msg="%s is not %s" % (repr(first), repr(second))) self.assertTrue(first is second, msg="%s is not %s" % (repr(first), repr(second)))
class AssertExceptionMixin:
"""A unittest.TestCase mixin adding assertException()."""
# unittest.assertRaisesRegexp() is not available until Python 2.7:
# http://docs.python.org/library/unittest.html#unittest.TestCase.assertRaisesRegexp
def assertException(self, exception_type, msg, callable, *args, **kwds):
try:
callable(*args, **kwds)
raise Exception("Expected exception: %s: %s" % (exception_type, repr(msg)))
except exception_type, err:
self.assertEqual(str(err), msg)
class SetupDefaults(object): class SetupDefaults(object):
""" """
......
...@@ -11,14 +11,15 @@ import sys ...@@ -11,14 +11,15 @@ import sys
import unittest import unittest
# TODO: remove this alias. # TODO: remove this alias.
from pystache.common import TemplateNotFoundError
from pystache.loader import Loader as Reader from pystache.loader import Loader as Reader
from pystache.locator import Locator from pystache.locator import Locator
from pystache.tests.common import DATA_DIR, EXAMPLES_DIR from pystache.tests.common import DATA_DIR, EXAMPLES_DIR, AssertExceptionMixin
from pystache.tests.data.views import SayHello from pystache.tests.data.views import SayHello
class LocatorTests(unittest.TestCase): class LocatorTests(unittest.TestCase, AssertExceptionMixin):
def _locator(self): def _locator(self):
return Locator(search_dirs=DATA_DIR) return Locator(search_dirs=DATA_DIR)
...@@ -110,7 +111,8 @@ class LocatorTests(unittest.TestCase): ...@@ -110,7 +111,8 @@ class LocatorTests(unittest.TestCase):
def test_find_name__non_existent_template_fails(self): def test_find_name__non_existent_template_fails(self):
locator = Locator() locator = Locator()
self.assertRaises(IOError, locator.find_name, search_dirs=[], template_name='doesnt_exist') self.assertException(TemplateNotFoundError, "File 'doesnt_exist.mustache' not found in dirs: []",
locator.find_name, search_dirs=[], template_name='doesnt_exist')
def test_find_object(self): def test_find_object(self):
locator = Locator() locator = Locator()
......
...@@ -13,9 +13,10 @@ import unittest ...@@ -13,9 +13,10 @@ import unittest
from examples.simple import Simple from examples.simple import Simple
from pystache import Renderer from pystache import Renderer
from pystache import TemplateSpec from pystache import TemplateSpec
from pystache.common import TemplateNotFoundError
from pystache.loader import Loader from pystache.loader import Loader
from pystache.tests.common import get_data_path, AssertStringMixin from pystache.tests.common import get_data_path, AssertStringMixin, AssertExceptionMixin
from pystache.tests.data.views import SayHello from pystache.tests.data.views import SayHello
...@@ -405,7 +406,7 @@ class RendererTests(unittest.TestCase, AssertStringMixin): ...@@ -405,7 +406,7 @@ class RendererTests(unittest.TestCase, AssertStringMixin):
# we no longer need to exercise all rendering code paths through # we no longer need to exercise all rendering code paths through
# the Renderer. It suffices to test rendering paths through the # the Renderer. It suffices to test rendering paths through the
# RenderEngine for the same amount of code coverage. # RenderEngine for the same amount of code coverage.
class Renderer_MakeRenderEngineTests(unittest.TestCase): class Renderer_MakeRenderEngineTests(unittest.TestCase, AssertExceptionMixin):
""" """
Check the RenderEngine returned by Renderer._make_render_engine(). Check the RenderEngine returned by Renderer._make_render_engine().
...@@ -444,7 +445,20 @@ class Renderer_MakeRenderEngineTests(unittest.TestCase): ...@@ -444,7 +445,20 @@ class Renderer_MakeRenderEngineTests(unittest.TestCase):
self.assertEqual(actual, "abc") self.assertEqual(actual, "abc")
self.assertEqual(type(actual), unicode) self.assertEqual(type(actual), unicode)
def test__load_partial__not_found(self): def test__load_partial__not_found__default(self):
"""
Check that load_partial provides a nice message when a template is not found.
"""
renderer = Renderer()
engine = renderer._make_render_engine()
load_partial = engine.load_partial
self.assertException(TemplateNotFoundError, "File 'foo.mustache' not found in dirs: ['.']",
load_partial, "foo")
def test__load_partial__not_found__dict(self):
""" """
Check that load_partial provides a nice message when a template is not found. Check that load_partial provides a nice message when a template is not found.
...@@ -455,11 +469,10 @@ class Renderer_MakeRenderEngineTests(unittest.TestCase): ...@@ -455,11 +469,10 @@ class Renderer_MakeRenderEngineTests(unittest.TestCase):
engine = renderer._make_render_engine() engine = renderer._make_render_engine()
load_partial = engine.load_partial load_partial = engine.load_partial
try: # Include dict directly since str(dict) is different in Python 2 and 3:
load_partial("foo") # <type 'dict'> versus <class 'dict'>, respectively.
raise Exception("Shouldn't get here") self.assertException(TemplateNotFoundError, "Name 'foo' not found in partials: %s" % dict,
except Exception, err: load_partial, "foo")
self.assertEqual(str(err), "Partial not found with name: 'foo'")
## Test the engine's literal attribute. ## Test the engine's literal attribute.
......
...@@ -16,6 +16,7 @@ from examples.lambdas import Lambdas ...@@ -16,6 +16,7 @@ from examples.lambdas import Lambdas
from examples.inverted import Inverted, InvertedLists from examples.inverted import Inverted, InvertedLists
from pystache import Renderer from pystache import Renderer
from pystache import TemplateSpec from pystache import TemplateSpec
from pystache.common import TemplateNotFoundError
from pystache.locator import Locator from pystache.locator import Locator
from pystache.loader import Loader from pystache.loader import Loader
from pystache.specloader import SpecLoader from pystache.specloader import SpecLoader
...@@ -42,7 +43,7 @@ class ViewTestCase(unittest.TestCase, AssertStringMixin): ...@@ -42,7 +43,7 @@ class ViewTestCase(unittest.TestCase, AssertStringMixin):
view = Tagless() view = Tagless()
renderer = Renderer() renderer = Renderer()
self.assertRaises(IOError, renderer.render, view) self.assertRaises(TemplateNotFoundError, renderer.render, view)
# 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"
...@@ -60,7 +61,8 @@ class ViewTestCase(unittest.TestCase, AssertStringMixin): ...@@ -60,7 +61,8 @@ class ViewTestCase(unittest.TestCase, AssertStringMixin):
renderer1 = Renderer() renderer1 = Renderer()
renderer2 = Renderer(search_dirs=EXAMPLES_DIR) renderer2 = Renderer(search_dirs=EXAMPLES_DIR)
self.assertRaises(IOError, renderer1.render, spec) actual = renderer1.render(spec)
self.assertString(actual, u"Partial: ")
actual = renderer2.render(spec) actual = renderer2.render(spec)
self.assertEqual(actual, "Partial: No tags...") self.assertEqual(actual, "Partial: No tags...")
......
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