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