diff options
author | Marcel Hellkamp <marc@gsites.de> | 2011-10-21 18:33:29 +0200 |
---|---|---|
committer | Marcel Hellkamp <marc@gsites.de> | 2011-10-23 14:18:34 +0200 |
commit | 096bd940a677be08fb8d16414de1b30898700241 (patch) | |
tree | bc8de5ef7f3cc21861623a695a1f3a352c15d348 | |
parent | 7ddc851e25e049b16952c94345cc8849a4c5e554 (diff) | |
download | bottle-stpl-1.5.tar.gz |
Added a new @block decorator to SimpleTemplates that turns a function into an extensible block.stpl-1.5
Example:
A super-template is a template that include()s a sub-template or uses a layout().
A sub-template is a template that was included by a super-template or used as a layout.
super.tpl:
% layout('sub')
% @block
% def navigation(super):
* Additional
* Links
super()
% end
sub.tpl:
% @block
% def navigation():
* Common
* Links
% end
-rwxr-xr-x | bottle.py | 60 | ||||
-rwxr-xr-x | test/test_stpl.py | 42 |
2 files changed, 68 insertions, 34 deletions
@@ -2690,52 +2690,62 @@ class SimpleTemplate(BaseTemplate): def co(self): return self.parser.compile(self.source, self.filename or '<string>') + def _cached(self, name): + if name not in self.cache: + self.cache[name] = self.__class__(name=name, lookup=self.lookup) + return self.cache[name] + + def _block(self, env, arg=None): + name = None if callable(arg) else arg + def decorator(func): + rname = name or func.__name__ + env['_blocks'][rname] = func + return func + return decorator(arg) if callable(arg) else decorator + + def _extends(self, env, arg=None): + name = None if callable(arg) else arg + def decorator(func): + rname = name or func.__name__ + if '_next' not in env: + raise Exception('Rendered as main template.') + if rname not in env['_next']['_blocks']: + raise Exception('No block with that name.') + return partial(env['_next']['_blocks'][rname], func) + return decorator(arg) if callable(arg) else decorator + def _include(self, _env, _name, *args, **kwargs): ''' Render a sub-template into the stdout buffer. ''' - if _name not in self.cache: - self.cache[_name] = self.__class__(name=_name, lookup=self.lookup) - return self.cache[_name].execute(_env['_stdout'], _env, *args, **kwargs) + newenv, subtpl = _env.copy(), self._cached(_name) + newenv['_next'] = _env + return subtpl.execute(_env['_stdout'], newenv, *args, **kwargs) def _layout(self, _env, _name, *args, **kwargs): ''' Add rebase info to the env namespace. ''' _env['_layout'] = _name, args, kwargs - def _next(self, env, name, default=Exception): - if '_super' in env: - if name in env['_super']: - return env['_super'] - elif default is Exception: - raise NameError('Layout template does not define %r' % name) - elif default is Exception: - raise RuntimeError('No layout template found.') - return default - - def execute(self, _stdout, *args, **kwargs): - env = self.defaults.copy() + def execute(self, stdout, env, *args, **kwargs): for dictarg in args: env.update(dictarg) env.update(kwargs) - env.update({'_stdout': _stdout, '_printlist': _stdout.extend, + env.update({'_stdout': stdout, '_printlist': stdout.extend, '_str': self._str, '_escape': self._escape, 'get': env.get, 'setdefault': env.setdefault, 'defined': env.__contains__, 'layout': partial(self._layout, env), '_layout': None, 'include': partial(self._include, env), - 'next': partial(self._next, env) - }) - if '_super' in env: kwargs['_super'] = env.pop('_super') + 'block': partial(self._block, env), '_blocks':{}}) eval(self.co, env) if env.get('_layout'): name, args, kwargs = env['_layout'] - kwargs['base'] = _stdout[:] #copy stdout - kwargs['_super'] = env - del _stdout[:] # clear stdout - return self._include(env, name, *args, **kwargs) + nenv = env.copy() + kwargs.update(base=stdout[:], _next=env) + del stdout[:] + return self._cached(name).execute(stdout, nenv, *args, **kwargs) return env def render(self, *args, **kwargs): """ Render the template using keyword arguments as local variables. """ - for dictarg in args: kwargs.update(dictarg) stdout = [] - self.execute(stdout, kwargs) + self.execute(stdout, self.defaults.copy(), *args, **kwargs) return ''.join(stdout) diff --git a/test/test_stpl.py b/test/test_stpl.py index f81903f..26da8a1 100755 --- a/test/test_stpl.py +++ b/test/test_stpl.py @@ -143,7 +143,7 @@ class TestSimpleTemplate(unittest.TestCase): """ Templates: Nobreak statements""" t = SimpleTemplate("start\\\\\n%pass\nend") self.assertEqual(u'startend', t.render()) - + def test_nonobreak(self): """ Templates: Escaped nobreak statements""" t = SimpleTemplate("start\\\\\n\\\\\n%pass\nend") @@ -183,7 +183,7 @@ class TestSimpleTemplate(unittest.TestCase): """ Templates: Exceptions""" self.assertRaises(SyntaxError, lambda: SimpleTemplate('%for badsyntax').co) self.assertRaises(IndexError, SimpleTemplate('{{i[5]}}').render, i=[0]) - + def test_winbreaks(self): """ Templates: Test windows line breaks """ t = SimpleTemplate('%var+=1\r\n{{var}}\r\n') @@ -232,20 +232,44 @@ class TestSimpleTemplate(unittest.TestCase): t = SimpleTemplate(u'anything') self.assertEqual(u'anything', t.render()) - def test_global_config(self): + def test_super_include(self): + main = SimpleTemplate(''' + % @block + % def func(): + content + % end + % include("sub") + ''') + sub = SimpleTemplate('''start + % @extend + % def func(super): + layout + % super() + % end + % func() + end''') + main.cache['sub'] = sub + self.assertEqual([u'start', u'content', u'layout', u'end'], + main.render().split()) + + def test_super_layout(self): content = SimpleTemplate('''% layout("layout") - % def x(): - content {{next('x')()}} + % @extend + % def func(super): + content + % super() % end ''') layout = SimpleTemplate('''start - % def x(): + % @block + % def func(): layout % end - %_super['x']() - ''') + % func() + end''') content.cache['layout'] = layout - self.assertEqual(u'anything', content.render()) + self.assertEqual([u'start', u'content', u'layout', u'end'], + content.render().split()) if __name__ == '__main__': #pragma: no cover unittest.main() |