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):
modifiers = Modifiers()
def __init__(self, template=None, load_template=None, output_encoding=None,
disable_escape=False):
def __init__(self, template=None, load_template=None, output_encoding=None, escape=None):
"""
Construct a Template instance.
......@@ -79,19 +78,27 @@ class Template(object):
example "utf-8". See the render() method's documentation for more
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:
loader = Loader()
load_template = loader.load_template
if markupsafe:
escape = markupsafe.escape
literal = markupsafe.Markup
else:
escape = lambda x: cgi.escape(unicode(x))
literal = unicode
if escape is None:
escape = markupsafe.escape if markupsafe else cgi.escape
literal = markupsafe.Markup if markupsafe else unicode
self.disable_escape = disable_escape
self.escape = escape
self.literal = literal
self.load_template = load_template
......@@ -100,6 +107,11 @@ class Template(object):
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):
"""
Initialize the context attribute.
......@@ -215,7 +227,7 @@ class Template(object):
def _render_dictionary(self, template, 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)
self.context.pop()
......@@ -245,7 +257,7 @@ class Template(object):
else:
return ''
return self._render_value(raw)
return self._unicode_and_escape(raw)
@modifiers.set('!')
def _render_comment(self, tag_name):
......@@ -254,7 +266,7 @@ class Template(object):
@modifiers.set('>')
def _render_partial(self, 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)
@modifiers.set('=')
......@@ -295,8 +307,6 @@ class Template(object):
"""
self._initialize_context(context, **kwargs)
self._render_value = self.literal if self.disable_escape else self.escape
result = self._render(self.template)
if self.output_encoding is not None:
......
......@@ -88,13 +88,18 @@ class View(object):
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.
"""
template = Template(self.get_template(), self.load_template, output_encoding=encoding,
disable_escape=disable_escape)
escape=escape)
return template.render(self.context)
def get(self, key, default=None):
......
......@@ -31,16 +31,17 @@ class TestView(unittest.TestCase):
self.assertEquals(UnicodeInput().render(),
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>")
def test_escaped_disabling(self):
self.assertEquals(Escaped().render(disable_escape=True), "<h1>Bear > Shark</h1>")
def test_escaping__custom(self):
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>")
def test_unescaped_sigil(self):
def test_literal_sigil(self):
view = Escaped(template="<h1>{{& thing}}</h1>", context={
'thing': 'Bear > Giraffe'
})
......
......@@ -8,6 +8,7 @@ Unit tests of template.py.
import codecs
import unittest
from pystache import template
from pystache.template import Template
......@@ -15,13 +16,44 @@ class TemplateTestCase(unittest.TestCase):
"""Test the Template class."""
def test_init__disable_escape(self):
# Test default value.
def setUp(self):
"""
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()
self.assertEquals(template.disable_escape, False)
self.assertEquals(template.escape(">'"), "&gt;&#39;")
template = Template(disable_escape=True)
self.assertEquals(template.disable_escape, True)
def test_init__escape(self):
escape = lambda s: "foo" + s
template = Template(escape=escape)
self.assertEquals(template.escape("bar"), "foobar")
def test_render__unicode(self):
template = Template(u'foo')
......@@ -138,7 +170,7 @@ class TemplateTestCase(unittest.TestCase):
self.assertEquals(template.render(context), '1 &lt; 2')
template.disable_escape = True
template.escape = lambda s: s
self.assertEquals(template.render(context), '1 < 2')
def test_render__html_escape_disabled_with_partial(self):
......@@ -148,7 +180,7 @@ class TemplateTestCase(unittest.TestCase):
self.assertEquals(template.render(context), '1 &lt; 2')
template.disable_escape = True
template.escape = lambda s: s
self.assertEquals(template.render(context), '1 < 2')
def test_render__html_escape_disabled_with_non_false_value(self):
......@@ -157,7 +189,7 @@ class TemplateTestCase(unittest.TestCase):
self.assertEquals(template.render(context), '1 &lt; 2')
template.disable_escape = True
template.escape = lambda s: s
self.assertEquals(template.render(context), '1 < 2')
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