Commit c4ebffd1 by Chris Jerdonek

Merge branch 'issue_52' into development: closing issue #52

parents 499e3d2c 1cb0d1f5
...@@ -3,6 +3,8 @@ History ...@@ -3,6 +3,8 @@ History
Next Release (version TBD) Next Release (version TBD)
-------------------------- --------------------------
* API change: pass the context to render to Template.render() instead of
Template.__init__(). [cjerdonek]
* Bugfix: Passing **kwargs to Template() modified the context. [cjerdonek] * Bugfix: Passing **kwargs to Template() modified the context. [cjerdonek]
* Bugfix: Passing **kwargs to Template() with no context raised an * Bugfix: Passing **kwargs to Template() with no context raised an
exception. [cjerdonek] exception. [cjerdonek]
......
...@@ -64,7 +64,8 @@ def main(sys_argv): ...@@ -64,7 +64,8 @@ def main(sys_argv):
except IOError: except IOError:
context = json.loads(context) context = json.loads(context)
print(Template(template, context).render()) template = Template(template)
print(template.render(context))
if __name__=='__main__': if __name__=='__main__':
......
...@@ -18,4 +18,5 @@ def render(template, context=None, **kwargs): ...@@ -18,4 +18,5 @@ def render(template, context=None, **kwargs):
Return the given template string rendered using the given context. Return the given template string rendered using the given context.
""" """
return Template(template, context, **kwargs).render() template = Template(template)
return template.render(context, **kwargs)
...@@ -62,26 +62,44 @@ class Template(object): ...@@ -62,26 +62,44 @@ class Template(object):
modifiers = Modifiers() modifiers = Modifiers()
def __init__(self, template=None, context=None, load_template=None, **kwargs): def __init__(self, template=None, load_template=None, output_encoding=None):
""" """
Construct a Template instance. Construct a Template instance.
Arguments: Arguments:
template: a template string as a unicode string. Behavior is
undefined if the string has type str.
context: a dictionary, Context, or View instance. context: a dictionary, Context, or View instance.
load_template: the function for loading partials. The function should load_template: the function for loading partials. The function should
accept a single template_name parameter and return a template as accept a single template_name parameter and return a template as
a string. Defaults to the default Loader's load_template() method. a string. Defaults to the default Loader's load_template() method.
""" output_encoding: the encoding to use when rendering to a string.
if context is None: The argument should be the name of an encoding as a string, for
context = {} example "utf-8". See the render() method's documentation for more
information.
"""
if load_template is None: if load_template is None:
loader = Loader() loader = Loader()
load_template = loader.load_template load_template = loader.load_template
load_template = getattr(context, 'load_template', load_template)
self.load_template = load_template
self.output_encoding = output_encoding
self.template = template
self._compile_regexps()
def _initialize_context(self, context, **kwargs):
"""
Initialize the context attribute.
"""
if context is None:
context = {}
if isinstance(context, Context): if isinstance(context, Context):
context = context.copy() context = context.copy()
...@@ -92,10 +110,7 @@ class Template(object): ...@@ -92,10 +110,7 @@ class Template(object):
context.push(kwargs) context.push(kwargs)
self.context = context self.context = context
self.load_template = load_template
self.template = template
self._compile_regexps()
def _compile_regexps(self): def _compile_regexps(self):
""" """
...@@ -172,8 +187,8 @@ class Template(object): ...@@ -172,8 +187,8 @@ 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, self.context, self.load_template) template = Template(template, load_template=self.load_template)
out = template.render() out = template.render(self.context)
self.context.pop() self.context.pop()
...@@ -211,8 +226,8 @@ class Template(object): ...@@ -211,8 +226,8 @@ 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, self.context, self.load_template) template = Template(markup, load_template=self.load_template)
return template.render() return template.render(self.context)
@modifiers.set('=') @modifiers.set('=')
def _change_delimiter(self, tag_name): def _change_delimiter(self, tag_name):
...@@ -234,15 +249,21 @@ class Template(object): ...@@ -234,15 +249,21 @@ class Template(object):
""" """
return literal(self.context.get(tag_name, '')) return literal(self.context.get(tag_name, ''))
def render(self, encoding=None): def render(self, context=None, **kwargs):
""" """
Return the template rendered using the current context. Return the template rendered using the current context.
The return value is a unicode string, unless the output_encoding
attribute is not None, in which case the return value has type str
and is encoded using that encoding.
""" """
self._initialize_context(context, **kwargs)
template = self._render_sections(self.template) template = self._render_sections(self.template)
result = self._render_tags(template) result = self._render_tags(template)
if encoding is not None: if self.output_encoding is not None:
result = result.encode(encoding) result = result.encode(self.output_encoding)
return result return result
...@@ -93,8 +93,8 @@ class View(object): ...@@ -93,8 +93,8 @@ class View(object):
Return the view rendered using the current context. Return the view rendered using the current context.
""" """
template = Template(self.get_template(), self.context, self.load_template) template = Template(self.get_template(), self.load_template, output_encoding=encoding)
return template.render(encoding=encoding) return template.render(self.context)
def get(self, key, default=None): def get(self, key, default=None):
return self.context.get(key, default) return self.context.get(key, default)
......
import unittest import unittest
import pystache import pystache
from pystache import Template
from examples.nested_context import NestedContext from examples.nested_context import NestedContext
from examples.complex_view import ComplexView from examples.complex_view import ComplexView
from examples.lambdas import Lambdas from examples.lambdas import Lambdas
...@@ -8,16 +9,15 @@ from examples.simple import Simple ...@@ -8,16 +9,15 @@ from examples.simple import Simple
class TestSimple(unittest.TestCase): class TestSimple(unittest.TestCase):
def test_simple_render(self):
self.assertEqual('herp', pystache.Template('{{derp}}', {'derp': 'herp'}).render())
def test_nested_context(self): def test_nested_context(self):
view = NestedContext() view = NestedContext()
self.assertEquals(pystache.Template('{{#foo}}{{thing1}} and {{thing2}} and {{outer_thing}}{{/foo}}{{^foo}}Not foo!{{/foo}}', view).render(), "one and foo and two") view.template = '{{#foo}}{{thing1}} and {{thing2}} and {{outer_thing}}{{/foo}}{{^foo}}Not foo!{{/foo}}'
self.assertEquals(view.render(), "one and foo and two")
def test_looping_and_negation_context(self): def test_looping_and_negation_context(self):
view = ComplexView() view = ComplexView()
self.assertEquals(pystache.Template('{{#item}}{{header}}: {{name}} {{/item}}{{^item}} Shouldnt see me{{/item}}', view).render(), "Colors: red Colors: green Colors: blue ") view.template = '{{#item}}{{header}}: {{name}} {{/item}}{{^item}} Shouldnt see me{{/item}}'
self.assertEquals(view.render(), "Colors: red Colors: green Colors: blue ")
def test_empty_context(self): def test_empty_context(self):
view = ComplexView() view = ComplexView()
...@@ -25,13 +25,16 @@ class TestSimple(unittest.TestCase): ...@@ -25,13 +25,16 @@ class TestSimple(unittest.TestCase):
def test_callables(self): def test_callables(self):
view = Lambdas() view = Lambdas()
self.assertEquals(pystache.Template('{{#replace_foo_with_bar}}foo != bar. oh, it does!{{/replace_foo_with_bar}}', view).render(), 'bar != bar. oh, it does!') view.template = '{{#replace_foo_with_bar}}foo != bar. oh, it does!{{/replace_foo_with_bar}}'
self.assertEquals(view.render(), 'bar != bar. oh, it does!')
def test_rendering_partial(self): def test_rendering_partial(self):
view = TemplatePartial() view = TemplatePartial()
self.assertEquals(pystache.Template('{{>inner_partial}}', view).render(), 'Again, Welcome!') view.template = '{{>inner_partial}}'
self.assertEquals(view.render(), 'Again, Welcome!')
self.assertEquals(pystache.Template('{{#looping}}{{>inner_partial}} {{/looping}}', view).render(), '''Again, Welcome! Again, Welcome! Again, Welcome! ''') view.template = '{{#looping}}{{>inner_partial}} {{/looping}}'
self.assertEquals(view.render(), '''Again, Welcome! Again, Welcome! Again, Welcome! ''')
def test_non_existent_value_renders_blank(self): def test_non_existent_value_renders_blank(self):
view = Simple() view = Simple()
......
...@@ -5,6 +5,7 @@ Unit tests of template.py. ...@@ -5,6 +5,7 @@ Unit tests of template.py.
""" """
import codecs
import unittest import unittest
from pystache.template import Template from pystache.template import Template
...@@ -14,20 +15,70 @@ class TemplateTestCase(unittest.TestCase): ...@@ -14,20 +15,70 @@ class TemplateTestCase(unittest.TestCase):
"""Test the Template class.""" """Test the Template class."""
def test_init__kwargs_with_no_context(self): def test_render__unicode(self):
template = Template(u'foo')
actual = template.render()
self.assertTrue(isinstance(actual, unicode))
self.assertEquals(actual, u'foo')
def test_render__str(self):
template = Template('foo')
actual = template.render()
self.assertTrue(isinstance(actual, str))
self.assertEquals(actual, 'foo')
def test_render__non_ascii_character(self):
template = Template(u'Poincaré')
actual = template.render()
self.assertTrue(isinstance(actual, unicode))
self.assertEquals(actual, u'Poincaré')
def test_render__context(self):
"""
Test render(): passing a context.
"""
template = Template('Hi {{person}}')
self.assertEquals(template.render({'person': 'Mom'}), 'Hi Mom')
def test_render__context_and_kwargs(self):
"""
Test render(): passing a context and **kwargs.
"""
template = Template('Hi {{person1}} and {{person2}}')
self.assertEquals(template.render({'person1': 'Mom'}, person2='Dad'), 'Hi Mom and Dad')
def test_render__kwargs_and_no_context(self):
"""
Test render(): passing **kwargs and no context.
"""
template = Template('Hi {{person}}')
self.assertEquals(template.render(person='Mom'), 'Hi Mom')
def test_render__context_and_kwargs__precedence(self):
""" """
Test passing **kwargs with no context. Test render(): **kwargs takes precedence over context.
""" """
# This test checks that the following line raises no exception. template = Template('Hi {{person}}')
template = Template(foo="bar") self.assertEquals(template.render({'person': 'Mom'}, person='Dad'), 'Hi Dad')
def test_init__kwargs_does_not_modify_context(self): def test_render__kwargs_does_not_modify_context(self):
""" """
Test that passing **kwargs does not modify the passed context. Test render(): passing **kwargs does not modify the passed context.
""" """
context = {} context = {}
template = Template(context=context, foo="bar") template = Template('Hi {{person}}')
template.render(context=context, foo="bar")
self.assertEquals(context, {}) self.assertEquals(context, {})
def test_render__output_encoding(self):
template = Template(u'Poincaré')
template.output_encoding = 'utf-8'
actual = template.render()
self.assertTrue(isinstance(actual, str))
self.assertEquals(actual, 'Poincaré')
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