[3] | 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 | |
---|