Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/mako/codegen.py: 12%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1# mako/codegen.py
2# Copyright 2006-2026 the Mako authors and contributors <see AUTHORS file>
3#
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
8source code."""
10import json
11import re
12import time
14from mako import ast
15from mako import exceptions
16from mako import filters
17from mako import parsetree
18from mako import util
19from mako.pygen import PythonPrinter
21MAGIC_NUMBER = 10
23# names which are hardwired into the
24# template and are not accessed via the
25# context itself
26TOPLEVEL_DECLARED = {"UNDEFINED", "STOP_RENDERING"}
27RESERVED_NAMES = {"context", "loop"}.union(TOPLEVEL_DECLARED)
30def compile( # noqa
31 node,
32 uri,
33 filename=None,
34 default_filters=None,
35 buffer_filters=None,
36 imports=None,
37 future_imports=None,
38 source_encoding=None,
39 generate_magic_comment=True,
40 strict_undefined=False,
41 enable_loop=True,
42 reserved_names=frozenset(),
43):
44 """Generate module source code given a parsetree node,
45 uri, and optional source filename"""
47 buf = util.FastEncodingBuffer()
49 printer = PythonPrinter(buf)
50 _GenerateRenderMethod(
51 printer,
52 _CompileContext(
53 uri,
54 filename,
55 default_filters,
56 buffer_filters,
57 imports,
58 future_imports,
59 source_encoding,
60 generate_magic_comment,
61 strict_undefined,
62 enable_loop,
63 reserved_names,
64 ),
65 node,
66 )
67 return buf.getvalue()
70class _CompileContext:
71 def __init__(
72 self,
73 uri,
74 filename,
75 default_filters,
76 buffer_filters,
77 imports,
78 future_imports,
79 source_encoding,
80 generate_magic_comment,
81 strict_undefined,
82 enable_loop,
83 reserved_names,
84 ):
85 self.uri = uri
86 self.filename = filename
87 self.default_filters = default_filters
88 self.buffer_filters = buffer_filters
89 self.imports = imports
90 self.future_imports = future_imports
91 self.source_encoding = source_encoding
92 self.generate_magic_comment = generate_magic_comment
93 self.strict_undefined = strict_undefined
94 self.enable_loop = enable_loop
95 self.reserved_names = reserved_names
98class _GenerateRenderMethod:
99 """A template visitor object which generates the
100 full module source for a template.
102 """
104 def __init__(self, printer, compiler, node):
105 self.printer = printer
106 self.compiler = compiler
107 self.node = node
108 self.identifier_stack = [None]
109 self.in_def = isinstance(node, (parsetree.DefTag, parsetree.BlockTag))
111 if self.in_def:
112 name = "render_%s" % node.funcname
113 args = node.get_argument_expressions()
114 filtered = len(node.filter_args.args) > 0
115 buffered = eval(node.attributes.get("buffered", "False"))
116 cached = eval(node.attributes.get("cached", "False"))
117 defs = None
118 pagetag = None
119 if node.is_block and not node.is_anonymous:
120 args += ["**pageargs"]
121 else:
122 defs = self.write_toplevel()
123 pagetag = self.compiler.pagetag
124 name = "render_body"
125 if pagetag is not None:
126 args = pagetag.body_decl.get_argument_expressions()
127 if not pagetag.body_decl.kwargs:
128 args += ["**pageargs"]
129 cached = eval(pagetag.attributes.get("cached", "False"))
130 self.compiler.enable_loop = self.compiler.enable_loop or eval(
131 pagetag.attributes.get("enable_loop", "False")
132 )
133 else:
134 args = ["**pageargs"]
135 cached = False
136 buffered = filtered = False
137 if args is None:
138 args = ["context"]
139 else:
140 args = [a for a in ["context"] + args]
142 self.write_render_callable(
143 pagetag or node, name, args, buffered, filtered, cached
144 )
146 if defs is not None:
147 for node in defs:
148 _GenerateRenderMethod(printer, compiler, node)
150 if not self.in_def:
151 self.write_metadata_struct()
153 def write_metadata_struct(self):
154 self.printer.source_map[self.printer.lineno] = max(
155 self.printer.source_map
156 )
157 struct = {
158 "filename": self.compiler.filename,
159 "uri": self.compiler.uri,
160 "source_encoding": self.compiler.source_encoding,
161 "line_map": self.printer.source_map,
162 }
163 self.printer.writelines(
164 '"""',
165 "__M_BEGIN_METADATA",
166 json.dumps(struct),
167 "__M_END_METADATA\n" '"""',
168 )
170 @property
171 def identifiers(self):
172 return self.identifier_stack[-1]
174 def write_toplevel(self):
175 """Traverse a template structure for module-level directives and
176 generate the start of module-level code.
178 """
179 inherit = []
180 namespaces = {}
181 module_code = []
183 self.compiler.pagetag = None
185 class FindTopLevel:
186 def visitInheritTag(s, node):
187 inherit.append(node)
189 def visitNamespaceTag(s, node):
190 namespaces[node.name] = node
192 def visitPageTag(s, node):
193 self.compiler.pagetag = node
195 def visitCode(s, node):
196 if node.ismodule:
197 module_code.append(node)
199 f = FindTopLevel()
200 for n in self.node.nodes:
201 n.accept_visitor(f)
203 self.compiler.namespaces = namespaces
205 module_ident = set()
206 for n in module_code:
207 module_ident = module_ident.union(n.declared_identifiers())
209 module_identifiers = _Identifiers(self.compiler)
210 module_identifiers.declared = module_ident
212 # module-level names, python code
213 if (
214 self.compiler.generate_magic_comment
215 and self.compiler.source_encoding
216 ):
217 self.printer.writeline(
218 "# -*- coding:%s -*-" % self.compiler.source_encoding
219 )
221 if self.compiler.future_imports:
222 self.printer.writeline(
223 "from __future__ import %s"
224 % (", ".join(self.compiler.future_imports),)
225 )
226 self.printer.writeline("from mako import runtime, filters, cache")
227 self.printer.writeline("UNDEFINED = runtime.UNDEFINED")
228 self.printer.writeline("STOP_RENDERING = runtime.STOP_RENDERING")
229 self.printer.writeline("__M_dict_builtin = dict")
230 self.printer.writeline("__M_locals_builtin = locals")
231 self.printer.writeline("_magic_number = %r" % MAGIC_NUMBER)
232 self.printer.writeline("_modified_time = %r" % time.time())
233 self.printer.writeline("_enable_loop = %r" % self.compiler.enable_loop)
234 self.printer.writeline(
235 "_template_filename = %r" % self.compiler.filename
236 )
237 self.printer.writeline("_template_uri = %r" % self.compiler.uri)
238 self.printer.writeline(
239 "_source_encoding = %r" % self.compiler.source_encoding
240 )
241 if self.compiler.imports:
242 buf = ""
243 for imp in self.compiler.imports:
244 buf += imp + "\n"
245 self.printer.writeline(imp)
246 impcode = ast.PythonCode(
247 buf,
248 source="",
249 lineno=0,
250 pos=0,
251 filename="template defined imports",
252 )
253 else:
254 impcode = None
256 main_identifiers = module_identifiers.branch(self.node)
257 mit = module_identifiers.topleveldefs
258 module_identifiers.topleveldefs = mit.union(
259 main_identifiers.topleveldefs
260 )
261 module_identifiers.declared.update(TOPLEVEL_DECLARED)
262 if impcode:
263 module_identifiers.declared.update(impcode.declared_identifiers)
265 self.compiler.identifiers = module_identifiers
266 self.printer.writeline(
267 "_exports = %r"
268 % [n.name for n in main_identifiers.topleveldefs.values()]
269 )
270 self.printer.write_blanks(2)
272 if len(module_code):
273 self.write_module_code(module_code)
275 if len(inherit):
276 self.write_namespaces(namespaces)
277 self.write_inherit(inherit[-1])
278 elif len(namespaces):
279 self.write_namespaces(namespaces)
281 return list(main_identifiers.topleveldefs.values())
283 def write_render_callable(
284 self, node, name, args, buffered, filtered, cached
285 ):
286 """write a top-level render callable.
288 this could be the main render() method or that of a top-level def."""
290 if self.in_def:
291 decorator = node.decorator
292 if decorator:
293 self.printer.writeline(
294 "@runtime._decorate_toplevel(%s)" % decorator
295 )
297 self.printer.start_source(node.lineno)
298 self.printer.writelines(
299 "def %s(%s):" % (name, ",".join(args)),
300 # push new frame, assign current frame to __M_caller
301 "__M_caller = context.caller_stack._push_frame()",
302 "try:",
303 )
304 if buffered or filtered or cached:
305 self.printer.writeline("context._push_buffer()")
307 self.identifier_stack.append(
308 self.compiler.identifiers.branch(self.node)
309 )
310 if (not self.in_def or self.node.is_block) and "**pageargs" in args:
311 self.identifier_stack[-1].argument_declared.add("pageargs")
313 if not self.in_def and (
314 len(self.identifiers.locally_assigned) > 0
315 or len(self.identifiers.argument_declared) > 0
316 ):
317 self.printer.writeline(
318 "__M_locals = __M_dict_builtin(%s)"
319 % ",".join(
320 [
321 "%s=%s" % (x, x)
322 for x in self.identifiers.argument_declared
323 ]
324 )
325 )
327 self.write_variable_declares(self.identifiers, toplevel=True)
329 for n in self.node.nodes:
330 n.accept_visitor(self)
332 self.write_def_finish(self.node, buffered, filtered, cached)
333 self.printer.writeline(None)
334 self.printer.write_blanks(2)
335 if cached:
336 self.write_cache_decorator(
337 node, name, args, buffered, self.identifiers, toplevel=True
338 )
340 def write_module_code(self, module_code):
341 """write module-level template code, i.e. that which
342 is enclosed in <%! %> tags in the template."""
343 for n in module_code:
344 self.printer.write_indented_block(n.text, starting_lineno=n.lineno)
346 def write_inherit(self, node):
347 """write the module-level inheritance-determination callable."""
349 self.printer.writelines(
350 "def _mako_inherit(template, context):",
351 "_mako_generate_namespaces(context)",
352 "return runtime._inherit_from(context, %s, _template_uri)"
353 % (node.parsed_attributes["file"]),
354 None,
355 )
357 def write_namespaces(self, namespaces):
358 """write the module-level namespace-generating callable."""
359 self.printer.writelines(
360 "def _mako_get_namespace(context, name):",
361 "try:",
362 "return context.namespaces[(__name__, name)]",
363 "except KeyError:",
364 "_mako_generate_namespaces(context)",
365 "return context.namespaces[(__name__, name)]",
366 None,
367 None,
368 )
369 self.printer.writeline("def _mako_generate_namespaces(context):")
371 for node in namespaces.values():
372 if "import" in node.attributes:
373 self.compiler.has_ns_imports = True
374 self.printer.start_source(node.lineno)
375 if len(node.nodes):
376 self.printer.writeline("def make_namespace():")
377 export = []
378 identifiers = self.compiler.identifiers.branch(node)
379 self.in_def = True
381 class NSDefVisitor:
382 def visitDefTag(s, node):
383 s.visitDefOrBase(node)
385 def visitBlockTag(s, node):
386 s.visitDefOrBase(node)
388 def visitDefOrBase(s, node):
389 if node.is_anonymous:
390 raise exceptions.CompileException(
391 "Can't put anonymous blocks inside "
392 "<%namespace>",
393 **node.exception_kwargs,
394 )
395 self.write_inline_def(node, identifiers, nested=False)
396 export.append(node.funcname)
398 vis = NSDefVisitor()
399 for n in node.nodes:
400 n.accept_visitor(vis)
401 self.printer.writeline("return [%s]" % (",".join(export)))
402 self.printer.writeline(None)
403 self.in_def = False
404 callable_name = "make_namespace()"
405 else:
406 callable_name = "None"
408 if "file" in node.parsed_attributes:
409 self.printer.writeline(
410 "ns = runtime.TemplateNamespace(%r,"
411 " context._clean_inheritance_tokens(),"
412 " templateuri=%s, callables=%s, "
413 " calling_uri=_template_uri)"
414 % (
415 node.name,
416 node.parsed_attributes.get("file", "None"),
417 callable_name,
418 )
419 )
420 elif "module" in node.parsed_attributes:
421 self.printer.writeline(
422 "ns = runtime.ModuleNamespace(%r,"
423 " context._clean_inheritance_tokens(),"
424 " callables=%s, calling_uri=_template_uri,"
425 " module=%s)"
426 % (
427 node.name,
428 callable_name,
429 node.parsed_attributes.get("module", "None"),
430 )
431 )
432 else:
433 self.printer.writeline(
434 "ns = runtime.Namespace(%r,"
435 " context._clean_inheritance_tokens(),"
436 " callables=%s, calling_uri=_template_uri)"
437 % (node.name, callable_name)
438 )
439 if eval(node.attributes.get("inheritable", "False")):
440 self.printer.writeline("context['self'].%s = ns" % (node.name))
442 self.printer.writeline(
443 "context.namespaces[(__name__, %s)] = ns" % repr(node.name)
444 )
445 self.printer.write_blanks(1)
446 if not len(namespaces):
447 self.printer.writeline("pass")
448 self.printer.writeline(None)
450 def write_variable_declares(self, identifiers, toplevel=False, limit=None):
451 """write variable declarations at the top of a function.
453 the variable declarations are in the form of callable
454 definitions for defs and/or name lookup within the
455 function's context argument. the names declared are based
456 on the names that are referenced in the function body,
457 which don't otherwise have any explicit assignment
458 operation. names that are assigned within the body are
459 assumed to be locally-scoped variables and are not
460 separately declared.
462 for def callable definitions, if the def is a top-level
463 callable then a 'stub' callable is generated which wraps
464 the current Context into a closure. if the def is not
465 top-level, it is fully rendered as a local closure.
467 """
469 # collection of all defs available to us in this scope
470 comp_idents = {c.funcname: c for c in identifiers.defs}
471 to_write = set()
473 # write "context.get()" for all variables we are going to
474 # need that arent in the namespace yet
475 to_write = to_write.union(identifiers.undeclared)
477 # write closure functions for closures that we define
478 # right here
479 to_write = to_write.union(
480 [c.funcname for c in identifiers.closuredefs.values()]
481 )
483 # remove identifiers that are declared in the argument
484 # signature of the callable
485 to_write = to_write.difference(identifiers.argument_declared)
487 # remove identifiers that we are going to assign to.
488 # in this way we mimic Python's behavior,
489 # i.e. assignment to a variable within a block
490 # means that variable is now a "locally declared" var,
491 # which cannot be referenced beforehand.
492 to_write = to_write.difference(identifiers.locally_declared)
494 if self.compiler.enable_loop:
495 has_loop = "loop" in to_write
496 to_write.discard("loop")
497 else:
498 has_loop = False
500 # if a limiting set was sent, constraint to those items in that list
501 # (this is used for the caching decorator)
502 if limit is not None:
503 to_write = to_write.intersection(limit)
505 if toplevel and getattr(self.compiler, "has_ns_imports", False):
506 self.printer.writeline("_import_ns = {}")
507 self.compiler.has_imports = True
508 for ident, ns in self.compiler.namespaces.items():
509 if "import" in ns.attributes:
510 self.printer.writeline(
511 "_mako_get_namespace(context, %r)."
512 "_populate(_import_ns, %r)"
513 % (
514 ident,
515 re.split(r"\s*,\s*", ns.attributes["import"]),
516 )
517 )
519 if has_loop:
520 self.printer.writeline("loop = __M_loop = runtime.LoopStack()")
522 for ident in to_write:
523 if ident in comp_idents:
524 comp = comp_idents[ident]
525 if comp.is_block:
526 if not comp.is_anonymous:
527 self.write_def_decl(comp, identifiers)
528 else:
529 self.write_inline_def(comp, identifiers, nested=True)
530 else:
531 if comp.is_root():
532 self.write_def_decl(comp, identifiers)
533 else:
534 self.write_inline_def(comp, identifiers, nested=True)
536 elif ident in self.compiler.namespaces:
537 self.printer.writeline(
538 "%s = _mako_get_namespace(context, %r)" % (ident, ident)
539 )
540 else:
541 if getattr(self.compiler, "has_ns_imports", False):
542 if self.compiler.strict_undefined:
543 self.printer.writelines(
544 "%s = _import_ns.get(%r, UNDEFINED)"
545 % (ident, ident),
546 "if %s is UNDEFINED:" % ident,
547 "try:",
548 "%s = context[%r]" % (ident, ident),
549 "except KeyError:",
550 "raise NameError(\"'%s' is not defined\")" % ident,
551 None,
552 None,
553 )
554 else:
555 self.printer.writeline(
556 "%s = _import_ns.get"
557 "(%r, context.get(%r, UNDEFINED))"
558 % (ident, ident, ident)
559 )
560 else:
561 if self.compiler.strict_undefined:
562 self.printer.writelines(
563 "try:",
564 "%s = context[%r]" % (ident, ident),
565 "except KeyError:",
566 "raise NameError(\"'%s' is not defined\")" % ident,
567 None,
568 )
569 else:
570 self.printer.writeline(
571 "%s = context.get(%r, UNDEFINED)" % (ident, ident)
572 )
574 self.printer.writeline("__M_writer = context.writer()")
576 def write_def_decl(self, node, identifiers):
577 """write a locally-available callable referencing a top-level def"""
578 funcname = node.funcname
579 namedecls = node.get_argument_expressions()
580 nameargs = node.get_argument_expressions(as_call=True)
582 if not self.in_def and (
583 len(self.identifiers.locally_assigned) > 0
584 or len(self.identifiers.argument_declared) > 0
585 ):
586 nameargs.insert(0, "context._locals(__M_locals)")
587 else:
588 nameargs.insert(0, "context")
589 self.printer.writeline("def %s(%s):" % (funcname, ",".join(namedecls)))
590 self.printer.writeline(
591 "return render_%s(%s)" % (funcname, ",".join(nameargs))
592 )
593 self.printer.writeline(None)
595 def write_inline_def(self, node, identifiers, nested):
596 """write a locally-available def callable inside an enclosing def."""
598 namedecls = node.get_argument_expressions()
600 decorator = node.decorator
601 if decorator:
602 self.printer.writeline(
603 "@runtime._decorate_inline(context, %s)" % decorator
604 )
605 self.printer.writeline(
606 "def %s(%s):" % (node.funcname, ",".join(namedecls))
607 )
608 filtered = len(node.filter_args.args) > 0
609 buffered = eval(node.attributes.get("buffered", "False"))
610 cached = eval(node.attributes.get("cached", "False"))
611 self.printer.writelines(
612 # push new frame, assign current frame to __M_caller
613 "__M_caller = context.caller_stack._push_frame()",
614 "try:",
615 )
616 if buffered or filtered or cached:
617 self.printer.writelines("context._push_buffer()")
619 identifiers = identifiers.branch(node, nested=nested)
621 self.write_variable_declares(identifiers)
623 self.identifier_stack.append(identifiers)
624 for n in node.nodes:
625 n.accept_visitor(self)
626 self.identifier_stack.pop()
628 self.write_def_finish(node, buffered, filtered, cached)
629 self.printer.writeline(None)
630 if cached:
631 self.write_cache_decorator(
632 node,
633 node.funcname,
634 namedecls,
635 False,
636 identifiers,
637 inline=True,
638 toplevel=False,
639 )
641 def write_def_finish(
642 self, node, buffered, filtered, cached, callstack=True
643 ):
644 """write the end section of a rendering function, either outermost or
645 inline.
647 this takes into account if the rendering function was filtered,
648 buffered, etc. and closes the corresponding try: block if any, and
649 writes code to retrieve captured content, apply filters, send proper
650 return value."""
652 if not buffered and not cached and not filtered:
653 self.printer.writeline("return ''")
654 if callstack:
655 self.printer.writelines(
656 "finally:", "context.caller_stack._pop_frame()", None
657 )
659 if buffered or filtered or cached:
660 if buffered or cached:
661 # in a caching scenario, don't try to get a writer
662 # from the context after popping; assume the caching
663 # implemenation might be using a context with no
664 # extra buffers
665 self.printer.writelines(
666 "finally:", "__M_buf = context._pop_buffer()"
667 )
668 else:
669 self.printer.writelines(
670 "finally:",
671 "__M_buf, __M_writer = context._pop_buffer_and_writer()",
672 )
674 if callstack:
675 self.printer.writeline("context.caller_stack._pop_frame()")
677 s = "__M_buf.getvalue()"
678 if filtered:
679 s = self.create_filter_callable(
680 node.filter_args.args, s, False
681 )
682 self.printer.writeline(None)
683 if buffered and not cached:
684 s = self.create_filter_callable(
685 self.compiler.buffer_filters, s, False
686 )
687 if buffered or cached:
688 self.printer.writeline("return %s" % s)
689 else:
690 self.printer.writelines("__M_writer(%s)" % s, "return ''")
692 def write_cache_decorator(
693 self,
694 node_or_pagetag,
695 name,
696 args,
697 buffered,
698 identifiers,
699 inline=False,
700 toplevel=False,
701 ):
702 """write a post-function decorator to replace a rendering
703 callable with a cached version of itself."""
705 self.printer.writeline("__M_%s = %s" % (name, name))
706 cachekey = node_or_pagetag.parsed_attributes.get(
707 "cache_key", repr(name)
708 )
710 cache_args = {}
711 if self.compiler.pagetag is not None:
712 cache_args.update(
713 (pa[6:], self.compiler.pagetag.parsed_attributes[pa])
714 for pa in self.compiler.pagetag.parsed_attributes
715 if pa.startswith("cache_") and pa != "cache_key"
716 )
717 cache_args.update(
718 (pa[6:], node_or_pagetag.parsed_attributes[pa])
719 for pa in node_or_pagetag.parsed_attributes
720 if pa.startswith("cache_") and pa != "cache_key"
721 )
722 if "timeout" in cache_args:
723 cache_args["timeout"] = int(eval(cache_args["timeout"]))
725 self.printer.writeline("def %s(%s):" % (name, ",".join(args)))
727 # form "arg1, arg2, arg3=arg3, arg4=arg4", etc.
728 pass_args = [
729 "%s=%s" % ((a.split("=")[0],) * 2) if "=" in a else a for a in args
730 ]
732 self.write_variable_declares(
733 identifiers,
734 toplevel=toplevel,
735 limit=node_or_pagetag.undeclared_identifiers(),
736 )
737 if buffered:
738 s = (
739 "context.get('local')."
740 "cache._ctx_get_or_create("
741 "%s, lambda:__M_%s(%s), context, %s__M_defname=%r)"
742 % (
743 cachekey,
744 name,
745 ",".join(pass_args),
746 "".join(
747 ["%s=%s, " % (k, v) for k, v in cache_args.items()]
748 ),
749 name,
750 )
751 )
752 # apply buffer_filters
753 s = self.create_filter_callable(
754 self.compiler.buffer_filters, s, False
755 )
756 self.printer.writelines("return " + s, None)
757 else:
758 self.printer.writelines(
759 "__M_writer(context.get('local')."
760 "cache._ctx_get_or_create("
761 "%s, lambda:__M_%s(%s), context, %s__M_defname=%r))"
762 % (
763 cachekey,
764 name,
765 ",".join(pass_args),
766 "".join(
767 ["%s=%s, " % (k, v) for k, v in cache_args.items()]
768 ),
769 name,
770 ),
771 "return ''",
772 None,
773 )
775 def create_filter_callable(self, args, target, is_expression):
776 """write a filter-applying expression based on the filters
777 present in the given filter names, adjusting for the global
778 'default' filter aliases as needed."""
780 def locate_encode(name):
781 if re.match(r"decode\..+", name):
782 return "filters." + name
783 else:
784 return filters.DEFAULT_ESCAPES.get(name, name)
786 if "n" not in args:
787 if is_expression:
788 if self.compiler.pagetag:
789 args = self.compiler.pagetag.filter_args.args + args
790 if self.compiler.default_filters and "n" not in args:
791 args = self.compiler.default_filters + args
792 for e in args:
793 # if filter given as a function, get just the identifier portion
794 if e == "n":
795 continue
796 m = re.match(r"(.+?)(\(.*\))", e)
797 if m:
798 ident, fargs = m.group(1, 2)
799 f = locate_encode(ident)
800 e = f + fargs
801 else:
802 e = locate_encode(e)
803 assert e is not None
804 target = "%s(%s)" % (e, target)
805 return target
807 def visitExpression(self, node):
808 self.printer.start_source(node.lineno)
809 if (
810 len(node.escapes)
811 or (
812 self.compiler.pagetag is not None
813 and len(self.compiler.pagetag.filter_args.args)
814 )
815 or len(self.compiler.default_filters)
816 ):
817 s = self.create_filter_callable(
818 node.escapes_code.args, "%s" % node.text, True
819 )
820 self.printer.writeline("__M_writer(%s)" % s)
821 else:
822 self.printer.writeline("__M_writer(%s)" % node.text)
824 def visitControlLine(self, node):
825 if node.isend:
826 self.printer.writeline(None)
827 if node.has_loop_context:
828 self.printer.writeline("finally:")
829 self.printer.writeline("loop = __M_loop._exit()")
830 self.printer.writeline(None)
831 else:
832 self.printer.start_source(node.lineno)
833 if self.compiler.enable_loop and node.keyword == "for":
834 text = mangle_mako_loop(node, self.printer)
835 else:
836 text = node.text
837 self.printer.writeline(text)
838 children = node.get_children()
840 # this covers the four situations where we want to insert a pass:
841 # 1) a ternary control line with no children,
842 # 2) a primary control line with nothing but its own ternary
843 # and end control lines, and
844 # 3) any control line with no content other than comments
845 # 4) the first control block with no content other than comments
846 def _search_for_control_line():
847 for c in children:
848 if isinstance(c, parsetree.Comment):
849 continue
850 elif isinstance(c, parsetree.ControlLine):
851 return True
852 return False
854 if (
855 not children
856 or all(
857 isinstance(c, (parsetree.Comment, parsetree.ControlLine))
858 for c in children
859 )
860 and all(
861 (node.is_ternary(c.keyword) or c.isend)
862 for c in children
863 if isinstance(c, parsetree.ControlLine)
864 )
865 or _search_for_control_line()
866 ):
867 self.printer.writeline("pass")
869 def visitText(self, node):
870 self.printer.start_source(node.lineno)
871 self.printer.writeline("__M_writer(%s)" % repr(node.content))
873 def visitTextTag(self, node):
874 filtered = len(node.filter_args.args) > 0
875 if filtered:
876 self.printer.writelines(
877 "__M_writer = context._push_writer()", "try:"
878 )
879 for n in node.nodes:
880 n.accept_visitor(self)
881 if filtered:
882 self.printer.writelines(
883 "finally:",
884 "__M_buf, __M_writer = context._pop_buffer_and_writer()",
885 "__M_writer(%s)"
886 % self.create_filter_callable(
887 node.filter_args.args, "__M_buf.getvalue()", False
888 ),
889 None,
890 )
892 def visitCode(self, node):
893 if not node.ismodule:
894 self.printer.write_indented_block(
895 node.text, starting_lineno=node.lineno
896 )
898 if not self.in_def and len(self.identifiers.locally_assigned) > 0:
899 # if we are the "template" def, fudge locally
900 # declared/modified variables into the "__M_locals" dictionary,
901 # which is used for def calls within the same template,
902 # to simulate "enclosing scope"
903 self.printer.writeline(
904 "__M_locals_builtin_stored = __M_locals_builtin()"
905 )
906 self.printer.writeline(
907 "__M_locals.update(__M_dict_builtin([(__M_key,"
908 " __M_locals_builtin_stored[__M_key]) for __M_key in"
909 " [%s] if __M_key in __M_locals_builtin_stored]))"
910 % ",".join([repr(x) for x in node.declared_identifiers()])
911 )
913 def visitIncludeTag(self, node):
914 self.printer.start_source(node.lineno)
915 args = node.attributes.get("args")
916 if args:
917 self.printer.writeline(
918 "runtime._include_file(context, %s, _template_uri, %s)"
919 % (node.parsed_attributes["file"], args)
920 )
921 else:
922 self.printer.writeline(
923 "runtime._include_file(context, %s, _template_uri)"
924 % (node.parsed_attributes["file"])
925 )
927 def visitNamespaceTag(self, node):
928 pass
930 def visitDefTag(self, node):
931 pass
933 def visitBlockTag(self, node):
934 if node.is_anonymous:
935 self.printer.writeline("%s()" % node.funcname)
936 else:
937 nameargs = node.get_argument_expressions(as_call=True)
938 nameargs += ["**pageargs"]
939 self.printer.writeline(
940 "if 'parent' not in context._data or "
941 "not hasattr(context._data['parent'], '%s'):" % node.funcname
942 )
943 self.printer.writeline(
944 "context['self'].%s(%s)" % (node.funcname, ",".join(nameargs))
945 )
946 self.printer.writeline("\n")
948 def visitCallNamespaceTag(self, node):
949 # TODO: we can put namespace-specific checks here, such
950 # as ensure the given namespace will be imported,
951 # pre-import the namespace, etc.
952 self.visitCallTag(node)
954 def visitCallTag(self, node):
955 self.printer.writeline("def ccall(caller):")
956 export = ["body"]
957 callable_identifiers = self.identifiers.branch(node, nested=True)
958 body_identifiers = callable_identifiers.branch(node, nested=False)
959 # we want the 'caller' passed to ccall to be used
960 # for the body() function, but for other non-body()
961 # <%def>s within <%call> we want the current caller
962 # off the call stack (if any)
963 body_identifiers.add_declared("caller")
965 self.identifier_stack.append(body_identifiers)
967 class DefVisitor:
968 def visitDefTag(s, node):
969 s.visitDefOrBase(node)
971 def visitBlockTag(s, node):
972 s.visitDefOrBase(node)
974 def visitDefOrBase(s, node):
975 self.write_inline_def(node, callable_identifiers, nested=False)
976 if not node.is_anonymous:
977 export.append(node.funcname)
978 # remove defs that are within the <%call> from the
979 # "closuredefs" defined in the body, so they dont render twice
980 if node.funcname in body_identifiers.closuredefs:
981 del body_identifiers.closuredefs[node.funcname]
983 vis = DefVisitor()
984 for n in node.nodes:
985 n.accept_visitor(vis)
986 self.identifier_stack.pop()
988 bodyargs = node.body_decl.get_argument_expressions()
989 self.printer.writeline("def body(%s):" % ",".join(bodyargs))
991 # TODO: figure out best way to specify
992 # buffering/nonbuffering (at call time would be better)
993 buffered = False
994 if buffered:
995 self.printer.writelines("context._push_buffer()", "try:")
996 self.write_variable_declares(body_identifiers)
997 self.identifier_stack.append(body_identifiers)
999 for n in node.nodes:
1000 n.accept_visitor(self)
1001 self.identifier_stack.pop()
1003 self.write_def_finish(node, buffered, False, False, callstack=False)
1004 self.printer.writelines(None, "return [%s]" % (",".join(export)), None)
1006 self.printer.writelines(
1007 # push on caller for nested call
1008 "context.caller_stack.nextcaller = "
1009 "runtime.Namespace('caller', context, "
1010 "callables=ccall(__M_caller))",
1011 "try:",
1012 )
1013 self.printer.start_source(node.lineno)
1014 self.printer.writelines(
1015 "__M_writer(%s)"
1016 % self.create_filter_callable([], node.expression, True),
1017 "finally:",
1018 "context.caller_stack.nextcaller = None",
1019 None,
1020 )
1023class _Identifiers:
1024 """tracks the status of identifier names as template code is rendered."""
1026 def __init__(self, compiler, node=None, parent=None, nested=False):
1027 if parent is not None:
1028 # if we are the branch created in write_namespaces(),
1029 # we don't share any context from the main body().
1030 if isinstance(node, parsetree.NamespaceTag):
1031 self.declared = set()
1032 self.topleveldefs = util.SetLikeDict()
1033 else:
1034 # things that have already been declared
1035 # in an enclosing namespace (i.e. names we can just use)
1036 self.declared = (
1037 set(parent.declared)
1038 .union([c.name for c in parent.closuredefs.values()])
1039 .union(parent.locally_declared)
1040 .union(parent.argument_declared)
1041 )
1043 # if these identifiers correspond to a "nested"
1044 # scope, it means whatever the parent identifiers
1045 # had as undeclared will have been declared by that parent,
1046 # and therefore we have them in our scope.
1047 if nested:
1048 self.declared = self.declared.union(parent.undeclared)
1050 # top level defs that are available
1051 self.topleveldefs = util.SetLikeDict(**parent.topleveldefs)
1052 else:
1053 self.declared = set()
1054 self.topleveldefs = util.SetLikeDict()
1056 self.compiler = compiler
1058 # things within this level that are referenced before they
1059 # are declared (e.g. assigned to)
1060 self.undeclared = set()
1062 # things that are declared locally. some of these things
1063 # could be in the "undeclared" list as well if they are
1064 # referenced before declared
1065 self.locally_declared = set()
1067 # assignments made in explicit python blocks.
1068 # these will be propagated to
1069 # the context of local def calls.
1070 self.locally_assigned = set()
1072 # things that are declared in the argument
1073 # signature of the def callable
1074 self.argument_declared = set()
1076 # closure defs that are defined in this level
1077 self.closuredefs = util.SetLikeDict()
1079 self.node = node
1081 if node is not None:
1082 node.accept_visitor(self)
1084 illegal_names = self.compiler.reserved_names.intersection(
1085 self.locally_declared
1086 )
1087 if illegal_names:
1088 raise exceptions.NameConflictError(
1089 "Reserved words declared in template: %s"
1090 % ", ".join(illegal_names)
1091 )
1093 def branch(self, node, **kwargs):
1094 """create a new Identifiers for a new Node, with
1095 this Identifiers as the parent."""
1097 return _Identifiers(self.compiler, node, self, **kwargs)
1099 @property
1100 def defs(self):
1101 return set(self.topleveldefs.union(self.closuredefs).values())
1103 def __repr__(self):
1104 return (
1105 "Identifiers(declared=%r, locally_declared=%r, "
1106 "undeclared=%r, topleveldefs=%r, closuredefs=%r, "
1107 "argumentdeclared=%r)"
1108 % (
1109 list(self.declared),
1110 list(self.locally_declared),
1111 list(self.undeclared),
1112 [c.name for c in self.topleveldefs.values()],
1113 [c.name for c in self.closuredefs.values()],
1114 self.argument_declared,
1115 )
1116 )
1118 def check_declared(self, node):
1119 """update the state of this Identifiers with the undeclared
1120 and declared identifiers of the given node."""
1122 for ident in node.undeclared_identifiers():
1123 if ident != "context" and ident not in self.declared.union(
1124 self.locally_declared
1125 ):
1126 self.undeclared.add(ident)
1127 for ident in node.declared_identifiers():
1128 self.locally_declared.add(ident)
1130 def add_declared(self, ident):
1131 self.declared.add(ident)
1132 if ident in self.undeclared:
1133 self.undeclared.remove(ident)
1135 def visitExpression(self, node):
1136 self.check_declared(node)
1138 def visitControlLine(self, node):
1139 self.check_declared(node)
1141 def visitCode(self, node):
1142 if not node.ismodule:
1143 self.check_declared(node)
1144 self.locally_assigned = self.locally_assigned.union(
1145 node.declared_identifiers()
1146 )
1148 def visitNamespaceTag(self, node):
1149 # only traverse into the sub-elements of a
1150 # <%namespace> tag if we are the branch created in
1151 # write_namespaces()
1152 if self.node is node:
1153 for n in node.nodes:
1154 n.accept_visitor(self)
1156 def _check_name_exists(self, collection, node):
1157 existing = collection.get(node.funcname)
1158 collection[node.funcname] = node
1159 if (
1160 existing is not None
1161 and existing is not node
1162 and (node.is_block or existing.is_block)
1163 ):
1164 raise exceptions.CompileException(
1165 "%%def or %%block named '%s' already "
1166 "exists in this template." % node.funcname,
1167 **node.exception_kwargs,
1168 )
1170 def visitDefTag(self, node):
1171 if node.is_root() and not node.is_anonymous:
1172 self._check_name_exists(self.topleveldefs, node)
1173 elif node is not self.node:
1174 self._check_name_exists(self.closuredefs, node)
1176 for ident in node.undeclared_identifiers():
1177 if ident != "context" and ident not in self.declared.union(
1178 self.locally_declared
1179 ):
1180 self.undeclared.add(ident)
1182 # visit defs only one level deep
1183 if node is self.node:
1184 for ident in node.declared_identifiers():
1185 self.argument_declared.add(ident)
1187 for n in node.nodes:
1188 n.accept_visitor(self)
1190 def visitBlockTag(self, node):
1191 if node is not self.node and not node.is_anonymous:
1192 if isinstance(self.node, parsetree.DefTag):
1193 raise exceptions.CompileException(
1194 "Named block '%s' not allowed inside of def '%s'"
1195 % (node.name, self.node.name),
1196 **node.exception_kwargs,
1197 )
1198 elif isinstance(
1199 self.node, (parsetree.CallTag, parsetree.CallNamespaceTag)
1200 ):
1201 raise exceptions.CompileException(
1202 "Named block '%s' not allowed inside of <%%call> tag"
1203 % (node.name,),
1204 **node.exception_kwargs,
1205 )
1207 for ident in node.undeclared_identifiers():
1208 if ident != "context" and ident not in self.declared.union(
1209 self.locally_declared
1210 ):
1211 self.undeclared.add(ident)
1213 if not node.is_anonymous:
1214 self._check_name_exists(self.topleveldefs, node)
1215 self.undeclared.add(node.funcname)
1216 elif node is not self.node:
1217 self._check_name_exists(self.closuredefs, node)
1218 for ident in node.declared_identifiers():
1219 self.argument_declared.add(ident)
1220 for n in node.nodes:
1221 n.accept_visitor(self)
1223 def visitTextTag(self, node):
1224 for ident in node.undeclared_identifiers():
1225 if ident != "context" and ident not in self.declared.union(
1226 self.locally_declared
1227 ):
1228 self.undeclared.add(ident)
1230 def visitIncludeTag(self, node):
1231 self.check_declared(node)
1233 def visitPageTag(self, node):
1234 for ident in node.declared_identifiers():
1235 self.argument_declared.add(ident)
1236 self.check_declared(node)
1238 def visitCallNamespaceTag(self, node):
1239 self.visitCallTag(node)
1241 def visitCallTag(self, node):
1242 if node is self.node:
1243 for ident in node.undeclared_identifiers():
1244 if ident != "context" and ident not in self.declared.union(
1245 self.locally_declared
1246 ):
1247 self.undeclared.add(ident)
1248 for ident in node.declared_identifiers():
1249 self.argument_declared.add(ident)
1250 for n in node.nodes:
1251 n.accept_visitor(self)
1252 else:
1253 for ident in node.undeclared_identifiers():
1254 if ident != "context" and ident not in self.declared.union(
1255 self.locally_declared
1256 ):
1257 self.undeclared.add(ident)
1260_FOR_LOOP = re.compile(
1261 r"^for\s+((?:\(?)\s*"
1262 r"(?:\(?)\s*[A-Za-z_][A-Za-z_0-9]*"
1263 r"(?:\s*,\s*(?:[A-Za-z_][A-Za-z_0-9]*),??)*\s*(?:\)?)"
1264 r"(?:\s*,\s*(?:"
1265 r"(?:\(?)\s*[A-Za-z_][A-Za-z_0-9]*"
1266 r"(?:\s*,\s*(?:[A-Za-z_][A-Za-z_0-9]*),??)*\s*(?:\)?)"
1267 r"),??)*\s*(?:\)?))\s+in\s+(.*):"
1268)
1271def mangle_mako_loop(node, printer):
1272 """converts a for loop into a context manager wrapped around a for loop
1273 when access to the `loop` variable has been detected in the for loop body
1274 """
1275 loop_variable = LoopVariable()
1276 node.accept_visitor(loop_variable)
1277 if loop_variable.detected:
1278 node.nodes[-1].has_loop_context = True
1279 match = _FOR_LOOP.match(node.text)
1280 if match:
1281 printer.writelines(
1282 "loop = __M_loop._enter(%s)" % match.group(2),
1283 "try:",
1284 # 'with __M_loop(%s) as loop:' % match.group(2)
1285 )
1286 text = "for %s in loop:" % match.group(1)
1287 else:
1288 raise SyntaxError("Couldn't apply loop context: %s" % node.text)
1289 else:
1290 text = node.text
1291 return text
1294class LoopVariable:
1295 """A node visitor which looks for the name 'loop' within undeclared
1296 identifiers."""
1298 def __init__(self):
1299 self.detected = False
1301 def _loop_reference_detected(self, node):
1302 if "loop" in node.undeclared_identifiers():
1303 self.detected = True
1304 else:
1305 for n in node.get_children():
1306 n.accept_visitor(self)
1308 def visitControlLine(self, node):
1309 self._loop_reference_detected(node)
1311 def visitCode(self, node):
1312 self._loop_reference_detected(node)
1314 def visitExpression(self, node):
1315 self._loop_reference_detected(node)