added "import='x, y'" / "import='*'" to namespace tag, will cut down on dots added filename-relative lookup to TemplateLookup
diff --git a/lib/mako/codegen.py b/lib/mako/codegen.py index a0ec91d..aaa9c47 100644 --- a/lib/mako/codegen.py +++ b/lib/mako/codegen.py
@@ -118,7 +118,7 @@ if not self.in_def and len(self.identifiers.locally_assigned) > 0: self.printer.writeline("__locals = {}") - self.write_variable_declares(self.identifiers, first="kwargs") + self.write_variable_declares(self.identifiers, toplevel=True) for n in self.node.nodes: n.accept_visitor(self) @@ -136,7 +136,7 @@ def write_inherit(self, node): self.printer.writeline("def _mako_inherit(context):") self.printer.writeline("_mako_generate_namespaces(context)") - self.printer.writeline("return runtime.inherit_from(context, %s)" % (repr(node.attributes['file']))) + self.printer.writeline("return runtime.inherit_from(context, %s, _template_filename)" % (repr(node.attributes['file']))) self.printer.writeline(None) def write_namespaces(self, namespaces): @@ -151,6 +151,8 @@ ) self.printer.writeline("def _mako_generate_namespaces(context):") for node in namespaces.values(): + if node.attributes.has_key('import'): + self.compiler.has_ns_imports = True self.write_source_comment(node) if len(node.nodes): self.printer.writeline("def make_namespace():") @@ -168,7 +170,7 @@ callable_name = "make_namespace()" else: callable_name = "None" - self.printer.writeline("ns = runtime.Namespace(%s, context.clean_inheritance_tokens(), templateuri=%s, callables=%s)" % (repr(node.name), node.parsed_attributes.get('file', 'None'), callable_name)) + self.printer.writeline("ns = runtime.Namespace(%s, context.clean_inheritance_tokens(), templateuri=%s, callables=%s, calling_filename=_template_filename)" % (repr(node.name), node.parsed_attributes.get('file', 'None'), callable_name)) if eval(node.attributes.get('inheritable', "False")): self.printer.writeline("context['self'].%s = ns" % (node.name)) self.printer.writeline("context.namespaces[(render, %s)] = ns" % repr(node.name)) @@ -177,7 +179,7 @@ self.printer.writeline("pass") self.printer.writeline(None) - def write_variable_declares(self, identifiers, first=None): + def write_variable_declares(self, identifiers, toplevel=False): """write variable declarations at the top of a function. the variable declarations are in the form of callable definitions for defs and/or @@ -209,6 +211,13 @@ # which cannot be referenced beforehand. to_write = to_write.difference(identifiers.locally_declared) + if toplevel and getattr(self.compiler, 'has_ns_imports', False): + self.printer.writeline("_import_ns = {}") + self.compiler.has_imports = True + for ident, ns in self.compiler.namespaces.iteritems(): + if ns.attributes.has_key('import'): + self.printer.writeline("_mako_get_namespace(context, %s).populate(_import_ns, %s)" % (repr(ident), repr(re.split(r'\s*,\s*', ns.attributes['import'])))) + for ident in to_write: if ident in comp_idents: comp = comp_idents[ident] @@ -219,10 +228,10 @@ elif ident in self.compiler.namespaces: self.printer.writeline("%s = _mako_get_namespace(context, %s)" % (ident, repr(ident))) else: - if first is not None: - self.printer.writeline("%s = %s.get(%s, context.get(%s, UNDEFINED))" % (ident, first, repr(ident), repr(ident))) + if getattr(self.compiler, 'has_ns_imports', False): + self.printer.writeline("%s = _import_ns.get(%s, kwargs.get(%s, context.get(%s, UNDEFINED)))" % (ident, repr(ident), repr(ident), repr(ident))) else: - self.printer.writeline("%s = context.get(%s, UNDEFINED)" % (ident, repr(ident))) + self.printer.writeline("%s = kwargs.get(%s, context.get(%s, UNDEFINED))" % (ident, repr(ident), repr(ident))) def write_source_comment(self, node): if self.last_source_line != node.lineno: @@ -331,7 +340,7 @@ def visitIncludeTag(self, node): self.write_source_comment(node) - self.printer.writeline("runtime.include_file(context, %s, import_symbols=%s)" % (node.parsed_attributes['file'], repr(node.attributes.get('import', False)))) + self.printer.writeline("runtime.include_file(context, %s, _template_filename)" % (node.parsed_attributes['file'])) def visitNamespaceTag(self, node): pass @@ -360,7 +369,7 @@ "context.push_buffer()", "try:" ) - self.write_variable_declares(body_identifiers, first="kwargs") + self.write_variable_declares(body_identifiers) for n in node.nodes: n.accept_visitor(self) self.write_def_finish(node, buffered, False)
diff --git a/lib/mako/lookup.py b/lib/mako/lookup.py index 6bdf586..4670eb5 100644 --- a/lib/mako/lookup.py +++ b/lib/mako/lookup.py
@@ -20,7 +20,7 @@ return True except exceptions.TemplateLookupException, e: return False - def get_template(self, uri): + def get_template(self, uri, relativeto=None): raise NotImplementedError() class TemplateLookup(TemplateCollection): @@ -36,15 +36,26 @@ self.__collection = util.LRUCache(collection_size) self._mutex = threading.Lock() - def get_template(self, uri): + def get_template(self, uri, relativeto=None): try: if self.filesystem_checks: return self.__check(uri, self.__collection[uri]) else: return self.__collection[uri] except KeyError: - u = re.sub(r'^\/+', '', uri) + if uri[0] != '/': + u = uri + if relativeto is not None: + for dir in self.directories: + print relativeto[0:len(dir)] + if relativeto[0:len(dir)] == dir: + u = posixpath.join(posixpath.dirname(relativeto[len(dir) + 1:]), u) + break + else: + u = re.sub(r'^\/+', '', uri) + for dir in self.directories: + srcfile = posixpath.join(dir, u) if os.access(srcfile, os.F_OK): return self.__load(srcfile, uri)
diff --git a/lib/mako/parsetree.py b/lib/mako/parsetree.py index 3aae9df..d1a35a1 100644 --- a/lib/mako/parsetree.py +++ b/lib/mako/parsetree.py
@@ -223,7 +223,7 @@ class NamespaceTag(Tag): __keyword__ = 'namespace' def __init__(self, keyword, attributes, **kwargs): - super(NamespaceTag, self).__init__(keyword, attributes, (), ('name','inheritable','file'), ('name',), **kwargs) + super(NamespaceTag, self).__init__(keyword, attributes, (), ('name','inheritable','file','import'), ('name',), **kwargs) self.name = attributes['name'] def declared_identifiers(self): return []
diff --git a/lib/mako/runtime.py b/lib/mako/runtime.py index 01a23a9..099c6b2 100644 --- a/lib/mako/runtime.py +++ b/lib/mako/runtime.py
@@ -77,11 +77,11 @@ 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, context, module=None, template=None, templateuri=None, callables=None, inherits=None, populate_self=True): + def __init__(self, name, context, module=None, template=None, templateuri=None, callables=None, inherits=None, populate_self=True, calling_filename=None): self.name = name self._module = module if templateuri is not None: - self.template = _lookup_template(context, templateuri) + self.template = _lookup_template(context, templateuri, calling_filename) else: self.template = template self.context = context @@ -96,6 +96,29 @@ module = property(lambda s:s._module or s.template.module) filename = property(lambda s:s._module and s._module.__file__ or s.template.filename) + def populate(self, d, l): + for ident in l: + if ident == '*': + for (k, v) in self._get_star(): + d[k] = v + else: + d[ident] = getattr(self, ident) + + def _get_star(self): + if self.callables is not None: + for k in self.callables: + yield (k, self.callables[key]) + if self.template is not None: + def get(key): + callable_ = self.template.get_def(key).callable_ + return lambda *args, **kwargs:callable_(self.context, *args, **kwargs) + for k in self.template.module._exports: + yield (k, get(k)) + if self.module is not None: + for k in dir(self.module): + if k[0] != '_': + yield (k, getattr(self.module, k)) + def __getattr__(self, key): if self.callables is not None: try: @@ -131,16 +154,16 @@ buf = context.pop_buffer() return buf.getvalue() -def include_file(context, uri, import_symbols): +def include_file(context, uri, calling_filename): """locate the template from the given uri and include it in the current output.""" - template = _lookup_template(context, uri) + template = _lookup_template(context, uri, calling_filename) (callable_, ctx) = _populate_self_namespace(context.clean_inheritance_tokens(), template) callable_(ctx) -def inherit_from(context, uri): +def inherit_from(context, uri, calling_filename): """called by the _inherit method in template modules to set up the inheritance chain at the start of a template's execution.""" - template = _lookup_template(context, uri) + template = _lookup_template(context, uri, calling_filename) self_ns = context['self'] ih = self_ns while ih.inherits is not None: @@ -157,9 +180,9 @@ gen_ns(context) return (template.callable_, lclcontext) -def _lookup_template(context, uri): +def _lookup_template(context, uri, relativeto): lookup = context._with_template.lookup - return lookup.get_template(uri) + return lookup.get_template(uri, relativeto) def _populate_self_namespace(context, template, self_ns=None): if self_ns is None:
diff --git a/test/def.py b/test/def.py index 1513422..a0e409a 100644 --- a/test/def.py +++ b/test/def.py
@@ -206,24 +206,20 @@ def test_scope_nine(self): """test that 'enclosing scope' doesnt get exported to other templates""" - tmpl = {} - class LocalTmplCollection(lookup.TemplateCollection): - def get_template(self, uri): - return tmpl[uri] - collection = LocalTmplCollection() - tmpl['main'] = Template(""" + l = lookup.TemplateLookup() + l.put_string('main', """ <% x = 5 %> this is main. <%include file="secondary"/> -""", lookup=collection) +""") - tmpl['secondary'] = Template(""" + l.put_string('secondary', """ this is secondary. x is ${x} -""", lookup=collection) +""") - assert flatten_result(tmpl['main'].render(x=2)) == "this is main. this is secondary. x is 2" + assert flatten_result(l.get_template('main').render(x=2)) == "this is main. this is secondary. x is 2" def test_scope_ten(self): t = Template("""
diff --git a/test/inheritance.py b/test/inheritance.py index 3dc9f5d..28bc78f 100644 --- a/test/inheritance.py +++ b/test/inheritance.py
@@ -5,13 +5,9 @@ class InheritanceTest(unittest.TestCase): def test_basic(self): - tmpl = {} - class LocalTmplCollection(lookup.TemplateCollection): - def get_template(self, uri): - return tmpl[uri] - collection = LocalTmplCollection() + collection = lookup.TemplateLookup() - tmpl['main'] = Template(""" + collection.put_string('main', """ <%inherit file="base"/> <%def name="header"> @@ -19,9 +15,9 @@ </%def> this is the content. -""", lookup=collection) +""") - tmpl['base'] = Template(""" + collection.put_string('base', """ This is base. header: ${self.header()} @@ -33,9 +29,9 @@ <%def name="footer"> this is the footer. header again ${next.header()} </%def> -""", lookup=collection) +""") - assert result_lines(tmpl['main'].render()) == [ + assert result_lines(collection.get_template('main').render()) == [ 'This is base.', 'header:', 'main header.',
diff --git a/test/lookup.py b/test/lookup.py new file mode 100644 index 0000000..376bb2d --- /dev/null +++ b/test/lookup.py
@@ -0,0 +1,35 @@ +from mako.template import Template +from mako import lookup +from util import flatten_result, result_lines +import unittest + +import os + +if not os.access('./test_htdocs', os.F_OK): + os.mkdir('./test_htdocs') + file('./test_htdocs/index.html', 'w').write("this is index") + file('./test_htdocs/incl.html', 'w').write("this is include 1") + os.mkdir('./test_htdocs/subdir') + file('./test_htdocs/subdir/incl.html', 'w').write(""" + this is include 2 + """) + file('./test_htdocs/subdir/index.html', 'w').write(""" + this is sub index + <%include file="incl.html"/> + """) +tl = lookup.TemplateLookup(directories=['./test_htdocs']) +class LookupTest(unittest.TestCase): + def test_basic(self): + t = tl.get_template('index.html') + assert result_lines(t.render()) == [ + "this is index" + ] + def test_subdir(self): + t = tl.get_template('/subdir/index.html') + assert result_lines(t.render()) == [ + "this is sub index", + "this is include 2" + ] + +if __name__ == '__main__': + unittest.main() \ No newline at end of file
diff --git a/test/namespace.py b/test/namespace.py index 603aaa0..b8eb554 100644 --- a/test/namespace.py +++ b/test/namespace.py
@@ -220,8 +220,106 @@ "caller body:", "call body" ] + + def test_import(self): + collection = lookup.TemplateLookup() + collection.put_string("functions.html",""" + <%def name="foo"> + this is foo + </%def> + + <%def name="bar"> + this is bar + </%def> + + <%def name="lala"> + this is lala + </%def> + """) + + collection.put_string("func2.html", """ + <%def name="a"> + this is a + </%def> + <%def name="b"> + this is b + </%def> + """) + collection.put_string("index.html", """ + <%namespace name="func" file="functions.html" import="*"/> + <%namespace name="func2" file="func2.html" import="a, b"/> + ${foo()} + ${bar()} + ${lala()} + ${a()} + ${b()} + ${x} + """) + assert result_lines(collection.get_template("index.html").render(bar="this is bar", x="this is x")) == [ + "this is foo", + "this is bar", + "this is lala", + "this is a", + "this is b", + "this is x" + ] + def test_closure_import(self): + collection = lookup.TemplateLookup() + collection.put_string("functions.html",""" + <%def name="foo"> + this is foo + </%def> + + <%def name="bar"> + this is bar + </%def> + """) + collection.put_string("index.html", """ + <%namespace name="func" file="functions.html" import="*"/> + <%def name="cl1"> + ${foo()} + </%def> + + <%def name="cl2"> + ${bar()} + </%def> + + ${cl1()} + ${cl2()} + """) + assert result_lines(collection.get_template("index.html").render(bar="this is bar", x="this is x")) == [ + "this is foo", + "this is bar", + ] + + def test_ccall_import(self): + collection = lookup.TemplateLookup() + collection.put_string("functions.html",""" + <%def name="foo"> + this is foo + </%def> + + <%def name="bar"> + this is bar. + ${caller.body()} + ${caller.lala()} + </%def> + """) + collection.put_string("index.html", """ + <%namespace name="func" file="functions.html" import="*"/> + <%call expr="bar()"> + this is index embedded + foo is ${foo()} + <%def name="lala"> + this is lala ${foo()} + </%def> + </%call> + """) + print collection.get_template("index.html").code + print collection.get_template("index.html").render() + if __name__ == '__main__': unittest.main()