diff --git a/CHANGES b/CHANGES index 07c9ff48098d9295dd19fe45dc8ae0a65a659be7..c24d19b6715f26a9fc873335441b8634dcf3aeb1 100644 --- a/CHANGES +++ b/CHANGES @@ -1,4 +1,12 @@ -0.5.1 +0.6.0 +- [feature/bug] Can now refer to context variables + within extra arguments to <%block>, <%def>, i.e. + <%block name="foo" cache_key="${somekey}">. + Filters can also be used in this way, i.e. + <%def name="foo()" filter="myfilter"> + then template.render(myfilter=some_callable) + [ticket:180] + - Template caching has been converted into a plugin system, whereby the usage of Beaker is just the default plugin. Template and TemplateLookup diff --git a/mako/__init__.py b/mako/__init__.py index d3c2796222070e496d38dc30adbc9967ab93be12..2362d3a447c929ce1f6861f9fe11da8c1a4ea34f 100644 --- a/mako/__init__.py +++ b/mako/__init__.py @@ -5,5 +5,5 @@ # the MIT License: http://www.opensource.org/licenses/mit-license.php -__version__ = '0.5.1' +__version__ = '0.6.0' diff --git a/mako/codegen.py b/mako/codegen.py index 0310964998a40e5c7dbf425bfd7d4224d9ab1553..5a7737bf61bd2c17a08cbb8d80487556462ed1a7 100644 --- a/mako/codegen.py +++ b/mako/codegen.py @@ -414,7 +414,7 @@ class _GenerateRenderMethod(object): # (this is used for the caching decorator) if limit is not None: to_write = to_write.intersection(limit) - + if toplevel and getattr(self.compiler, 'has_ns_imports', False): self.printer.writeline("_import_ns = {}") self.compiler.has_imports = True @@ -866,7 +866,6 @@ class _Identifiers(object): """tracks the status of identifier names as template code is rendered.""" def __init__(self, node=None, parent=None, nested=False): - if parent is not None: # if we are the branch created in write_namespaces(), # we don't share any context from the main body(). @@ -1000,6 +999,7 @@ class _Identifiers(object): if node is self.node: for ident in node.declared_identifiers(): self.argument_declared.add(ident) + for n in node.nodes: n.accept_visitor(self) @@ -1016,6 +1016,10 @@ class _Identifiers(object): "Named block '%s' not allowed inside of <%%call> tag" % (node.name, ), **node.exception_kwargs) + for ident in node.undeclared_identifiers(): + if ident != 'context' and ident not in self.declared.union(self.locally_declared): + self.undeclared.add(ident) + if not node.is_anonymous: self._check_name_exists(self.topleveldefs, node) self.undeclared.add(node.funcname) diff --git a/mako/parsetree.py b/mako/parsetree.py index 9896dd8e6db2833395e7933a39c9f0a12f736427..98a8701054603a7395098f544dfd4d67846546a1 100644 --- a/mako/parsetree.py +++ b/mako/parsetree.py @@ -431,10 +431,13 @@ class DefTag(Tag): for c in self.function_decl.defaults: res += list(ast.PythonCode(c, **self.exception_kwargs). undeclared_identifiers) - return res + list(self.filter_args.\ + return set(res).union( + self.filter_args.\ undeclared_identifiers.\ difference(filters.DEFAULT_ESCAPES.keys()) - ) + ).union( + self.expression_undeclared_identifiers + ) class BlockTag(Tag): __keyword__ = 'block' @@ -487,7 +490,12 @@ class BlockTag(Tag): return self.body_decl.argnames def undeclared_identifiers(self): - return [] + return (self.filter_args.\ + undeclared_identifiers.\ + difference(filters.DEFAULT_ESCAPES.keys()) + ).union(self.expression_undeclared_identifiers) + + class CallTag(Tag): __keyword__ = 'call' diff --git a/test/test_cache.py b/test/test_cache.py index d898b02955c85a7dfa914b888d0080967a5536b8..1c7b42aed09278a048aeb9537def789ea8b0e219 100644 --- a/test/test_cache.py +++ b/test/test_cache.py @@ -139,6 +139,37 @@ class CacheTest(TemplateTest): ] assert m.kwargs == {} + def test_dynamic_key_with_context(self): + t = Template(""" + <%block name="foo" cached="True" cache_key="${mykey}"> + some block + </%block> + """) + m = self._install_mock_cache(t) + t.render(mykey="thekey") + t.render(mykey="thekey") + eq_( + result_lines(t.render(mykey="thekey")), + ["some block"] + ) + eq_(m.key, "thekey") + + t = Template(""" + <%def name="foo()" cached="True" cache_key="${mykey}"> + some def + </%def> + ${foo()} + """) + m = self._install_mock_cache(t) + t.render(mykey="thekey") + t.render(mykey="thekey") + eq_( + result_lines(t.render(mykey="thekey")), + ["some def"] + ) + eq_(m.key, "thekey") + + def test_dynamic_key_with_funcargs(self): t = Template(""" <%def name="foo(num=5)" cached="True" cache_key="foo_${str(num)}"> diff --git a/test/test_filters.py b/test/test_filters.py index 13492d8b3e19371533f6f87011a7419d9e986d68..684705d8da25935eba2af7784827ef2c2f270983 100644 --- a/test/test_filters.py +++ b/test/test_filters.py @@ -3,7 +3,7 @@ from mako.template import Template import unittest from mako import util -from test import TemplateTest, eq_, skip_if +from test import TemplateTest, eq_, skip_if, assert_raises from util import result_lines, flatten_result class FilterTest(TemplateTest): @@ -60,7 +60,11 @@ class FilterTest(TemplateTest): ${foo()} """) - assert flatten_result(t.render(x="this is x", myfilter=lambda t: "MYFILTER->%s<-MYFILTER" % t)) == "MYFILTER-> this is foo <-MYFILTER" + eq_( + flatten_result(t.render(x="this is x", + myfilter=lambda t: "MYFILTER->%s<-MYFILTER" % t)), + "MYFILTER-> this is foo <-MYFILTER" + ) def test_import(self): t = Template(""" @@ -104,6 +108,33 @@ class FilterTest(TemplateTest): """) assert t.render().strip() == "<tag>this is html</tag>" + def test_block_via_context(self): + t = Template(""" + <%block name="foo" filter="myfilter"> + some text + </%block> + """) + def myfilter(text): + return "MYTEXT" + text + eq_( + result_lines(t.render(myfilter=myfilter)), + ["MYTEXT", "some text"] + ) + + def test_def_via_context(self): + t = Template(""" + <%def name="foo()" filter="myfilter"> + some text + </%def> + ${foo()} + """) + def myfilter(text): + return "MYTEXT" + text + eq_( + result_lines(t.render(myfilter=myfilter)), + ["MYTEXT", "some text"] + ) + def test_nflag(self): t = Template(""" ${"<tag>this is html</tag>" | n} @@ -122,7 +153,7 @@ class FilterTest(TemplateTest): """) assert t.render().strip() == "<tag>this is html</tag>" - def testnonexpression(self): + def test_non_expression(self): t = Template(""" <%! def a(text):