codekingpro/portable-devtools
115k
1# mako/codegen.py2# Copyright 2006-2024 the Mako authors and contributors <see AUTHORS file>3#4# This module is part of Mako and is released under5# the MIT License: http://www.opensource.org/licenses/mit-license.php6 7"""provides functionality for rendering a parsetree constructing into module8source code."""9 10import json11import re12import time13 14from mako import ast15from mako import exceptions16from mako import filters17from mako import parsetree18from mako import util19from mako.pygen import PythonPrinter20 21 22MAGIC_NUMBER = 1023 24# names which are hardwired into the25# template and are not accessed via the26# context itself27TOPLEVEL_DECLARED = {"UNDEFINED", "STOP_RENDERING"}28RESERVED_NAMES = {"context", "loop"}.union(TOPLEVEL_DECLARED)29 30 31def compile( # noqa32 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"""47 48 buf = util.FastEncodingBuffer()49 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()69 70 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 = uri87 self.filename = filename88 self.default_filters = default_filters89 self.buffer_filters = buffer_filters90 self.imports = imports91 self.future_imports = future_imports92 self.source_encoding = source_encoding93 self.generate_magic_comment = generate_magic_comment94 self.strict_undefined = strict_undefined95 self.enable_loop = enable_loop96 self.reserved_names = reserved_names97 98 99class _GenerateRenderMethod:100 101 """A template visitor object which generates the102 full module source for a template.103 104 """105 106 def __init__(self, printer, compiler, node):107 self.printer = printer108 self.compiler = compiler109 self.node = node110 self.identifier_stack = [None]111 self.in_def = isinstance(node, (parsetree.DefTag, parsetree.BlockTag))112 113 if self.in_def:114 name = "render_%s" % node.funcname115 args = node.get_argument_expressions()116 filtered = len(node.filter_args.args) > 0117 buffered = eval(node.attributes.get("buffered", "False"))118 cached = eval(node.attributes.get("cached", "False"))119 defs = None120 pagetag = None121 if node.is_block and not node.is_anonymous:122 args += ["**pageargs"]123 else:124 defs = self.write_toplevel()125 pagetag = self.compiler.pagetag126 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 = False138 buffered = filtered = False139 if args is None:140 args = ["context"]141 else:142 args = [a for a in ["context"] + args]143 144 self.write_render_callable(145 pagetag or node, name, args, buffered, filtered, cached146 )147 148 if defs is not None:149 for node in defs:150 _GenerateRenderMethod(printer, compiler, node)151 152 if not self.in_def:153 self.write_metadata_struct()154 155 def write_metadata_struct(self):156 self.printer.source_map[self.printer.lineno] = max(157 self.printer.source_map158 )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 )171 172 @property173 def identifiers(self):174 return self.identifier_stack[-1]175 176 def write_toplevel(self):177 """Traverse a template structure for module-level directives and178 generate the start of module-level code.179 180 """181 inherit = []182 namespaces = {}183 module_code = []184 185 self.compiler.pagetag = None186 187 class FindTopLevel:188 def visitInheritTag(s, node):189 inherit.append(node)190 191 def visitNamespaceTag(s, node):192 namespaces[node.name] = node193 194 def visitPageTag(s, node):195 self.compiler.pagetag = node196 197 def visitCode(s, node):198 if node.ismodule:199 module_code.append(node)200 201 f = FindTopLevel()202 for n in self.node.nodes:203 n.accept_visitor(f)204 205 self.compiler.namespaces = namespaces206 207 module_ident = set()208 for n in module_code:209 module_ident = module_ident.union(n.declared_identifiers())210 211 module_identifiers = _Identifiers(self.compiler)212 module_identifiers.declared = module_ident213 214 # module-level names, python code215 if (216 self.compiler.generate_magic_comment217 and self.compiler.source_encoding218 ):219 self.printer.writeline(220 "# -*- coding:%s -*-" % self.compiler.source_encoding221 )222 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.filename238 )239 self.printer.writeline("_template_uri = %r" % self.compiler.uri)240 self.printer.writeline(241 "_source_encoding = %r" % self.compiler.source_encoding242 )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 = None257 258 main_identifiers = module_identifiers.branch(self.node)259 mit = module_identifiers.topleveldefs260 module_identifiers.topleveldefs = mit.union(261 main_identifiers.topleveldefs262 )263 module_identifiers.declared.update(TOPLEVEL_DECLARED)264 if impcode:265 module_identifiers.declared.update(impcode.declared_identifiers)266 267 self.compiler.identifiers = module_identifiers268 self.printer.writeline(269 "_exports = %r"270 % [n.name for n in main_identifiers.topleveldefs.values()]271 )272 self.printer.write_blanks(2)273 274 if len(module_code):275 self.write_module_code(module_code)276 277 if len(inherit):278 self.write_namespaces(namespaces)279 self.write_inherit(inherit[-1])280 elif len(namespaces):281 self.write_namespaces(namespaces)282 283 return list(main_identifiers.topleveldefs.values())284 285 def write_render_callable(286 self, node, name, args, buffered, filtered, cached287 ):288 """write a top-level render callable.289 290 this could be the main render() method or that of a top-level def."""291 292 if self.in_def:293 decorator = node.decorator294 if decorator:295 self.printer.writeline(296 "@runtime._decorate_toplevel(%s)" % decorator297 )298 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_caller303 "__M_caller = context.caller_stack._push_frame()",304 "try:",305 )306 if buffered or filtered or cached:307 self.printer.writeline("context._push_buffer()")308 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")314 315 if not self.in_def and (316 len(self.identifiers.locally_assigned) > 0317 or len(self.identifiers.argument_declared) > 0318 ):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_declared325 ]326 )327 )328 329 self.write_variable_declares(self.identifiers, toplevel=True)330 331 for n in self.node.nodes:332 n.accept_visitor(self)333 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=True340 )341 342 def write_module_code(self, module_code):343 """write module-level template code, i.e. that which344 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)347 348 def write_inherit(self, node):349 """write the module-level inheritance-determination callable."""350 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 )358 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):")372 373 for node in namespaces.values():374 if "import" in node.attributes:375 self.compiler.has_ns_imports = True376 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 = True382 383 class NSDefVisitor:384 def visitDefTag(s, node):385 s.visitDefOrBase(node)386 387 def visitBlockTag(s, node):388 s.visitDefOrBase(node)389 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)399 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 = False406 callable_name = "make_namespace()"407 else:408 callable_name = "None"409 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))443 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)451 452 def write_variable_declares(self, identifiers, toplevel=False, limit=None):453 """write variable declarations at the top of a function.454 455 the variable declarations are in the form of callable456 definitions for defs and/or name lookup within the457 function's context argument. the names declared are based458 on the names that are referenced in the function body,459 which don't otherwise have any explicit assignment460 operation. names that are assigned within the body are461 assumed to be locally-scoped variables and are not462 separately declared.463 464 for def callable definitions, if the def is a top-level465 callable then a 'stub' callable is generated which wraps466 the current Context into a closure. if the def is not467 top-level, it is fully rendered as a local closure.468 469 """470 471 # collection of all defs available to us in this scope472 comp_idents = {c.funcname: c for c in identifiers.defs}473 to_write = set()474 475 # write "context.get()" for all variables we are going to476 # need that arent in the namespace yet477 to_write = to_write.union(identifiers.undeclared)478 479 # write closure functions for closures that we define480 # right here481 to_write = to_write.union(482 [c.funcname for c in identifiers.closuredefs.values()]483 )484 485 # remove identifiers that are declared in the argument486 # signature of the callable487 to_write = to_write.difference(identifiers.argument_declared)488 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 block492 # means that variable is now a "locally declared" var,493 # which cannot be referenced beforehand.494 to_write = to_write.difference(identifiers.locally_declared)495 496 if self.compiler.enable_loop:497 has_loop = "loop" in to_write498 to_write.discard("loop")499 else:500 has_loop = False501 502 # if a limiting set was sent, constraint to those items in that list503 # (this is used for the caching decorator)504 if limit is not None:505 to_write = to_write.intersection(limit)506 507 if toplevel and getattr(self.compiler, "has_ns_imports", False):508 self.printer.writeline("_import_ns = {}")509 self.compiler.has_imports = True510 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 )520 521 if has_loop:522 self.printer.writeline("loop = __M_loop = runtime.LoopStack()")523 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)537 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 )575 576 self.printer.writeline("__M_writer = context.writer()")577 578 def write_def_decl(self, node, identifiers):579 """write a locally-available callable referencing a top-level def"""580 funcname = node.funcname581 namedecls = node.get_argument_expressions()582 nameargs = node.get_argument_expressions(as_call=True)583 584 if not self.in_def and (585 len(self.identifiers.locally_assigned) > 0586 or len(self.identifiers.argument_declared) > 0587 ):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)596 597 def write_inline_def(self, node, identifiers, nested):598 """write a locally-available def callable inside an enclosing def."""599 600 namedecls = node.get_argument_expressions()601 602 decorator = node.decorator603 if decorator:604 self.printer.writeline(605 "@runtime._decorate_inline(context, %s)" % decorator606 )607 self.printer.writeline(608 "def %s(%s):" % (node.funcname, ",".join(namedecls))609 )610 filtered = len(node.filter_args.args) > 0611 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_caller615 "__M_caller = context.caller_stack._push_frame()",616 "try:",617 )618 if buffered or filtered or cached:619 self.printer.writelines("context._push_buffer()")620 621 identifiers = identifiers.branch(node, nested=nested)622 623 self.write_variable_declares(identifiers)624 625 self.identifier_stack.append(identifiers)626 for n in node.nodes:627 n.accept_visitor(self)628 self.identifier_stack.pop()629 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 )642 643 def write_def_finish(644 self, node, buffered, filtered, cached, callstack=True645 ):646 """write the end section of a rendering function, either outermost or647 inline.648 649 this takes into account if the rendering function was filtered,650 buffered, etc. and closes the corresponding try: block if any, and651 writes code to retrieve captured content, apply filters, send proper652 return value."""653 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()", None659 )660 661 if buffered or filtered or cached:662 if buffered or cached:663 # in a caching scenario, don't try to get a writer664 # from the context after popping; assume the caching665 # implemenation might be using a context with no666 # extra buffers667 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 )675 676 if callstack:677 self.printer.writeline("context.caller_stack._pop_frame()")678 679 s = "__M_buf.getvalue()"680 if filtered:681 s = self.create_filter_callable(682 node.filter_args.args, s, False683 )684 self.printer.writeline(None)685 if buffered and not cached:686 s = self.create_filter_callable(687 self.compiler.buffer_filters, s, False688 )689 if buffered or cached:690 self.printer.writeline("return %s" % s)691 else:692 self.printer.writelines("__M_writer(%s)" % s, "return ''")693 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 rendering705 callable with a cached version of itself."""706 707 self.printer.writeline("__M_%s = %s" % (name, name))708 cachekey = node_or_pagetag.parsed_attributes.get(709 "cache_key", repr(name)710 )711 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_attributes717 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_attributes722 if pa.startswith("cache_") and pa != "cache_key"723 )724 if "timeout" in cache_args:725 cache_args["timeout"] = int(eval(cache_args["timeout"]))726 727 self.printer.writeline("def %s(%s):" % (name, ",".join(args)))728 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 args732 ]733 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_filters755 s = self.create_filter_callable(756 self.compiler.buffer_filters, s, False757 )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 )776 777 def create_filter_callable(self, args, target, is_expression):778 """write a filter-applying expression based on the filters779 present in the given filter names, adjusting for the global780 'default' filter aliases as needed."""781 782 def locate_encode(name):783 if re.match(r"decode\..+", name):784 return "filters." + name785 else:786 return filters.DEFAULT_ESCAPES.get(name, name)787 788 if "n" not in args:789 if is_expression:790 if self.compiler.pagetag:791 args = self.compiler.pagetag.filter_args.args + args792 if self.compiler.default_filters and "n" not in args:793 args = self.compiler.default_filters + args794 for e in args:795 # if filter given as a function, get just the identifier portion796 if e == "n":797 continue798 m = re.match(r"(.+?)(\(.*\))", e)799 if m:800 ident, fargs = m.group(1, 2)801 f = locate_encode(ident)802 e = f + fargs803 else:804 e = locate_encode(e)805 assert e is not None806 target = "%s(%s)" % (e, target)807 return target808 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 None815 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, True821 )822 self.printer.writeline("__M_writer(%s)" % s)823 else:824 self.printer.writeline("__M_writer(%s)" % node.text)825 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.text839 self.printer.writeline(text)840 children = node.get_children()841 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 ternary845 # and end control lines, and846 # 3) any control line with no content other than comments847 # 4) the first control block with no content other than comments848 def _search_for_control_line():849 for c in children:850 if isinstance(c, parsetree.Comment):851 continue852 elif isinstance(c, parsetree.ControlLine):853 return True854 return False855 856 if (857 not children858 or all(859 isinstance(c, (parsetree.Comment, parsetree.ControlLine))860 for c in children861 )862 and all(863 (node.is_ternary(c.keyword) or c.isend)864 for c in children865 if isinstance(c, parsetree.ControlLine)866 )867 or _search_for_control_line()868 ):869 self.printer.writeline("pass")870 871 def visitText(self, node):872 self.printer.start_source(node.lineno)873 self.printer.writeline("__M_writer(%s)" % repr(node.content))874 875 def visitTextTag(self, node):876 filtered = len(node.filter_args.args) > 0877 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()", False890 ),891 None,892 )893 894 def visitCode(self, node):895 if not node.ismodule:896 self.printer.write_indented_block(897 node.text, starting_lineno=node.lineno898 )899 900 if not self.in_def and len(self.identifiers.locally_assigned) > 0:901 # if we are the "template" def, fudge locally902 # 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 )914 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 )928 929 def visitNamespaceTag(self, node):930 pass931 932 def visitDefTag(self, node):933 pass934 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.funcname944 )945 self.printer.writeline(946 "context['self'].%s(%s)" % (node.funcname, ",".join(nameargs))947 )948 self.printer.writeline("\n")949 950 def visitCallNamespaceTag(self, node):951 # TODO: we can put namespace-specific checks here, such952 # as ensure the given namespace will be imported,953 # pre-import the namespace, etc.954 self.visitCallTag(node)955 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 used962 # for the body() function, but for other non-body()963 # <%def>s within <%call> we want the current caller964 # off the call stack (if any)965 body_identifiers.add_declared("caller")966 967 self.identifier_stack.append(body_identifiers)968 969 class DefVisitor:970 def visitDefTag(s, node):971 s.visitDefOrBase(node)972 973 def visitBlockTag(s, node):974 s.visitDefOrBase(node)975 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 the981 # "closuredefs" defined in the body, so they dont render twice982 if node.funcname in body_identifiers.closuredefs:983 del body_identifiers.closuredefs[node.funcname]984 985 vis = DefVisitor()986 for n in node.nodes:987 n.accept_visitor(vis)988 self.identifier_stack.pop()989 990 bodyargs = node.body_decl.get_argument_expressions()991 self.printer.writeline("def body(%s):" % ",".join(bodyargs))992 993 # TODO: figure out best way to specify994 # buffering/nonbuffering (at call time would be better)995 buffered = False996 if buffered:997 self.printer.writelines("context._push_buffer()", "try:")998 self.write_variable_declares(body_identifiers)999 self.identifier_stack.append(body_identifiers)1000 1001 for n in node.nodes:1002 n.accept_visitor(self)1003 self.identifier_stack.pop()1004 1005 self.write_def_finish(node, buffered, False, False, callstack=False)1006 self.printer.writelines(None, "return [%s]" % (",".join(export)), None)1007 1008 self.printer.writelines(1009 # push on caller for nested call1010 "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 )1023 1024 1025class _Identifiers:1026 1027 """tracks the status of identifier names as template code is rendered."""1028 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 declared1038 # 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 )1045 1046 # if these identifiers correspond to a "nested"1047 # scope, it means whatever the parent identifiers1048 # 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)1052 1053 # top level defs that are available1054 self.topleveldefs = util.SetLikeDict(**parent.topleveldefs)1055 else:1056 self.declared = set()1057 self.topleveldefs = util.SetLikeDict()1058 1059 self.compiler = compiler1060 1061 # things within this level that are referenced before they1062 # are declared (e.g. assigned to)1063 self.undeclared = set()1064 1065 # things that are declared locally. some of these things1066 # could be in the "undeclared" list as well if they are1067 # referenced before declared1068 self.locally_declared = set()1069 1070 # assignments made in explicit python blocks.1071 # these will be propagated to1072 # the context of local def calls.1073 self.locally_assigned = set()1074 1075 # things that are declared in the argument1076 # signature of the def callable1077 self.argument_declared = set()1078 1079 # closure defs that are defined in this level1080 self.closuredefs = util.SetLikeDict()1081 1082 self.node = node1083 1084 if node is not None:1085 node.accept_visitor(self)1086 1087 illegal_names = self.compiler.reserved_names.intersection(1088 self.locally_declared1089 )1090 if illegal_names:1091 raise exceptions.NameConflictError(1092 "Reserved words declared in template: %s"1093 % ", ".join(illegal_names)1094 )1095 1096 def branch(self, node, **kwargs):1097 """create a new Identifiers for a new Node, with1098 this Identifiers as the parent."""1099 1100 return _Identifiers(self.compiler, node, self, **kwargs)1101 1102 @property1103 def defs(self):1104 return set(self.topleveldefs.union(self.closuredefs).values())1105 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 )1120 1121 def check_declared(self, node):1122 """update the state of this Identifiers with the undeclared1123 and declared identifiers of the given node."""1124 1125 for ident in node.undeclared_identifiers():1126 if ident != "context" and ident not in self.declared.union(1127 self.locally_declared1128 ):1129 self.undeclared.add(ident)1130 for ident in node.declared_identifiers():1131 self.locally_declared.add(ident)1132 1133 def add_declared(self, ident):1134 self.declared.add(ident)1135 if ident in self.undeclared:1136 self.undeclared.remove(ident)1137 1138 def visitExpression(self, node):1139 self.check_declared(node)1140 1141 def visitControlLine(self, node):1142 self.check_declared(node)1143 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 )1150 1151 def visitNamespaceTag(self, node):1152 # only traverse into the sub-elements of a1153 # <%namespace> tag if we are the branch created in1154 # write_namespaces()1155 if self.node is node:1156 for n in node.nodes:1157 n.accept_visitor(self)1158 1159 def _check_name_exists(self, collection, node):1160 existing = collection.get(node.funcname)1161 collection[node.funcname] = node1162 if (1163 existing is not None1164 and existing is not node1165 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 )1172 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)1178 1179 for ident in node.undeclared_identifiers():1180 if ident != "context" and ident not in self.declared.union(1181 self.locally_declared1182 ):1183 self.undeclared.add(ident)1184 1185 # visit defs only one level deep1186 if node is self.node:1187 for ident in node.declared_identifiers():1188 self.argument_declared.add(ident)1189 1190 for n in node.nodes:1191 n.accept_visitor(self)1192 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 )