Coverage for /home/martinb/.local/share/virtualenvs/camcops/lib/python3.6/site-packages/mako/codegen.py : 12%

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