From 8e27c3282ea37a756d4954ceea3a2af5be8d6917 Mon Sep 17 00:00:00 2001 From: Mike Bayer <mike_mp@zzzcomputing.com> Date: Fri, 5 Jan 2007 18:41:04 +0000 Subject: [PATCH] - fix to code generation to correctly track multiple defs with the same name. this is implemented by changing the "topleveldefs" and "closuredefs" collections from a Set to a dictionary. a unit test was added with alternate set-ordering as the original issue only appeared on linux to start. - "backslash" -> "slash" in syntax doc --- CHANGES | 1 + doc/build/content/syntax.txt | 2 +- lib/mako/codegen.py | 29 ++++++++++++++++------------- lib/mako/util.py | 12 +++++++++++- test/call.py | 17 ++++++++++++++++- 5 files changed, 45 insertions(+), 16 deletions(-) diff --git a/CHANGES b/CHANGES index 83cdf93..25d9966 100644 --- a/CHANGES +++ b/CHANGES @@ -7,6 +7,7 @@ cant handle those files, setuptools not very good at "pruning" certain directori occurs before filtering - better error message when a lookup is attempted with a template that has no lookup - implemented "module" attribute for namespace +- fix to code generation to correctly track multiple defs with the same name 0.1.0 diff --git a/doc/build/content/syntax.txt b/doc/build/content/syntax.txt index f47757c..6d90ae6 100644 --- a/doc/build/content/syntax.txt +++ b/doc/build/content/syntax.txt @@ -96,7 +96,7 @@ Any number of `<%! %>` blocks can be declared anywhere in a template; they will ### Tags -The rest of what Mako offers takes place in the form of tags. All tags use the same syntax, which is similar to an XML tag except that the first character of the tag name is a `%` character. The tag is closed either by a contained backslash character, or an explicit closing tag: +The rest of what Mako offers takes place in the form of tags. All tags use the same syntax, which is similar to an XML tag except that the first character of the tag name is a `%` character. The tag is closed either by a contained slash character, or an explicit closing tag: <%include file="foo.txt"/> diff --git a/lib/mako/codegen.py b/lib/mako/codegen.py index f54df0d..a927b8f 100644 --- a/lib/mako/codegen.py +++ b/lib/mako/codegen.py @@ -13,6 +13,7 @@ from mako import util, ast, parsetree, filters MAGIC_NUMBER = 1 + def compile(node, uri, filename=None): """generate module source code given a parsetree node, uri, and optional source filename""" buf = util.FastEncodingBuffer() @@ -117,7 +118,7 @@ class _GenerateRenderMethod(object): module_identifiers.topleveldefs = module_identifiers.topleveldefs.union(main_identifiers.topleveldefs) [module_identifiers.declared.add(x) for x in ["UNDEFINED"]] self.compiler.identifiers = module_identifiers - self.printer.writeline("_exports = %s" % repr([n.name for n in main_identifiers.topleveldefs])) + self.printer.writeline("_exports = %s" % repr([n.name for n in main_identifiers.topleveldefs.values()])) self.printer.write("\n\n") if len(module_code): @@ -129,7 +130,7 @@ class _GenerateRenderMethod(object): elif len(namespaces): self.write_namespaces(namespaces) - return main_identifiers.topleveldefs + return main_identifiers.topleveldefs.values() def write_render_callable(self, node, name, args, buffered, filtered, cached): """write a top-level render callable. @@ -228,14 +229,13 @@ class _GenerateRenderMethod(object): # collection of all defs available to us in this scope comp_idents = dict([(c.name, c) for c in identifiers.defs]) - to_write = util.Set() # write "context.get()" for all variables we are going to need that arent in the namespace yet to_write = to_write.union(identifiers.undeclared) # write closure functions for closures that we define right here - to_write = to_write.union(util.Set([c.name for c in identifiers.closuredefs])) + to_write = to_write.union(util.Set([c.name for c in identifiers.closuredefs.values()])) # remove identifiers that are declared in the argument signature of the callable to_write = to_write.difference(identifiers.argument_declared) @@ -462,6 +462,9 @@ class _GenerateRenderMethod(object): def visitDefTag(s, node): self.write_inline_def(node, callable_identifiers, nested=False) export.append(node.name) + # remove defs that are within the <%call> from the "closuredefs" defined + # in the body, so they dont render twice + del body_identifiers.closuredefs[node.name] vis = DefVisitor() for n in node.nodes: n.accept_visitor(vis) @@ -507,7 +510,7 @@ class _Identifiers(object): def __init__(self, node=None, parent=None, 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).union(parent.argument_declared) + self.declared = util.Set(parent.declared).union([c.name for c in parent.closuredefs.values()]).union(parent.locally_declared).union(parent.argument_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, @@ -516,10 +519,10 @@ class _Identifiers(object): self.declared = self.declared.union(parent.undeclared) # top level defs that are available - self.topleveldefs = util.Set(parent.topleveldefs) + self.topleveldefs = util.SetLikeDict(**parent.topleveldefs) else: self.declared = util.Set() - self.topleveldefs = util.Set() + self.topleveldefs = util.SetLikeDict() # things within this level that are referenced before they are declared (e.g. assigned to) self.undeclared = util.Set() @@ -536,7 +539,7 @@ class _Identifiers(object): self.argument_declared = util.Set() # closure defs that are defined in this level - self.closuredefs = util.Set() + self.closuredefs = util.SetLikeDict() self.node = node @@ -546,11 +549,11 @@ class _Identifiers(object): def branch(self, node, **kwargs): """create a new Identifiers for a new Node, with this Identifiers as the parent.""" return _Identifiers(node, self, **kwargs) - - defs = property(lambda s:s.topleveldefs.union(s.closuredefs)) + + defs = property(lambda self:util.Set(self.topleveldefs.union(self.closuredefs).values())) def __repr__(self): - return "Identifiers(declared=%s, locally_declared=%s, undeclared=%s, topleveldefs=%s, closuredefs=%s, argumenetdeclared=%s)" % (repr(list(self.declared)), repr(list(self.locally_declared)), repr(list(self.undeclared)), repr([c.name for c in self.topleveldefs]), repr([c.name for c in self.closuredefs]), repr(self.argument_declared)) + return "Identifiers(declared=%s, locally_declared=%s, undeclared=%s, topleveldefs=%s, closuredefs=%s, argumenetdeclared=%s)" % (repr(list(self.declared)), repr(list(self.locally_declared)), repr(list(self.undeclared)), repr([c.name for c in self.topleveldefs.values()]), repr([c.name for c in self.closuredefs.values()]), repr(self.argument_declared)) def check_declared(self, node): """update the state of this Identifiers with the undeclared and declared identifiers of the given node.""" @@ -575,9 +578,9 @@ class _Identifiers(object): self.locally_assigned = self.locally_assigned.union(node.declared_identifiers()) def visitDefTag(self, node): if node.is_root(): - self.topleveldefs.add(node) + self.topleveldefs[node.name] = node elif node is not self.node: - self.closuredefs.add(node) + self.closuredefs[node.name] = node for ident in node.undeclared_identifiers(): if ident != 'context' and ident not in self.declared.union(self.locally_declared): self.undeclared.add(ident) diff --git a/lib/mako/util.py b/lib/mako/util.py index 8ac58d6..b7be1b1 100644 --- a/lib/mako/util.py +++ b/lib/mako/util.py @@ -34,7 +34,17 @@ def verify_directory(dir): except: if tries > 5: raise - + +class SetLikeDict(dict): + """a dictionary that has some setlike methods on it""" + def union(self, other): + """produce a 'union' of this dict and another (at the key level). + + values in the second dict take precedence over that of the first""" + x = SetLikeDict(**self) + x.update(other) + return x + class FastEncodingBuffer(object): """a very rudimentary buffer that is faster than StringIO, but doesnt crash on unicode data like cStringIO.""" def __init__(self, encoding=None): diff --git a/test/call.py b/test/call.py index 2837eaa..28d8656 100644 --- a/test/call.py +++ b/test/call.py @@ -1,4 +1,5 @@ from mako.template import Template +from mako import util import unittest from util import result_lines @@ -41,9 +42,23 @@ class CallTest(unittest.TestCase): ${bar()} """) - 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_compound_call_revset(self): + # monkeypatch Set to return items in reverse + oldset = util.Set + class goofyset(oldset): + def __iter__(self): + x = list(oldset.__iter__(self)) + x.reverse() + return iter(x) + util.Set = goofyset + + try: + self.test_compound_call() + finally: + util.Set = oldset + def test_chained_call(self): """test %calls that are chained through their targets""" t = Template(""" -- GitLab