| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319 |
- # mako/codegen.py
- # Copyright 2006-2025 the Mako authors and contributors <see AUTHORS file>
- #
- # This module is part of Mako and is released under
- # the MIT License: http://www.opensource.org/licenses/mit-license.php
- """provides functionality for rendering a parsetree constructing into module
- source code."""
- import json
- import re
- import time
- from mako import ast
- from mako import exceptions
- from mako import filters
- from mako import parsetree
- from mako import util
- from mako.pygen import PythonPrinter
- MAGIC_NUMBER = 10
- # names which are hardwired into the
- # template and are not accessed via the
- # context itself
- TOPLEVEL_DECLARED = {"UNDEFINED", "STOP_RENDERING"}
- RESERVED_NAMES = {"context", "loop"}.union(TOPLEVEL_DECLARED)
- def compile( # noqa
- node,
- uri,
- filename=None,
- default_filters=None,
- buffer_filters=None,
- imports=None,
- future_imports=None,
- source_encoding=None,
- generate_magic_comment=True,
- strict_undefined=False,
- enable_loop=True,
- reserved_names=frozenset(),
- ):
- """Generate module source code given a parsetree node,
- uri, and optional source filename"""
- buf = util.FastEncodingBuffer()
- printer = PythonPrinter(buf)
- _GenerateRenderMethod(
- printer,
- _CompileContext(
- uri,
- filename,
- default_filters,
- buffer_filters,
- imports,
- future_imports,
- source_encoding,
- generate_magic_comment,
- strict_undefined,
- enable_loop,
- reserved_names,
- ),
- node,
- )
- return buf.getvalue()
- class _CompileContext:
- def __init__(
- self,
- uri,
- filename,
- default_filters,
- buffer_filters,
- imports,
- future_imports,
- source_encoding,
- generate_magic_comment,
- strict_undefined,
- enable_loop,
- reserved_names,
- ):
- self.uri = uri
- self.filename = filename
- self.default_filters = default_filters
- self.buffer_filters = buffer_filters
- self.imports = imports
- self.future_imports = future_imports
- self.source_encoding = source_encoding
- self.generate_magic_comment = generate_magic_comment
- self.strict_undefined = strict_undefined
- self.enable_loop = enable_loop
- self.reserved_names = reserved_names
- class _GenerateRenderMethod:
- """A template visitor object which generates the
- full module source for a template.
- """
- def __init__(self, printer, compiler, node):
- self.printer = printer
- self.compiler = compiler
- self.node = node
- self.identifier_stack = [None]
- self.in_def = isinstance(node, (parsetree.DefTag, parsetree.BlockTag))
- if self.in_def:
- name = "render_%s" % node.funcname
- args = node.get_argument_expressions()
- filtered = len(node.filter_args.args) > 0
- buffered = eval(node.attributes.get("buffered", "False"))
- cached = eval(node.attributes.get("cached", "False"))
- defs = None
- pagetag = None
- if node.is_block and not node.is_anonymous:
- args += ["**pageargs"]
- else:
- defs = self.write_toplevel()
- pagetag = self.compiler.pagetag
- name = "render_body"
- if pagetag is not None:
- args = pagetag.body_decl.get_argument_expressions()
- if not pagetag.body_decl.kwargs:
- args += ["**pageargs"]
- cached = eval(pagetag.attributes.get("cached", "False"))
- self.compiler.enable_loop = self.compiler.enable_loop or eval(
- pagetag.attributes.get("enable_loop", "False")
- )
- else:
- args = ["**pageargs"]
- cached = False
- buffered = filtered = False
- if args is None:
- args = ["context"]
- else:
- args = [a for a in ["context"] + args]
- self.write_render_callable(
- pagetag or node, name, args, buffered, filtered, cached
- )
- if defs is not None:
- for node in defs:
- _GenerateRenderMethod(printer, compiler, node)
- if not self.in_def:
- self.write_metadata_struct()
- def write_metadata_struct(self):
- self.printer.source_map[self.printer.lineno] = max(
- self.printer.source_map
- )
- struct = {
- "filename": self.compiler.filename,
- "uri": self.compiler.uri,
- "source_encoding": self.compiler.source_encoding,
- "line_map": self.printer.source_map,
- }
- self.printer.writelines(
- '"""',
- "__M_BEGIN_METADATA",
- json.dumps(struct),
- "__M_END_METADATA\n" '"""',
- )
- @property
- def identifiers(self):
- return self.identifier_stack[-1]
- def write_toplevel(self):
- """Traverse a template structure for module-level directives and
- generate the start of module-level code.
- """
- inherit = []
- namespaces = {}
- module_code = []
- self.compiler.pagetag = None
- class FindTopLevel:
- def visitInheritTag(s, node):
- inherit.append(node)
- def visitNamespaceTag(s, node):
- namespaces[node.name] = node
- def visitPageTag(s, node):
- self.compiler.pagetag = node
- def visitCode(s, node):
- if node.ismodule:
- module_code.append(node)
- f = FindTopLevel()
- for n in self.node.nodes:
- n.accept_visitor(f)
- self.compiler.namespaces = namespaces
- module_ident = set()
- for n in module_code:
- module_ident = module_ident.union(n.declared_identifiers())
- module_identifiers = _Identifiers(self.compiler)
- module_identifiers.declared = module_ident
- # module-level names, python code
- if (
- self.compiler.generate_magic_comment
- and self.compiler.source_encoding
- ):
- self.printer.writeline(
- "# -*- coding:%s -*-" % self.compiler.source_encoding
- )
- if self.compiler.future_imports:
- self.printer.writeline(
- "from __future__ import %s"
- % (", ".join(self.compiler.future_imports),)
- )
- self.printer.writeline("from mako import runtime, filters, cache")
- self.printer.writeline("UNDEFINED = runtime.UNDEFINED")
- self.printer.writeline("STOP_RENDERING = runtime.STOP_RENDERING")
- self.printer.writeline("__M_dict_builtin = dict")
- self.printer.writeline("__M_locals_builtin = locals")
- self.printer.writeline("_magic_number = %r" % MAGIC_NUMBER)
- self.printer.writeline("_modified_time = %r" % time.time())
- self.printer.writeline("_enable_loop = %r" % self.compiler.enable_loop)
- self.printer.writeline(
- "_template_filename = %r" % self.compiler.filename
- )
- self.printer.writeline("_template_uri = %r" % self.compiler.uri)
- self.printer.writeline(
- "_source_encoding = %r" % self.compiler.source_encoding
- )
- if self.compiler.imports:
- buf = ""
- for imp in self.compiler.imports:
- buf += imp + "\n"
- self.printer.writeline(imp)
- impcode = ast.PythonCode(
- buf,
- source="",
- lineno=0,
- pos=0,
- filename="template defined imports",
- )
- else:
- impcode = None
- main_identifiers = module_identifiers.branch(self.node)
- mit = module_identifiers.topleveldefs
- module_identifiers.topleveldefs = mit.union(
- main_identifiers.topleveldefs
- )
- module_identifiers.declared.update(TOPLEVEL_DECLARED)
- if impcode:
- module_identifiers.declared.update(impcode.declared_identifiers)
- self.compiler.identifiers = module_identifiers
- self.printer.writeline(
- "_exports = %r"
- % [n.name for n in main_identifiers.topleveldefs.values()]
- )
- self.printer.write_blanks(2)
- if len(module_code):
- self.write_module_code(module_code)
- if len(inherit):
- self.write_namespaces(namespaces)
- self.write_inherit(inherit[-1])
- elif len(namespaces):
- self.write_namespaces(namespaces)
- return list(main_identifiers.topleveldefs.values())
- def write_render_callable(
- self, node, name, args, buffered, filtered, cached
- ):
- """write a top-level render callable.
- this could be the main render() method or that of a top-level def."""
- if self.in_def:
- decorator = node.decorator
- if decorator:
- self.printer.writeline(
- "@runtime._decorate_toplevel(%s)" % decorator
- )
- self.printer.start_source(node.lineno)
- self.printer.writelines(
- "def %s(%s):" % (name, ",".join(args)),
- # push new frame, assign current frame to __M_caller
- "__M_caller = context.caller_stack._push_frame()",
- "try:",
- )
- if buffered or filtered or cached:
- self.printer.writeline("context._push_buffer()")
- self.identifier_stack.append(
- self.compiler.identifiers.branch(self.node)
- )
- if (not self.in_def or self.node.is_block) and "**pageargs" in args:
- self.identifier_stack[-1].argument_declared.add("pageargs")
- if not self.in_def and (
- len(self.identifiers.locally_assigned) > 0
- or len(self.identifiers.argument_declared) > 0
- ):
- self.printer.writeline(
- "__M_locals = __M_dict_builtin(%s)"
- % ",".join(
- [
- "%s=%s" % (x, x)
- for x in self.identifiers.argument_declared
- ]
- )
- )
- self.write_variable_declares(self.identifiers, toplevel=True)
- for n in self.node.nodes:
- n.accept_visitor(self)
- self.write_def_finish(self.node, buffered, filtered, cached)
- self.printer.writeline(None)
- self.printer.write_blanks(2)
- if cached:
- self.write_cache_decorator(
- node, name, args, buffered, self.identifiers, toplevel=True
- )
- def write_module_code(self, module_code):
- """write module-level template code, i.e. that which
- is enclosed in <%! %> tags in the template."""
- for n in module_code:
- self.printer.write_indented_block(n.text, starting_lineno=n.lineno)
- def write_inherit(self, node):
- """write the module-level inheritance-determination callable."""
- self.printer.writelines(
- "def _mako_inherit(template, context):",
- "_mako_generate_namespaces(context)",
- "return runtime._inherit_from(context, %s, _template_uri)"
- % (node.parsed_attributes["file"]),
- None,
- )
- def write_namespaces(self, namespaces):
- """write the module-level namespace-generating callable."""
- self.printer.writelines(
- "def _mako_get_namespace(context, name):",
- "try:",
- "return context.namespaces[(__name__, name)]",
- "except KeyError:",
- "_mako_generate_namespaces(context)",
- "return context.namespaces[(__name__, name)]",
- None,
- None,
- )
- self.printer.writeline("def _mako_generate_namespaces(context):")
- for node in namespaces.values():
- if "import" in node.attributes:
- self.compiler.has_ns_imports = True
- self.printer.start_source(node.lineno)
- if len(node.nodes):
- self.printer.writeline("def make_namespace():")
- export = []
- identifiers = self.compiler.identifiers.branch(node)
- self.in_def = True
- class NSDefVisitor:
- def visitDefTag(s, node):
- s.visitDefOrBase(node)
- def visitBlockTag(s, node):
- s.visitDefOrBase(node)
- def visitDefOrBase(s, node):
- if node.is_anonymous:
- raise exceptions.CompileException(
- "Can't put anonymous blocks inside "
- "<%namespace>",
- **node.exception_kwargs,
- )
- self.write_inline_def(node, identifiers, nested=False)
- export.append(node.funcname)
- vis = NSDefVisitor()
- for n in node.nodes:
- n.accept_visitor(vis)
- self.printer.writeline("return [%s]" % (",".join(export)))
- self.printer.writeline(None)
- self.in_def = False
- callable_name = "make_namespace()"
- else:
- callable_name = "None"
- if "file" in node.parsed_attributes:
- self.printer.writeline(
- "ns = runtime.TemplateNamespace(%r,"
- " context._clean_inheritance_tokens(),"
- " templateuri=%s, callables=%s, "
- " calling_uri=_template_uri)"
- % (
- node.name,
- node.parsed_attributes.get("file", "None"),
- callable_name,
- )
- )
- elif "module" in node.parsed_attributes:
- self.printer.writeline(
- "ns = runtime.ModuleNamespace(%r,"
- " context._clean_inheritance_tokens(),"
- " callables=%s, calling_uri=_template_uri,"
- " module=%s)"
- % (
- node.name,
- callable_name,
- node.parsed_attributes.get("module", "None"),
- )
- )
- else:
- self.printer.writeline(
- "ns = runtime.Namespace(%r,"
- " context._clean_inheritance_tokens(),"
- " callables=%s, calling_uri=_template_uri)"
- % (node.name, callable_name)
- )
- if eval(node.attributes.get("inheritable", "False")):
- self.printer.writeline("context['self'].%s = ns" % (node.name))
- self.printer.writeline(
- "context.namespaces[(__name__, %s)] = ns" % repr(node.name)
- )
- self.printer.write_blanks(1)
- if not len(namespaces):
- self.printer.writeline("pass")
- self.printer.writeline(None)
- def write_variable_declares(self, identifiers, toplevel=False, limit=None):
- """write variable declarations at the top of a function.
- the variable declarations are in the form of callable
- definitions for defs and/or name lookup within the
- function's context argument. the names declared are based
- on the names that are referenced in the function body,
- which don't otherwise have any explicit assignment
- operation. names that are assigned within the body are
- assumed to be locally-scoped variables and are not
- separately declared.
- for def callable definitions, if the def is a top-level
- callable then a 'stub' callable is generated which wraps
- the current Context into a closure. if the def is not
- top-level, it is fully rendered as a local closure.
- """
- # collection of all defs available to us in this scope
- comp_idents = {c.funcname: c for c in identifiers.defs}
- to_write = set()
- # write "context.get()" for all variables we are going to
- # need that arent in the namespace yet
- to_write = to_write.union(identifiers.undeclared)
- # write closure functions for closures that we define
- # right here
- to_write = to_write.union(
- [c.funcname for c in identifiers.closuredefs.values()]
- )
- # remove identifiers that are declared in the argument
- # signature of the callable
- to_write = to_write.difference(identifiers.argument_declared)
- # remove identifiers that we are going to assign to.
- # in this way we mimic Python's behavior,
- # i.e. assignment to a variable within a block
- # means that variable is now a "locally declared" var,
- # which cannot be referenced beforehand.
- to_write = to_write.difference(identifiers.locally_declared)
- if self.compiler.enable_loop:
- has_loop = "loop" in to_write
- to_write.discard("loop")
- else:
- has_loop = False
- # if a limiting set was sent, constraint to those items in that list
- # (this is used for the caching decorator)
- if limit is not None:
- to_write = to_write.intersection(limit)
- if toplevel and getattr(self.compiler, "has_ns_imports", False):
- self.printer.writeline("_import_ns = {}")
- self.compiler.has_imports = True
- for ident, ns in self.compiler.namespaces.items():
- if "import" in ns.attributes:
- self.printer.writeline(
- "_mako_get_namespace(context, %r)."
- "_populate(_import_ns, %r)"
- % (
- ident,
- re.split(r"\s*,\s*", ns.attributes["import"]),
- )
- )
- if has_loop:
- self.printer.writeline("loop = __M_loop = runtime.LoopStack()")
- for ident in to_write:
- if ident in comp_idents:
- comp = comp_idents[ident]
- if comp.is_block:
- if not comp.is_anonymous:
- self.write_def_decl(comp, identifiers)
- else:
- self.write_inline_def(comp, identifiers, nested=True)
- else:
- if comp.is_root():
- self.write_def_decl(comp, identifiers)
- else:
- self.write_inline_def(comp, identifiers, nested=True)
- elif ident in self.compiler.namespaces:
- self.printer.writeline(
- "%s = _mako_get_namespace(context, %r)" % (ident, ident)
- )
- else:
- if getattr(self.compiler, "has_ns_imports", False):
- if self.compiler.strict_undefined:
- self.printer.writelines(
- "%s = _import_ns.get(%r, UNDEFINED)"
- % (ident, ident),
- "if %s is UNDEFINED:" % ident,
- "try:",
- "%s = context[%r]" % (ident, ident),
- "except KeyError:",
- "raise NameError(\"'%s' is not defined\")" % ident,
- None,
- None,
- )
- else:
- self.printer.writeline(
- "%s = _import_ns.get"
- "(%r, context.get(%r, UNDEFINED))"
- % (ident, ident, ident)
- )
- else:
- if self.compiler.strict_undefined:
- self.printer.writelines(
- "try:",
- "%s = context[%r]" % (ident, ident),
- "except KeyError:",
- "raise NameError(\"'%s' is not defined\")" % ident,
- None,
- )
- else:
- self.printer.writeline(
- "%s = context.get(%r, UNDEFINED)" % (ident, ident)
- )
- self.printer.writeline("__M_writer = context.writer()")
- def write_def_decl(self, node, identifiers):
- """write a locally-available callable referencing a top-level def"""
- funcname = node.funcname
- namedecls = node.get_argument_expressions()
- nameargs = node.get_argument_expressions(as_call=True)
- if not self.in_def and (
- len(self.identifiers.locally_assigned) > 0
- or len(self.identifiers.argument_declared) > 0
- ):
- nameargs.insert(0, "context._locals(__M_locals)")
- else:
- nameargs.insert(0, "context")
- self.printer.writeline("def %s(%s):" % (funcname, ",".join(namedecls)))
- self.printer.writeline(
- "return render_%s(%s)" % (funcname, ",".join(nameargs))
- )
- self.printer.writeline(None)
- def write_inline_def(self, node, identifiers, nested):
- """write a locally-available def callable inside an enclosing def."""
- namedecls = node.get_argument_expressions()
- decorator = node.decorator
- if decorator:
- self.printer.writeline(
- "@runtime._decorate_inline(context, %s)" % decorator
- )
- self.printer.writeline(
- "def %s(%s):" % (node.funcname, ",".join(namedecls))
- )
- filtered = len(node.filter_args.args) > 0
- buffered = eval(node.attributes.get("buffered", "False"))
- cached = eval(node.attributes.get("cached", "False"))
- self.printer.writelines(
- # push new frame, assign current frame to __M_caller
- "__M_caller = context.caller_stack._push_frame()",
- "try:",
- )
- if buffered or filtered or cached:
- self.printer.writelines("context._push_buffer()")
- identifiers = identifiers.branch(node, nested=nested)
- self.write_variable_declares(identifiers)
- self.identifier_stack.append(identifiers)
- for n in node.nodes:
- n.accept_visitor(self)
- self.identifier_stack.pop()
- self.write_def_finish(node, buffered, filtered, cached)
- self.printer.writeline(None)
- if cached:
- self.write_cache_decorator(
- node,
- node.funcname,
- namedecls,
- False,
- identifiers,
- inline=True,
- toplevel=False,
- )
- def write_def_finish(
- self, node, buffered, filtered, cached, callstack=True
- ):
- """write the end section of a rendering function, either outermost or
- inline.
- this takes into account if the rendering function was filtered,
- buffered, etc. and closes the corresponding try: block if any, and
- writes code to retrieve captured content, apply filters, send proper
- return value."""
- if not buffered and not cached and not filtered:
- self.printer.writeline("return ''")
- if callstack:
- self.printer.writelines(
- "finally:", "context.caller_stack._pop_frame()", None
- )
- if buffered or filtered or cached:
- if buffered or cached:
- # in a caching scenario, don't try to get a writer
- # from the context after popping; assume the caching
- # implemenation might be using a context with no
- # extra buffers
- self.printer.writelines(
- "finally:", "__M_buf = context._pop_buffer()"
- )
- else:
- self.printer.writelines(
- "finally:",
- "__M_buf, __M_writer = context._pop_buffer_and_writer()",
- )
- if callstack:
- self.printer.writeline("context.caller_stack._pop_frame()")
- s = "__M_buf.getvalue()"
- if filtered:
- s = self.create_filter_callable(
- node.filter_args.args, s, False
- )
- self.printer.writeline(None)
- if buffered and not cached:
- s = self.create_filter_callable(
- self.compiler.buffer_filters, s, False
- )
- if buffered or cached:
- self.printer.writeline("return %s" % s)
- else:
- self.printer.writelines("__M_writer(%s)" % s, "return ''")
- def write_cache_decorator(
- self,
- node_or_pagetag,
- name,
- args,
- buffered,
- identifiers,
- inline=False,
- toplevel=False,
- ):
- """write a post-function decorator to replace a rendering
- callable with a cached version of itself."""
- self.printer.writeline("__M_%s = %s" % (name, name))
- cachekey = node_or_pagetag.parsed_attributes.get(
- "cache_key", repr(name)
- )
- cache_args = {}
- if self.compiler.pagetag is not None:
- cache_args.update(
- (pa[6:], self.compiler.pagetag.parsed_attributes[pa])
- for pa in self.compiler.pagetag.parsed_attributes
- if pa.startswith("cache_") and pa != "cache_key"
- )
- cache_args.update(
- (pa[6:], node_or_pagetag.parsed_attributes[pa])
- for pa in node_or_pagetag.parsed_attributes
- if pa.startswith("cache_") and pa != "cache_key"
- )
- if "timeout" in cache_args:
- cache_args["timeout"] = int(eval(cache_args["timeout"]))
- self.printer.writeline("def %s(%s):" % (name, ",".join(args)))
- # form "arg1, arg2, arg3=arg3, arg4=arg4", etc.
- pass_args = [
- "%s=%s" % ((a.split("=")[0],) * 2) if "=" in a else a for a in args
- ]
- self.write_variable_declares(
- identifiers,
- toplevel=toplevel,
- limit=node_or_pagetag.undeclared_identifiers(),
- )
- if buffered:
- s = (
- "context.get('local')."
- "cache._ctx_get_or_create("
- "%s, lambda:__M_%s(%s), context, %s__M_defname=%r)"
- % (
- cachekey,
- name,
- ",".join(pass_args),
- "".join(
- ["%s=%s, " % (k, v) for k, v in cache_args.items()]
- ),
- name,
- )
- )
- # apply buffer_filters
- s = self.create_filter_callable(
- self.compiler.buffer_filters, s, False
- )
- self.printer.writelines("return " + s, None)
- else:
- self.printer.writelines(
- "__M_writer(context.get('local')."
- "cache._ctx_get_or_create("
- "%s, lambda:__M_%s(%s), context, %s__M_defname=%r))"
- % (
- cachekey,
- name,
- ",".join(pass_args),
- "".join(
- ["%s=%s, " % (k, v) for k, v in cache_args.items()]
- ),
- name,
- ),
- "return ''",
- None,
- )
- def create_filter_callable(self, args, target, is_expression):
- """write a filter-applying expression based on the filters
- present in the given filter names, adjusting for the global
- 'default' filter aliases as needed."""
- def locate_encode(name):
- if re.match(r"decode\..+", name):
- return "filters." + name
- else:
- return filters.DEFAULT_ESCAPES.get(name, name)
- if "n" not in args:
- if is_expression:
- if self.compiler.pagetag:
- args = self.compiler.pagetag.filter_args.args + args
- if self.compiler.default_filters and "n" not in args:
- args = self.compiler.default_filters + args
- for e in args:
- # if filter given as a function, get just the identifier portion
- if e == "n":
- continue
- m = re.match(r"(.+?)(\(.*\))", e)
- if m:
- ident, fargs = m.group(1, 2)
- f = locate_encode(ident)
- e = f + fargs
- else:
- e = locate_encode(e)
- assert e is not None
- target = "%s(%s)" % (e, target)
- return target
- def visitExpression(self, node):
- self.printer.start_source(node.lineno)
- if (
- len(node.escapes)
- or (
- self.compiler.pagetag is not None
- and len(self.compiler.pagetag.filter_args.args)
- )
- or len(self.compiler.default_filters)
- ):
- s = self.create_filter_callable(
- node.escapes_code.args, "%s" % node.text, True
- )
- self.printer.writeline("__M_writer(%s)" % s)
- else:
- self.printer.writeline("__M_writer(%s)" % node.text)
- def visitControlLine(self, node):
- if node.isend:
- self.printer.writeline(None)
- if node.has_loop_context:
- self.printer.writeline("finally:")
- self.printer.writeline("loop = __M_loop._exit()")
- self.printer.writeline(None)
- else:
- self.printer.start_source(node.lineno)
- if self.compiler.enable_loop and node.keyword == "for":
- text = mangle_mako_loop(node, self.printer)
- else:
- text = node.text
- self.printer.writeline(text)
- children = node.get_children()
- # this covers the four situations where we want to insert a pass:
- # 1) a ternary control line with no children,
- # 2) a primary control line with nothing but its own ternary
- # and end control lines, and
- # 3) any control line with no content other than comments
- # 4) the first control block with no content other than comments
- def _search_for_control_line():
- for c in children:
- if isinstance(c, parsetree.Comment):
- continue
- elif isinstance(c, parsetree.ControlLine):
- return True
- return False
- if (
- not children
- or all(
- isinstance(c, (parsetree.Comment, parsetree.ControlLine))
- for c in children
- )
- and all(
- (node.is_ternary(c.keyword) or c.isend)
- for c in children
- if isinstance(c, parsetree.ControlLine)
- )
- or _search_for_control_line()
- ):
- self.printer.writeline("pass")
- def visitText(self, node):
- self.printer.start_source(node.lineno)
- self.printer.writeline("__M_writer(%s)" % repr(node.content))
- def visitTextTag(self, node):
- filtered = len(node.filter_args.args) > 0
- if filtered:
- self.printer.writelines(
- "__M_writer = context._push_writer()", "try:"
- )
- for n in node.nodes:
- n.accept_visitor(self)
- if filtered:
- self.printer.writelines(
- "finally:",
- "__M_buf, __M_writer = context._pop_buffer_and_writer()",
- "__M_writer(%s)"
- % self.create_filter_callable(
- node.filter_args.args, "__M_buf.getvalue()", False
- ),
- None,
- )
- def visitCode(self, node):
- if not node.ismodule:
- self.printer.write_indented_block(
- node.text, starting_lineno=node.lineno
- )
- if not self.in_def and len(self.identifiers.locally_assigned) > 0:
- # if we are the "template" def, fudge locally
- # declared/modified variables into the "__M_locals" dictionary,
- # which is used for def calls within the same template,
- # to simulate "enclosing scope"
- self.printer.writeline(
- "__M_locals_builtin_stored = __M_locals_builtin()"
- )
- self.printer.writeline(
- "__M_locals.update(__M_dict_builtin([(__M_key,"
- " __M_locals_builtin_stored[__M_key]) for __M_key in"
- " [%s] if __M_key in __M_locals_builtin_stored]))"
- % ",".join([repr(x) for x in node.declared_identifiers()])
- )
- def visitIncludeTag(self, node):
- self.printer.start_source(node.lineno)
- args = node.attributes.get("args")
- if args:
- self.printer.writeline(
- "runtime._include_file(context, %s, _template_uri, %s)"
- % (node.parsed_attributes["file"], args)
- )
- else:
- self.printer.writeline(
- "runtime._include_file(context, %s, _template_uri)"
- % (node.parsed_attributes["file"])
- )
- def visitNamespaceTag(self, node):
- pass
- def visitDefTag(self, node):
- pass
- def visitBlockTag(self, node):
- if node.is_anonymous:
- self.printer.writeline("%s()" % node.funcname)
- else:
- nameargs = node.get_argument_expressions(as_call=True)
- nameargs += ["**pageargs"]
- self.printer.writeline(
- "if 'parent' not in context._data or "
- "not hasattr(context._data['parent'], '%s'):" % node.funcname
- )
- self.printer.writeline(
- "context['self'].%s(%s)" % (node.funcname, ",".join(nameargs))
- )
- self.printer.writeline("\n")
- def visitCallNamespaceTag(self, node):
- # TODO: we can put namespace-specific checks here, such
- # as ensure the given namespace will be imported,
- # pre-import the namespace, etc.
- self.visitCallTag(node)
- def visitCallTag(self, node):
- self.printer.writeline("def ccall(caller):")
- export = ["body"]
- callable_identifiers = self.identifiers.branch(node, nested=True)
- body_identifiers = callable_identifiers.branch(node, nested=False)
- # we want the 'caller' passed to ccall to be used
- # for the body() function, but for other non-body()
- # <%def>s within <%call> we want the current caller
- # off the call stack (if any)
- body_identifiers.add_declared("caller")
- self.identifier_stack.append(body_identifiers)
- class DefVisitor:
- def visitDefTag(s, node):
- s.visitDefOrBase(node)
- def visitBlockTag(s, node):
- s.visitDefOrBase(node)
- def visitDefOrBase(s, node):
- self.write_inline_def(node, callable_identifiers, nested=False)
- if not node.is_anonymous:
- export.append(node.funcname)
- # remove defs that are within the <%call> from the
- # "closuredefs" defined in the body, so they dont render twice
- if node.funcname in body_identifiers.closuredefs:
- del body_identifiers.closuredefs[node.funcname]
- vis = DefVisitor()
- for n in node.nodes:
- n.accept_visitor(vis)
- self.identifier_stack.pop()
- bodyargs = node.body_decl.get_argument_expressions()
- self.printer.writeline("def body(%s):" % ",".join(bodyargs))
- # TODO: figure out best way to specify
- # buffering/nonbuffering (at call time would be better)
- buffered = False
- if buffered:
- self.printer.writelines("context._push_buffer()", "try:")
- self.write_variable_declares(body_identifiers)
- self.identifier_stack.append(body_identifiers)
- for n in node.nodes:
- n.accept_visitor(self)
- self.identifier_stack.pop()
- self.write_def_finish(node, buffered, False, False, callstack=False)
- self.printer.writelines(None, "return [%s]" % (",".join(export)), None)
- self.printer.writelines(
- # push on caller for nested call
- "context.caller_stack.nextcaller = "
- "runtime.Namespace('caller', context, "
- "callables=ccall(__M_caller))",
- "try:",
- )
- self.printer.start_source(node.lineno)
- self.printer.writelines(
- "__M_writer(%s)"
- % self.create_filter_callable([], node.expression, True),
- "finally:",
- "context.caller_stack.nextcaller = None",
- None,
- )
- class _Identifiers:
- """tracks the status of identifier names as template code is rendered."""
- def __init__(self, compiler, node=None, parent=None, nested=False):
- if parent is not None:
- # if we are the branch created in write_namespaces(),
- # we don't share any context from the main body().
- if isinstance(node, parsetree.NamespaceTag):
- self.declared = set()
- self.topleveldefs = util.SetLikeDict()
- else:
- # things that have already been declared
- # in an enclosing namespace (i.e. names we can just use)
- self.declared = (
- set(parent.declared)
- .union([c.name for c in parent.closuredefs.values()])
- .union(parent.locally_declared)
- .union(parent.argument_declared)
- )
- # if these identifiers correspond to a "nested"
- # scope, it means whatever the parent identifiers
- # had as undeclared will have been declared by that parent,
- # and therefore we have them in our scope.
- if nested:
- self.declared = self.declared.union(parent.undeclared)
- # top level defs that are available
- self.topleveldefs = util.SetLikeDict(**parent.topleveldefs)
- else:
- self.declared = set()
- self.topleveldefs = util.SetLikeDict()
- self.compiler = compiler
- # things within this level that are referenced before they
- # are declared (e.g. assigned to)
- self.undeclared = set()
- # things that are declared locally. some of these things
- # could be in the "undeclared" list as well if they are
- # referenced before declared
- self.locally_declared = set()
- # assignments made in explicit python blocks.
- # these will be propagated to
- # the context of local def calls.
- self.locally_assigned = set()
- # things that are declared in the argument
- # signature of the def callable
- self.argument_declared = set()
- # closure defs that are defined in this level
- self.closuredefs = util.SetLikeDict()
- self.node = node
- if node is not None:
- node.accept_visitor(self)
- illegal_names = self.compiler.reserved_names.intersection(
- self.locally_declared
- )
- if illegal_names:
- raise exceptions.NameConflictError(
- "Reserved words declared in template: %s"
- % ", ".join(illegal_names)
- )
- def branch(self, node, **kwargs):
- """create a new Identifiers for a new Node, with
- this Identifiers as the parent."""
- return _Identifiers(self.compiler, node, self, **kwargs)
- @property
- def defs(self):
- return set(self.topleveldefs.union(self.closuredefs).values())
- def __repr__(self):
- return (
- "Identifiers(declared=%r, locally_declared=%r, "
- "undeclared=%r, topleveldefs=%r, closuredefs=%r, "
- "argumentdeclared=%r)"
- % (
- list(self.declared),
- list(self.locally_declared),
- list(self.undeclared),
- [c.name for c in self.topleveldefs.values()],
- [c.name for c in self.closuredefs.values()],
- self.argument_declared,
- )
- )
- def check_declared(self, node):
- """update the state of this Identifiers with the undeclared
- and declared identifiers of the given node."""
- for ident in node.undeclared_identifiers():
- if ident != "context" and ident not in self.declared.union(
- self.locally_declared
- ):
- self.undeclared.add(ident)
- for ident in node.declared_identifiers():
- self.locally_declared.add(ident)
- def add_declared(self, ident):
- self.declared.add(ident)
- if ident in self.undeclared:
- self.undeclared.remove(ident)
- def visitExpression(self, node):
- self.check_declared(node)
- def visitControlLine(self, node):
- self.check_declared(node)
- def visitCode(self, node):
- if not node.ismodule:
- self.check_declared(node)
- self.locally_assigned = self.locally_assigned.union(
- node.declared_identifiers()
- )
- def visitNamespaceTag(self, node):
- # only traverse into the sub-elements of a
- # <%namespace> tag if we are the branch created in
- # write_namespaces()
- if self.node is node:
- for n in node.nodes:
- n.accept_visitor(self)
- def _check_name_exists(self, collection, node):
- existing = collection.get(node.funcname)
- collection[node.funcname] = node
- if (
- existing is not None
- and existing is not node
- and (node.is_block or existing.is_block)
- ):
- raise exceptions.CompileException(
- "%%def or %%block named '%s' already "
- "exists in this template." % node.funcname,
- **node.exception_kwargs,
- )
- def visitDefTag(self, node):
- if node.is_root() and not node.is_anonymous:
- self._check_name_exists(self.topleveldefs, node)
- elif node is not self.node:
- self._check_name_exists(self.closuredefs, node)
- for ident in node.undeclared_identifiers():
- if ident != "context" and ident not in self.declared.union(
- self.locally_declared
- ):
- self.undeclared.add(ident)
- # visit defs only one level deep
- if node is self.node:
- for ident in node.declared_identifiers():
- self.argument_declared.add(ident)
- for n in node.nodes:
- n.accept_visitor(self)
- def visitBlockTag(self, node):
- if node is not self.node and not node.is_anonymous:
- if isinstance(self.node, parsetree.DefTag):
- raise exceptions.CompileException(
- "Named block '%s' not allowed inside of def '%s'"
- % (node.name, self.node.name),
- **node.exception_kwargs,
- )
- elif isinstance(
- self.node, (parsetree.CallTag, parsetree.CallNamespaceTag)
- ):
- raise exceptions.CompileException(
- "Named block '%s' not allowed inside of <%%call> tag"
- % (node.name,),
- **node.exception_kwargs,
- )
- for ident in node.undeclared_identifiers():
- if ident != "context" and ident not in self.declared.union(
- self.locally_declared
- ):
- self.undeclared.add(ident)
- if not node.is_anonymous:
- self._check_name_exists(self.topleveldefs, node)
- self.undeclared.add(node.funcname)
- elif node is not self.node:
- self._check_name_exists(self.closuredefs, node)
- for ident in node.declared_identifiers():
- self.argument_declared.add(ident)
- for n in node.nodes:
- n.accept_visitor(self)
- def visitTextTag(self, node):
- for ident in node.undeclared_identifiers():
- if ident != "context" and ident not in self.declared.union(
- self.locally_declared
- ):
- self.undeclared.add(ident)
- def visitIncludeTag(self, node):
- self.check_declared(node)
- def visitPageTag(self, node):
- for ident in node.declared_identifiers():
- self.argument_declared.add(ident)
- self.check_declared(node)
- def visitCallNamespaceTag(self, node):
- self.visitCallTag(node)
- def visitCallTag(self, node):
- if node is self.node:
- for ident in node.undeclared_identifiers():
- if ident != "context" and ident not in self.declared.union(
- self.locally_declared
- ):
- self.undeclared.add(ident)
- for ident in node.declared_identifiers():
- self.argument_declared.add(ident)
- for n in node.nodes:
- n.accept_visitor(self)
- else:
- for ident in node.undeclared_identifiers():
- if ident != "context" and ident not in self.declared.union(
- self.locally_declared
- ):
- self.undeclared.add(ident)
- _FOR_LOOP = re.compile(
- r"^for\s+((?:\(?)\s*"
- r"(?:\(?)\s*[A-Za-z_][A-Za-z_0-9]*"
- r"(?:\s*,\s*(?:[A-Za-z_][A-Za-z_0-9]*),??)*\s*(?:\)?)"
- r"(?:\s*,\s*(?:"
- r"(?:\(?)\s*[A-Za-z_][A-Za-z_0-9]*"
- r"(?:\s*,\s*(?:[A-Za-z_][A-Za-z_0-9]*),??)*\s*(?:\)?)"
- r"),??)*\s*(?:\)?))\s+in\s+(.*):"
- )
- def mangle_mako_loop(node, printer):
- """converts a for loop into a context manager wrapped around a for loop
- when access to the `loop` variable has been detected in the for loop body
- """
- loop_variable = LoopVariable()
- node.accept_visitor(loop_variable)
- if loop_variable.detected:
- node.nodes[-1].has_loop_context = True
- match = _FOR_LOOP.match(node.text)
- if match:
- printer.writelines(
- "loop = __M_loop._enter(%s)" % match.group(2),
- "try:"
- # 'with __M_loop(%s) as loop:' % match.group(2)
- )
- text = "for %s in loop:" % match.group(1)
- else:
- raise SyntaxError("Couldn't apply loop context: %s" % node.text)
- else:
- text = node.text
- return text
- class LoopVariable:
- """A node visitor which looks for the name 'loop' within undeclared
- identifiers."""
- def __init__(self):
- self.detected = False
- def _loop_reference_detected(self, node):
- if "loop" in node.undeclared_identifiers():
- self.detected = True
- else:
- for n in node.get_children():
- n.accept_visitor(self)
- def visitControlLine(self, node):
- self._loop_reference_detected(node)
- def visitCode(self, node):
- self._loop_reference_detected(node)
- def visitExpression(self, node):
- self._loop_reference_detected(node)
|