summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--examples/bench/basic.py9
-rw-r--r--examples/bench/mako_inheritance/base.html20
-rw-r--r--examples/bench/mako_inheritance/footer.html2
-rw-r--r--examples/bench/mako_inheritance/header.html5
-rw-r--r--examples/bench/mako_inheritance/template.html15
-rw-r--r--lib/mako/codegen.py64
-rw-r--r--lib/mako/runtime.py133
-rw-r--r--lib/mako/template.py59
-rw-r--r--test/inheritance.py75
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()