diff options
-rw-r--r-- | examples/bench/basic.py | 9 | ||||
-rw-r--r-- | examples/bench/mako_inheritance/base.html | 20 | ||||
-rw-r--r-- | examples/bench/mako_inheritance/footer.html | 2 | ||||
-rw-r--r-- | examples/bench/mako_inheritance/header.html | 5 | ||||
-rw-r--r-- | examples/bench/mako_inheritance/template.html | 15 | ||||
-rw-r--r-- | lib/mako/codegen.py | 64 | ||||
-rw-r--r-- | lib/mako/runtime.py | 133 | ||||
-rw-r--r-- | lib/mako/template.py | 59 | ||||
-rw-r--r-- | test/inheritance.py | 75 |
9 files changed, 269 insertions, 113 deletions
diff --git a/examples/bench/basic.py b/examples/bench/basic.py index 26ed9be..bf531ef 100644 --- a/examples/bench/basic.py +++ b/examples/bench/basic.py @@ -34,7 +34,7 @@ from StringIO import StringIO import sys import timeit -__all__ = ['mako', 'cheetah', 'django', 'myghty', 'genshi', 'kid'] +__all__ = ['mako', 'mako_inheritance', 'cheetah', 'django', 'myghty', 'genshi', 'kid'] def genshi(dirname, verbose=False): from genshi.template import TemplateLoader @@ -70,9 +70,10 @@ def mako(dirname, verbose=False): def render(): return template.render(title="Just a test", user="joe", list_items=[u'Number %d' % num for num in range(1,15)]) if verbose: - print template.code + render() + print render() return render - +mako_inheritance = mako + def cheetah(dirname, verbose=False): from Cheetah.Template import Template filename = os.path.join(dirname, 'template.tmpl') @@ -83,6 +84,8 @@ def cheetah(dirname, verbose=False): return template.respond() if verbose: + print dir(template) + print template.generatedModuleCode() print render() return render diff --git a/examples/bench/mako_inheritance/base.html b/examples/bench/mako_inheritance/base.html new file mode 100644 index 0000000..618dde7 --- /dev/null +++ b/examples/bench/mako_inheritance/base.html @@ -0,0 +1,20 @@ +<!DOCTYPE html + PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" lang="en"> + <head> + <title>${title}</title> + </head> + <body> + +<%component name="greeting(name)"> + <p>hello ${name}!</p> +</%component> + + <%include file="header.html"/> + + ${self.body()} + + <%include file="footer.html"/> + </body> +</html> diff --git a/examples/bench/mako_inheritance/footer.html b/examples/bench/mako_inheritance/footer.html new file mode 100644 index 0000000..1b00330 --- /dev/null +++ b/examples/bench/mako_inheritance/footer.html @@ -0,0 +1,2 @@ +<div id="footer"> +</div> diff --git a/examples/bench/mako_inheritance/header.html b/examples/bench/mako_inheritance/header.html new file mode 100644 index 0000000..e4f3382 --- /dev/null +++ b/examples/bench/mako_inheritance/header.html @@ -0,0 +1,5 @@ +<div id="header"> + <h1>${title}</h1> +</div> + + diff --git a/examples/bench/mako_inheritance/template.html b/examples/bench/mako_inheritance/template.html new file mode 100644 index 0000000..45a6822 --- /dev/null +++ b/examples/bench/mako_inheritance/template.html @@ -0,0 +1,15 @@ +<%inherit file="base.html"/> + + ${parent.greeting(user)} + ${parent.greeting('me')} + ${parent.greeting('world')} + + <h2>Loop</h2> + % if list_items: + <ul> + % for list_item in list_items: + <li>${list_item}</li> + % endfor + </ul> + % endif + diff --git a/lib/mako/codegen.py b/lib/mako/codegen.py index 261553b..092bf05 100644 --- a/lib/mako/codegen.py +++ b/lib/mako/codegen.py @@ -20,14 +20,6 @@ class Compiler(object): def render(self): buf = util.FastEncodingBuffer() printer = PythonPrinter(buf) - - # module-level names, python code - printer.writeline("from mako import runtime") - printer.writeline("_magic_number = %s" % repr(MAGIC_NUMBER)) - printer.writeline("_modified_time = %s" % repr(time.time())) - printer.writeline("_template_filename=%s" % repr(self.filename)) - printer.writeline("UNDEFINED = runtime.UNDEFINED") - printer.write("\n\n") module_code = [] class FindPyDecls(object): @@ -40,12 +32,23 @@ class Compiler(object): module_identifiers = _Identifiers() for n in module_code: 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) + # module-level names, python code + printer.writeline("from mako import runtime") + printer.writeline("_magic_number = %s" % repr(MAGIC_NUMBER)) + printer.writeline("_modified_time = %s" % repr(time.time())) + printer.writeline("_template_filename=%s" % repr(self.filename)) + printer.writeline("UNDEFINED = runtime.UNDEFINED") + printer.writeline("_exports = %s" % repr([n.name for n in main_identifiers.toplevelcomponents])) + printer.write("\n\n") + + for n in module_code: + printer.writeline("# SOURCE LINE %d" % n.lineno, is_comment=True) + printer.write_indented_block(n.text) + # print main render() method _GenerateRenderMethod(printer, module_identifiers, self.node) @@ -55,6 +58,7 @@ class Compiler(object): return buf.getvalue() + class _GenerateRenderMethod(object): def __init__(self, printer, identifiers, node): self.printer = printer @@ -74,12 +78,15 @@ class _GenerateRenderMethod(object): args = ['context'] else: args = [a for a in ['context'] + args] + + if not self.in_component: + self._inherit() printer.writeline("def %s(%s):" % (name, ','.join(args))) + self.identifiers = identifiers.branch(node) if len(self.identifiers.locally_assigned) > 0: printer.writeline("__locals = {}") - self.write_variable_declares(self.identifiers) for n in node.nodes: n.accept_visitor(self) @@ -87,6 +94,16 @@ class _GenerateRenderMethod(object): printer.writeline(None) printer.write("\n\n") + def _inherit(self): + class FindInherit(object): + def visitInheritTag(s, node): + self.printer.writeline("def _inherit(context):") + self.printer.writeline("runtime.inherit_from(context, %s)" % (repr(node.attributes['file']))) + self.printer.writeline(None) + f = FindInherit() + for n in self.node.nodes: + n.accept_visitor(f) + def write_variable_declares(self, identifiers): """write variable declarations at the top of a function. @@ -153,25 +170,12 @@ class _GenerateRenderMethod(object): #print "INLINE NAME", node.name identifiers = identifiers.branch(node) - # if we assign to variables in this closure, then we have to nest inside - # of another callable so that the "context" variable is copied into the local scope - #make_closure = len(identifiers.locally_declared) > 0 - - #if make_closure: - # self.printer.writeline("try:") - # self.printer.writeline("context.push()") 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: - # self.printer.writeline("finally:") - # self.printer.writeline("context.pop()") - # self.printer.writeline(None) - # self.printer.writeline(None) def visitExpression(self, node): self.write_source_comment(node) @@ -212,12 +216,7 @@ class _GenerateRenderMethod(object): n.accept_visitor(vis) 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, repr(node.name))) + self.printer.writeline("%s = runtime.ContextualNamespace(%s, context, callables=make_namespace())" % (node.name, repr(node.name))) def visitComponentTag(self, node): pass @@ -238,7 +237,7 @@ class _GenerateRenderMethod(object): n.accept_visitor(self) self.printer.writeline("return ''") self.printer.writeline(None) - self.printer.writeline("context.push(**{%s})" % + self.printer.writeline("context.push({%s})" % (','.join(["%s:%s" % (repr(x), x) for x in export]) ) ) self.printer.writeline("context.write(unicode(%s))" % node.attributes['expr']) @@ -246,9 +245,6 @@ class _GenerateRenderMethod(object): self.printer.writeline(None) self.printer.writeline("ccall()") - def visitInheritTag(self, node): - pass - class _Identifiers(object): """tracks the status of identifier names as template code is rendered.""" def __init__(self, node=None, parent=None): diff --git a/lib/mako/runtime.py b/lib/mako/runtime.py index c1f2d94..123cb53 100644 --- a/lib/mako/runtime.py +++ b/lib/mako/runtime.py @@ -5,33 +5,36 @@ # the MIT License: http://www.opensource.org/licenses/mit-license.php """provides the Context class, the runtime namespace for templates.""" -from mako import exceptions - +from mako import exceptions, util +import inspect class Context(object): """provides runtime namespace and output buffer for templates.""" - def __init__(self, template, buffer, **data): - # TODO: not sure if Context should need template + buffer etc. + def __init__(self, buffer, **data): self.buffer = buffer - self.stack = [data] - # the Template instance currently rendering with this context. - self.with_template = template + self._argstack = [data] + self.with_template = None data['args'] = _AttrFacade(self) def __getitem__(self, key): - return self.stack[-1][key] + return self._argstack[-1][key] def get(self, key, default=None): - return self.stack[-1].get(key, default) + return self._argstack[-1].get(key, default) def write(self, string): self.buffer.write(string) - def push(self, **args): - x = self.stack[-1].copy() + def push(self, args): + x = self._argstack[-1].copy() x.update(args) - self.stack.append(x) + self._argstack.append(x) def pop(self): - self.stack.pop() + self._argstack.pop() def locals_(self, d): - c = Context(self.with_template, self.buffer, **self.stack[-1]) - c.stack[-1].update(**d) + c = Context.__new__(Context) + c.buffer = self.buffer + c._argstack = [x for x in self._argstack] + c.with_template = self.with_template + if c.with_template is None: + raise "hi" + c.push(d) return c class _AttrFacade(object): @@ -49,9 +52,11 @@ UNDEFINED = Undefined() 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): + def __init__(self, name, module=None, template=None, callables=None, inherits=None): + self.name = name self.module = module self.template = template + self.inherits = inherits if callables is not None: self.callables = dict([(c.func_name, c) for c in callables]) else: @@ -64,21 +69,105 @@ class Namespace(object): except KeyError: pass if self.template is not None: - try: - callable_ = self.template.get_component(key) + if key == 'body': + callable_ = self.template.module.render + else: + try: + callable_ = self.template.get_component(key).callable_ + except AttributeError: + callable_ = None + if callable_ is not None: return lambda *args, **kwargs:callable_(context, *args, **kwargs) - except AttributeError: - pass if self.module is not None: try: callable_ = getattr(self.module, key) return lambda *args, **kwargs:callable_(context, *args, **kwargs) except AttributeError: pass + if self.inherits is not None: + return self.inherits.contextual_callable(context, key) raise exceptions.RuntimeException("Namespace '%s' has no member '%s'" % (self.name, key)) - + +class ContextualNamespace(Namespace): + def __init__(self, name, context, **kwargs): + super(ContextualNamespace, self).__init__(name, **kwargs) + self.context = context + def __getattr__(self, key): + return self.contextual_callable(self.context, key) + +def _lookup_template(context, uri): + lookup = context.with_template.lookup + return lookup.get_template(uri) + def include_file(context, uri, import_symbols): lookup = context.with_template.lookup template = lookup.get_template(uri) - template.render_context(context) + template.callable_(context) +def inherit_from(context, uri): + template = _lookup_template(context, uri) + self_ns = context.get('self', None) + if self_ns is None: + fromtempl = context.with_template + self_ns = ContextualNamespace('self', context, template=fromtempl) + context._argstack[-1]['self'] = self_ns + ih = self_ns + while ih.inherits is not None: + ih = ih.inherits + lclcontext = context.locals_({'next':ih}) + ih.inherits = ContextualNamespace('self', lclcontext, template = template) + context._argstack[-1]['parent'] = ih.inherits + callable_ = getattr(template.module, '_inherit', getattr(template.module, 'render')) + callable_(lclcontext) + +def _render(template, callable_, args, data, as_unicode=False): + """given a Template and a callable_ from that template, create a Context and return the string output.""" + if as_unicode: + buf = util.FastEncodingBuffer() + elif template.output_encoding: + buf = util.FastEncodingBuffer(template.output_encoding) + else: + buf = util.StringIO() + context = Context(buf, **data) + kwargs = {} + if callable_.__name__ == 'render': + callable_ = getattr(template.module, '_inherit', callable_) + argspec = inspect.getargspec(callable_) + namedargs = argspec[0] + [v for v in argspec[1:3] if v is not None] + for arg in namedargs: + if arg != 'context' and arg in data: + kwargs[arg] = data[arg] + _render_context(template, callable_, context, *args, **kwargs) + return buf.getvalue() + +def _render_context(template, callable_, context, *args, **kwargs): + context.with_template = template + _exec_template(callable_, context, args=args, kwargs=kwargs) + +def _exec_template(callable_, context, args=None, kwargs=None): + """execute a rendering callable given the callable, a Context, and optional explicit arguments + + the contextual Template will be located if it exists, and the error handling options specified + on that Template will be interpreted here. + """ + template = context.with_template + if template is not None and (template.format_exceptions or template.error_handler): + error = None + try: + callable_(context, *args, **kwargs) + except Exception, e: + error = e + except: + e = sys.exc_info()[0] + error = e + if error: + if template.error_handler: + result = template.error_handler(context, error) + if not result: + raise error + else: + # TODO + source = _get_template_source(callable_) + raise error + else: + callable_(context, *args, **kwargs) diff --git a/lib/mako/template.py b/lib/mako/template.py index d6e40d3..470d5d0 100644 --- a/lib/mako/template.py +++ b/lib/mako/template.py @@ -9,9 +9,9 @@ as well as template runtime operations.""" from mako.lexer import Lexer from mako.codegen import Compiler -from mako.runtime import Context +from mako import runtime from mako import util -import imp, time, inspect, weakref, sys +import imp, time, weakref _modules = weakref.WeakValueDictionary() _inmemory_templates = weakref.WeakValueDictionary() @@ -67,17 +67,17 @@ class Template(object): a Context object is created corresponding to the given data. Arguments that are explictly declared by this template's internal rendering method are also pulled from the given *args, **data members.""" - return _render(self, self.callable_, args, data) + return runtime._render(self, self.callable_, args, data) def render_unicode(self, *args, **data): """render the output of this template as a unicode object.""" - return _render(self, self.callable_, args, data, as_unicode=True) + return runtime._render(self, self.callable_, args, data, as_unicode=True) def render_context(self, context, *args, **kwargs): """render this Template with the given context. the data is written to the context's buffer.""" - _render_context(self, self.callable_, context, *args, **kwargs) + runtime._render_context(self, self.callable_, context, *args, **kwargs) def get_component(self, name): """return a component of this template as an individual Template of its own.""" @@ -101,55 +101,6 @@ def _compile_text(text, identifier, filename): exec code in module.__dict__, module.__dict__ return (source, module) -def _render(template, callable_, args, data, as_unicode=False): - """given a Template and a callable_ from that template, create a Context and return the string output.""" - if as_unicode: - buf = util.FastEncodingBuffer() - elif template.output_encoding: - buf = util.FastEncodingBuffer(template.output_encoding) - else: - buf = util.StringIO() - context = Context(template, buf, **data) - kwargs = {} - argspec = inspect.getargspec(callable_) - namedargs = argspec[0] + [v for v in argspec[1:3] if v is not None] - for arg in namedargs: - if arg != 'context' and arg in data: - kwargs[arg] = data[arg] - _render_context(template, callable_, context, *args, **kwargs) - return buf.getvalue() - -def _render_context(template, callable_, context, *args, **kwargs): - _exec_template(callable_, context, args=args, kwargs=kwargs) - -def _exec_template(callable_, context, args=None, kwargs=None): - """execute a rendering callable given the callable, a Context, and optional explicit arguments - - the contextual Template will be located if it exists, and the error handling options specified - on that Template will be interpreted here. - """ - template = context.with_template - if template is not None and (template.format_exceptions or template.error_handler): - error = None - try: - callable_(context, *args, **kwargs) - except Exception, e: - error = e - except: - e = sys.exc_info()[0] - error = e - if error: - if template.error_handler: - result = template.error_handler(context, error) - if not result: - raise error - else: - # TODO - source = _get_template_source(callable_) - raise error - else: - callable_(context, *args, **kwargs) - def _get_template_source(callable_): """return the source code for the template that produced the given rendering callable""" name = callable_.func_globals['__name__'] diff --git a/test/inheritance.py b/test/inheritance.py new file mode 100644 index 0000000..2289104 --- /dev/null +++ b/test/inheritance.py @@ -0,0 +1,75 @@ +from mako.template import Template +from mako import lookup +import unittest +from util import flatten_result + +class InheritanceTest(unittest.TestCase): + def test_basic(self): + tmpl = {} + class LocalTmplCollection(lookup.TemplateCollection): + def get_template(self, uri): + return tmpl[uri] + collection = LocalTmplCollection() + + tmpl['main'] = Template(""" +<%inherit file="base"/> + +<%component name="header"> + main header. +</%component> + +this is the content. +""", lookup=collection) + + tmpl['base'] = Template(""" +This is base. + +header: ${self.header()} + +body: ${self.body()} + +footer: ${self.footer()} + +<%component name="footer"> + this is the footer +</%component> +""", lookup=collection) + + print tmpl['main'].render() + + def test_multilevel_nesting(self): + tmpl = {} + class LocalTmplCollection(lookup.TemplateCollection): + def get_template(self, uri): + return tmpl[uri] + collection = LocalTmplCollection() + + tmpl['main'] = Template(""" +<%inherit file="layout"/> +main_body ${parent.footer()} +""", lookup=collection) + + tmpl['layout'] = Template(""" +<%inherit file="general"/> +layout_body +${next.body()} +""") + + tmpl['general'] = Template(""" +<%inherit file="base"/> +general_body +${next.body()} +""") + tmpl['base'] = Template(""" +base_body +${next.body()} +<%component name="footer"> + base footer +</%component> +""", lookup=collection) + + print tmpl['main'].render() + + +if __name__ == '__main__': + unittest.main() |