2
# Copyright (C) 2006, 2007 Michael Bayer mike_mp@zzzcomputing.com
4
# This module is part of Mako and is released under
5
# the MIT License: http://www.opensource.org/licenses/mit-license.php
7
"""provides functionality for rendering a parsetree constructing into module source code."""
11
from mako.pygen import PythonPrinter
12
from mako import util, ast, parsetree, filters
17
def compile(node, uri, filename=None, default_filters=None, buffer_filters=None, imports=None, source_encoding=None):
18
"""generate module source code given a parsetree node, uri, and optional source filename"""
19
buf = util.FastEncodingBuffer()
20
printer = PythonPrinter(buf)
21
_GenerateRenderMethod(printer, _CompileContext(uri, filename, default_filters, buffer_filters, imports, source_encoding), node)
24
class _CompileContext(object):
25
def __init__(self, uri, filename, default_filters, buffer_filters, imports, source_encoding):
27
self.filename = filename
28
self.default_filters = default_filters
29
self.buffer_filters = buffer_filters
30
self.imports = imports
31
self.source_encoding = source_encoding
33
class _GenerateRenderMethod(object):
34
"""a template visitor object which generates the full module source for a template."""
35
def __init__(self, printer, compiler, node):
36
self.printer = printer
37
self.last_source_line = -1
38
self.compiler = compiler
40
self.identifier_stack = [None]
42
self.in_def = isinstance(node, parsetree.DefTag)
45
name = "render_" + node.name
46
args = node.function_decl.get_argument_expressions()
47
filtered = len(node.filter_args.args) > 0
48
buffered = eval(node.attributes.get('buffered', 'False'))
49
cached = eval(node.attributes.get('cached', 'False'))
53
defs = self.write_toplevel()
54
pagetag = self.compiler.pagetag
56
if pagetag is not None:
57
args = pagetag.body_decl.get_argument_expressions()
58
if not pagetag.body_decl.kwargs:
59
args += ['**pageargs']
60
cached = eval(pagetag.attributes.get('cached', 'False'))
64
buffered = filtered = False
68
args = [a for a in ['context'] + args]
70
self.write_render_callable(pagetag or node, name, args, buffered, filtered, cached)
74
_GenerateRenderMethod(printer, compiler, node)
76
identifiers = property(lambda self:self.identifier_stack[-1])
78
def write_toplevel(self):
79
"""traverse a template structure for module-level directives and generate the
80
start of module-level code."""
86
self.compiler.pagetag = None
88
class FindTopLevel(object):
89
def visitInheritTag(s, node):
91
def visitNamespaceTag(s, node):
92
namespaces[node.name] = node
93
def visitPageTag(s, node):
94
self.compiler.pagetag = node
95
def visitCode(s, node):
97
module_code.append(node)
100
for n in self.node.nodes:
103
self.compiler.namespaces = namespaces
105
module_ident = util.Set()
106
for n in module_code:
107
module_ident = module_ident.union(n.declared_identifiers())
109
module_identifiers = _Identifiers()
110
module_identifiers.declared = module_ident
112
# module-level names, python code
113
self.printer.writeline("from mako import runtime, filters, cache")
114
self.printer.writeline("UNDEFINED = runtime.UNDEFINED")
115
self.printer.writeline("_magic_number = %s" % repr(MAGIC_NUMBER))
116
self.printer.writeline("_modified_time = %s" % repr(time.time()))
117
self.printer.writeline("_template_filename=%s" % repr(self.compiler.filename))
118
self.printer.writeline("_template_uri=%s" % repr(self.compiler.uri))
119
self.printer.writeline("_template_cache=cache.Cache(__name__, _modified_time)")
120
self.printer.writeline("_source_encoding=%s" % repr(self.compiler.source_encoding))
121
if self.compiler.imports:
123
for imp in self.compiler.imports:
125
self.printer.writeline(imp)
126
impcode = ast.PythonCode(buf, source='', lineno=0, pos=0, filename='template defined imports')
130
main_identifiers = module_identifiers.branch(self.node)
131
module_identifiers.topleveldefs = module_identifiers.topleveldefs.union(main_identifiers.topleveldefs)
132
[module_identifiers.declared.add(x) for x in ["UNDEFINED"]]
134
[module_identifiers.declared.add(x) for x in impcode.declared_identifiers]
136
self.compiler.identifiers = module_identifiers
137
self.printer.writeline("_exports = %s" % repr([n.name for n in main_identifiers.topleveldefs.values()]))
138
self.printer.write("\n\n")
141
self.write_module_code(module_code)
144
self.write_namespaces(namespaces)
145
self.write_inherit(inherit[-1])
146
elif len(namespaces):
147
self.write_namespaces(namespaces)
149
return main_identifiers.topleveldefs.values()
151
def write_render_callable(self, node, name, args, buffered, filtered, cached):
152
"""write a top-level render callable.
154
this could be the main render() method or that of a top-level def."""
155
self.printer.writelines(
156
"def %s(%s):" % (name, ','.join(args)),
157
"context.caller_stack.push_frame()",
160
if buffered or filtered or cached:
161
self.printer.writeline("context.push_buffer()")
163
self.identifier_stack.append(self.compiler.identifiers.branch(self.node))
164
if not self.in_def and '**pageargs' in args:
165
self.identifier_stack[-1].argument_declared.add('pageargs')
167
if not self.in_def and (len(self.identifiers.locally_assigned) > 0 or len(self.identifiers.argument_declared)>0):
168
self.printer.writeline("__M_locals = dict(%s)" % ','.join(["%s=%s" % (x, x) for x in self.identifiers.argument_declared]))
170
self.write_variable_declares(self.identifiers, toplevel=True)
172
for n in self.node.nodes:
173
n.accept_visitor(self)
175
self.write_def_finish(self.node, buffered, filtered, cached)
176
self.printer.writeline(None)
177
self.printer.write("\n\n")
179
self.write_cache_decorator(node, name, buffered, self.identifiers)
181
def write_module_code(self, module_code):
182
"""write module-level template code, i.e. that which is enclosed in <%! %> tags
184
for n in module_code:
185
self.write_source_comment(n)
186
self.printer.write_indented_block(n.text)
188
def write_inherit(self, node):
189
"""write the module-level inheritance-determination callable."""
190
self.printer.writelines(
191
"def _mako_inherit(template, context):",
192
"_mako_generate_namespaces(context)",
193
"return runtime._inherit_from(context, %s, _template_uri)" % (node.parsed_attributes['file']),
197
def write_namespaces(self, namespaces):
198
"""write the module-level namespace-generating callable."""
199
self.printer.writelines(
200
"def _mako_get_namespace(context, name):",
202
"return context.namespaces[(__name__, name)]",
204
"_mako_generate_namespaces(context)",
205
"return context.namespaces[(__name__, name)]",
208
self.printer.writeline("def _mako_generate_namespaces(context):")
209
for node in namespaces.values():
210
if node.attributes.has_key('import'):
211
self.compiler.has_ns_imports = True
212
self.write_source_comment(node)
214
self.printer.writeline("def make_namespace():")
216
identifiers = self.compiler.identifiers.branch(node)
217
class NSDefVisitor(object):
218
def visitDefTag(s, node):
219
self.write_inline_def(node, identifiers, nested=False)
220
export.append(node.name)
223
n.accept_visitor(vis)
224
self.printer.writeline("return [%s]" % (','.join(export)))
225
self.printer.writeline(None)
226
callable_name = "make_namespace()"
228
callable_name = "None"
229
self.printer.writeline("ns = runtime.Namespace(%s, context._clean_inheritance_tokens(), templateuri=%s, callables=%s, calling_uri=_template_uri, module=%s)" % (repr(node.name), node.parsed_attributes.get('file', 'None'), callable_name, node.parsed_attributes.get('module', 'None')))
230
if eval(node.attributes.get('inheritable', "False")):
231
self.printer.writeline("context['self'].%s = ns" % (node.name))
232
self.printer.writeline("context.namespaces[(__name__, %s)] = ns" % repr(node.name))
233
self.printer.write("\n")
234
if not len(namespaces):
235
self.printer.writeline("pass")
236
self.printer.writeline(None)
238
def write_variable_declares(self, identifiers, toplevel=False, limit=None):
239
"""write variable declarations at the top of a function.
241
the variable declarations are in the form of callable definitions for defs and/or
242
name lookup within the function's context argument. the names declared are based on the
243
names that are referenced in the function body, which don't otherwise have any explicit
244
assignment operation. names that are assigned within the body are assumed to be
245
locally-scoped variables and are not separately declared.
247
for def callable definitions, if the def is a top-level callable then a
248
'stub' callable is generated which wraps the current Context into a closure. if the def
249
is not top-level, it is fully rendered as a local closure."""
251
# collection of all defs available to us in this scope
252
comp_idents = dict([(c.name, c) for c in identifiers.defs])
253
to_write = util.Set()
255
# write "context.get()" for all variables we are going to need that arent in the namespace yet
256
to_write = to_write.union(identifiers.undeclared)
258
# write closure functions for closures that we define right here
259
to_write = to_write.union(util.Set([c.name for c in identifiers.closuredefs.values()]))
261
# remove identifiers that are declared in the argument signature of the callable
262
to_write = to_write.difference(identifiers.argument_declared)
264
# remove identifiers that we are going to assign to. in this way we mimic Python's behavior,
265
# i.e. assignment to a variable within a block means that variable is now a "locally declared" var,
266
# which cannot be referenced beforehand.
267
to_write = to_write.difference(identifiers.locally_declared)
269
# if a limiting set was sent, constraint to those items in that list
270
# (this is used for the caching decorator)
271
if limit is not None:
272
to_write = to_write.intersection(limit)
274
if toplevel and getattr(self.compiler, 'has_ns_imports', False):
275
self.printer.writeline("_import_ns = {}")
276
self.compiler.has_imports = True
277
for ident, ns in self.compiler.namespaces.iteritems():
278
if ns.attributes.has_key('import'):
279
self.printer.writeline("_mako_get_namespace(context, %s)._populate(_import_ns, %s)" % (repr(ident), repr(re.split(r'\s*,\s*', ns.attributes['import']))))
281
for ident in to_write:
282
if ident in comp_idents:
283
comp = comp_idents[ident]
285
self.write_def_decl(comp, identifiers)
287
self.write_inline_def(comp, identifiers, nested=True)
288
elif ident in self.compiler.namespaces:
289
self.printer.writeline("%s = _mako_get_namespace(context, %s)" % (ident, repr(ident)))
291
if getattr(self.compiler, 'has_ns_imports', False):
292
self.printer.writeline("%s = _import_ns.get(%s, context.get(%s, UNDEFINED))" % (ident, repr(ident), repr(ident)))
294
self.printer.writeline("%s = context.get(%s, UNDEFINED)" % (ident, repr(ident)))
296
def write_source_comment(self, node):
297
"""write a source comment containing the line number of the corresponding template line."""
298
if self.last_source_line != node.lineno:
299
self.printer.writeline("# SOURCE LINE %d" % node.lineno)
300
self.last_source_line = node.lineno
302
def write_def_decl(self, node, identifiers):
303
"""write a locally-available callable referencing a top-level def"""
304
funcname = node.function_decl.funcname
305
namedecls = node.function_decl.get_argument_expressions()
306
nameargs = node.function_decl.get_argument_expressions(include_defaults=False)
307
if not self.in_def and (len(self.identifiers.locally_assigned) > 0 or len(self.identifiers.argument_declared) > 0):
308
nameargs.insert(0, 'context.locals_(__M_locals)')
310
nameargs.insert(0, 'context')
311
self.printer.writeline("def %s(%s):" % (funcname, ",".join(namedecls)))
312
self.printer.writeline("return render_%s(%s)" % (funcname, ",".join(nameargs)))
313
self.printer.writeline(None)
315
def write_inline_def(self, node, identifiers, nested):
316
"""write a locally-available def callable inside an enclosing def."""
317
namedecls = node.function_decl.get_argument_expressions()
318
self.printer.writeline("def %s(%s):" % (node.name, ",".join(namedecls)))
319
filtered = len(node.filter_args.args) > 0
320
buffered = eval(node.attributes.get('buffered', 'False'))
321
cached = eval(node.attributes.get('cached', 'False'))
322
self.printer.writelines(
323
"context.caller_stack.push_frame()",
326
if buffered or filtered or cached:
327
self.printer.writelines(
328
"context.push_buffer()",
331
identifiers = identifiers.branch(node, nested=nested)
333
self.write_variable_declares(identifiers)
335
self.identifier_stack.append(identifiers)
337
n.accept_visitor(self)
338
self.identifier_stack.pop()
340
self.write_def_finish(node, buffered, filtered, cached)
341
self.printer.writeline(None)
343
self.write_cache_decorator(node, node.name, False, identifiers, inline=True)
345
def write_def_finish(self, node, buffered, filtered, cached, callstack=True):
346
"""write the end section of a rendering function, either outermost or inline.
348
this takes into account if the rendering function was filtered, buffered, etc.
349
and closes the corresponding try: block if any, and writes code to retrieve captured content,
350
apply filters, send proper return value."""
351
if not buffered and not cached and not filtered:
352
self.printer.writeline("return ''")
354
self.printer.writelines(
356
"context.caller_stack.pop_frame()",
359
if buffered or filtered or cached:
360
self.printer.writelines(
362
"__M_buf = context.pop_buffer()"
365
self.printer.writeline("context.caller_stack.pop_frame()")
366
s = "__M_buf.getvalue()"
368
s = self.create_filter_callable(node.filter_args.args, s, False)
369
self.printer.writeline(None)
370
if buffered and not cached:
371
s = self.create_filter_callable(self.compiler.buffer_filters, s, False)
372
if buffered or cached:
373
self.printer.writeline("return %s" % s)
375
self.printer.writelines(
376
"context.write(%s)" % s,
380
def write_cache_decorator(self, node_or_pagetag, name, buffered, identifiers, inline=False):
381
"""write a post-function decorator to replace a rendering callable with a cached version of itself."""
382
self.printer.writeline("__M_%s = %s" % (name, name))
383
cachekey = node_or_pagetag.parsed_attributes.get('cache_key', repr(name))
385
for arg in (('cache_type', 'type'), ('cache_dir', 'data_dir'), ('cache_timeout', 'expiretime'), ('cache_url', 'url')):
386
val = node_or_pagetag.parsed_attributes.get(arg[0], None)
388
if arg[1] == 'expiretime':
389
cacheargs[arg[1]] = int(eval(val))
391
cacheargs[arg[1]] = val
393
if self.compiler.pagetag is not None:
394
val = self.compiler.pagetag.parsed_attributes.get(arg[0], None)
396
if arg[1] == 'expiretime':
397
cacheargs[arg[1]] == int(eval(val))
399
cacheargs[arg[1]] = val
404
ctx_arg = "context, "
406
self.printer.writeline("def %s(%s*args, **kwargs):" % (name, ctx_arg))
408
self.write_variable_declares(identifiers, limit=node_or_pagetag.undeclared_identifiers())
410
s = "context.get('local').get_cached(%s, %screatefunc=lambda:__M_%s(%s*args, **kwargs))" % (cachekey, ''.join(["%s=%s, " % (k,v) for k, v in cacheargs.iteritems()]), name, ctx_arg)
411
# apply buffer_filters
412
s = self.create_filter_callable(self.compiler.buffer_filters, s, False)
413
self.printer.writelines("return " + s,None)
415
self.printer.writelines(
416
"context.write(context.get('local').get_cached(%s, %screatefunc=lambda:__M_%s(%s*args, **kwargs)))" % (cachekey, ''.join(["%s=%s, " % (k,v) for k, v in cacheargs.iteritems()]), name, ctx_arg),
421
def create_filter_callable(self, args, target, is_expression):
422
"""write a filter-applying expression based on the filters present in the given
423
filter names, adjusting for the global 'default' filter aliases as needed."""
424
def locate_encode(name):
425
if re.match(r'decode\..+', name):
426
return "filters." + name
427
elif name == 'unicode':
430
return filters.DEFAULT_ESCAPES.get(name, name)
434
if self.compiler.pagetag:
435
args = self.compiler.pagetag.filter_args.args + args
436
if self.compiler.default_filters:
437
args = self.compiler.default_filters + args
439
# if filter given as a function, get just the identifier portion
442
m = re.match(r'(.+?)(\(.*\))', e)
444
(ident, fargs) = m.group(1,2)
445
f = locate_encode(ident)
451
target = "%s(%s)" % (e, target)
454
def visitExpression(self, node):
455
self.write_source_comment(node)
456
if len(node.escapes) or (self.compiler.pagetag is not None and len(self.compiler.pagetag.filter_args.args)) or len(self.compiler.default_filters):
457
s = self.create_filter_callable(node.escapes_code.args, "%s" % node.text, True)
458
self.printer.writeline("context.write(%s)" % s)
460
self.printer.writeline("context.write(%s)" % node.text)
462
def visitControlLine(self, node):
464
self.printer.writeline(None)
466
self.write_source_comment(node)
467
self.printer.writeline(node.text)
468
def visitText(self, node):
469
self.write_source_comment(node)
470
self.printer.writeline("context.write(%s)" % repr(node.content))
471
def visitTextTag(self, node):
472
filtered = len(node.filter_args.args) > 0
474
self.printer.writelines(
475
"context.push_buffer()",
479
n.accept_visitor(self)
481
self.printer.writelines(
483
"__M_buf = context.pop_buffer()",
484
"context.write(%s)" % self.create_filter_callable(node.filter_args.args, "__M_buf.getvalue()", False),
488
def visitCode(self, node):
489
if not node.ismodule:
490
self.write_source_comment(node)
491
self.printer.write_indented_block(node.text)
493
if not self.in_def and len(self.identifiers.locally_assigned) > 0:
494
# if we are the "template" def, fudge locally declared/modified variables into the "__M_locals" dictionary,
495
# which is used for def calls within the same template, to simulate "enclosing scope"
496
self.printer.writeline('__M_locals.update(dict([(__M_key, locals()[__M_key]) for __M_key in [%s] if __M_key in locals()]))' % ','.join([repr(x) for x in node.declared_identifiers()]))
498
def visitIncludeTag(self, node):
499
self.write_source_comment(node)
500
args = node.attributes.get('args')
502
self.printer.writeline("runtime._include_file(context, %s, _template_uri, %s)" % (node.parsed_attributes['file'], args))
504
self.printer.writeline("runtime._include_file(context, %s, _template_uri)" % (node.parsed_attributes['file']))
506
def visitNamespaceTag(self, node):
509
def visitDefTag(self, node):
512
def visitCallTag(self, node):
513
self.printer.writeline("def ccall(caller):")
515
callable_identifiers = self.identifiers.branch(node, nested=True)
516
body_identifiers = callable_identifiers.branch(node, nested=False)
517
# we want the 'caller' passed to ccall to be used for the body() function,
518
# but for other non-body() <%def>s within <%call> we want the current caller off the call stack (if any)
519
body_identifiers.add_declared('caller')
521
self.identifier_stack.append(body_identifiers)
522
class DefVisitor(object):
523
def visitDefTag(s, node):
524
self.write_inline_def(node, callable_identifiers, nested=False)
525
export.append(node.name)
526
# remove defs that are within the <%call> from the "closuredefs" defined
527
# in the body, so they dont render twice
528
if node.name in body_identifiers.closuredefs:
529
del body_identifiers.closuredefs[node.name]
533
n.accept_visitor(vis)
534
self.identifier_stack.pop()
536
bodyargs = node.body_decl.get_argument_expressions()
537
self.printer.writeline("def body(%s):" % ','.join(bodyargs))
538
# TODO: figure out best way to specify buffering/nonbuffering (at call time would be better)
541
self.printer.writelines(
542
"context.push_buffer()",
545
self.write_variable_declares(body_identifiers)
546
self.identifier_stack.append(body_identifiers)
548
n.accept_visitor(self)
549
self.identifier_stack.pop()
551
self.write_def_finish(node, buffered, False, False, callstack=False)
552
self.printer.writelines(
554
"return [%s]" % (','.join(export)),
558
self.printer.writelines(
559
# get local reference to current caller, if any
560
"caller = context.caller_stack._get_caller()",
561
# push on caller for nested call
562
"context.caller_stack.nextcaller = runtime.Namespace('caller', context, callables=ccall(caller))",
564
self.write_source_comment(node)
565
self.printer.writelines(
566
"context.write(unicode(%s))" % node.attributes['expr'],
568
"context.caller_stack.nextcaller = None",
572
class _Identifiers(object):
573
"""tracks the status of identifier names as template code is rendered."""
574
def __init__(self, node=None, parent=None, nested=False):
575
if parent is not None:
576
# things that have already been declared in an enclosing namespace (i.e. names we can just use)
577
self.declared = util.Set(parent.declared).union([c.name for c in parent.closuredefs.values()]).union(parent.locally_declared).union(parent.argument_declared)
579
# if these identifiers correspond to a "nested" scope, it means whatever the
580
# parent identifiers had as undeclared will have been declared by that parent,
581
# and therefore we have them in our scope.
583
self.declared = self.declared.union(parent.undeclared)
585
# top level defs that are available
586
self.topleveldefs = util.SetLikeDict(**parent.topleveldefs)
588
self.declared = util.Set()
589
self.topleveldefs = util.SetLikeDict()
591
# things within this level that are referenced before they are declared (e.g. assigned to)
592
self.undeclared = util.Set()
594
# things that are declared locally. some of these things could be in the "undeclared"
595
# list as well if they are referenced before declared
596
self.locally_declared = util.Set()
598
# assignments made in explicit python blocks. these will be propigated to
599
# the context of local def calls.
600
self.locally_assigned = util.Set()
602
# things that are declared in the argument signature of the def callable
603
self.argument_declared = util.Set()
605
# closure defs that are defined in this level
606
self.closuredefs = util.SetLikeDict()
611
node.accept_visitor(self)
613
def branch(self, node, **kwargs):
614
"""create a new Identifiers for a new Node, with this Identifiers as the parent."""
615
return _Identifiers(node, self, **kwargs)
617
defs = property(lambda self:util.Set(self.topleveldefs.union(self.closuredefs).values()))
620
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))
622
def check_declared(self, node):
623
"""update the state of this Identifiers with the undeclared and declared identifiers of the given node."""
624
for ident in node.undeclared_identifiers():
625
if ident != 'context' and ident not in self.declared.union(self.locally_declared):
626
self.undeclared.add(ident)
627
for ident in node.declared_identifiers():
628
self.locally_declared.add(ident)
630
def add_declared(self, ident):
631
self.declared.add(ident)
632
if ident in self.undeclared:
633
self.undeclared.remove(ident)
635
def visitExpression(self, node):
636
self.check_declared(node)
637
def visitControlLine(self, node):
638
self.check_declared(node)
639
def visitCode(self, node):
640
if not node.ismodule:
641
self.check_declared(node)
642
self.locally_assigned = self.locally_assigned.union(node.declared_identifiers())
643
def visitDefTag(self, node):
645
self.topleveldefs[node.name] = node
646
elif node is not self.node:
647
self.closuredefs[node.name] = node
648
for ident in node.undeclared_identifiers():
649
if ident != 'context' and ident not in self.declared.union(self.locally_declared):
650
self.undeclared.add(ident)
651
# visit defs only one level deep
652
if node is self.node:
653
for ident in node.declared_identifiers():
654
self.argument_declared.add(ident)
656
n.accept_visitor(self)
657
def visitIncludeTag(self, node):
658
self.check_declared(node)
659
def visitPageTag(self, node):
660
for ident in node.declared_identifiers():
661
self.argument_declared.add(ident)
662
self.check_declared(node)
664
def visitCallTag(self, node):
665
if node is self.node:
666
for ident in node.undeclared_identifiers():
667
if ident != 'context' and ident not in self.declared.union(self.locally_declared):
668
self.undeclared.add(ident)
669
for ident in node.declared_identifiers():
670
self.argument_declared.add(ident)
672
n.accept_visitor(self)
674
for ident in node.undeclared_identifiers():
675
if ident != 'context' and ident not in self.declared.union(self.locally_declared):
676
self.undeclared.add(ident)