diff --git a/CHANGES b/CHANGES index 91bd57a2b50dcdd2c0c4b1a1703d18f64a1df1fe..956942e178d605f09ca6e63b2e9cc1907935f191 100644 --- a/CHANGES +++ b/CHANGES @@ -7,6 +7,7 @@ either one "#" sign or two for now; two is preferred going forward, i.e. ## coding:<someencoding>. - a new multiline comment form of [PICK ONE:]"#* a comment *#"/"<%doc> a comment </%doc>" - UNDEFINED evaluates to False +- improvement to scoping of "caller" variable when using <%call> tag 0.1.2 - fix to parsing of code/expression blocks to insure that non-ascii diff --git a/lib/mako/codegen.py b/lib/mako/codegen.py index 477ccadd98cac5581ead03429bef0d01ba94647d..285e3927efb88800fd383ff256c0ad4a21e6f700 100644 --- a/lib/mako/codegen.py +++ b/lib/mako/codegen.py @@ -523,14 +523,15 @@ class _GenerateRenderMethod(object): self.printer.writelines( # push on global "caller" to be picked up by the next ccall - "context.caller_stack.append(runtime.Namespace('caller', context, callables=ccall(context.caller_stack[-1])))", + "caller = context['caller']._get_actual_caller()", + "context.push_caller(runtime.Namespace('caller', context, callables=ccall(runtime._StackFacade(context, caller))))", "try:") self.write_source_comment(node) self.printer.writelines( "context.write(unicode(%s))" % node.attributes['expr'], "finally:", # pop it off - "context.caller_stack.pop()", + "context.pop_caller()", None ) diff --git a/lib/mako/runtime.py b/lib/mako/runtime.py index 20748b9e41dbf87c547cfe5e7ab020ba6cc4b0d4..e6e640b85519a0a42ae2d092a245135678b5f1b7 100644 --- a/lib/mako/runtime.py +++ b/lib/mako/runtime.py @@ -23,9 +23,13 @@ class Context(object): # "caller" stack used by def calls with content self.caller_stack = [Undefined] - data['caller'] = _StackFacade(self.caller_stack) + data['caller'] = _StackFacade(self, None) lookup = property(lambda self:self._with_template.lookup) kwargs = property(lambda self:self._kwargs.copy()) + def push_caller(self, caller): + self.caller_stack.append(caller) + def pop_caller(self): + del self.caller_stack[-1] def keys(self): return self._data.keys() def __getitem__(self, key): @@ -67,10 +71,26 @@ class Context(object): return c class _StackFacade(object): - def __init__(self, stack): - self.target = stack + def __init__(self, context, local): + self.__stack = context.caller_stack + self.__local = local + def _get_actual_caller(self): + caller = self.__stack[-1] + if caller is None: + return self.__local + else: + return caller def __getattr__(self, key): - return getattr(self.target[-1], key) + caller = self._get_actual_caller() + callable_ = getattr(caller, key) + def call_wno_caller(*args, **kwargs): + try: + self.__stack.append(None) + return callable_(*args, **kwargs) + finally: + self.__stack.pop() + return call_wno_caller + class Undefined(object): """represents an undefined value in a template.""" diff --git a/test/call.py b/test/call.py index 28d8656eff657ecf114ddee10c450229e91b9f38..41fc27ce968bcd63a2dac350da98a97f6daf1205 100644 --- a/test/call.py +++ b/test/call.py @@ -58,7 +58,33 @@ class CallTest(unittest.TestCase): self.test_compound_call() finally: util.Set = oldset - + + def test_ccall_caller(self): + t = Template(""" + <%def name="outer_func()"> + OUTER BEGIN + <%call expr="caller.inner_func()"> + INNER CALL + </%call> + OUTER END + </%def> + + <%call expr="outer_func()"> + <%def name="inner_func()"> + INNER BEGIN + ${caller.body()} + INNER END + </%def> + </%call> + + """) + assert result_lines(t.render()) == [ + "OUTER BEGIN", + "INNER BEGIN", + "INNER CALL", + "INNER END", + "OUTER END", + ] def test_chained_call(self): """test %calls that are chained through their targets""" t = Template(""" @@ -167,6 +193,8 @@ class CallTest(unittest.TestCase): </%def> ${embedded()} """) + #print t.code + #print result_lines(t.render()) assert result_lines(t.render()) == [ 'this is a.', 'this is b. heres my body:',