still trying to get nested components up. not happening in this version.
diff --git a/lib/mako/codegen.py b/lib/mako/codegen.py index 537926a..20b32b6 100644 --- a/lib/mako/codegen.py +++ b/lib/mako/codegen.py
@@ -3,7 +3,7 @@ from StringIO import StringIO import time from mako.pygen import PythonPrinter -from mako import util, ast +from mako import util, ast, parsetree MAGIC_NUMBER = 1 @@ -12,18 +12,6 @@ self.node = node self.filename = filename def render(self): - module_code = [] - class FindPyDecls(object): - def visitCode(self, node): - if node.ismodule: - module_code.append(node) - f = FindPyDecls() - self.node.accept_visitor(f) - - components = _find_top_level_components(self.node) - - (module_declared, module_undeclared) = (util.Set(), util.Set()) - buf = StringIO() printer = PythonPrinter(buf) @@ -32,55 +20,76 @@ printer.writeline("_magic_number = %s" % repr(MAGIC_NUMBER)) printer.writeline("_modified_time = %s" % repr(time.time())) printer.writeline("_template_filename=%s" % repr(self.filename)) - buf.write("\n\n") + printer.write("\n\n") + + module_code = [] + class FindPyDecls(object): + def visitCode(self, node): + if node.ismodule: + module_code.append(node) + f = FindPyDecls() + self.node.accept_visitor(f) + + module_identifiers = _Identifiers() for n in module_code: - (module_declared, module_undeclared) = _find_declared_identifiers([n], module_declared, module_undeclared) + module_identifiers = module_identifiers.branch(n) printer.writeline("# SOURCE LINE %d" % n.lineno, is_comment=True) printer.write_indented_block(n.text) + + main_identifiers = module_identifiers.branch(self.node) + module_identifiers.toplevelcomponents = module_identifiers.toplevelcomponents.union(main_identifiers.toplevelcomponents) + + print "module_ident", module_identifiers + # print main render() method - (declared, undeclared) = _find_declared_identifiers(self.node.nodes, module_declared) - self.node.accept_visitor(_GenerateRenderMethod(printer, declared, undeclared, components)) - printer.writeline(None) - buf.write("\n\n") + _GenerateRenderMethod(printer, module_identifiers, self.node) # print render() for each top-level component - for node in components: - declared = util.Set(node.declared_identifiers()).union(module_declared) - (declared, undeclared) = _find_declared_identifiers(node.nodes, declared) - local_components = _find_top_level_components(node.nodes) - render = _GenerateRenderMethod(printer, declared, undeclared, components + local_components, name="render_%s" % node.name, args=node.function_decl.get_argument_expressions()) - for n in node.nodes: - n.accept_visitor(render) - printer.writeline("return ''") - printer.writeline(None) - buf.write("\n\n") + for node in main_identifiers.toplevelcomponents: + _GenerateRenderMethod(printer, module_identifiers, node) return buf.getvalue() class _GenerateRenderMethod(object): - def __init__(self, printer, declared, undeclared, components, name='render', in_component=False, args=None): + def __init__(self, printer, identifiers, node): self.printer = printer - self.in_component = in_component self.last_source_line = -1 + + self.node = node + if isinstance(node, parsetree.ComponentTag): + name = "render_" + node.name + args = node.function_decl.get_argument_expressions() + else: + name = "render" + args = None + if args is None: args = ['context'] else: args = [a for a in ['context'] + args] + printer.writeline("def %s(%s):" % (name, ','.join(args))) - - self.write_variable_declares(declared, undeclared, components) + identifiers = identifiers.branch(node) + print "write comp", name, identifiers + self.write_variable_declares(identifiers) + for n in node.nodes: + n.accept_visitor(self) + printer.writeline("return ''") + printer.writeline(None) + printer.write("\n\n") - def write_variable_declares(self, declared, undeclared, components): - comp_idents = dict([(c.name, c) for c in components]) - for ident in undeclared: + def write_variable_declares(self, identifiers): + comp_idents = dict([(c.name, c) for c in identifiers.components]) + print "Write variable declares, set is", identifiers.undeclared.union(util.Set([c.name for c in identifiers.closurecomponents if c.parent is self.node])) + for ident in identifiers.undeclared.union(util.Set([c.name for c in identifiers.closurecomponents if c.parent is self.node])): if ident in comp_idents: comp = comp_idents[ident] if comp.is_root(): - self.write_component_decl(comp) + self.write_component_decl(comp, identifiers) else: - self.write_inline_component(comp, declared.union(undeclared), None) + self.write_inline_component(comp, identifiers) else: self.printer.writeline("%s = context.get(%s, None)" % (ident, repr(ident))) @@ -89,7 +98,7 @@ self.printer.writeline("# SOURCE LINE %d" % node.lineno, is_comment=True) self.last_source_line = node.lineno - def write_component_decl(self, node): + def write_component_decl(self, node, identifiers): funcname = node.function_decl.funcname namedecls = node.function_decl.get_argument_expressions() nameargs = node.function_decl.get_argument_expressions(include_defaults=False) @@ -98,16 +107,33 @@ self.printer.writeline("return render_%s(%s)" % (funcname, ",".join(nameargs))) self.printer.writeline(None) - def write_inline_component(self, node, declared, undeclared): + def write_inline_component(self, node, identifiers): namedecls = node.function_decl.get_argument_expressions() self.printer.writeline("def %s(%s):" % (node.name, ",".join(namedecls))) - components = _find_top_level_components(node.nodes) - (declared, undeclared) = _find_declared_identifiers(node.nodes, declared, undeclared) - self.write_variable_declares(declared, undeclared, components) + + + print "inline component, name", node.name, identifiers + identifiers = identifiers.branch(node) + print "updated", identifiers, "\n" + raise "hi" + make_closure = False #len(localdecl) > 0 + +# if make_closure: +# self.printer.writeline("def %s(%s):" % (node.name, ",".join(['context'] + namedecls))) +# (compdecl, compundecl) = _find_declared_identifiers(node.nodes, declared, localundecl) +# else: +# (compdecl, compundecl) = _find_declared_identifiers(node.nodes, declared, undeclared) +# raise "hi" + self.write_variable_declares(identifiers) + for n in node.nodes: n.accept_visitor(self) self.printer.writeline("return ''") self.printer.writeline(None) + if make_closure: + namedecls = node.function_decl.get_argument_expressions(include_defaults=False) + self.printer.writeline("return %s(%s)" % (node.name, ",".join(["%s=%s" % (x,x) for x in ['context'] + namedecls]))) + self.printer.writeline(None) def visitExpression(self, node): self.write_source_comment(node) @@ -125,6 +151,8 @@ if not node.ismodule: self.write_source_comment(node) self.printer.write_indented_block(node.text) + self.printer.writeline('context = context.update(%s)' % (",".join(["%s=%s" % (x, x) for x in node.declared_identifiers()]))) + def visitIncludeTag(self, node): self.write_source_comment(node) self.printer.writeline("context.include_file(%s, import=%s)" % (repr(node.attributes['file']), repr(node.attributes.get('import', False)))) @@ -139,14 +167,14 @@ vis = NSComponentVisitor() for n in node.nodes: n.accept_visitor(vis) - self.printer.writeline("return %s" % (repr(export))) + self.printer.writeline("return [%s]" % (','.join(export))) self.printer.writeline(None) self.printer.writeline("class %sNamespace(runtime.Namespace):" % node.name) self.printer.writeline("def __getattr__(self, key):") self.printer.writeline("return self.contextual_callable(context, key)") self.printer.writeline(None) self.printer.writeline(None) - self.printer.writeline("%s = %sNamespace(%s, callables=make_namespace())" % (node.name, node.name)) + self.printer.writeline("%s = %sNamespace(%s, callables=make_namespace())" % (node.name, node.name, repr(node.name))) def visitComponentTag(self, node): pass @@ -155,51 +183,56 @@ def visitInheritTag(self, node): pass -def _find_top_level_components(nodes): - components = [] - class FindTopLevelComponents(object): - def visitComponentTag(self, node): - components.append(node) - ftl = FindTopLevelComponents() - if isinstance(nodes, list): - for n in nodes: - n.accept_visitor(ftl) - else: - nodes.accept_visitor(ftl) - return components - -def _find_declared_identifiers(nodes, declared=None, undeclared=None): - if declared is None: - declared = util.Set() - else: - declared = util.Set(declared) - if undeclared is None: - undeclared = util.Set() - else: - undeclared = util.Set(undeclared) - def check_declared(node): +class _Identifiers(object): + def __init__(self, node=None, parent=None): + if parent is not None: + self.declared = util.Set(parent.declared).union([c.name for c in parent.closurecomponents]) + self.undeclared = util.Set() + self.toplevelcomponents = util.Set(parent.toplevelcomponents) + self.closurecomponents = util.Set() + else: + self.declared = util.Set() + self.undeclared = util.Set() + self.toplevelcomponents = util.Set() + self.closurecomponents = util.Set() + + self.node = node + if node is not None: + node.accept_visitor(self) + + def branch(self, node): + return _Identifiers(node, self) + + components = property(lambda s:s.toplevelcomponents.union(s.closurecomponents)) + + def __repr__(self): + return "Identifiers(%s, %s, %s, %s)" % (repr(list(self.declared)), repr(list(self.undeclared)), repr([c.name for c in self.toplevelcomponents]), repr([c.name for c in self.closurecomponents])) + + def check_declared(self, node): for ident in node.declared_identifiers(): - declared.add(ident) + self.declared.add(ident) for ident in node.undeclared_identifiers(): - if ident != 'context' and ident not in declared: - undeclared.add(ident) - class FindUndeclared(object): - def visitExpression(self, node): - check_declared(node) - def visitControlLine(self, node): - check_declared(node) - def visitCode(self, node): - if not node.ismodule: - check_declared(node) - def visitComponentTag(self, node): - pass - #check_declared(node) - def visitIncludeTag(self, node): - # TODO: expressions for attributes - pass - def visitNamespaceTag(self, node): - check_declared(node) - fd = FindUndeclared() - for n in nodes: - n.accept_visitor(FindUndeclared()) - return (declared, undeclared) + if ident != 'context' and ident not in self.declared: + self.undeclared.add(ident) + def visitExpression(self, node): + self.check_declared(node) + def visitControlLine(self, node): + self.check_declared(node) + def visitCode(self, node): + if not node.ismodule: + self.check_declared(node) + def visitComponentTag(self, node): + #print "component tag", node.name, "our node is:", getattr(self.node, 'name', self.node.__class__.__name__), (node is self.node or node.parent is self.node) + if node.is_root(): + self.toplevelcomponents.add(node) + else: + self.closurecomponents.add(node) + self.check_declared(node) + if node is self.node: + for n in node.nodes: + n.accept_visitor(self) + def visitIncludeTag(self, node): + # TODO: expressions for attributes + pass + def visitNamespaceTag(self, node): + self.check_declared(node)
diff --git a/lib/mako/parsetree.py b/lib/mako/parsetree.py index 34dbf77..23936b9 100644 --- a/lib/mako/parsetree.py +++ b/lib/mako/parsetree.py
@@ -226,7 +226,7 @@ self.function_decl = ast.FunctionDecl("def " + name + ":pass", self.lineno, self.pos) self.name = self.function_decl.funcname def declared_identifiers(self): - return [self.function_decl.funcname] + list(self.function_decl.argnames) + return self.function_decl.argnames def undeclared_identifiers(self): res = [] for c in self.function_decl.defaults:
diff --git a/lib/mako/pygen.py b/lib/mako/pygen.py index 7b66f5d..1cd0d0d 100644 --- a/lib/mako/pygen.py +++ b/lib/mako/pygen.py
@@ -28,6 +28,9 @@ self._reset_multi_line_flags() + def write(self, text): + self.stream.write(text) + def write_indented_block(self, block): """print a line or lines of python which already contains indentation.
diff --git a/lib/mako/runtime.py b/lib/mako/runtime.py index 76936c8..76cb4b4 100644 --- a/lib/mako/runtime.py +++ b/lib/mako/runtime.py
@@ -14,16 +14,24 @@ return self.data.get(key, default) def write(self, string): self.buffer.write(string) - + def update(self, **args): + x = self.data.copy() + x.update(args) + c = Context(self.buffer, **x) + c.with_template = self.with_template + return c class Namespace(object): """provides access to collections of rendering methods, which can be local, from other templates, or from imported modules""" def __init__(self, name, module=None, template=None, callables=None): self.module = module self.template = template - self.callables = callables + if callables is not None: + self.callables = dict((c.func_name, c) for c in callables) + else: + self.callables = {} - def load(self, key): + def contextual_callable(self, context, key): if self.callables is not None: try: return self.callables[key] @@ -31,16 +39,16 @@ pass if self.template is not None: try: - return self.template.get_component(key) + callable_ = self.template.get_component(key) + return lambda *args, **kwargs:callable_(context, *args, **kwargs) except AttributeError: pass if self.module is not None: try: - return getattr(self.module, key) + callable_ = getattr(self.module, key) + return lambda *args, **kwargs:callable_(context, *args, **kwargs) except AttributeError: pass raise exceptions.RuntimeException("Namespace '%s' has no member '%s'" % (self.name, key)) - def contextual_callable(self, context, key): - return lambda context, *args, **kwargs:self.load(key)(context, *args, **kwargs) \ No newline at end of file
diff --git a/test/template.py b/test/template.py index 167557e..e907a98 100644 --- a/test/template.py +++ b/test/template.py
@@ -1,6 +1,6 @@ from mako.template import Template import unittest - +import re class ComponentTest(unittest.TestCase): def test_component_noargs(self): @@ -13,6 +13,7 @@ </%component> """) + print template.code assert template.render(variable='hi').strip() == """hello mycomp hi""" def test_component_blankargs(self): @@ -22,6 +23,7 @@ </%component> ${mycomp()}""") + #print template.code assert template.render(variable='hi').strip() == """hello mycomp hi""" def test_component_args(self): @@ -31,6 +33,7 @@ </%component> ${mycomp(5, 6)}""") + #print template.code assert template.render(variable='hi', a=5, b=6).strip() == """hello mycomp hi, 5, 6""" def test_inter_component(self): @@ -51,6 +54,7 @@ im c </%component> """) + #print template.code # check that "a" is declared in "b", but not in "c" assert "a" not in template.module.render_c.func_code.co_varnames assert "a" in template.module.render_b.func_code.co_varnames @@ -58,6 +62,7 @@ # then test output assert template.render().strip() == "im b\nand heres a: im a" +class NestedComponentTest(unittest.TestCase): def test_nested_component(self): template = """ @@ -77,15 +82,34 @@ </%component> """ t = Template(template) - #print t.code - print t.render() + print t.code + result = t.render() + result = re.sub(r'[\s\n]+', ' ', result).strip() + assert result == "hey, im hi. and heres this is foo , this is bar" + def test_nested_component_2(self): + template = Template(""" + ${a()} + <%component name="a"> + <%component name="b"> + <%component name="c"> + comp c + </%component> + ${c()} + </%component> + ${b()} + </%component> +""") + print template.code + def test_nested_nested_component(self): template = """ ${a()} <%component name="a"> + a <%component name="b1"> + a_b1 </%component> <%component name="b2"> a_b2 ${c1()} @@ -114,10 +138,138 @@ </%component> """ + t = Template(template, format_exceptions=False) + print t.code + result = t.render(x=5) + result = re.sub(r'[\s\n]+', ' ', result).strip() + #print result + assert result == "a a_b1 a_b2 a_b2_c1 a_b3 a_b3_c1 heres x: 5 y is 7 a_b3_c2 y is None c1 is a_b3_c1 heres x: 5 y is 7" + + def test_nested_nested_component_2(self): + template = """ + + <%component name="a"> + this is a ${b()} + <%component name="b"> + this is b + ${c()} + </%component> + + <%component name="c"> + this is c + </%component> + </%component> + ${a()} +""" + t = Template(template) + print t.code + + def test_local_names(self): + template = """ + + <%component name="a"> + this is a, and y is ${y} + </%component> + + ${a()} + + <% + y = 7 + %> + + ${a()} + +""" t = Template(template) #print t.code - print t.render(x=5) + result = t.render() + result = re.sub(r'[\s\n]+', ' ', result).strip() + assert result == "this is a, and y is None this is a, and y is 7" + + def test_local_names_2(self): + template = """ + y is ${y} + <% + y = 7 + %> + + y is ${y} +""" + t = Template(template) + result = t.render() + result = re.sub(r'[\s\n]+', ' ', result).strip() + assert result == "y is None y is 7" + + def test_local_local_names(self): + """test assignment of variables inside nested components, which requires extra scoping logic""" + template = """ + heres y: ${y} + + <%component name="a"> + <%component name="b"> + b, heres y: ${y} + + <% + y = 19 + %> + + b, heres c: ${c()} + + b, heres y again: ${y} + </%component> + + a, heres y: ${y} + <% + y = 10 + x = 12 + %> + + a, now heres y: ${y} + a, heres b: ${b()} + + <%component name="c"> + this is c + </%component> + </%component> + + <% + y = 7 + %> + + now heres y ${y} + + ${a()} + + heres y again: ${y} +""" + t = Template(template) + print t.code + result = t.render(y=5) + print result + result = re.sub(r'[\s\n]+', ' ', result).strip() + assert result == "heres y: 5 now heres y 7 a, heres y: 7 a, now heres y: 10 a, heres b: b, heres y: 10 heres y again: 7" + +class NamespaceTest(unittest.TestCase): + def test_inline(self): + template = """ + <%namespace name="x"> + <%component name="a"> + this is x a + </%component> + <%component name="b"> + this is x b + </%component> + </%namespace> + + ${x.a()} + + ${x.b()} +""" + t = Template(template) + print t.code + print t.render() + class ExceptionTest(unittest.TestCase): def test_raise(self): template = Template("""