| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929 |
- # codegen.py
- # Copyright (C) 2006, 2007, 2008, 2009, 2010 Michael Bayer mike_mp@zzzcomputing.com
- #
- # 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 time
- import re
- from mako.pygen import PythonPrinter
- from mako import util, ast, parsetree, filters
- MAGIC_NUMBER = 5
- def compile(node,
- uri,
- filename=None,
- default_filters=None,
- buffer_filters=None,
- imports=None,
- source_encoding=None,
- generate_magic_comment=True,
- disable_unicode=False):
-
- """Generate module source code given a parsetree node,
- uri, and optional source filename"""
- # if on Py2K, push the "source_encoding" string to be
- # a bytestring itself, as we will be embedding it into
- # the generated source and we don't want to coerce the
- # result into a unicode object, in "disable_unicode" mode
- if not util.py3k and isinstance(source_encoding, unicode):
- source_encoding = source_encoding.encode(source_encoding)
-
-
- buf = util.FastEncodingBuffer()
- printer = PythonPrinter(buf)
- _GenerateRenderMethod(printer,
- _CompileContext(uri,
- filename,
- default_filters,
- buffer_filters,
- imports,
- source_encoding,
- generate_magic_comment,
- disable_unicode),
- node)
- return buf.getvalue()
- class _CompileContext(object):
- def __init__(self,
- uri,
- filename,
- default_filters,
- buffer_filters,
- imports,
- source_encoding,
- generate_magic_comment,
- disable_unicode):
- self.uri = uri
- self.filename = filename
- self.default_filters = default_filters
- self.buffer_filters = buffer_filters
- self.imports = imports
- self.source_encoding = source_encoding
- self.generate_magic_comment = generate_magic_comment
- self.disable_unicode = disable_unicode
-
- class _GenerateRenderMethod(object):
- """A template visitor object which generates the
- full module source for a template.
-
- """
- def __init__(self, printer, compiler, node):
- self.printer = printer
- self.last_source_line = -1
- self.compiler = compiler
- self.node = node
- self.identifier_stack = [None]
-
- self.in_def = isinstance(node, parsetree.DefTag)
- if self.in_def:
- name = "render_" + node.name
- args = node.function_decl.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
- 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'))
- 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)
-
- @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 = []
- encoding =[None]
- self.compiler.pagetag = None
-
- class FindTopLevel(object):
- 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()
- module_identifiers.declared = module_ident
-
- # module-level names, python code
- if self.compiler.generate_magic_comment and \
- self.compiler.source_encoding:
- self.printer.writeline("# -*- encoding:%s -*-" %
- self.compiler.source_encoding)
-
- self.printer.writeline("from mako import runtime, filters, cache")
- self.printer.writeline("UNDEFINED = runtime.UNDEFINED")
- 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(
- "_template_filename=%r" % self.compiler.filename)
- self.printer.writeline("_template_uri=%r" % self.compiler.uri)
- self.printer.writeline(
- "_template_cache=cache.Cache(__name__, _modified_time)")
- 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)
- module_identifiers.topleveldefs = \
- module_identifiers.topleveldefs.\
- union(main_identifiers.topleveldefs)
- module_identifiers.declared.add("UNDEFINED")
- 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("\n\n")
- 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 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.writelines(
- "def %s(%s):" % (name, ','.join(args)),
- "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 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("\n\n")
- 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.write_source_comment(n)
- self.printer.write_indented_block(n.text)
- 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 node.attributes.has_key('import'):
- self.compiler.has_ns_imports = True
- self.write_source_comment(node)
- if len(node.nodes):
- self.printer.writeline("def make_namespace():")
- export = []
- identifiers = self.compiler.identifiers.branch(node)
- self.in_def = True
- class NSDefVisitor(object):
- def visitDefTag(s, node):
- self.write_inline_def(node, identifiers, nested=False)
- export.append(node.name)
- 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"
- self.printer.writeline(
- "ns = runtime.Namespace(%r, context._clean_inheritance_tokens(),"
- " templateuri=%s, callables=%s, calling_uri=_template_uri, module=%s)" %
- (
- node.name,
- node.parsed_attributes.get('file', 'None'),
- callable_name,
- node.parsed_attributes.get('module', 'None'))
- )
- 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("\n")
- 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 = dict([(c.name, 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.name 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 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.iteritems():
- if ns.attributes.has_key('import'):
- self.printer.writeline(
- "_mako_get_namespace(context, %r)._populate(_import_ns, %r)" %
- (
- ident,
- re.split(r'\s*,\s*', ns.attributes['import'])
- ))
-
- for ident in to_write:
- if ident in comp_idents:
- comp = comp_idents[ident]
- 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):
- self.printer.writeline(
- "%s = _import_ns.get(%r, context.get(%r, UNDEFINED))" %
- (ident, ident, ident))
- else:
- self.printer.writeline("%s = context.get(%r, UNDEFINED)" % (ident, ident))
-
- self.printer.writeline("__M_writer = context.writer()")
-
- def write_source_comment(self, node):
- """write a source comment containing the line number of the corresponding template line."""
- if self.last_source_line != node.lineno:
- self.printer.writeline("# SOURCE LINE %d" % node.lineno)
- self.last_source_line = node.lineno
- def write_def_decl(self, node, identifiers):
- """write a locally-available callable referencing a top-level def"""
- funcname = node.function_decl.funcname
- namedecls = node.function_decl.get_argument_expressions()
- nameargs = node.function_decl.get_argument_expressions(include_defaults=False)
-
- 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.function_decl.get_argument_expressions()
-
- decorator = node.decorator
- if decorator:
- self.printer.writeline("@runtime._decorate_inline(context, %s)" % decorator)
- self.printer.writeline("def %s(%s):" % (node.name, ",".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(
- "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.name,
- 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))
- cacheargs = {}
- for arg in (
- ('cache_type', 'type'), ('cache_dir', 'data_dir'),
- ('cache_timeout', 'expiretime'), ('cache_url', 'url')):
- val = node_or_pagetag.parsed_attributes.get(arg[0], None)
- if val is not None:
- if arg[1] == 'expiretime':
- cacheargs[arg[1]] = int(eval(val))
- else:
- cacheargs[arg[1]] = val
- else:
- if self.compiler.pagetag is not None:
- val = self.compiler.pagetag.parsed_attributes.get(arg[0], None)
- if val is not None:
- if arg[1] == 'expiretime':
- cacheargs[arg[1]] == int(eval(val))
- else:
- cacheargs[arg[1]] = val
-
- self.printer.writeline("def %s(%s):" % (name, ','.join(args)))
-
- # form "arg1, arg2, arg3=arg3, arg4=arg4", etc.
- pass_args = [
- '=' in a and "%s=%s" % ((a.split('=')[0],)*2) or a
- for a in args
- ]
- self.write_variable_declares(
- identifiers,
- toplevel=toplevel,
- limit=node_or_pagetag.undeclared_identifiers()
- )
- if buffered:
- s = "context.get('local')."\
- "get_cached(%s, defname=%r, %screatefunc=lambda:__M_%s(%s))" % \
- (cachekey, name,
- ''.join(["%s=%s, " % (k,v) for k, v in cacheargs.iteritems()]),
- name, ','.join(pass_args))
- # 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')."
- "get_cached(%s, defname=%r, %screatefunc=lambda:__M_%s(%s)))" %
- (cachekey, name,
- ''.join(["%s=%s, " % (k,v) for k, v in cacheargs.iteritems()]),
- name, ','.join(pass_args)),
- "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
- elif self.compiler.disable_unicode:
- return filters.NON_UNICODE_ESCAPES.get(name, 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:
- 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:
- x = e
- e = locate_encode(e)
- assert e is not None
- target = "%s(%s)" % (e, target)
- return target
-
- def visitExpression(self, node):
- self.write_source_comment(node)
- 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:
- if not node.get_children():
- self.printer.writeline("pass")
- self.printer.writeline(None)
- else:
- self.write_source_comment(node)
- self.printer.writeline(node.text)
-
- def visitText(self, node):
- self.write_source_comment(node)
- 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.write_source_comment(node)
- self.printer.write_indented_block(node.text)
- 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.write_source_comment(node)
- 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 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(object):
- def visitDefTag(s, node):
- self.write_inline_def(node, callable_identifiers, nested=False)
- export.append(node.name)
- # remove defs that are within the <%call> from the "closuredefs" defined
- # in the body, so they dont render twice
- if node.name in body_identifiers.closuredefs:
- del body_identifiers.closuredefs[node.name]
- 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(
- # get local reference to current caller, if any
- "caller = context.caller_stack._get_caller()",
- # push on caller for nested call
- "context.caller_stack.nextcaller = "
- "runtime.Namespace('caller', context, callables=ccall(caller))",
- "try:")
- self.write_source_comment(node)
- self.printer.writelines(
- "__M_writer(%s)" % self.create_filter_callable([], node.expression, True),
- "finally:",
- "context.caller_stack.nextcaller = None",
- None
- )
- class _Identifiers(object):
- """tracks the status of identifier names as template code is rendered."""
-
- def __init__(self, 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()
-
- # 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)
-
- def branch(self, node, **kwargs):
- """create a new Identifiers for a new Node, with
- this Identifiers as the parent."""
-
- return _Identifiers(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 visitDefTag(self, node):
- if node.is_root():
- self.topleveldefs[node.name] = node
- elif node is not self.node:
- self.closuredefs[node.name] = 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 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)
-
|