Commit 1953e28d by Chris Jerdonek

Finished implementing strict mode for missing tags.

parent 4a4b4ddc
......@@ -7,10 +7,17 @@ Defines a class responsible for rendering logic.
import re
from pystache.context import KeyNotFoundError
from pystache.parser import Parser
def context_get(stack, name):
"""
Find and return a name from a ContextStack instance.
"""
return stack.get(name)
class RenderEngine(object):
"""
......@@ -30,14 +37,11 @@ class RenderEngine(object):
"""
def __init__(self, resolve_partial=None, literal=None, escape=None):
def __init__(self, literal=None, escape=None, resolve_context=None,
resolve_partial=None):
"""
Arguments:
resolve_partial: the function to call when loading a partial.
The function should accept a string template name and return a
template string of type unicode (not a subclass).
literal: the function used to convert unescaped variable tag
values to unicode, e.g. the value corresponding to a tag
"{{{name}}}". The function should accept a string of type
......@@ -59,17 +63,21 @@ class RenderEngine(object):
incoming strings of type markupsafe.Markup differently
from plain unicode strings.
resolve_context: the function to call to resolve a name against
a context stack. The function should accept two positional
arguments: a ContextStack instance and a name to resolve.
resolve_partial: the function to call when loading a partial.
The function should accept a template name string and return a
template string of type unicode (not a subclass).
"""
self.escape = escape
self.literal = literal
self.resolve_context = resolve_context
self.resolve_partial = resolve_partial
def resolve_context(self, stack, name):
try:
return stack.get(name)
except KeyNotFoundError:
return u''
# TODO: Rename context to stack throughout this module.
def _get_string_value(self, context, tag_name):
"""
Get a value from the given context as a basestring instance.
......
......@@ -9,9 +9,9 @@ import sys
from pystache import defaults
from pystache.common import TemplateNotFoundError, MissingTags
from pystache.context import ContextStack
from pystache.context import ContextStack, KeyNotFoundError
from pystache.loader import Loader
from pystache.renderengine import RenderEngine
from pystache.renderengine import context_get, RenderEngine
from pystache.specloader import SpecLoader
from pystache.template_spec import TemplateSpec
......@@ -258,6 +258,13 @@ class Renderer(object):
return load_partial
def _is_missing_tags_strict(self):
"""
Return whether missing_tags is set to strict.
"""
return self.missing_tags == MissingTags.strict
def _make_resolve_partial(self):
"""
Return the resolve_partial function to pass to RenderEngine.__init__().
......@@ -265,7 +272,7 @@ class Renderer(object):
"""
load_partial = self._make_load_partial()
if self.missing_tags == MissingTags.strict:
if self._is_missing_tags_strict():
return load_partial
# Otherwise, ignore missing tags.
......@@ -277,16 +284,35 @@ class Renderer(object):
return resolve_partial
def _make_resolve_context(self):
"""
Return the resolve_context function to pass to RenderEngine.__init__().
"""
if self._is_missing_tags_strict():
return context_get
# Otherwise, ignore missing tags.
def resolve_context(stack, name):
try:
return context_get(stack, name)
except KeyNotFoundError:
return u''
return resolve_context
def _make_render_engine(self):
"""
Return a RenderEngine instance for rendering.
"""
resolve_context = self._make_resolve_context()
resolve_partial = self._make_resolve_partial()
engine = RenderEngine(resolve_partial=resolve_partial,
literal=self._to_unicode_hard,
escape=self._escape_to_unicode)
engine = RenderEngine(literal=self._to_unicode_hard,
escape=self._escape_to_unicode,
resolve_context=resolve_context,
resolve_partial=resolve_partial)
return engine
# TODO: add unit tests for this method.
......
......@@ -7,11 +7,11 @@ Unit tests of renderengine.py.
import unittest
from pystache.context import ContextStack
from pystache.context import ContextStack, KeyNotFoundError
from pystache import defaults
from pystache.parser import ParsingError
from pystache.renderengine import RenderEngine
from pystache.tests.common import AssertStringMixin, Attachable
from pystache.renderengine import context_get, RenderEngine
from pystache.tests.common import AssertStringMixin, AssertExceptionMixin, Attachable
def mock_literal(s):
......@@ -52,7 +52,7 @@ class RenderEngineTestCase(unittest.TestCase):
self.assertEqual(engine.resolve_partial, "foo")
class RenderTests(unittest.TestCase, AssertStringMixin):
class RenderTests(unittest.TestCase, AssertStringMixin, AssertExceptionMixin):
"""
Tests RenderEngine.render().
......@@ -69,7 +69,10 @@ class RenderTests(unittest.TestCase, AssertStringMixin):
"""
escape = defaults.TAG_ESCAPE
engine = RenderEngine(literal=unicode, escape=escape, resolve_partial=None)
engine = RenderEngine(literal=unicode, escape=escape,
resolve_context=context_get,
resolve_partial=None)
return engine
def _assert_render(self, expected, template, *context, **kwargs):
......@@ -594,33 +597,15 @@ class RenderTests(unittest.TestCase, AssertStringMixin):
context = {'person': person}
self._assert_render(u'Hello, Biggles. I see you are 42.', template, context)
def test_dot_notation__missing_attributes_or_keys(self):
"""
Test dot notation with missing keys or attributes.
Check that if a key or attribute in a dotted name does not exist, then
the tag renders as the empty string.
"""
template = """I cannot see {{person.name}}'s age: {{person.age}}.
Nor {{other_person.name}}'s: ."""
expected = u"""I cannot see Biggles's age: .
Nor Mr. Bradshaw's: ."""
context = {'person': {'name': 'Biggles'},
'other_person': Attachable(name='Mr. Bradshaw')}
self._assert_render(expected, template, context)
def test_dot_notation__multiple_levels(self):
"""
Test dot notation with multiple levels.
"""
template = """Hello, Mr. {{person.name.lastname}}.
I see you're back from {{person.travels.last.country.city}}.
I'm missing some of your details: {{person.details.private.editor}}."""
I see you're back from {{person.travels.last.country.city}}."""
expected = u"""Hello, Mr. Pither.
I see you're back from Cornwall.
I'm missing some of your details: ."""
I see you're back from Cornwall."""
context = {'person': {'name': {'firstname': 'unknown', 'lastname': 'Pither'},
'travels': {'last': {'country': {'city': 'Cornwall'}}},
'details': {'public': 'likes cycling'}}}
......@@ -652,6 +637,14 @@ class RenderTests(unittest.TestCase, AssertStringMixin):
https://github.com/mustache/spec/pull/48
"""
template = '{{a.b}} :: ({{#c}}{{a}} :: {{a.b}}{{/c}})'
context = {'a': {'b': 'A.B'}, 'c': {'a': 'A'} }
self._assert_render(u'A.B :: (A :: )', template, context)
template = '{{a.b}}'
self._assert_render(u'A.B', template, context)
template = '{{#c}}{{a}}{{/c}}'
self._assert_render(u'A', template, context)
template = '{{#c}}{{a.b}}{{/c}}'
self.assertException(KeyNotFoundError, "Key u'a.b' not found: missing u'b'",
self._assert_render, u'A.B :: (A :: )', template, context)
......@@ -14,6 +14,7 @@ from examples.simple import Simple
from pystache import Renderer
from pystache import TemplateSpec
from pystache.common import TemplateNotFoundError
from pystache.context import ContextStack, KeyNotFoundError
from pystache.loader import Loader
from pystache.tests.common import get_data_path, AssertStringMixin, AssertExceptionMixin
......@@ -461,7 +462,7 @@ class Renderer_MakeRenderEngineTests(unittest.TestCase, AssertStringMixin, Asser
self.assertEqual(actual, "abc")
self.assertEqual(type(actual), unicode)
def test__resolve_partial__not_found__default(self):
def test__resolve_partial__not_found(self):
"""
Check that resolve_partial returns the empty string when a template is not found.
......@@ -473,7 +474,7 @@ class Renderer_MakeRenderEngineTests(unittest.TestCase, AssertStringMixin, Asser
self.assertString(resolve_partial('foo'), u'')
def test__resolve_partial__not_found__strict__default(self):
def test__resolve_partial__not_found__missing_tags_strict(self):
"""
Check that resolve_partial provides a nice message when a template is not found.
......@@ -487,7 +488,7 @@ class Renderer_MakeRenderEngineTests(unittest.TestCase, AssertStringMixin, Asser
self.assertException(TemplateNotFoundError, "File 'foo.mustache' not found in dirs: ['.']",
resolve_partial, "foo")
def test__resolve_partial__not_found__dict(self):
def test__resolve_partial__not_found__partials_dict(self):
"""
Check that resolve_partial returns the empty string when a template is not found.
......@@ -500,7 +501,7 @@ class Renderer_MakeRenderEngineTests(unittest.TestCase, AssertStringMixin, Asser
self.assertString(resolve_partial('foo'), u'')
def test__resolve_partial__not_found__strict__dict(self):
def test__resolve_partial__not_found__partials_dict__missing_tags_strict(self):
"""
Check that resolve_partial provides a nice message when a template is not found.
......@@ -638,3 +639,34 @@ class Renderer_MakeRenderEngineTests(unittest.TestCase, AssertStringMixin, Asser
self.assertTrue(isinstance(s, unicode))
self.assertEqual(type(escape(s)), unicode)
## Test the engine's resolve_context attribute.
def test__resolve_context(self):
"""
Check resolve_context(): default arguments.
"""
renderer = Renderer()
engine = renderer._make_render_engine()
stack = ContextStack({'foo': 'bar'})
self.assertEqual('bar', engine.resolve_context(stack, 'foo'))
self.assertString(u'', engine.resolve_context(stack, 'missing'))
def test__resolve_context__missing_tags_strict(self):
"""
Check resolve_context(): missing_tags 'strict'.
"""
renderer = Renderer()
renderer.missing_tags = 'strict'
engine = renderer._make_render_engine()
stack = ContextStack({'foo': 'bar'})
self.assertEqual('bar', engine.resolve_context(stack, 'foo'))
self.assertException(KeyNotFoundError, "Key 'missing' not found: first part",
engine.resolve_context, stack, 'missing')
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