summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMarcel Hellkamp <marc@gsites.de>2011-10-21 18:33:29 +0200
committerMarcel Hellkamp <marc@gsites.de>2011-10-23 14:18:34 +0200
commit096bd940a677be08fb8d16414de1b30898700241 (patch)
treebc8de5ef7f3cc21861623a695a1f3a352c15d348
parent7ddc851e25e049b16952c94345cc8849a4c5e554 (diff)
downloadbottle-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-xbottle.py60
-rwxr-xr-xtest/test_stpl.py42
2 files changed, 68 insertions, 34 deletions
diff --git a/bottle.py b/bottle.py
index d40d6aa..1957846 100755
--- a/bottle.py
+++ b/bottle.py
@@ -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()