| 1 | # codegen.py |
|---|
| 2 | # Copyright (C) 2006, 2007, 2008, 2009 Michael Bayer mike_mp@zzzcomputing.com |
|---|
| 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 source code.""" |
|---|
| 8 | |
|---|
| 9 | import time |
|---|
| 10 | import re |
|---|
| 11 | from mako.pygen import PythonPrinter |
|---|
| 12 | from mako import util, ast, parsetree, filters |
|---|
| 13 | |
|---|
| 14 | MAGIC_NUMBER = 5 |
|---|
| 15 | |
|---|
| 16 | |
|---|
| 17 | def compile(node, uri, filename=None, default_filters=None, buffer_filters=None, imports=None, source_encoding=None, generate_unicode=True): |
|---|
| 18 | """generate module source code given a parsetree node, uri, and optional source filename""" |
|---|
| 19 | |
|---|
| 20 | buf = util.FastEncodingBuffer(unicode=generate_unicode) |
|---|
| 21 | |
|---|
| 22 | printer = PythonPrinter(buf) |
|---|
| 23 | _GenerateRenderMethod(printer, _CompileContext(uri, filename, default_filters, buffer_filters, imports, source_encoding, generate_unicode), node) |
|---|
| 24 | return buf.getvalue() |
|---|
| 25 | |
|---|
| 26 | class _CompileContext(object): |
|---|
| 27 | def __init__(self, uri, filename, default_filters, buffer_filters, imports, source_encoding, generate_unicode): |
|---|
| 28 | self.uri = uri |
|---|
| 29 | self.filename = filename |
|---|
| 30 | self.default_filters = default_filters |
|---|
| 31 | self.buffer_filters = buffer_filters |
|---|
| 32 | self.imports = imports |
|---|
| 33 | self.source_encoding = source_encoding |
|---|
| 34 | self.generate_unicode = generate_unicode |
|---|
| 35 | |
|---|
| 36 | class _GenerateRenderMethod(object): |
|---|
| 37 | """a template visitor object which generates the full module source for a template.""" |
|---|
| 38 | def __init__(self, printer, compiler, node): |
|---|
| 39 | self.printer = printer |
|---|
| 40 | self.last_source_line = -1 |
|---|
| 41 | self.compiler = compiler |
|---|
| 42 | self.node = node |
|---|
| 43 | self.identifier_stack = [None] |
|---|
| 44 | |
|---|
| 45 | self.in_def = isinstance(node, parsetree.DefTag) |
|---|
| 46 | |
|---|
| 47 | if self.in_def: |
|---|
| 48 | name = "render_" + node.name |
|---|
| 49 | args = node.function_decl.get_argument_expressions() |
|---|
| 50 | filtered = len(node.filter_args.args) > 0 |
|---|
| 51 | buffered = eval(node.attributes.get('buffered', 'False')) |
|---|
| 52 | cached = eval(node.attributes.get('cached', 'False')) |
|---|
| 53 | defs = None |
|---|
| 54 | pagetag = None |
|---|
| 55 | else: |
|---|
| 56 | defs = self.write_toplevel() |
|---|
| 57 | pagetag = self.compiler.pagetag |
|---|
| 58 | name = "render_body" |
|---|
| 59 | if pagetag is not None: |
|---|
| 60 | args = pagetag.body_decl.get_argument_expressions() |
|---|
| 61 | if not pagetag.body_decl.kwargs: |
|---|
| 62 | args += ['**pageargs'] |
|---|
| 63 | cached = eval(pagetag.attributes.get('cached', 'False')) |
|---|
| 64 | else: |
|---|
| 65 | args = ['**pageargs'] |
|---|
| 66 | cached = False |
|---|
| 67 | buffered = filtered = False |
|---|
| 68 | if args is None: |
|---|
| 69 | args = ['context'] |
|---|
| 70 | else: |
|---|
| 71 | args = [a for a in ['context'] + args] |
|---|
| 72 | |
|---|
| 73 | self.write_render_callable(pagetag or node, name, args, buffered, filtered, cached) |
|---|
| 74 | |
|---|
| 75 | if defs is not None: |
|---|
| 76 | for node in defs: |
|---|
| 77 | _GenerateRenderMethod(printer, compiler, node) |
|---|
| 78 | |
|---|
| 79 | identifiers = property(lambda self:self.identifier_stack[-1]) |
|---|
| 80 | |
|---|
| 81 | def write_toplevel(self): |
|---|
| 82 | """traverse a template structure for module-level directives and generate the |
|---|
| 83 | start of module-level code.""" |
|---|
| 84 | inherit = [] |
|---|
| 85 | namespaces = {} |
|---|
| 86 | module_code = [] |
|---|
| 87 | encoding =[None] |
|---|
| 88 | |
|---|
| 89 | self.compiler.pagetag = None |
|---|
| 90 | |
|---|
| 91 | class FindTopLevel(object): |
|---|
| 92 | def visitInheritTag(s, node): |
|---|
| 93 | inherit.append(node) |
|---|
| 94 | def visitNamespaceTag(s, node): |
|---|
| 95 | namespaces[node.name] = node |
|---|
| 96 | def visitPageTag(s, node): |
|---|
| 97 | self.compiler.pagetag = node |
|---|
| 98 | def visitCode(s, node): |
|---|
| 99 | if node.ismodule: |
|---|
| 100 | module_code.append(node) |
|---|
| 101 | |
|---|
| 102 | f = FindTopLevel() |
|---|
| 103 | for n in self.node.nodes: |
|---|
| 104 | n.accept_visitor(f) |
|---|
| 105 | |
|---|
| 106 | self.compiler.namespaces = namespaces |
|---|
| 107 | |
|---|
| 108 | module_ident = util.Set() |
|---|
| 109 | for n in module_code: |
|---|
| 110 | module_ident = module_ident.union(n.declared_identifiers()) |
|---|
| 111 | |
|---|
| 112 | module_identifiers = _Identifiers() |
|---|
| 113 | module_identifiers.declared = module_ident |
|---|
| 114 | |
|---|
| 115 | # module-level names, python code |
|---|
| 116 | if not self.compiler.generate_unicode and self.compiler.source_encoding: |
|---|
| 117 | self.printer.writeline("# -*- encoding:%s -*-" % self.compiler.source_encoding) |
|---|
| 118 | |
|---|
| 119 | self.printer.writeline("from mako import runtime, filters, cache") |
|---|
| 120 | self.printer.writeline("UNDEFINED = runtime.UNDEFINED") |
|---|
| 121 | self.printer.writeline("__M_dict_builtin = dict") |
|---|
| 122 | self.printer.writeline("__M_locals_builtin = locals") |
|---|
| 123 | self.printer.writeline("_magic_number = %s" % repr(MAGIC_NUMBER)) |
|---|
| 124 | self.printer.writeline("_modified_time = %s" % repr(time.time())) |
|---|
| 125 | self.printer.writeline("_template_filename=%s" % repr(self.compiler.filename)) |
|---|
| 126 | self.printer.writeline("_template_uri=%s" % repr(self.compiler.uri)) |
|---|
| 127 | self.printer.writeline("_template_cache=cache.Cache(__name__, _modified_time)") |
|---|
| 128 | self.printer.writeline("_source_encoding=%s" % repr(self.compiler.source_encoding)) |
|---|
| 129 | if self.compiler.imports: |
|---|
| 130 | buf = '' |
|---|
| 131 | for imp in self.compiler.imports: |
|---|
| 132 | buf += imp + "\n" |
|---|
| 133 | self.printer.writeline(imp) |
|---|
| 134 | impcode = ast.PythonCode(buf, source='', lineno=0, pos=0, filename='template defined imports') |
|---|
| 135 | else: |
|---|
| 136 | impcode = None |
|---|
| 137 | |
|---|
| 138 | main_identifiers = module_identifiers.branch(self.node) |
|---|
| 139 | module_identifiers.topleveldefs = module_identifiers.topleveldefs.union(main_identifiers.topleveldefs) |
|---|
| 140 | [module_identifiers.declared.add(x) for x in ["UNDEFINED"]] |
|---|
| 141 | if impcode: |
|---|
| 142 | [module_identifiers.declared.add(x) for x in impcode.declared_identifiers] |
|---|
| 143 | |
|---|
| 144 | self.compiler.identifiers = module_identifiers |
|---|
| 145 | self.printer.writeline("_exports = %s" % repr([n.name for n in main_identifiers.topleveldefs.values()])) |
|---|
| 146 | self.printer.write("\n\n") |
|---|
| 147 | |
|---|
| 148 | if len(module_code): |
|---|
| 149 | self.write_module_code(module_code) |
|---|
| 150 | |
|---|
| 151 | if len(inherit): |
|---|
| 152 | self.write_namespaces(namespaces) |
|---|
| 153 | self.write_inherit(inherit[-1]) |
|---|
| 154 | elif len(namespaces): |
|---|
| 155 | self.write_namespaces(namespaces) |
|---|
| 156 | |
|---|
| 157 | return main_identifiers.topleveldefs.values() |
|---|
| 158 | |
|---|
| 159 | def write_render_callable(self, node, name, args, buffered, filtered, cached): |
|---|
| 160 | """write a top-level render callable. |
|---|
| 161 | |
|---|
| 162 | this could be the main render() method or that of a top-level def.""" |
|---|
| 163 | |
|---|
| 164 | if self.in_def: |
|---|
| 165 | decorator = node.decorator |
|---|
| 166 | if decorator: |
|---|
| 167 | self.printer.writeline("@runtime._decorate_toplevel(%s)" % decorator) |
|---|
| 168 | |
|---|
| 169 | self.printer.writelines( |
|---|
| 170 | "def %s(%s):" % (name, ','.join(args)), |
|---|
| 171 | "context.caller_stack._push_frame()", |
|---|
| 172 | "try:" |
|---|
| 173 | ) |
|---|
| 174 | if buffered or filtered or cached: |
|---|
| 175 | self.printer.writeline("context._push_buffer()") |
|---|
| 176 | |
|---|
| 177 | self.identifier_stack.append(self.compiler.identifiers.branch(self.node)) |
|---|
| 178 | if not self.in_def and '**pageargs' in args: |
|---|
| 179 | self.identifier_stack[-1].argument_declared.add('pageargs') |
|---|
| 180 | |
|---|
| 181 | if not self.in_def and (len(self.identifiers.locally_assigned) > 0 or len(self.identifiers.argument_declared)>0): |
|---|
| 182 | self.printer.writeline("__M_locals = __M_dict_builtin(%s)" % ','.join(["%s=%s" % (x, x) for x in self.identifiers.argument_declared])) |
|---|
| 183 | |
|---|
| 184 | self.write_variable_declares(self.identifiers, toplevel=True) |
|---|
| 185 | |
|---|
| 186 | for n in self.node.nodes: |
|---|
| 187 | n.accept_visitor(self) |
|---|
| 188 | |
|---|
| 189 | self.write_def_finish(self.node, buffered, filtered, cached) |
|---|
| 190 | self.printer.writeline(None) |
|---|
| 191 | self.printer.write("\n\n") |
|---|
| 192 | if cached: |
|---|
| 193 | self.write_cache_decorator(node, name, args, buffered, self.identifiers, toplevel=True) |
|---|
| 194 | |
|---|
| 195 | def write_module_code(self, module_code): |
|---|
| 196 | """write module-level template code, i.e. that which is enclosed in <%! %> tags |
|---|
| 197 | in the template.""" |
|---|
| 198 | for n in module_code: |
|---|
| 199 | self.write_source_comment(n) |
|---|
| 200 | self.printer.write_indented_block(n.text) |
|---|
| 201 | |
|---|
| 202 | def write_inherit(self, node): |
|---|
| 203 | """write the module-level inheritance-determination callable.""" |
|---|
| 204 | self.printer.writelines( |
|---|
| 205 | "def _mako_inherit(template, context):", |
|---|
| 206 | "_mako_generate_namespaces(context)", |
|---|
| 207 | "return runtime._inherit_from(context, %s, _template_uri)" % (node.parsed_attributes['file']), |
|---|
| 208 | None |
|---|
| 209 | ) |
|---|
| 210 | |
|---|
| 211 | def write_namespaces(self, namespaces): |
|---|
| 212 | """write the module-level namespace-generating callable.""" |
|---|
| 213 | self.printer.writelines( |
|---|
| 214 | "def _mako_get_namespace(context, name):", |
|---|
| 215 | "try:", |
|---|
| 216 | "return context.namespaces[(__name__, name)]", |
|---|
| 217 | "except KeyError:", |
|---|
| 218 | "_mako_generate_namespaces(context)", |
|---|
| 219 | "return context.namespaces[(__name__, name)]", |
|---|
| 220 | None,None |
|---|
| 221 | ) |
|---|
| 222 | self.printer.writeline("def _mako_generate_namespaces(context):") |
|---|
| 223 | for node in namespaces.values(): |
|---|
| 224 | if node.attributes.has_key('import'): |
|---|
| 225 | self.compiler.has_ns_imports = True |
|---|
| 226 | self.write_source_comment(node) |
|---|
| 227 | if len(node.nodes): |
|---|
| 228 | self.printer.writeline("def make_namespace():") |
|---|
| 229 | export = [] |
|---|
| 230 | identifiers = self.compiler.identifiers.branch(node) |
|---|
| 231 | class NSDefVisitor(object): |
|---|
| 232 | def visitDefTag(s, node): |
|---|
| 233 | self.write_inline_def(node, identifiers, nested=False) |
|---|
| 234 | export.append(node.name) |
|---|
| 235 | vis = NSDefVisitor() |
|---|
| 236 | for n in node.nodes: |
|---|
| 237 | n.accept_visitor(vis) |
|---|
| 238 | self.printer.writeline("return [%s]" % (','.join(export))) |
|---|
| 239 | self.printer.writeline(None) |
|---|
| 240 | callable_name = "make_namespace()" |
|---|
| 241 | else: |
|---|
| 242 | callable_name = "None" |
|---|
| 243 | self.printer.writeline("ns = runtime.Namespace(%s, context._clean_inheritance_tokens(), templateuri=%s, callables=%s, calling_uri=_template_uri, module=%s)" % (repr(node.name), node.parsed_attributes.get('file', 'None'), callable_name, node.parsed_attributes.get('module', 'None'))) |
|---|
| 244 | if eval(node.attributes.get('inheritable', "False")): |
|---|
| 245 | self.printer.writeline("context['self'].%s = ns" % (node.name)) |
|---|
| 246 | self.printer.writeline("context.namespaces[(__name__, %s)] = ns" % repr(node.name)) |
|---|
| 247 | self.printer.write("\n") |
|---|
| 248 | if not len(namespaces): |
|---|
| 249 | self.printer.writeline("pass") |
|---|
| 250 | self.printer.writeline(None) |
|---|
| 251 | |
|---|
| 252 | def write_variable_declares(self, identifiers, toplevel=False, limit=None): |
|---|
| 253 | """write variable declarations at the top of a function. |
|---|
| 254 | |
|---|
| 255 | the variable declarations are in the form of callable definitions for defs and/or |
|---|
| 256 | name lookup within the function's context argument. the names declared are based on the |
|---|
| 257 | names that are referenced in the function body, which don't otherwise have any explicit |
|---|
| 258 | assignment operation. names that are assigned within the body are assumed to be |
|---|
| 259 | locally-scoped variables and are not separately declared. |
|---|
| 260 | |
|---|
| 261 | for def callable definitions, if the def is a top-level callable then a |
|---|
| 262 | 'stub' callable is generated which wraps the current Context into a closure. if the def |
|---|
| 263 | is not top-level, it is fully rendered as a local closure.""" |
|---|
| 264 | |
|---|
| 265 | # collection of all defs available to us in this scope |
|---|
| 266 | comp_idents = dict([(c.name, c) for c in identifiers.defs]) |
|---|
| 267 | to_write = util.Set() |
|---|
| 268 | |
|---|
| 269 | # write "context.get()" for all variables we are going to need that arent in the namespace yet |
|---|
| 270 | to_write = to_write.union(identifiers.undeclared) |
|---|
| 271 | |
|---|
| 272 | # write closure functions for closures that we define right here |
|---|
| 273 | to_write = to_write.union(util.Set([c.name for c in identifiers.closuredefs.values()])) |
|---|
| 274 | |
|---|
| 275 | # remove identifiers that are declared in the argument signature of the callable |
|---|
| 276 | to_write = to_write.difference(identifiers.argument_declared) |
|---|
| 277 | |
|---|
| 278 | # remove identifiers that we are going to assign to. in this way we mimic Python's behavior, |
|---|
| 279 | # i.e. assignment to a variable within a block means that variable is now a "locally declared" var, |
|---|
| 280 | # which cannot be referenced beforehand. |
|---|
| 281 | to_write = to_write.difference(identifiers.locally_declared) |
|---|
| 282 | |
|---|
| 283 | # if a limiting set was sent, constraint to those items in that list |
|---|
| 284 | # (this is used for the caching decorator) |
|---|
| 285 | if limit is not None: |
|---|
| 286 | to_write = to_write.intersection(limit) |
|---|
| 287 | |
|---|
| 288 | if toplevel and getattr(self.compiler, 'has_ns_imports', False): |
|---|
| 289 | self.printer.writeline("_import_ns = {}") |
|---|
| 290 | self.compiler.has_imports = True |
|---|
| 291 | for ident, ns in self.compiler.namespaces.iteritems(): |
|---|
| 292 | if ns.attributes.has_key('import'): |
|---|
| 293 | self.printer.writeline("_mako_get_namespace(context, %s)._populate(_import_ns, %s)" % (repr(ident), repr(re.split(r'\s*,\s*', ns.attributes['import'])))) |
|---|
| 294 | |
|---|
| 295 | for ident in to_write: |
|---|
| 296 | if ident in comp_idents: |
|---|
| 297 | comp = comp_idents[ident] |
|---|
| 298 | if comp.is_root(): |
|---|
| 299 | self.write_def_decl(comp, identifiers) |
|---|
| 300 | else: |
|---|
| 301 | self.write_inline_def(comp, identifiers, nested=True) |
|---|
| 302 | elif ident in self.compiler.namespaces: |
|---|
| 303 | self.printer.writeline("%s = _mako_get_namespace(context, %s)" % (ident, repr(ident))) |
|---|
| 304 | else: |
|---|
| 305 | if getattr(self.compiler, 'has_ns_imports', False): |
|---|
| 306 | self.printer.writeline("%s = _import_ns.get(%s, context.get(%s, UNDEFINED))" % (ident, repr(ident), repr(ident))) |
|---|
| 307 | else: |
|---|
| 308 | self.printer.writeline("%s = context.get(%s, UNDEFINED)" % (ident, repr(ident))) |
|---|
| 309 | |
|---|
| 310 | self.printer.writeline("__M_writer = context.writer()") |
|---|
| 311 | |
|---|
| 312 | def write_source_comment(self, node): |
|---|
| 313 | """write a source comment containing the line number of the corresponding template line.""" |
|---|
| 314 | if self.last_source_line != node.lineno: |
|---|
| 315 | self.printer.writeline("# SOURCE LINE %d" % node.lineno) |
|---|
| 316 | self.last_source_line = node.lineno |
|---|
| 317 | |
|---|
| 318 | def write_def_decl(self, node, identifiers): |
|---|
| 319 | """write a locally-available callable referencing a top-level def""" |
|---|
| 320 | funcname = node.function_decl.funcname |
|---|
| 321 | namedecls = node.function_decl.get_argument_expressions() |
|---|
| 322 | nameargs = node.function_decl.get_argument_expressions(include_defaults=False) |
|---|
| 323 | if not self.in_def and (len(self.identifiers.locally_assigned) > 0 or len(self.identifiers.argument_declared) > 0): |
|---|
| 324 | nameargs.insert(0, 'context.locals_(__M_locals)') |
|---|
| 325 | else: |
|---|
| 326 | nameargs.insert(0, 'context') |
|---|
| 327 | self.printer.writeline("def %s(%s):" % (funcname, ",".join(namedecls))) |
|---|
| 328 | self.printer.writeline("return render_%s(%s)" % (funcname, ",".join(nameargs))) |
|---|
| 329 | self.printer.writeline(None) |
|---|
| 330 | |
|---|
| 331 | def write_inline_def(self, node, identifiers, nested): |
|---|
| 332 | """write a locally-available def callable inside an enclosing def.""" |
|---|
| 333 | namedecls = node.function_decl.get_argument_expressions() |
|---|
| 334 | |
|---|
| 335 | decorator = node.decorator |
|---|
| 336 | if decorator: |
|---|
| 337 | self.printer.writeline("@runtime._decorate_inline(context, %s)" % decorator) |
|---|
| 338 | self.printer.writeline("def %s(%s):" % (node.name, ",".join(namedecls))) |
|---|
| 339 | filtered = len(node.filter_args.args) > 0 |
|---|
| 340 | buffered = eval(node.attributes.get('buffered', 'False')) |
|---|
| 341 | cached = eval(node.attributes.get('cached', 'False')) |
|---|
| 342 | self.printer.writelines( |
|---|
| 343 | "context.caller_stack._push_frame()", |
|---|
| 344 | "try:" |
|---|
| 345 | ) |
|---|
| 346 | if buffered or filtered or cached: |
|---|
| 347 | self.printer.writelines( |
|---|
| 348 | "context._push_buffer()", |
|---|
| 349 | ) |
|---|
| 350 | |
|---|
| 351 | identifiers = identifiers.branch(node, nested=nested) |
|---|
| 352 | |
|---|
| 353 | self.write_variable_declares(identifiers) |
|---|
| 354 | |
|---|
| 355 | self.identifier_stack.append(identifiers) |
|---|
| 356 | for n in node.nodes: |
|---|
| 357 | n.accept_visitor(self) |
|---|
| 358 | self.identifier_stack.pop() |
|---|
| 359 | |
|---|
| 360 | self.write_def_finish(node, buffered, filtered, cached) |
|---|
| 361 | self.printer.writeline(None) |
|---|
| 362 | if cached: |
|---|
| 363 | self.write_cache_decorator(node, node.name, namedecls, False, identifiers, inline=True, toplevel=False) |
|---|
| 364 | |
|---|
| 365 | def write_def_finish(self, node, buffered, filtered, cached, callstack=True): |
|---|
| 366 | """write the end section of a rendering function, either outermost or inline. |
|---|
| 367 | |
|---|
| 368 | this takes into account if the rendering function was filtered, buffered, etc. |
|---|
| 369 | and closes the corresponding try: block if any, and writes code to retrieve captured content, |
|---|
| 370 | apply filters, send proper return value.""" |
|---|
| 371 | if not buffered and not cached and not filtered: |
|---|
| 372 | self.printer.writeline("return ''") |
|---|
| 373 | if callstack: |
|---|
| 374 | self.printer.writelines( |
|---|
| 375 | "finally:", |
|---|
| 376 | "context.caller_stack._pop_frame()", |
|---|
| 377 | None |
|---|
| 378 | ) |
|---|
| 379 | |
|---|
| 380 | if buffered or filtered or cached: |
|---|
| 381 | if buffered or cached: |
|---|
| 382 | # in a caching scenario, don't try to get a writer |
|---|
| 383 | # from the context after popping; assume the caching |
|---|
| 384 | # implemenation might be using a context with no |
|---|
| 385 | # extra buffers |
|---|
| 386 | self.printer.writelines( |
|---|
| 387 | "finally:", |
|---|
| 388 | "__M_buf = context._pop_buffer()" |
|---|
| 389 | ) |
|---|
| 390 | else: |
|---|
| 391 | self.printer.writelines( |
|---|
| 392 | "finally:", |
|---|
| 393 | "__M_buf, __M_writer = context._pop_buffer_and_writer()" |
|---|
| 394 | ) |
|---|
| 395 | |
|---|
| 396 | if callstack: |
|---|
| 397 | self.printer.writeline("context.caller_stack._pop_frame()") |
|---|
| 398 | |
|---|
| 399 | s = "__M_buf.getvalue()" |
|---|
| 400 | if filtered: |
|---|
| 401 | s = self.create_filter_callable(node.filter_args.args, s, False) |
|---|
| 402 | self.printer.writeline(None) |
|---|
| 403 | if buffered and not cached: |
|---|
| 404 | s = self.create_filter_callable(self.compiler.buffer_filters, s, False) |
|---|
| 405 | if buffered or cached: |
|---|
| 406 | self.printer.writeline("return %s" % s) |
|---|
| 407 | else: |
|---|
| 408 | self.printer.writelines( |
|---|
| 409 | "__M_writer(%s)" % s, |
|---|
| 410 | "return ''" |
|---|
| 411 | ) |
|---|
| 412 | |
|---|
| 413 | def write_cache_decorator(self, node_or_pagetag, name, args, buffered, identifiers, inline=False, toplevel=False): |
|---|
| 414 | """write a post-function decorator to replace a rendering callable with a cached version of itself.""" |
|---|
| 415 | self.printer.writeline("__M_%s = %s" % (name, name)) |
|---|
| 416 | cachekey = node_or_pagetag.parsed_attributes.get('cache_key', repr(name)) |
|---|
| 417 | cacheargs = {} |
|---|
| 418 | for arg in (('cache_type', 'type'), ('cache_dir', 'data_dir'), ('cache_timeout', 'expiretime'), ('cache_url', 'url')): |
|---|
| 419 | val = node_or_pagetag.parsed_attributes.get(arg[0], None) |
|---|
| 420 | if val is not None: |
|---|
| 421 | if arg[1] == 'expiretime': |
|---|
| 422 | cacheargs[arg[1]] = int(eval(val)) |
|---|
| 423 | else: |
|---|
| 424 | cacheargs[arg[1]] = val |
|---|
| 425 | else: |
|---|
| 426 | if self.compiler.pagetag is not None: |
|---|
| 427 | val = self.compiler.pagetag.parsed_attributes.get(arg[0], None) |
|---|
| 428 | if val is not None: |
|---|
| 429 | if arg[1] == 'expiretime': |
|---|
| 430 | cacheargs[arg[1]] == int(eval(val)) |
|---|
| 431 | else: |
|---|
| 432 | cacheargs[arg[1]] = val |
|---|
| 433 | |
|---|
| 434 | self.printer.writeline("def %s(%s):" % (name, ','.join(args))) |
|---|
| 435 | |
|---|
| 436 | # form "arg1, arg2, arg3=arg3, arg4=arg4", etc. |
|---|
| 437 | pass_args = [ '=' in a and "%s=%s" % ((a.split('=')[0],)*2) or a for a in args] |
|---|
| 438 | |
|---|
| 439 | self.write_variable_declares(identifiers, toplevel=toplevel, limit=node_or_pagetag.undeclared_identifiers()) |
|---|
| 440 | if buffered: |
|---|
| 441 | 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)) |
|---|
| 442 | # apply buffer_filters |
|---|
| 443 | s = self.create_filter_callable(self.compiler.buffer_filters, s, False) |
|---|
| 444 | self.printer.writelines("return " + s,None) |
|---|
| 445 | else: |
|---|
| 446 | self.printer.writelines( |
|---|
| 447 | "__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)), |
|---|
| 448 | "return ''", |
|---|
| 449 | None |
|---|
| 450 | ) |
|---|
| 451 | |
|---|
| 452 | def create_filter_callable(self, args, target, is_expression): |
|---|
| 453 | """write a filter-applying expression based on the filters present in the given |
|---|
| 454 | filter names, adjusting for the global 'default' filter aliases as needed.""" |
|---|
| 455 | def locate_encode(name): |
|---|
| 456 | if re.match(r'decode\..+', name): |
|---|
| 457 | return "filters." + name |
|---|
| 458 | else: |
|---|
| 459 | return filters.DEFAULT_ESCAPES.get(name, name) |
|---|
| 460 | |
|---|
| 461 | if 'n' not in args: |
|---|
| 462 | if is_expression: |
|---|
| 463 | if self.compiler.pagetag: |
|---|
| 464 | args = self.compiler.pagetag.filter_args.args + args |
|---|
| 465 | if self.compiler.default_filters: |
|---|
| 466 | args = self.compiler.default_filters + args |
|---|
| 467 | for e in args: |
|---|
| 468 | # if filter given as a function, get just the identifier portion |
|---|
| 469 | if e == 'n': |
|---|
| 470 | continue |
|---|
| 471 | m = re.match(r'(.+?)(\(.*\))', e) |
|---|
| 472 | if m: |
|---|
| 473 | (ident, fargs) = m.group(1,2) |
|---|
| 474 | f = locate_encode(ident) |
|---|
| 475 | e = f + fargs |
|---|
| 476 | else: |
|---|
| 477 | x = e |
|---|
| 478 | e = locate_encode(e) |
|---|
| 479 | assert e is not None |
|---|
| 480 | target = "%s(%s)" % (e, target) |
|---|
| 481 | return target |
|---|
| 482 | |
|---|
| 483 | def visitExpression(self, node): |
|---|
| 484 | self.write_source_comment(node) |
|---|
| 485 | 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): |
|---|
| 486 | s = self.create_filter_callable(node.escapes_code.args, "%s" % node.text, True) |
|---|
| 487 | self.printer.writeline("__M_writer(%s)" % s) |
|---|
| 488 | else: |
|---|
| 489 | self.printer.writeline("__M_writer(%s)" % node.text) |
|---|
| 490 | |
|---|
| 491 | def visitControlLine(self, node): |
|---|
| 492 | if node.isend: |
|---|
| 493 | self.printer.writeline(None) |
|---|
| 494 | else: |
|---|
| 495 | self.write_source_comment(node) |
|---|
| 496 | self.printer.writeline(node.text) |
|---|
| 497 | def visitText(self, node): |
|---|
| 498 | self.write_source_comment(node) |
|---|
| 499 | self.printer.writeline("__M_writer(%s)" % repr(node.content)) |
|---|
| 500 | def visitTextTag(self, node): |
|---|
| 501 | filtered = len(node.filter_args.args) > 0 |
|---|
| 502 | if filtered: |
|---|
| 503 | self.printer.writelines( |
|---|
| 504 | "__M_writer = context._push_writer()", |
|---|
| 505 | "try:", |
|---|
| 506 | ) |
|---|
| 507 | for n in node.nodes: |
|---|
| 508 | n.accept_visitor(self) |
|---|
| 509 | if filtered: |
|---|
| 510 | self.printer.writelines( |
|---|
| 511 | "finally:", |
|---|
| 512 | "__M_buf, __M_writer = context._pop_buffer_and_writer()", |
|---|
| 513 | "__M_writer(%s)" % self.create_filter_callable(node.filter_args.args, "__M_buf.getvalue()", False), |
|---|
| 514 | None |
|---|
| 515 | ) |
|---|
| 516 | |
|---|
| 517 | def visitCode(self, node): |
|---|
| 518 | if not node.ismodule: |
|---|
| 519 | self.write_source_comment(node) |
|---|
| 520 | self.printer.write_indented_block(node.text) |
|---|
| 521 | |
|---|
| 522 | if not self.in_def and len(self.identifiers.locally_assigned) > 0: |
|---|
| 523 | # if we are the "template" def, fudge locally declared/modified variables into the "__M_locals" dictionary, |
|---|
| 524 | # which is used for def calls within the same template, to simulate "enclosing scope" |
|---|
| 525 | self.printer.writeline('__M_locals.update(__M_dict_builtin([(__M_key, __M_locals_builtin()[__M_key]) for __M_key in [%s] if __M_key in __M_locals_builtin()]))' % ','.join([repr(x) for x in node.declared_identifiers()])) |
|---|
| 526 | |
|---|
| 527 | def visitIncludeTag(self, node): |
|---|
| 528 | self.write_source_comment(node) |
|---|
| 529 | args = node.attributes.get('args') |
|---|
| 530 | if args: |
|---|
| 531 | self.printer.writeline("runtime._include_file(context, %s, _template_uri, %s)" % (node.parsed_attributes['file'], args)) |
|---|
| 532 | else: |
|---|
| 533 | self.printer.writeline("runtime._include_file(context, %s, _template_uri)" % (node.parsed_attributes['file'])) |
|---|
| 534 | |
|---|
| 535 | def visitNamespaceTag(self, node): |
|---|
| 536 | pass |
|---|
| 537 | |
|---|
| 538 | def visitDefTag(self, node): |
|---|
| 539 | pass |
|---|
| 540 | |
|---|
| 541 | def visitCallNamespaceTag(self, node): |
|---|
| 542 | # TODO: we can put namespace-specific checks here, such |
|---|
| 543 | # as ensure the given namespace will be imported, |
|---|
| 544 | # pre-import the namespace, etc. |
|---|
| 545 | self.visitCallTag(node) |
|---|
| 546 | |
|---|
| 547 | def visitCallTag(self, node): |
|---|
| 548 | self.printer.writeline("def ccall(caller):") |
|---|
| 549 | export = ['body'] |
|---|
| 550 | callable_identifiers = self.identifiers.branch(node, nested=True) |
|---|
| 551 | body_identifiers = callable_identifiers.branch(node, nested=False) |
|---|
| 552 | # we want the 'caller' passed to ccall to be used for the body() function, |
|---|
| 553 | # but for other non-body() <%def>s within <%call> we want the current caller off the call stack (if any) |
|---|
| 554 | body_identifiers.add_declared('caller') |
|---|
| 555 | |
|---|
| 556 | self.identifier_stack.append(body_identifiers) |
|---|
| 557 | class DefVisitor(object): |
|---|
| 558 | def visitDefTag(s, node): |
|---|
| 559 | self.write_inline_def(node, callable_identifiers, nested=False) |
|---|
| 560 | export.append(node.name) |
|---|
| 561 | # remove defs that are within the <%call> from the "closuredefs" defined |
|---|
| 562 | # in the body, so they dont render twice |
|---|
| 563 | if node.name in body_identifiers.closuredefs: |
|---|
| 564 | del body_identifiers.closuredefs[node.name] |
|---|
| 565 | |
|---|
| 566 | vis = DefVisitor() |
|---|
| 567 | for n in node.nodes: |
|---|
| 568 | n.accept_visitor(vis) |
|---|
| 569 | self.identifier_stack.pop() |
|---|
| 570 | |
|---|
| 571 | bodyargs = node.body_decl.get_argument_expressions() |
|---|
| 572 | self.printer.writeline("def body(%s):" % ','.join(bodyargs)) |
|---|
| 573 | # TODO: figure out best way to specify buffering/nonbuffering (at call time would be better) |
|---|
| 574 | buffered = False |
|---|
| 575 | if buffered: |
|---|
| 576 | self.printer.writelines( |
|---|
| 577 | "context._push_buffer()", |
|---|
| 578 | "try:" |
|---|
| 579 | ) |
|---|
| 580 | self.write_variable_declares(body_identifiers) |
|---|
| 581 | self.identifier_stack.append(body_identifiers) |
|---|
| 582 | |
|---|
| 583 | for n in node.nodes: |
|---|
| 584 | n.accept_visitor(self) |
|---|
| 585 | self.identifier_stack.pop() |
|---|
| 586 | |
|---|
| 587 | self.write_def_finish(node, buffered, False, False, callstack=False) |
|---|
| 588 | self.printer.writelines( |
|---|
| 589 | None, |
|---|
| 590 | "return [%s]" % (','.join(export)), |
|---|
| 591 | None |
|---|
| 592 | ) |
|---|
| 593 | |
|---|
| 594 | self.printer.writelines( |
|---|
| 595 | # get local reference to current caller, if any |
|---|
| 596 | "caller = context.caller_stack._get_caller()", |
|---|
| 597 | # push on caller for nested call |
|---|
| 598 | "context.caller_stack.nextcaller = runtime.Namespace('caller', context, callables=ccall(caller))", |
|---|
| 599 | "try:") |
|---|
| 600 | self.write_source_comment(node) |
|---|
| 601 | self.printer.writelines( |
|---|
| 602 | "__M_writer(%s)" % self.create_filter_callable([], node.expression, True), |
|---|
| 603 | "finally:", |
|---|
| 604 | "context.caller_stack.nextcaller = None", |
|---|
| 605 | None |
|---|
| 606 | ) |
|---|
| 607 | |
|---|
| 608 | class _Identifiers(object): |
|---|
| 609 | """tracks the status of identifier names as template code is rendered.""" |
|---|
| 610 | def __init__(self, node=None, parent=None, nested=False): |
|---|
| 611 | if parent is not None: |
|---|
| 612 | # things that have already been declared in an enclosing namespace (i.e. names we can just use) |
|---|
| 613 | self.declared = util.Set(parent.declared).union([c.name for c in parent.closuredefs.values()]).union(parent.locally_declared).union(parent.argument_declared) |
|---|
| 614 | |
|---|
| 615 | # if these identifiers correspond to a "nested" scope, it means whatever the |
|---|
| 616 | # parent identifiers had as undeclared will have been declared by that parent, |
|---|
| 617 | # and therefore we have them in our scope. |
|---|
| 618 | if nested: |
|---|
| 619 | self.declared = self.declared.union(parent.undeclared) |
|---|
| 620 | |
|---|
| 621 | # top level defs that are available |
|---|
| 622 | self.topleveldefs = util.SetLikeDict(**parent.topleveldefs) |
|---|
| 623 | else: |
|---|
| 624 | self.declared = util.Set() |
|---|
| 625 | self.topleveldefs = util.SetLikeDict() |
|---|
| 626 | |
|---|
| 627 | # things within this level that are referenced before they are declared (e.g. assigned to) |
|---|
| 628 | self.undeclared = util.Set() |
|---|
| 629 | |
|---|
| 630 | # things that are declared locally. some of these things could be in the "undeclared" |
|---|
| 631 | # list as well if they are referenced before declared |
|---|
| 632 | self.locally_declared = util.Set() |
|---|
| 633 | |
|---|
| 634 | # assignments made in explicit python blocks. these will be propigated to |
|---|
| 635 | # the context of local def calls. |
|---|
| 636 | self.locally_assigned = util.Set() |
|---|
| 637 | |
|---|
| 638 | # things that are declared in the argument signature of the def callable |
|---|
| 639 | self.argument_declared = util.Set() |
|---|
| 640 | |
|---|
| 641 | # closure defs that are defined in this level |
|---|
| 642 | self.closuredefs = util.SetLikeDict() |
|---|
| 643 | |
|---|
| 644 | self.node = node |
|---|
| 645 | |
|---|
| 646 | if node is not None: |
|---|
| 647 | node.accept_visitor(self) |
|---|
| 648 | |
|---|
| 649 | def branch(self, node, **kwargs): |
|---|
| 650 | """create a new Identifiers for a new Node, with this Identifiers as the parent.""" |
|---|
| 651 | return _Identifiers(node, self, **kwargs) |
|---|
| 652 | |
|---|
| 653 | defs = property(lambda self:util.Set(self.topleveldefs.union(self.closuredefs).values())) |
|---|
| 654 | |
|---|
| 655 | def __repr__(self): |
|---|
| 656 | return "Identifiers(declared=%s, locally_declared=%s, undeclared=%s, topleveldefs=%s, closuredefs=%s, argumenetdeclared=%s)" % (repr(list(self.declared)), repr(list(self.locally_declared)), repr(list(self.undeclared)), repr([c.name for c in self.topleveldefs.values()]), repr([c.name for c in self.closuredefs.values()]), repr(self.argument_declared)) |
|---|
| 657 | |
|---|
| 658 | def check_declared(self, node): |
|---|
| 659 | """update the state of this Identifiers with the undeclared and declared identifiers of the given node.""" |
|---|
| 660 | for ident in node.undeclared_identifiers(): |
|---|
| 661 | if ident != 'context' and ident not in self.declared.union(self.locally_declared): |
|---|
| 662 | self.undeclared.add(ident) |
|---|
| 663 | for ident in node.declared_identifiers(): |
|---|
| 664 | self.locally_declared.add(ident) |
|---|
| 665 | |
|---|
| 666 | def add_declared(self, ident): |
|---|
| 667 | self.declared.add(ident) |
|---|
| 668 | if ident in self.undeclared: |
|---|
| 669 | self.undeclared.remove(ident) |
|---|
| 670 | |
|---|
| 671 | def visitExpression(self, node): |
|---|
| 672 | self.check_declared(node) |
|---|
| 673 | def visitControlLine(self, node): |
|---|
| 674 | self.check_declared(node) |
|---|
| 675 | def visitCode(self, node): |
|---|
| 676 | if not node.ismodule: |
|---|
| 677 | self.check_declared(node) |
|---|
| 678 | self.locally_assigned = self.locally_assigned.union(node.declared_identifiers()) |
|---|
| 679 | def visitDefTag(self, node): |
|---|
| 680 | if node.is_root(): |
|---|
| 681 | self.topleveldefs[node.name] = node |
|---|
| 682 | elif node is not self.node: |
|---|
| 683 | self.closuredefs[node.name] = node |
|---|
| 684 | for ident in node.undeclared_identifiers(): |
|---|
| 685 | if ident != 'context' and ident not in self.declared.union(self.locally_declared): |
|---|
| 686 | self.undeclared.add(ident) |
|---|
| 687 | # visit defs only one level deep |
|---|
| 688 | if node is self.node: |
|---|
| 689 | for ident in node.declared_identifiers(): |
|---|
| 690 | self.argument_declared.add(ident) |
|---|
| 691 | for n in node.nodes: |
|---|
| 692 | n.accept_visitor(self) |
|---|
| 693 | def visitIncludeTag(self, node): |
|---|
| 694 | self.check_declared(node) |
|---|
| 695 | def visitPageTag(self, node): |
|---|
| 696 | for ident in node.declared_identifiers(): |
|---|
| 697 | self.argument_declared.add(ident) |
|---|
| 698 | self.check_declared(node) |
|---|
| 699 | |
|---|
| 700 | def visitCallNamespaceTag(self, node): |
|---|
| 701 | self.visitCallTag(node) |
|---|
| 702 | |
|---|
| 703 | def visitCallTag(self, node): |
|---|
| 704 | if node is self.node: |
|---|
| 705 | for ident in node.undeclared_identifiers(): |
|---|
| 706 | if ident != 'context' and ident not in self.declared.union(self.locally_declared): |
|---|
| 707 | self.undeclared.add(ident) |
|---|
| 708 | for ident in node.declared_identifiers(): |
|---|
| 709 | self.argument_declared.add(ident) |
|---|
| 710 | for n in node.nodes: |
|---|
| 711 | n.accept_visitor(self) |
|---|
| 712 | else: |
|---|
| 713 | for ident in node.undeclared_identifiers(): |
|---|
| 714 | if ident != 'context' and ident not in self.declared.union(self.locally_declared): |
|---|
| 715 | self.undeclared.add(ident) |
|---|
| 716 | |
|---|