diff -r 0bc68597e0ee mako/codegen.py --- a/mako/codegen.py Tue Mar 22 10:09:25 2011 -0400 +++ b/mako/codegen.py Tue Apr 05 18:48:05 2011 -0400 @@ -9,7 +9,7 @@ import time import re from mako.pygen import PythonPrinter -from mako import util, ast, parsetree, filters +from mako import util, ast, parsetree, filters, exceptions MAGIC_NUMBER = 6 @@ -84,11 +84,11 @@ self.node = node self.identifier_stack = [None] - self.in_def = isinstance(node, parsetree.DefTag) + self.in_def = isinstance(node, (parsetree.DefTag, parsetree.BlockTag)) if self.in_def: - name = "render_" + node.name - args = node.function_decl.get_argument_expressions() + name = "render_%s" % node.funcname + args = node.get_argument_expressions() filtered = len(node.filter_args.args) > 0 buffered = eval(node.attributes.get('buffered', 'False')) cached = eval(node.attributes.get('cached', 'False')) @@ -308,8 +308,19 @@ self.in_def = True class NSDefVisitor(object): def visitDefTag(s, node): + s.visitDefOrBase(node) + + def visitBlockTag(s, node): + s.visitDefOrBase(node) + + def visitDefOrBase(s, node): + if node.is_anonymous: + raise exceptions.CompileException( + "Can't put anonymous blocks inside <%namespace>", + **node.exception_kwargs + ) self.write_inline_def(node, identifiers, nested=False) - export.append(node.name) + export.append(node.funcname) vis = NSDefVisitor() for n in node.nodes: n.accept_visitor(vis) @@ -378,7 +389,7 @@ """ # collection of all defs available to us in this scope - comp_idents = dict([(c.name, c) for c in identifiers.defs]) + comp_idents = dict([(c.funcname, c) for c in identifiers.defs]) to_write = set() # write "context.get()" for all variables we are going to @@ -387,7 +398,7 @@ # write closure functions for closures that we define # right here - to_write = to_write.union([c.name for c in identifiers.closuredefs.values()]) + to_write = to_write.union([c.funcname for c in identifiers.closuredefs.values()]) # remove identifiers that are declared in the argument # signature of the callable @@ -420,7 +431,7 @@ for ident in to_write: if ident in comp_idents: comp = comp_idents[ident] - if comp.is_root(): + if comp.is_root() and not comp.is_anonymous: self.write_def_decl(comp, identifiers) else: self.write_inline_def(comp, identifiers, nested=True) @@ -472,9 +483,9 @@ def write_def_decl(self, node, identifiers): """write a locally-available callable referencing a top-level def""" - funcname = node.function_decl.funcname - namedecls = node.function_decl.get_argument_expressions() - nameargs = node.function_decl.get_argument_expressions(include_defaults=False) + funcname = node.funcname + namedecls = node.get_argument_expressions() + nameargs = node.get_argument_expressions(include_defaults=False) if not self.in_def and ( len(self.identifiers.locally_assigned) > 0 or @@ -488,13 +499,13 @@ 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() + + namedecls = node.get_argument_expressions() decorator = node.decorator if decorator: self.printer.writeline("@runtime._decorate_inline(context, %s)" % decorator) - self.printer.writeline("def %s(%s):" % (node.name, ",".join(namedecls))) + self.printer.writeline("def %s(%s):" % (node.funcname, ",".join(namedecls))) filtered = len(node.filter_args.args) > 0 buffered = eval(node.attributes.get('buffered', 'False')) cached = eval(node.attributes.get('cached', 'False')) @@ -519,7 +530,7 @@ self.write_def_finish(node, buffered, filtered, cached) self.printer.writeline(None) if cached: - self.write_cache_decorator(node, node.name, + self.write_cache_decorator(node, node.funcname, namedecls, False, identifiers, inline=True, toplevel=False) @@ -750,6 +761,16 @@ def visitDefTag(self, node): pass + def visitBlockTag(self, node): + if self.in_def or node.is_anonymous or not node.is_root(): + self.printer.writeline("%s()" % node.funcname) + else: + self.printer.writeline("if 'parent' not in context._data or " + "not hasattr(context._data['parent'], '%s'):" + % node.funcname) + self.printer.writeline("context['self'].%s()" % node.funcname) + self.printer.writeline("\n") + def visitCallNamespaceTag(self, node): # TODO: we can put namespace-specific checks here, such # as ensure the given namespace will be imported, @@ -770,12 +791,19 @@ self.identifier_stack.append(body_identifiers) class DefVisitor(object): def visitDefTag(s, node): + s.visitDefOrBase(node) + + def visitBlockTag(s, node): + s.visitDefOrBase(node) + + def visitDefOrBase(s, node): self.write_inline_def(node, callable_identifiers, nested=False) - export.append(node.name) + if not node.is_anonymous: + export.append(node.funcname) # remove defs that are within the <%call> from the "closuredefs" defined # in the body, so they dont render twice - if node.name in body_identifiers.closuredefs: - del body_identifiers.closuredefs[node.name] + if node.funcname in body_identifiers.closuredefs: + del body_identifiers.closuredefs[node.funcname] vis = DefVisitor() for n in node.nodes: @@ -934,12 +962,23 @@ if self.node is node: for n in node.nodes: n.accept_visitor(self) - - def visitDefTag(self, node): - if node.is_root(): - self.topleveldefs[node.name] = node + + def _check_name_exists(self, collection, node): + existing = collection.get(node.funcname) + collection[node.funcname] = node + if existing is not None and \ + existing is not node and \ + (node.is_block or existing.is_block): + raise exceptions.CompileException( + "%%def or %%block named '%s' already " + "exists in this scope" % + node.funcname, **node.exception_kwargs) + + def visitDefOrBlock(self, node): + if node.is_root() and not node.is_anonymous: + self._check_name_exists(self.topleveldefs, node) elif node is not self.node: - self.closuredefs[node.name] = node + self._check_name_exists(self.closuredefs, node) for ident in node.undeclared_identifiers(): if ident != 'context' and ident not in self.declared.union(self.locally_declared): @@ -952,6 +991,13 @@ for n in node.nodes: n.accept_visitor(self) + + def visitDefTag(self, node): + self.visitDefOrBlock(node) + + def visitBlockTag(self, node): + self.visitDefOrBlock(node) + def visitIncludeTag(self, node): self.check_declared(node) diff -r 0bc68597e0ee mako/parsetree.py --- a/mako/parsetree.py Tue Mar 22 10:09:25 2011 -0400 +++ b/mako/parsetree.py Tue Apr 05 18:48:05 2011 -0400 @@ -410,6 +410,16 @@ attributes.get('filter', ''), **self.exception_kwargs) + is_anonymous = False + is_block = False + + @property + def funcname(self): + return self.function_decl.funcname + + def get_argument_expressions(self, **kw): + return self.function_decl.get_argument_expressions(**kw) + def declared_identifiers(self): return self.function_decl.argnames @@ -423,6 +433,49 @@ difference(filters.DEFAULT_ESCAPES.keys()) ) +class BlockTag(Tag): + __keyword__ = 'block' + + def __init__(self, keyword, attributes, **kwargs): + super(BlockTag, self).__init__( + keyword, + attributes, + ('buffered', 'cached', 'cache_key', 'cache_timeout', + 'cache_type', 'cache_dir', 'cache_url'), + ('name','filter', 'decorator'), + (), + **kwargs) + name = attributes.get('name') + if name and not re.match(r'^[\w_]+$',name): + raise exceptions.CompileException( + "%block may not specify an argument signature", + **self.exception_kwargs) + self.name = name + self.decorator = attributes.get('decorator', '') + self.filter_args = ast.ArgumentList( + attributes.get('filter', ''), + **self.exception_kwargs) + + + is_block = True + + @property + def is_anonymous(self): + return self.name is None + + @property + def funcname(self): + return self.name or "__M_anon_%d" % (self.lineno, ) + + def get_argument_expressions(self, **kw): + return [] + + def declared_identifiers(self): + return [] + + def undeclared_identifiers(self): + return [] + class CallTag(Tag): __keyword__ = 'call' diff -r 0bc68597e0ee test/test_block.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/test_block.py Tue Apr 05 18:48:05 2011 -0400 @@ -0,0 +1,42 @@ +""" +anonymous block in namespace raises + +anonymous block in <%self:somecall> + +named block in <%self:somecall> + +_def calls named block in <%self:somecall> ? + +multiple inline named blocks with same name render (though pointless). e.g. block name="h" twice in a row, both render + +base tempalte with block renders + +inherited template with block render from base renders, doesnt render twice + +same, two levels + +inherited template with block that is *not* in base *does* render - nested, toplevel, anon, not + +same, two levels + +blocks inside of blocks: + + anon in non-anon + non-anon in anon + anon in anon + non-anon in non-anon + + +names: + + block named "x", _def named "x", raises - nested, toplevel + + _def named "x", block named "x", raises - nested, toplevel + + block named "x", block named "x", raises - nested, toplevel + + _def named "x", _def named "x", doenst raise (for compatibility) - nested, toplevel +""" + + +