inheritance works for namespaces pulled in via <%namespace>
diff --git a/doc/makotemplates.txt b/doc/makotemplates.txt index 9abb728..3c1b019 100644 --- a/doc/makotemplates.txt +++ b/doc/makotemplates.txt
@@ -21,8 +21,8 @@ - Python Server Pages. Templates compile into Python modules for maximum performance. <li> <p>Insanely Fast. An included bench suite, adapted from a suite included with Genshi, has these results for a simple three-sectioned layout:</p> <table> -<tr><td>Mako:</td><td>0.51 ms</td></tr> -<tr><td>Cheetah:</td><td>0.80 ms</td></tr> +<tr><td>Mako:</td><td>0.66 ms</td></tr> +<tr><td>Cheetah:</td><td>0.74 ms</td></tr> <tr><td>Django:</td><td>5.43 ms</td></tr> <tr><td>Myghty:</td><td>5.25 ms</td></tr> <tr><td>Genshi:</td><td>12.53 ms</td></tr>
diff --git a/lib/mako/codegen.py b/lib/mako/codegen.py index c8c6143..ff801a5 100644 --- a/lib/mako/codegen.py +++ b/lib/mako/codegen.py
@@ -42,9 +42,11 @@ printer.writeline("_modified_time = %s" % repr(time.time())) printer.writeline("_template_filename=%s" % repr(self.filename)) printer.writeline("UNDEFINED = runtime.UNDEFINED") + module_identifiers.declared.add("UNDEFINED") printer.writeline("_exports = %s" % repr([n.name for n in main_identifiers.toplevelcomponents])) printer.write("\n\n") + for n in module_code: printer.writeline("# SOURCE LINE %d" % n.lineno, is_comment=True) printer.write_indented_block(n.text) @@ -98,7 +100,7 @@ class FindInherit(object): def visitInheritTag(s, node): self.printer.writeline("def _inherit(context):") - self.printer.writeline("runtime.inherit_from(context, %s)" % (repr(node.attributes['file']))) + self.printer.writeline("return runtime.inherit_from(context, %s)" % (repr(node.attributes['file']))) self.printer.writeline(None) f = FindInherit() for n in self.node.nodes: @@ -220,7 +222,7 @@ n.accept_visitor(vis) self.printer.writeline("return [%s]" % (','.join(export))) self.printer.writeline(None) - self.printer.writeline("%s = runtime.Namespace(%s, context.clean_inheritance_tokens(), callables=make_namespace())" % (node.name, repr(node.name))) + self.printer.writeline("%s = runtime.Namespace(%s, context.clean_inheritance_tokens(), templateuri=%s, callables=make_namespace())" % (node.name, repr(node.name), repr(node.attributes['file']))) def visitComponentTag(self, node): pass
diff --git a/lib/mako/runtime.py b/lib/mako/runtime.py index c7a47d1..ac979e8 100644 --- a/lib/mako/runtime.py +++ b/lib/mako/runtime.py
@@ -13,7 +13,7 @@ def __init__(self, buffer, **data): self.buffer = buffer self._argstack = [data] - self._template_stack = [] + self._with_template = None data['args'] = _AttrFacade(self) def keys(self): return self._argstack[-1].keys() @@ -39,7 +39,7 @@ c.buffer = self.buffer x = self._argstack[-1].copy() c._argstack = [x] - c._template_stack = self._template_stack + c._with_template = self._with_template x['args'] = _AttrFacade(c) return c def locals_(self, d): @@ -49,9 +49,10 @@ return c def clean_inheritance_tokens(self): c = self._copy() - c._argstack[-1].pop('self', None) - c._argstack[-1].pop('parent', None) - c._argstack[-1].pop('next', None) + x = c._argstack[-1] + x.pop('self', None) + x.pop('parent', None) + x.pop('next', None) return c class _AttrFacade(object): @@ -69,16 +70,21 @@ class Namespace(object): """provides access to collections of rendering methods, which can be local, from other templates, or from imported modules""" - def __init__(self, name, context, module=None, template=None, callables=None, inherits=None): + def __init__(self, name, context, module=None, template=None, templateuri=None, callables=None, inherits=None, populate_self=True): self.name = name - self.context = context self.module = module - self.template = template + if templateuri is not None: + self.template = _lookup_template(context, templateuri) + else: + self.template = template + self.context = context self.inherits = inherits if callables is not None: self.callables = dict([(c.func_name, c) for c in callables]) else: self.callables = {} + if populate_self and self.template is not None: + (lclcallable, self.context) = _populate_self_namespace(context, self.template, self_ns=self) def __getattr__(self, key): if self.callables is not None: @@ -106,42 +112,39 @@ return getattr(self.inherits, key) raise exceptions.RuntimeException("Namespace '%s' has no member '%s'" % (self.name, key)) -def _lookup_template(context, uri): - lookup = context._template_stack[0].lookup - return lookup.get_template(uri) def include_file(context, uri, import_symbols): template = _lookup_template(context, uri) - callable_ = getattr(template.module, '_inherit', getattr(template.module, 'render')) - context._template_stack.append(template) - try: - callable_(context.clean_inheritance_tokens()) - finally: - context._template_stack.pop() + (callable_, ctx) = _populate_self_namespace(context.clean_inheritance_tokens(), template) + callable_(ctx) def inherit_from(context, uri): template = _lookup_template(context, uri) - self_ns = context.get('self', None) - if self_ns is None: - fromtempl = context._template_stack[-1] - self_ns = Namespace('self:%s' % fromtempl.description, context, template=fromtempl) - context._argstack[-1]['self'] = self_ns + self_ns = context['self'] ih = self_ns while ih.inherits is not None: ih = ih.inherits lclcontext = context.locals_({'next':ih}) - ih.inherits = Namespace("self:%s" % template.description, lclcontext, template = template) + ih.inherits = Namespace("self:%s" % template.description, lclcontext, template = template, populate_self=False) context._argstack[-1]['parent'] = ih.inherits - callable_ = getattr(template.module, '_inherit', getattr(template.module, 'render')) - callable_(lclcontext) - -def populate_inheritance_namespace(context, template): - context._dont_exec = True - self_ns = Namespace('self:%s' % template.description, context, template=template) - context._argstack[-1]['self'] = self_ns callable_ = getattr(template.module, '_inherit', None) - if callable_ is not None: - callable_(context) + if callable_ is not None: + return callable_(lclcontext) + else: + return (template.callable_, lclcontext) + +def _lookup_template(context, uri): + lookup = context._with_template.lookup + return lookup.get_template(uri) + +def _populate_self_namespace(context, template, self_ns=None): + if self_ns is None: + self_ns = Namespace('self:%s' % template.description, context, template=template, populate_self=False) + context._argstack[-1]['self'] = self_ns + if hasattr(template.module, '_inherit'): + return template.module._inherit(context) + else: + return (template.callable_, context) def _render(template, callable_, args, data, as_unicode=False): """given a Template and a callable_ from that template, create a Context and return the string output.""" @@ -153,8 +156,6 @@ buf = util.StringIO() context = Context(buf, **data) kwargs = {} - if callable_.__name__ == 'render': - callable_ = getattr(template.module, '_inherit', callable_) argspec = inspect.getargspec(callable_) namedargs = argspec[0] + [v for v in argspec[1:3] if v is not None] for arg in namedargs: @@ -164,18 +165,23 @@ return buf.getvalue() def _render_context(template, callable_, context, *args, **kwargs): - context._template_stack.append(template) - try: + context._with_template = template + # create polymorphic 'self' namespace for this template with possibly updated context + (inherit, lclcontext) = _populate_self_namespace(context, template) + if callable_.__name__ == 'render': + # if main render method, call from the base of the inheritance stack + _exec_template(inherit, lclcontext, args=args, kwargs=kwargs) + else: + # otherwise, call the actual rendering method specified _exec_template(callable_, context, args=args, kwargs=kwargs) - finally: - context._template_stack.pop() + def _exec_template(callable_, context, args=None, kwargs=None): """execute a rendering callable given the callable, a Context, and optional explicit arguments the contextual Template will be located if it exists, and the error handling options specified on that Template will be interpreted here. """ - template = context._template_stack[0] + template = context._with_template if template is not None and (template.format_exceptions or template.error_handler): error = None try:
diff --git a/test/inheritance.py b/test/inheritance.py index 5f6c3af..ccd9b15 100644 --- a/test/inheritance.py +++ b/test/inheritance.py
@@ -62,9 +62,13 @@ <%inherit file="base"/> <%component name="d">general_d</%component> general_body + % if next is UNDEFINED: + next is undefined ! + % else: ${next.d()} ${next.context['next'].d()} ${next.body()} + % endif """) collection.put_string('base', """ base_body @@ -102,6 +106,46 @@ """) print collection.get_template("index").render() + + def test_namespaces(self): + """test that templates used via <%namespace> have access to an inheriting 'self', and that + the full 'self' is also exported.""" + collection = lookup.TemplateLookup() + + collection.put_string("base", """ + <html> + <%component name="a">base_a</%component> + <%component name="b">base_b</%component> + This is the base. + ${next.body()} + </html> +""") + + collection.put_string("layout", """ + <html> + <%inherit file="base"/> + <%component name="a">layout_a</%component> + This is the layout.. + ${next.body()} + </html> +""") + + collection.put_string("index",""" + <%inherit file="base"/> + <%namespace name="sc" file="secondary"/> + this is index. + a is: ${self.a()} + sc.a is: ${sc.a()} + sc.b is: ${sc.b()} +""") + + collection.put_string("secondary",""" + <%inherit file="layout"/> + <%component name="c">secondary_c. a is ${self.a()} b is ${self.b()}</%component> + this is secondary. + a is: ${self.a()} +""") + print collection.get_template('index').render() if __name__ == '__main__': unittest.main()