diff --git a/lib/mako/codegen.py b/lib/mako/codegen.py index 480c620d117a0b1ac827a1b230f86399f45d0efa..66c72a17dd2a80312bf6e19bf65fb501f9b7e2f2 100644 --- a/lib/mako/codegen.py +++ b/lib/mako/codegen.py @@ -178,7 +178,7 @@ class _GenerateRenderMethod(object): identifiers = self.compiler.identifiers.branch(node) class NSDefVisitor(object): def visitDefTag(s, node): - self.write_inline_def(node, identifiers) + self.write_inline_def(node, identifiers, nested=False) export.append(node.name) vis = NSDefVisitor() for n in node.nodes: @@ -247,7 +247,7 @@ class _GenerateRenderMethod(object): if comp.is_root(): self.write_def_decl(comp, identifiers) else: - self.write_inline_def(comp, identifiers) + self.write_inline_def(comp, identifiers, nested=True) elif ident in self.compiler.namespaces: self.printer.writeline("%s = _mako_get_namespace(context, %s)" % (ident, repr(ident))) else: @@ -275,7 +275,7 @@ class _GenerateRenderMethod(object): self.printer.writeline("return render_%s(%s)" % (funcname, ",".join(nameargs))) self.printer.writeline(None) - def write_inline_def(self, node, identifiers): + def write_inline_def(self, node, identifiers, nested): """write a locally-available def callable inside an enclosing def.""" namedecls = node.function_decl.get_argument_expressions() self.printer.writeline("def %s(%s):" % (node.name, ",".join(namedecls))) @@ -288,7 +288,7 @@ class _GenerateRenderMethod(object): "try:" ) - identifiers = identifiers.branch(node) + identifiers = identifiers.branch(node, nested=nested) self.write_variable_declares(identifiers) for n in node.nodes: @@ -405,16 +405,16 @@ class _GenerateRenderMethod(object): def visitCallTag(self, node): self.printer.writeline("def ccall(context):") export = ['body'] - identifiers = self.identifiers.branch(node) + identifiers = self.identifiers.branch(node, includedefs=True, nested=True) + body_identifiers = identifiers.branch(node, includedefs=True, nested=False) class DefVisitor(object): def visitDefTag(s, node): - self.write_inline_def(node, identifiers) + self.write_inline_def(node, identifiers, nested=False) export.append(node.name) vis = DefVisitor() for n in node.nodes: n.accept_visitor(vis) self.printer.writeline("def body(**kwargs):") - body_identifiers = identifiers.branch(node, includedefs=False, includenode=False) # TODO: figure out best way to specify buffering/nonbuffering (at call time would be better) buffered = False if buffered: @@ -431,11 +431,18 @@ class _GenerateRenderMethod(object): "return [%s]" % (','.join(export)), None ) + + self.printer.writeline( + # preserve local instance of current caller in local scope + "__cl = context.locals_({'caller':context.caller_stack[-1]})", + ) + self.printer.writelines( - # preserve local instance of current caller in local scope - "__cl = context.locals_({'caller':context.caller_stack[-1]})", # push on global "caller" to be picked up by the next ccall "context.caller_stack.append(runtime.Namespace('caller', __cl, callables=ccall(__cl)))", + # TODO: clean this up - insure proper caller is set + "context._data['caller'] = runtime._StackFacade(context.caller_stack)", + #"context.write('GOING TO CALL %s WITH CONTEXT ID '+ repr(id(context)) + ' CALLER ' + repr(context.get('caller')))" % node.attributes['expr'], "try:") self.write_source_comment(node) self.printer.writelines( @@ -448,11 +455,17 @@ class _GenerateRenderMethod(object): class _Identifiers(object): """tracks the status of identifier names as template code is rendered.""" - def __init__(self, node=None, parent=None, includedefs=True, includenode=True): + def __init__(self, node=None, parent=None, includedefs=True, includenode=True, nested=False): if parent is not None: # things that have already been declared in an enclosing namespace (i.e. names we can just use) self.declared = util.Set(parent.declared).union([c.name for c in parent.closuredefs]).union(parent.locally_declared) + # if these identifiers correspond to a "nested" scope, it means whatever the + # parent identifiers had as undeclared will have been declared by that parent, + # and therefore we have them in our scope. + if nested: + self.declared = self.declared.union(parent.undeclared) + # top level defs that are available self.topleveldefs = util.Set(parent.topleveldefs) else: diff --git a/lib/mako/runtime.py b/lib/mako/runtime.py index 79c58684ea224bd830f17a796715f635a0481d55..16d7cee4f4c72cd8d23f089b4ab79261925e2b20 100644 --- a/lib/mako/runtime.py +++ b/lib/mako/runtime.py @@ -26,6 +26,8 @@ class Context(object): def keys(self): return self._data.keys() def __getitem__(self, key): + if key == 'caller': + return _StackFacade(self.caller_stack) return self._data[key] def _put(self, key, value): self._data[key] = value @@ -47,6 +49,8 @@ class Context(object): c._with_template = self._with_template c.namespaces = self.namespaces c.caller_stack = self.caller_stack + if not c._data.has_key('caller'): + raise "WTF" return c def locals_(self, d): """create a new Context with a copy of this Context's current state, updated with the given dictionary.""" diff --git a/test/call.py b/test/call.py index fce43f47d6b4cb9165ae50ba06d12040e66694ae..c821d9e3e3ef4f7508e28cac2f31d749a09a6200 100644 --- a/test/call.py +++ b/test/call.py @@ -13,6 +13,8 @@ class CallTest(unittest.TestCase): this is the body, y is ${y} </%call> """) + print t.code + print t.render() assert result_lines(t.render()) == ['hi im foo', 'this is the body, y is 5'] @@ -41,6 +43,7 @@ class CallTest(unittest.TestCase): ${bar()} """) + print t.code assert result_lines(t.render()) == ['foo calling comp1:', 'this is comp1, 5', 'foo calling body:', 'this is the body,', 'this is comp1, 6', 'this is bar'] def test_multi_call(self): @@ -61,6 +64,7 @@ class CallTest(unittest.TestCase): </%call> """) + print t.render() assert result_lines(t.render()) == [ 'this is a.', 'this is b. heres my body:', @@ -90,6 +94,7 @@ class CallTest(unittest.TestCase): </%def> ${embedded()} """) + print t.render() assert result_lines(t.render()) == [ 'this is a.', 'this is b. heres my body:', @@ -117,6 +122,44 @@ class CallTest(unittest.TestCase): """) assert result_lines(t.render()) == ['this is a', 'this is b', 'this is c:', "this is the body in b's call"] + def test_ccall_args(self): + t = Template(""" + <%def name="foo"> + foo context id: ${id(context)} + foo cstack: ${repr(context.caller_stack)} + foo, ccaller is ${context.get('caller')} + foo, context data is ${repr(context._data)} + ${caller.body(x=10)} + </%def> + + <%def name="bar"> + bar context id: ${id(context)} + bar cstack: ${repr(context.caller_stack)} + bar, cs is ${context.caller_stack[-1]} + bar, caller is ${caller} + bar, ccaller is ${context.get('caller')} + bar, body is ${context.caller_stack[-1].body()} + bar, context data is ${repr(context._data)} + </%def> + + x is: ${x} + + main context id: ${id(context)} + main cstack: ${repr(context.caller_stack)} + + <%call expr="foo()"> + this is foo body: ${x} + + foocall context id: ${id(context)} + foocall cstack: ${repr(context.caller_stack)} + <%call expr="bar()"> + this is bar body: ${x} + </%call> + </%call> +""") + print t.code + print t.render(x=5) + def test_call_in_nested_2(self): t = Template(""" <%def name="a"> diff --git a/test/def.py b/test/def.py index a0e409afbc8b0ba3068ad59dc47db20bc5513beb..b7a7b86c7416ddc220bf5067c38e88c2040fcaa8 100644 --- a/test/def.py +++ b/test/def.py @@ -317,6 +317,21 @@ class NestedDefTest(unittest.TestCase): """) assert flatten_result(t.render()) == "hey, im hi. and heres this is foo , this is bar" + def test_nested_2(self): + t = Template(""" + x is ${x} + <%def name="a"> + this is a, x is ${x} + ${b()} + <%def name="b"> + this is b: ${x} + </%def> + </%def> + ${a()} +""") + print t.code + print t.render(x=10) + def test_nested_with_args(self): t = Template(""" ${a()} diff --git a/test/namespace.py b/test/namespace.py index 6e9614b7cb0ae783229421965efa59176c213696..c8d2e11dcc65374be6bd8382e3bdfde7d03eae6e 100644 --- a/test/namespace.py +++ b/test/namespace.py @@ -344,6 +344,7 @@ class NamespaceTest(unittest.TestCase): </%call> """) print collection.get_template("index.html").code + print collection.get_template("functions.html").code print collection.get_template("index.html").render() if __name__ == '__main__':