1 | # codegen.py |
---|
2 | # Copyright (C) 2006, 2007, 2008, 2009 Michael Bayer mike_mp@zzzcomputing.com |
---|
3 | # |
---|
4 | # This module is part of Mako and is released under |
---|
5 | # the MIT License: http://www.opensource.org/licenses/mit-license.php |
---|
6 | |
---|
7 | """provides functionality for rendering a parsetree constructing into module source code.""" |
---|
8 | |
---|
9 | import time |
---|
10 | import re |
---|
11 | from mako.pygen import PythonPrinter |
---|
12 | from mako import util, ast, parsetree, filters |
---|
13 | |
---|
14 | MAGIC_NUMBER = 5 |
---|
15 | |
---|
16 | |
---|
17 | def compile(node, uri, filename=None, default_filters=None, buffer_filters=None, imports=None, source_encoding=None, generate_unicode=True): |
---|
18 | """generate module source code given a parsetree node, uri, and optional source filename""" |
---|
19 | |
---|
20 | buf = util.FastEncodingBuffer(unicode=generate_unicode) |
---|
21 | |
---|
22 | printer = PythonPrinter(buf) |
---|
23 | _GenerateRenderMethod(printer, _CompileContext(uri, filename, default_filters, buffer_filters, imports, source_encoding, generate_unicode), node) |
---|
24 | return buf.getvalue() |
---|
25 | |
---|
26 | class _CompileContext(object): |
---|
27 | def __init__(self, uri, filename, default_filters, buffer_filters, imports, source_encoding, generate_unicode): |
---|
28 | self.uri = uri |
---|
29 | self.filename = filename |
---|
30 | self.default_filters = default_filters |
---|
31 | self.buffer_filters = buffer_filters |
---|
32 | self.imports = imports |
---|
33 | self.source_encoding = source_encoding |
---|
34 | self.generate_unicode = generate_unicode |
---|
35 | |
---|
36 | class _GenerateRenderMethod(object): |
---|
37 | """a template visitor object which generates the full module source for a template.""" |
---|
38 | def __init__(self, printer, compiler, node): |
---|
39 | self.printer = printer |
---|
40 | self.last_source_line = -1 |
---|
41 | self.compiler = compiler |
---|
42 | self.node = node |
---|
43 | self.identifier_stack = [None] |
---|
44 | |
---|
45 | self.in_def = isinstance(node, parsetree.DefTag) |
---|
46 | |
---|
47 | if self.in_def: |
---|
48 | name = "render_" + node.name |
---|
49 | args = node.function_decl.get_argument_expressions() |
---|
50 | filtered = len(node.filter_args.args) > 0 |
---|
51 | buffered = eval(node.attributes.get('buffered', 'False')) |
---|
52 | cached = eval(node.attributes.get('cached', 'False')) |
---|
53 | defs = None |
---|
54 | pagetag = None |
---|
55 | else: |
---|
56 | defs = self.write_toplevel() |
---|
57 | pagetag = self.compiler.pagetag |
---|
58 | name = "render_body" |
---|
59 | if pagetag is not None: |
---|
60 | args = pagetag.body_decl.get_argument_expressions() |
---|
61 | if not pagetag.body_decl.kwargs: |
---|
62 | args += ['**pageargs'] |
---|
63 | cached = eval(pagetag.attributes.get('cached', 'False')) |
---|
64 | else: |
---|
65 | args = ['**pageargs'] |
---|
66 | cached = False |
---|
67 | buffered = filtered = False |
---|
68 | if args is None: |
---|
69 | args = ['context'] |
---|
70 | else: |
---|
71 | args = [a for a in ['context'] + args] |
---|
72 | |
---|
73 | self.write_render_callable(pagetag or node, name, args, buffered, filtered, cached) |
---|
74 | |
---|
75 | if defs is not None: |
---|
76 | for node in defs: |
---|
77 | _GenerateRenderMethod(printer, compiler, node) |
---|
78 | |
---|
79 | identifiers = property(lambda self:self.identifier_stack[-1]) |
---|
80 | |
---|
81 | def write_toplevel(self): |
---|
82 | """traverse a template structure for module-level directives and generate the |
---|
83 | start of module-level code.""" |
---|
84 | inherit = [] |
---|
85 | namespaces = {} |
---|
86 | module_code = [] |
---|
87 | encoding =[None] |
---|
88 | |
---|
89 | self.compiler.pagetag = None |
---|
90 | |
---|
91 | class FindTopLevel(object): |
---|
92 | def visitInheritTag(s, node): |
---|
93 | inherit.append(node) |
---|
94 | def visitNamespaceTag(s, node): |
---|
95 | namespaces[node.name] = node |
---|
96 | def visitPageTag(s, node): |
---|
97 | self.compiler.pagetag = node |
---|
98 | def visitCode(s, node): |
---|
99 | if node.ismodule: |
---|
100 | module_code.append(node) |
---|
101 | |
---|
102 | f = FindTopLevel() |
---|
103 | for n in self.node.nodes: |
---|
104 | n.accept_visitor(f) |
---|
105 | |
---|
106 | self.compiler.namespaces = namespaces |
---|
107 | |
---|
108 | module_ident = util.Set() |
---|
109 | for n in module_code: |
---|
110 | module_ident = module_ident.union(n.declared_identifiers()) |
---|
111 | |
---|
112 | module_identifiers = _Identifiers() |
---|
113 | module_identifiers.declared = module_ident |
---|
114 | |
---|
115 | # module-level names, python code |
---|
116 | if not self.compiler.generate_unicode and self.compiler.source_encoding: |
---|
117 | self.printer.writeline("# -*- encoding:%s -*-" % self.compiler.source_encoding) |
---|
118 | |
---|
119 | self.printer.writeline("from mako import runtime, filters, cache") |
---|
120 | self.printer.writeline("UNDEFINED = runtime.UNDEFINED") |
---|
121 | self.printer.writeline("__M_dict_builtin = dict") |
---|
122 | self.printer.writeline("__M_locals_builtin = locals") |
---|
123 | self.printer.writeline("_magic_number = %s" % repr(MAGIC_NUMBER)) |
---|
124 | self.printer.writeline("_modified_time = %s" % repr(time.time())) |
---|
125 | self.printer.writeline("_template_filename=%s" % repr(self.compiler.filename)) |
---|
126 | self.printer.writeline("_template_uri=%s" % repr(self.compiler.uri)) |
---|
127 | self.printer.writeline("_template_cache=cache.Cache(__name__, _modified_time)") |
---|
128 | self.printer.writeline("_source_encoding=%s" % repr(self.compiler.source_encoding)) |
---|
129 | if self.compiler.imports: |
---|
130 | buf = '' |
---|
131 | for imp in self.compiler.imports: |
---|
132 | buf += imp + "\n" |
---|
133 | self.printer.writeline(imp) |
---|
134 | impcode = ast.PythonCode(buf, source='', lineno=0, pos=0, filename='template defined imports') |
---|
135 | else: |
---|
136 | impcode = None |
---|
137 | |
---|
138 | main_identifiers = module_identifiers.branch(self.node) |
---|
139 | module_identifiers.topleveldefs = module_identifiers.topleveldefs.union(main_identifiers.topleveldefs) |
---|
140 | [module_identifiers.declared.add(x) for x in ["UNDEFINED"]] |
---|
141 | if impcode: |
---|
142 | [module_identifiers.declared.add(x) for x in impcode.declared_identifiers] |
---|
143 | |
---|
144 | self.compiler.identifiers = module_identifiers |
---|
145 | self.printer.writeline("_exports = %s" % repr([n.name for n in main_identifiers.topleveldefs.values()])) |
---|
146 | self.printer.write("\n\n") |
---|
147 | |
---|
148 | if len(module_code): |
---|
149 | self.write_module_code(module_code) |
---|
150 | |
---|
151 | if len(inherit): |
---|
152 | self.write_namespaces(namespaces) |
---|
153 | self.write_inherit(inherit[-1]) |
---|
154 | elif len(namespaces): |
---|
155 | self.write_namespaces(namespaces) |
---|
156 | |
---|
157 | return main_identifiers.topleveldefs.values() |
---|
158 | |
---|
159 | def write_render_callable(self, node, name, args, buffered, filtered, cached): |
---|
160 | """write a top-level render callable. |
---|
161 | |
---|
162 | this could be the main render() method or that of a top-level def.""" |
---|
163 | |
---|
164 | if self.in_def: |
---|
165 | decorator = node.decorator |
---|
166 | if decorator: |
---|
167 | self.printer.writeline("@runtime._decorate_toplevel(%s)" % decorator) |
---|
168 | |
---|
169 | self.printer.writelines( |
---|
170 | "def %s(%s):" % (name, ','.join(args)), |
---|
171 | "context.caller_stack._push_frame()", |
---|
172 | "try:" |
---|
173 | ) |
---|
174 | if buffered or filtered or cached: |
---|
175 | self.printer.writeline("context._push_buffer()") |
---|
176 | |
---|
177 | self.identifier_stack.append(self.compiler.identifiers.branch(self.node)) |
---|
178 | if not self.in_def and '**pageargs' in args: |
---|
179 | self.identifier_stack[-1].argument_declared.add('pageargs') |
---|
180 | |
---|
181 | if not self.in_def and (len(self.identifiers.locally_assigned) > 0 or len(self.identifiers.argument_declared)>0): |
---|
182 | self.printer.writeline("__M_locals = __M_dict_builtin(%s)" % ','.join(["%s=%s" % (x, x) for x in self.identifiers.argument_declared])) |
---|
183 | |
---|
184 | self.write_variable_declares(self.identifiers, toplevel=True) |
---|
185 | |
---|
186 | for n in self.node.nodes: |
---|
187 | n.accept_visitor(self) |
---|
188 | |
---|
189 | self.write_def_finish(self.node, buffered, filtered, cached) |
---|
190 | self.printer.writeline(None) |
---|
191 | self.printer.write("\n\n") |
---|
192 | if cached: |
---|
193 | self.write_cache_decorator(node, name, args, buffered, self.identifiers, toplevel=True) |
---|
194 | |
---|
195 | def write_module_code(self, module_code): |
---|
196 | """write module-level template code, i.e. that which is enclosed in <%! %> tags |
---|
197 | in the template.""" |
---|
198 | for n in module_code: |
---|
199 | self.write_source_comment(n) |
---|
200 | self.printer.write_indented_block(n.text) |
---|
201 | |
---|
202 | def write_inherit(self, node): |
---|
203 | """write the module-level inheritance-determination callable.""" |
---|
204 | self.printer.writelines( |
---|
205 | "def _mako_inherit(template, context):", |
---|
206 | "_mako_generate_namespaces(context)", |
---|
207 | "return runtime._inherit_from(context, %s, _template_uri)" % (node.parsed_attributes['file']), |
---|
208 | None |
---|
209 | ) |
---|
210 | |
---|
211 | def write_namespaces(self, namespaces): |
---|
212 | """write the module-level namespace-generating callable.""" |
---|
213 | self.printer.writelines( |
---|
214 | "def _mako_get_namespace(context, name):", |
---|
215 | "try:", |
---|
216 | "return context.namespaces[(__name__, name)]", |
---|
217 | "except KeyError:", |
---|
218 | "_mako_generate_namespaces(context)", |
---|
219 | "return context.namespaces[(__name__, name)]", |
---|
220 | None,None |
---|
221 | ) |
---|
222 | self.printer.writeline("def _mako_generate_namespaces(context):") |
---|
223 | for node in namespaces.values(): |
---|
224 | if node.attributes.has_key('import'): |
---|
225 | self.compiler.has_ns_imports = True |
---|
226 | self.write_source_comment(node) |
---|
227 | if len(node.nodes): |
---|
228 | self.printer.writeline("def make_namespace():") |
---|
229 | export = [] |
---|
230 | identifiers = self.compiler.identifiers.branch(node) |
---|
231 | class NSDefVisitor(object): |
---|
232 | def visitDefTag(s, node): |
---|
233 | self.write_inline_def(node, identifiers, nested=False) |
---|
234 | export.append(node.name) |
---|
235 | vis = NSDefVisitor() |
---|
236 | for n in node.nodes: |
---|
237 | n.accept_visitor(vis) |
---|
238 | self.printer.writeline("return [%s]" % (','.join(export))) |
---|
239 | self.printer.writeline(None) |
---|
240 | callable_name = "make_namespace()" |
---|
241 | else: |
---|
242 | callable_name = "None" |
---|
243 | 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'))) |
---|
244 | if eval(node.attributes.get('inheritable', "False")): |
---|
245 | self.printer.writeline("context['self'].%s = ns" % (node.name)) |
---|
246 | self.printer.writeline("context.namespaces[(__name__, %s)] = ns" % repr(node.name)) |
---|
247 | self.printer.write("\n") |
---|
248 | if not len(namespaces): |
---|
249 | self.printer.writeline("pass") |
---|
250 | self.printer.writeline(None) |
---|
251 | |
---|
252 | def write_variable_declares(self, identifiers, toplevel=False, limit=None): |
---|
253 | """write variable declarations at the top of a function. |
---|
254 | |
---|
255 | the variable declarations are in the form of callable definitions for defs and/or |
---|
256 | name lookup within the function's context argument. the names declared are based on the |
---|
257 | names that are referenced in the function body, which don't otherwise have any explicit |
---|
258 | assignment operation. names that are assigned within the body are assumed to be |
---|
259 | locally-scoped variables and are not separately declared. |
---|
260 | |
---|
261 | for def callable definitions, if the def is a top-level callable then a |
---|
262 | 'stub' callable is generated which wraps the current Context into a closure. if the def |
---|
263 | is not top-level, it is fully rendered as a local closure.""" |
---|
264 | |
---|
265 | # collection of all defs available to us in this scope |
---|
266 | comp_idents = dict([(c.name, c) for c in identifiers.defs]) |
---|
267 | to_write = util.Set() |
---|
268 | |
---|
269 | # write "context.get()" for all variables we are going to need that arent in the namespace yet |
---|
270 | to_write = to_write.union(identifiers.undeclared) |
---|
271 | |
---|
272 | # write closure functions for closures that we define right here |
---|
273 | to_write = to_write.union(util.Set([c.name for c in identifiers.closuredefs.values()])) |
---|
274 | |
---|
275 | # remove identifiers that are declared in the argument signature of the callable |
---|
276 | to_write = to_write.difference(identifiers.argument_declared) |
---|
277 | |
---|
278 | # remove identifiers that we are going to assign to. in this way we mimic Python's behavior, |
---|
279 | # i.e. assignment to a variable within a block means that variable is now a "locally declared" var, |
---|
280 | # which cannot be referenced beforehand. |
---|
281 | to_write = to_write.difference(identifiers.locally_declared) |
---|
282 | |
---|
283 | # if a limiting set was sent, constraint to those items in that list |
---|
284 | # (this is used for the caching decorator) |
---|
285 | if limit is not None: |
---|
286 | to_write = to_write.intersection(limit) |
---|
287 | |
---|
288 | if toplevel and getattr(self.compiler, 'has_ns_imports', False): |
---|
289 | self.printer.writeline("_import_ns = {}") |
---|
290 | self.compiler.has_imports = True |
---|
291 | for ident, ns in self.compiler.namespaces.iteritems(): |
---|
292 | if ns.attributes.has_key('import'): |
---|
293 | self.printer.writeline("_mako_get_namespace(context, %s)._populate(_import_ns, %s)" % (repr(ident), repr(re.split(r'\s*,\s*', ns.attributes['import'])))) |
---|
294 | |
---|
295 | for ident in to_write: |
---|
296 | if ident in comp_idents: |
---|
297 | comp = comp_idents[ident] |
---|
298 | if comp.is_root(): |
---|
299 | self.write_def_decl(comp, identifiers) |
---|
300 | else: |
---|
301 | self.write_inline_def(comp, identifiers, nested=True) |
---|
302 | elif ident in self.compiler.namespaces: |
---|
303 | self.printer.writeline("%s = _mako_get_namespace(context, %s)" % (ident, repr(ident))) |
---|
304 | else: |
---|
305 | if getattr(self.compiler, 'has_ns_imports', False): |
---|
306 | self.printer.writeline("%s = _import_ns.get(%s, context.get(%s, UNDEFINED))" % (ident, repr(ident), repr(ident))) |
---|
307 | else: |
---|
308 | self.printer.writeline("%s = context.get(%s, UNDEFINED)" % (ident, repr(ident))) |
---|
309 | |
---|
310 | self.printer.writeline("__M_writer = context.writer()") |
---|
311 | |
---|
312 | def write_source_comment(self, node): |
---|
313 | """write a source comment containing the line number of the corresponding template line.""" |
---|
314 | if self.last_source_line != node.lineno: |
---|
315 | self.printer.writeline("# SOURCE LINE %d" % node.lineno) |
---|
316 | self.last_source_line = node.lineno |
---|
317 | |
---|
318 | def write_def_decl(self, node, identifiers): |
---|
319 | """write a locally-available callable referencing a top-level def""" |
---|
320 | funcname = node.function_decl.funcname |
---|
321 | namedecls = node.function_decl.get_argument_expressions() |
---|
322 | nameargs = node.function_decl.get_argument_expressions(include_defaults=False) |
---|
323 | if not self.in_def and (len(self.identifiers.locally_assigned) > 0 or len(self.identifiers.argument_declared) > 0): |
---|
324 | nameargs.insert(0, 'context.locals_(__M_locals)') |
---|
325 | else: |
---|
326 | nameargs.insert(0, 'context') |
---|
327 | self.printer.writeline("def %s(%s):" % (funcname, ",".join(namedecls))) |
---|
328 | self.printer.writeline("return render_%s(%s)" % (funcname, ",".join(nameargs))) |
---|
329 | self.printer.writeline(None) |
---|
330 | |
---|
331 | def write_inline_def(self, node, identifiers, nested): |
---|
332 | """write a locally-available def callable inside an enclosing def.""" |
---|
333 | namedecls = node.function_decl.get_argument_expressions() |
---|
334 | |
---|
335 | decorator = node.decorator |
---|
336 | if decorator: |
---|
337 | self.printer.writeline("@runtime._decorate_inline(context, %s)" % decorator) |
---|
338 | self.printer.writeline("def %s(%s):" % (node.name, ",".join(namedecls))) |
---|
339 | filtered = len(node.filter_args.args) > 0 |
---|
340 | buffered = eval(node.attributes.get('buffered', 'False')) |
---|
341 | cached = eval(node.attributes.get('cached', 'False')) |
---|
342 | self.printer.writelines( |
---|
343 | "context.caller_stack._push_frame()", |
---|
344 | "try:" |
---|
345 | ) |
---|
346 | if buffered or filtered or cached: |
---|
347 | self.printer.writelines( |
---|
348 | "context._push_buffer()", |
---|
349 | ) |
---|
350 | |
---|
351 | identifiers = identifiers.branch(node, nested=nested) |
---|
352 | |
---|
353 | self.write_variable_declares(identifiers) |
---|
354 | |
---|
355 | self.identifier_stack.append(identifiers) |
---|
356 | for n in node.nodes: |
---|
357 | n.accept_visitor(self) |
---|
358 | self.identifier_stack.pop() |
---|
359 | |
---|
360 | self.write_def_finish(node, buffered, filtered, cached) |
---|
361 | self.printer.writeline(None) |
---|
362 | if cached: |
---|
363 | self.write_cache_decorator(node, node.name, namedecls, False, identifiers, inline=True, toplevel=False) |
---|
364 | |
---|
365 | def write_def_finish(self, node, buffered, filtered, cached, callstack=True): |
---|
366 | """write the end section of a rendering function, either outermost or inline. |
---|
367 | |
---|
368 | this takes into account if the rendering function was filtered, buffered, etc. |
---|
369 | and closes the corresponding try: block if any, and writes code to retrieve captured content, |
---|
370 | apply filters, send proper return value.""" |
---|
371 | if not buffered and not cached and not filtered: |
---|
372 | self.printer.writeline("return ''") |
---|
373 | if callstack: |
---|
374 | self.printer.writelines( |
---|
375 | "finally:", |
---|
376 | "context.caller_stack._pop_frame()", |
---|
377 | None |
---|
378 | ) |
---|
379 | |
---|
380 | if buffered or filtered or cached: |
---|
381 | if buffered or cached: |
---|
382 | # in a caching scenario, don't try to get a writer |
---|
383 | # from the context after popping; assume the caching |
---|
384 | # implemenation might be using a context with no |
---|
385 | # extra buffers |
---|
386 | self.printer.writelines( |
---|
387 | "finally:", |
---|
388 | "__M_buf = context._pop_buffer()" |
---|
389 | ) |
---|
390 | else: |
---|
391 | self.printer.writelines( |
---|
392 | "finally:", |
---|
393 | "__M_buf, __M_writer = context._pop_buffer_and_writer()" |
---|
394 | ) |
---|
395 | |
---|
396 | if callstack: |
---|
397 | self.printer.writeline("context.caller_stack._pop_frame()") |
---|
398 | |
---|
399 | s = "__M_buf.getvalue()" |
---|
400 | if filtered: |
---|
401 | s = self.create_filter_callable(node.filter_args.args, s, False) |
---|
402 | self.printer.writeline(None) |
---|
403 | if buffered and not cached: |
---|
404 | s = self.create_filter_callable(self.compiler.buffer_filters, s, False) |
---|
405 | if buffered or cached: |
---|
406 | self.printer.writeline("return %s" % s) |
---|
407 | else: |
---|
408 | self.printer.writelines( |
---|
409 | "__M_writer(%s)" % s, |
---|
410 | "return ''" |
---|
411 | ) |
---|
412 | |
---|
413 | def write_cache_decorator(self, node_or_pagetag, name, args, buffered, identifiers, inline=False, toplevel=False): |
---|
414 | """write a post-function decorator to replace a rendering callable with a cached version of itself.""" |
---|
415 | self.printer.writeline("__M_%s = %s" % (name, name)) |
---|
416 | cachekey = node_or_pagetag.parsed_attributes.get('cache_key', repr(name)) |
---|
417 | cacheargs = {} |
---|
418 | for arg in (('cache_type', 'type'), ('cache_dir', 'data_dir'), ('cache_timeout', 'expiretime'), ('cache_url', 'url')): |
---|
419 | val = node_or_pagetag.parsed_attributes.get(arg[0], None) |
---|
420 | if val is not None: |
---|
421 | if arg[1] == 'expiretime': |
---|
422 | cacheargs[arg[1]] = int(eval(val)) |
---|
423 | else: |
---|
424 | cacheargs[arg[1]] = val |
---|
425 | else: |
---|
426 | if self.compiler.pagetag is not None: |
---|
427 | val = self.compiler.pagetag.parsed_attributes.get(arg[0], None) |
---|
428 | if val is not None: |
---|
429 | if arg[1] == 'expiretime': |
---|
430 | cacheargs[arg[1]] == int(eval(val)) |
---|
431 | else: |
---|
432 | cacheargs[arg[1]] = val |
---|
433 | |
---|
434 | self.printer.writeline("def %s(%s):" % (name, ','.join(args))) |
---|
435 | |
---|
436 | # form "arg1, arg2, arg3=arg3, arg4=arg4", etc. |
---|
437 | pass_args = [ '=' in a and "%s=%s" % ((a.split('=')[0],)*2) or a for a in args] |
---|
438 | |
---|
439 | self.write_variable_declares(identifiers, toplevel=toplevel, limit=node_or_pagetag.undeclared_identifiers()) |
---|
440 | if buffered: |
---|
441 | s = "context.get('local').get_cached(%s, defname=%r, %screatefunc=lambda:__M_%s(%s))" % (cachekey, name, ''.join(["%s=%s, " % (k,v) for k, v in cacheargs.iteritems()]), name, ','.join(pass_args)) |
---|
442 | # apply buffer_filters |
---|
443 | s = self.create_filter_callable(self.compiler.buffer_filters, s, False) |
---|
444 | self.printer.writelines("return " + s,None) |
---|
445 | else: |
---|
446 | self.printer.writelines( |
---|
447 | "__M_writer(context.get('local').get_cached(%s, defname=%r, %screatefunc=lambda:__M_%s(%s)))" % (cachekey, name, ''.join(["%s=%s, " % (k,v) for k, v in cacheargs.iteritems()]), name, ','.join(pass_args)), |
---|
448 | "return ''", |
---|
449 | None |
---|
450 | ) |
---|
451 | |
---|
452 | def create_filter_callable(self, args, target, is_expression): |
---|
453 | """write a filter-applying expression based on the filters present in the given |
---|
454 | filter names, adjusting for the global 'default' filter aliases as needed.""" |
---|
455 | def locate_encode(name): |
---|
456 | if re.match(r'decode\..+', name): |
---|
457 | return "filters." + name |
---|
458 | else: |
---|
459 | return filters.DEFAULT_ESCAPES.get(name, name) |
---|
460 | |
---|
461 | if 'n' not in args: |
---|
462 | if is_expression: |
---|
463 | if self.compiler.pagetag: |
---|
464 | args = self.compiler.pagetag.filter_args.args + args |
---|
465 | if self.compiler.default_filters: |
---|
466 | args = self.compiler.default_filters + args |
---|
467 | for e in args: |
---|
468 | # if filter given as a function, get just the identifier portion |
---|
469 | if e == 'n': |
---|
470 | continue |
---|
471 | m = re.match(r'(.+?)(\(.*\))', e) |
---|
472 | if m: |
---|
473 | (ident, fargs) = m.group(1,2) |
---|
474 | f = locate_encode(ident) |
---|
475 | e = f + fargs |
---|
476 | else: |
---|
477 | x = e |
---|
478 | e = locate_encode(e) |
---|
479 | assert e is not None |
---|
480 | target = "%s(%s)" % (e, target) |
---|
481 | return target |
---|
482 | |
---|
483 | def visitExpression(self, node): |
---|
484 | self.write_source_comment(node) |
---|
485 | 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): |
---|
486 | s = self.create_filter_callable(node.escapes_code.args, "%s" % node.text, True) |
---|
487 | self.printer.writeline("__M_writer(%s)" % s) |
---|
488 | else: |
---|
489 | self.printer.writeline("__M_writer(%s)" % node.text) |
---|
490 | |
---|
491 | def visitControlLine(self, node): |
---|
492 | if node.isend: |
---|
493 | self.printer.writeline(None) |
---|
494 | else: |
---|
495 | self.write_source_comment(node) |
---|
496 | self.printer.writeline(node.text) |
---|
497 | def visitText(self, node): |
---|
498 | self.write_source_comment(node) |
---|
499 | self.printer.writeline("__M_writer(%s)" % repr(node.content)) |
---|
500 | def visitTextTag(self, node): |
---|
501 | filtered = len(node.filter_args.args) > 0 |
---|
502 | if filtered: |
---|
503 | self.printer.writelines( |
---|
504 | "__M_writer = context._push_writer()", |
---|
505 | "try:", |
---|
506 | ) |
---|
507 | for n in node.nodes: |
---|
508 | n.accept_visitor(self) |
---|
509 | if filtered: |
---|
510 | self.printer.writelines( |
---|
511 | "finally:", |
---|
512 | "__M_buf, __M_writer = context._pop_buffer_and_writer()", |
---|
513 | "__M_writer(%s)" % self.create_filter_callable(node.filter_args.args, "__M_buf.getvalue()", False), |
---|
514 | None |
---|
515 | ) |
---|
516 | |
---|
517 | def visitCode(self, node): |
---|
518 | if not node.ismodule: |
---|
519 | self.write_source_comment(node) |
---|
520 | self.printer.write_indented_block(node.text) |
---|
521 | |
---|
522 | if not self.in_def and len(self.identifiers.locally_assigned) > 0: |
---|
523 | # if we are the "template" def, fudge locally declared/modified variables into the "__M_locals" dictionary, |
---|
524 | # which is used for def calls within the same template, to simulate "enclosing scope" |
---|
525 | self.printer.writeline('__M_locals.update(__M_dict_builtin([(__M_key, __M_locals_builtin()[__M_key]) for __M_key in [%s] if __M_key in __M_locals_builtin()]))' % ','.join([repr(x) for x in node.declared_identifiers()])) |
---|
526 | |
---|
527 | def visitIncludeTag(self, node): |
---|
528 | self.write_source_comment(node) |
---|
529 | args = node.attributes.get('args') |
---|
530 | if args: |
---|
531 | self.printer.writeline("runtime._include_file(context, %s, _template_uri, %s)" % (node.parsed_attributes['file'], args)) |
---|
532 | else: |
---|
533 | self.printer.writeline("runtime._include_file(context, %s, _template_uri)" % (node.parsed_attributes['file'])) |
---|
534 | |
---|
535 | def visitNamespaceTag(self, node): |
---|
536 | pass |
---|
537 | |
---|
538 | def visitDefTag(self, node): |
---|
539 | pass |
---|
540 | |
---|
541 | def visitCallNamespaceTag(self, node): |
---|
542 | # TODO: we can put namespace-specific checks here, such |
---|
543 | # as ensure the given namespace will be imported, |
---|
544 | # pre-import the namespace, etc. |
---|
545 | self.visitCallTag(node) |
---|
546 | |
---|
547 | def visitCallTag(self, node): |
---|
548 | self.printer.writeline("def ccall(caller):") |
---|
549 | export = ['body'] |
---|
550 | callable_identifiers = self.identifiers.branch(node, nested=True) |
---|
551 | body_identifiers = callable_identifiers.branch(node, nested=False) |
---|
552 | # we want the 'caller' passed to ccall to be used for the body() function, |
---|
553 | # but for other non-body() <%def>s within <%call> we want the current caller off the call stack (if any) |
---|
554 | body_identifiers.add_declared('caller') |
---|
555 | |
---|
556 | self.identifier_stack.append(body_identifiers) |
---|
557 | class DefVisitor(object): |
---|
558 | def visitDefTag(s, node): |
---|
559 | self.write_inline_def(node, callable_identifiers, nested=False) |
---|
560 | export.append(node.name) |
---|
561 | # remove defs that are within the <%call> from the "closuredefs" defined |
---|
562 | # in the body, so they dont render twice |
---|
563 | if node.name in body_identifiers.closuredefs: |
---|
564 | del body_identifiers.closuredefs[node.name] |
---|
565 | |
---|
566 | vis = DefVisitor() |
---|
567 | for n in node.nodes: |
---|
568 | n.accept_visitor(vis) |
---|
569 | self.identifier_stack.pop() |
---|
570 | |
---|
571 | bodyargs = node.body_decl.get_argument_expressions() |
---|
572 | self.printer.writeline("def body(%s):" % ','.join(bodyargs)) |
---|
573 | # TODO: figure out best way to specify buffering/nonbuffering (at call time would be better) |
---|
574 | buffered = False |
---|
575 | if buffered: |
---|
576 | self.printer.writelines( |
---|
577 | "context._push_buffer()", |
---|
578 | "try:" |
---|
579 | ) |
---|
580 | self.write_variable_declares(body_identifiers) |
---|
581 | self.identifier_stack.append(body_identifiers) |
---|
582 | |
---|
583 | for n in node.nodes: |
---|
584 | n.accept_visitor(self) |
---|
585 | self.identifier_stack.pop() |
---|
586 | |
---|
587 | self.write_def_finish(node, buffered, False, False, callstack=False) |
---|
588 | self.printer.writelines( |
---|
589 | None, |
---|
590 | "return [%s]" % (','.join(export)), |
---|
591 | None |
---|
592 | ) |
---|
593 | |
---|
594 | self.printer.writelines( |
---|
595 | # get local reference to current caller, if any |
---|
596 | "caller = context.caller_stack._get_caller()", |
---|
597 | # push on caller for nested call |
---|
598 | "context.caller_stack.nextcaller = runtime.Namespace('caller', context, callables=ccall(caller))", |
---|
599 | "try:") |
---|
600 | self.write_source_comment(node) |
---|
601 | self.printer.writelines( |
---|
602 | "__M_writer(%s)" % self.create_filter_callable([], node.expression, True), |
---|
603 | "finally:", |
---|
604 | "context.caller_stack.nextcaller = None", |
---|
605 | None |
---|
606 | ) |
---|
607 | |
---|
608 | class _Identifiers(object): |
---|
609 | """tracks the status of identifier names as template code is rendered.""" |
---|
610 | def __init__(self, node=None, parent=None, nested=False): |
---|
611 | if parent is not None: |
---|
612 | # things that have already been declared in an enclosing namespace (i.e. names we can just use) |
---|
613 | self.declared = util.Set(parent.declared).union([c.name for c in parent.closuredefs.values()]).union(parent.locally_declared).union(parent.argument_declared) |
---|
614 | |
---|
615 | # if these identifiers correspond to a "nested" scope, it means whatever the |
---|
616 | # parent identifiers had as undeclared will have been declared by that parent, |
---|
617 | # and therefore we have them in our scope. |
---|
618 | if nested: |
---|
619 | self.declared = self.declared.union(parent.undeclared) |
---|
620 | |
---|
621 | # top level defs that are available |
---|
622 | self.topleveldefs = util.SetLikeDict(**parent.topleveldefs) |
---|
623 | else: |
---|
624 | self.declared = util.Set() |
---|
625 | self.topleveldefs = util.SetLikeDict() |
---|
626 | |
---|
627 | # things within this level that are referenced before they are declared (e.g. assigned to) |
---|
628 | self.undeclared = util.Set() |
---|
629 | |
---|
630 | # things that are declared locally. some of these things could be in the "undeclared" |
---|
631 | # list as well if they are referenced before declared |
---|
632 | self.locally_declared = util.Set() |
---|
633 | |
---|
634 | # assignments made in explicit python blocks. these will be propigated to |
---|
635 | # the context of local def calls. |
---|
636 | self.locally_assigned = util.Set() |
---|
637 | |
---|
638 | # things that are declared in the argument signature of the def callable |
---|
639 | self.argument_declared = util.Set() |
---|
640 | |
---|
641 | # closure defs that are defined in this level |
---|
642 | self.closuredefs = util.SetLikeDict() |
---|
643 | |
---|
644 | self.node = node |
---|
645 | |
---|
646 | if node is not None: |
---|
647 | node.accept_visitor(self) |
---|
648 | |
---|
649 | def branch(self, node, **kwargs): |
---|
650 | """create a new Identifiers for a new Node, with this Identifiers as the parent.""" |
---|
651 | return _Identifiers(node, self, **kwargs) |
---|
652 | |
---|
653 | defs = property(lambda self:util.Set(self.topleveldefs.union(self.closuredefs).values())) |
---|
654 | |
---|
655 | def __repr__(self): |
---|
656 | 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)) |
---|
657 | |
---|
658 | def check_declared(self, node): |
---|
659 | """update the state of this Identifiers with the undeclared and declared identifiers of the given node.""" |
---|
660 | for ident in node.undeclared_identifiers(): |
---|
661 | if ident != 'context' and ident not in self.declared.union(self.locally_declared): |
---|
662 | self.undeclared.add(ident) |
---|
663 | for ident in node.declared_identifiers(): |
---|
664 | self.locally_declared.add(ident) |
---|
665 | |
---|
666 | def add_declared(self, ident): |
---|
667 | self.declared.add(ident) |
---|
668 | if ident in self.undeclared: |
---|
669 | self.undeclared.remove(ident) |
---|
670 | |
---|
671 | def visitExpression(self, node): |
---|
672 | self.check_declared(node) |
---|
673 | def visitControlLine(self, node): |
---|
674 | self.check_declared(node) |
---|
675 | def visitCode(self, node): |
---|
676 | if not node.ismodule: |
---|
677 | self.check_declared(node) |
---|
678 | self.locally_assigned = self.locally_assigned.union(node.declared_identifiers()) |
---|
679 | def visitDefTag(self, node): |
---|
680 | if node.is_root(): |
---|
681 | self.topleveldefs[node.name] = node |
---|
682 | elif node is not self.node: |
---|
683 | self.closuredefs[node.name] = node |
---|
684 | for ident in node.undeclared_identifiers(): |
---|
685 | if ident != 'context' and ident not in self.declared.union(self.locally_declared): |
---|
686 | self.undeclared.add(ident) |
---|
687 | # visit defs only one level deep |
---|
688 | if node is self.node: |
---|
689 | for ident in node.declared_identifiers(): |
---|
690 | self.argument_declared.add(ident) |
---|
691 | for n in node.nodes: |
---|
692 | n.accept_visitor(self) |
---|
693 | def visitIncludeTag(self, node): |
---|
694 | self.check_declared(node) |
---|
695 | def visitPageTag(self, node): |
---|
696 | for ident in node.declared_identifiers(): |
---|
697 | self.argument_declared.add(ident) |
---|
698 | self.check_declared(node) |
---|
699 | |
---|
700 | def visitCallNamespaceTag(self, node): |
---|
701 | self.visitCallTag(node) |
---|
702 | |
---|
703 | def visitCallTag(self, node): |
---|
704 | if node is self.node: |
---|
705 | for ident in node.undeclared_identifiers(): |
---|
706 | if ident != 'context' and ident not in self.declared.union(self.locally_declared): |
---|
707 | self.undeclared.add(ident) |
---|
708 | for ident in node.declared_identifiers(): |
---|
709 | self.argument_declared.add(ident) |
---|
710 | for n in node.nodes: |
---|
711 | n.accept_visitor(self) |
---|
712 | else: |
---|
713 | for ident in node.undeclared_identifiers(): |
---|
714 | if ident != 'context' and ident not in self.declared.union(self.locally_declared): |
---|
715 | self.undeclared.add(ident) |
---|
716 | |
---|