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