Commit f2108401 by Chris Jerdonek

Fixed issue #56: Template() now accepts an "escape" argument.

From the issue:

    The constructor `Template.__init__()` should accept an optional
    `escape` function argument instead of the boolean `disable_escape`.
    This is more natural.

    Examples:

     * Users wanting to disable escaping can pass `lambda s: s`.
     * Non-markupsafe users wanting double-quotes escaped can pass
       `lambda s: cgi.escape(s, True)`.  The cgi.escape() function
       doesn't escape double-quotes by default.
     * Similarly, markupsafe users can specify an alternative to
       `markupsafe.escape()`.
parent e2ce832b
...@@ -60,8 +60,7 @@ class Template(object): ...@@ -60,8 +60,7 @@ class Template(object):
modifiers = Modifiers() modifiers = Modifiers()
def __init__(self, template=None, load_template=None, output_encoding=None, def __init__(self, template=None, load_template=None, output_encoding=None, escape=None):
disable_escape=False):
""" """
Construct a Template instance. Construct a Template instance.
...@@ -79,19 +78,27 @@ class Template(object): ...@@ -79,19 +78,27 @@ class Template(object):
example "utf-8". See the render() method's documentation for more example "utf-8". See the render() method's documentation for more
information. information.
escape: the function used to escape mustache variable values
when rendering a template. The function should accept a unicode
string and return an escaped string of the same type. It need
not handle strings of type `str` because this class will only
pass it unicode strings. The constructor assigns this escape
function to the constructed instance's Template.escape() method.
The argument defaults to markupsafe.escape when markupsafe is
importable and cgi.escape otherwise. To disable escaping entirely,
one can pass `lambda s: s` as the escape function, for example.
""" """
if load_template is None: if load_template is None:
loader = Loader() loader = Loader()
load_template = loader.load_template load_template = loader.load_template
if markupsafe: if escape is None:
escape = markupsafe.escape escape = markupsafe.escape if markupsafe else cgi.escape
literal = markupsafe.Markup
else: literal = markupsafe.Markup if markupsafe else unicode
escape = lambda x: cgi.escape(unicode(x))
literal = unicode
self.disable_escape = disable_escape
self.escape = escape self.escape = escape
self.literal = literal self.literal = literal
self.load_template = load_template self.load_template = load_template
...@@ -100,6 +107,11 @@ class Template(object): ...@@ -100,6 +107,11 @@ class Template(object):
self._compile_regexps() self._compile_regexps()
def _unicode_and_escape(self, s):
if not isinstance(s, unicode):
s = unicode(s)
return self.escape(s)
def _initialize_context(self, context, **kwargs): def _initialize_context(self, context, **kwargs):
""" """
Initialize the context attribute. Initialize the context attribute.
...@@ -215,7 +227,7 @@ class Template(object): ...@@ -215,7 +227,7 @@ class Template(object):
def _render_dictionary(self, template, context): def _render_dictionary(self, template, context):
self.context.push(context) self.context.push(context)
template = Template(template, load_template=self.load_template, disable_escape=self.disable_escape) template = Template(template, load_template=self.load_template, escape=self.escape)
out = template.render(self.context) out = template.render(self.context)
self.context.pop() self.context.pop()
...@@ -245,7 +257,7 @@ class Template(object): ...@@ -245,7 +257,7 @@ class Template(object):
else: else:
return '' return ''
return self._render_value(raw) return self._unicode_and_escape(raw)
@modifiers.set('!') @modifiers.set('!')
def _render_comment(self, tag_name): def _render_comment(self, tag_name):
...@@ -254,7 +266,7 @@ class Template(object): ...@@ -254,7 +266,7 @@ class Template(object):
@modifiers.set('>') @modifiers.set('>')
def _render_partial(self, template_name): def _render_partial(self, template_name):
markup = self.load_template(template_name) markup = self.load_template(template_name)
template = Template(markup, load_template=self.load_template, disable_escape=self.disable_escape) template = Template(markup, load_template=self.load_template, escape=self.escape)
return template.render(self.context) return template.render(self.context)
@modifiers.set('=') @modifiers.set('=')
...@@ -295,8 +307,6 @@ class Template(object): ...@@ -295,8 +307,6 @@ class Template(object):
""" """
self._initialize_context(context, **kwargs) self._initialize_context(context, **kwargs)
self._render_value = self.literal if self.disable_escape else self.escape
result = self._render(self.template) result = self._render(self.template)
if self.output_encoding is not None: if self.output_encoding is not None:
......
...@@ -88,13 +88,18 @@ class View(object): ...@@ -88,13 +88,18 @@ class View(object):
return re.sub('[A-Z]', repl, template_name)[1:] return re.sub('[A-Z]', repl, template_name)[1:]
def render(self, encoding=None, disable_escape=False): # TODO: the View class should probably have some sort of template renderer
# associated with it to encapsulate all of the render-specific behavior
# and options like encoding, escape, etc. This would probably be better
# than passing all of these options to render(), especially as the list
# of possible options grows.
def render(self, encoding=None, escape=None):
""" """
Return the view rendered using the current context. Return the view rendered using the current context.
""" """
template = Template(self.get_template(), self.load_template, output_encoding=encoding, template = Template(self.get_template(), self.load_template, output_encoding=encoding,
disable_escape=disable_escape) escape=escape)
return template.render(self.context) return template.render(self.context)
def get(self, key, default=None): def get(self, key, default=None):
......
...@@ -31,16 +31,17 @@ class TestView(unittest.TestCase): ...@@ -31,16 +31,17 @@ class TestView(unittest.TestCase):
self.assertEquals(UnicodeInput().render(), self.assertEquals(UnicodeInput().render(),
u'<p>If alive today, Henri Poincaré would be 156 years old.</p>') u'<p>If alive today, Henri Poincaré would be 156 years old.</p>')
def test_escaped(self): def test_escaping(self):
self.assertEquals(Escaped().render(), "<h1>Bear &gt; Shark</h1>") self.assertEquals(Escaped().render(), "<h1>Bear &gt; Shark</h1>")
def test_escaped_disabling(self): def test_escaping__custom(self):
self.assertEquals(Escaped().render(disable_escape=True), "<h1>Bear > Shark</h1>") escape = lambda s: s.upper()
self.assertEquals(Escaped().render(escape=escape), "<h1>BEAR > SHARK</h1>")
def test_unescaped(self): def test_literal(self):
self.assertEquals(Unescaped().render(), "<h1>Bear > Shark</h1>") self.assertEquals(Unescaped().render(), "<h1>Bear > Shark</h1>")
def test_unescaped_sigil(self): def test_literal_sigil(self):
view = Escaped(template="<h1>{{& thing}}</h1>", context={ view = Escaped(template="<h1>{{& thing}}</h1>", context={
'thing': 'Bear > Giraffe' 'thing': 'Bear > Giraffe'
}) })
......
...@@ -8,6 +8,7 @@ Unit tests of template.py. ...@@ -8,6 +8,7 @@ Unit tests of template.py.
import codecs import codecs
import unittest import unittest
from pystache import template
from pystache.template import Template from pystache.template import Template
...@@ -15,13 +16,44 @@ class TemplateTestCase(unittest.TestCase): ...@@ -15,13 +16,44 @@ class TemplateTestCase(unittest.TestCase):
"""Test the Template class.""" """Test the Template class."""
def test_init__disable_escape(self): def setUp(self):
# Test default value. """
Disable markupsafe.
"""
self.original_markupsafe = template.markupsafe
template.markupsafe = None
def tearDown(self):
self._restore_markupsafe()
def _was_markupsafe_imported(self):
return bool(self.original_markupsafe)
def _restore_markupsafe(self):
"""
Restore markupsafe to its original state.
"""
template.markupsafe = self.original_markupsafe
def test_init__escape__default_without_markupsafe(self):
template = Template()
self.assertEquals(template.escape(">'"), "&gt;'")
def test_init__escape__default_with_markupsafe(self):
if not self._was_markupsafe_imported():
# Then we cannot test this case.
return
self._restore_markupsafe()
template = Template() template = Template()
self.assertEquals(template.disable_escape, False) self.assertEquals(template.escape(">'"), "&gt;&#39;")
template = Template(disable_escape=True) def test_init__escape(self):
self.assertEquals(template.disable_escape, True) escape = lambda s: "foo" + s
template = Template(escape=escape)
self.assertEquals(template.escape("bar"), "foobar")
def test_render__unicode(self): def test_render__unicode(self):
template = Template(u'foo') template = Template(u'foo')
...@@ -138,7 +170,7 @@ class TemplateTestCase(unittest.TestCase): ...@@ -138,7 +170,7 @@ class TemplateTestCase(unittest.TestCase):
self.assertEquals(template.render(context), '1 &lt; 2') self.assertEquals(template.render(context), '1 &lt; 2')
template.disable_escape = True template.escape = lambda s: s
self.assertEquals(template.render(context), '1 < 2') self.assertEquals(template.render(context), '1 < 2')
def test_render__html_escape_disabled_with_partial(self): def test_render__html_escape_disabled_with_partial(self):
...@@ -148,7 +180,7 @@ class TemplateTestCase(unittest.TestCase): ...@@ -148,7 +180,7 @@ class TemplateTestCase(unittest.TestCase):
self.assertEquals(template.render(context), '1 &lt; 2') self.assertEquals(template.render(context), '1 &lt; 2')
template.disable_escape = True template.escape = lambda s: s
self.assertEquals(template.render(context), '1 < 2') self.assertEquals(template.render(context), '1 < 2')
def test_render__html_escape_disabled_with_non_false_value(self): def test_render__html_escape_disabled_with_non_false_value(self):
...@@ -157,7 +189,7 @@ class TemplateTestCase(unittest.TestCase): ...@@ -157,7 +189,7 @@ class TemplateTestCase(unittest.TestCase):
self.assertEquals(template.render(context), '1 &lt; 2') self.assertEquals(template.render(context), '1 &lt; 2')
template.disable_escape = True template.escape = lambda s: s
self.assertEquals(template.render(context), '1 < 2') self.assertEquals(template.render(context), '1 < 2')
def test_render__list_referencing_outer_context(self): def test_render__list_referencing_outer_context(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