Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1# mako/codegen.py 

2# Copyright 2006-2020 the Mako authors and contributors <see AUTHORS file> 

3# 

4# This module is part of Mako and is released under 

5# the MIT License: http://www.opensource.org/licenses/mit-license.php 

6 

7"""provides functionality for rendering a parsetree constructing into module 

8source code.""" 

9 

10import json 

11import re 

12import time 

13 

14from mako import ast 

15from mako import compat 

16from mako import exceptions 

17from mako import filters 

18from mako import parsetree 

19from mako import util 

20from mako.pygen import PythonPrinter 

21 

22 

23MAGIC_NUMBER = 10 

24 

25# names which are hardwired into the 

26# template and are not accessed via the 

27# context itself 

28TOPLEVEL_DECLARED = set(["UNDEFINED", "STOP_RENDERING"]) 

29RESERVED_NAMES = set(["context", "loop"]).union(TOPLEVEL_DECLARED) 

30 

31 

32def compile( # noqa 

33 node, 

34 uri, 

35 filename=None, 

36 default_filters=None, 

37 buffer_filters=None, 

38 imports=None, 

39 future_imports=None, 

40 source_encoding=None, 

41 generate_magic_comment=True, 

42 disable_unicode=False, 

43 strict_undefined=False, 

44 enable_loop=True, 

45 reserved_names=frozenset(), 

46): 

47 """Generate module source code given a parsetree node, 

48 uri, and optional source filename""" 

49 

50 # if on Py2K, push the "source_encoding" string to be 

51 # a bytestring itself, as we will be embedding it into 

52 # the generated source and we don't want to coerce the 

53 # result into a unicode object, in "disable_unicode" mode 

54 if not compat.py3k and isinstance(source_encoding, compat.text_type): 

55 source_encoding = source_encoding.encode(source_encoding) 

56 

57 buf = util.FastEncodingBuffer() 

58 

59 printer = PythonPrinter(buf) 

60 _GenerateRenderMethod( 

61 printer, 

62 _CompileContext( 

63 uri, 

64 filename, 

65 default_filters, 

66 buffer_filters, 

67 imports, 

68 future_imports, 

69 source_encoding, 

70 generate_magic_comment, 

71 disable_unicode, 

72 strict_undefined, 

73 enable_loop, 

74 reserved_names, 

75 ), 

76 node, 

77 ) 

78 return buf.getvalue() 

79 

80 

81class _CompileContext(object): 

82 def __init__( 

83 self, 

84 uri, 

85 filename, 

86 default_filters, 

87 buffer_filters, 

88 imports, 

89 future_imports, 

90 source_encoding, 

91 generate_magic_comment, 

92 disable_unicode, 

93 strict_undefined, 

94 enable_loop, 

95 reserved_names, 

96 ): 

97 self.uri = uri 

98 self.filename = filename 

99 self.default_filters = default_filters 

100 self.buffer_filters = buffer_filters 

101 self.imports = imports 

102 self.future_imports = future_imports 

103 self.source_encoding = source_encoding 

104 self.generate_magic_comment = generate_magic_comment 

105 self.disable_unicode = disable_unicode 

106 self.strict_undefined = strict_undefined 

107 self.enable_loop = enable_loop 

108 self.reserved_names = reserved_names 

109 

110 

111class _GenerateRenderMethod(object): 

112 

113 """A template visitor object which generates the 

114 full module source for a template. 

115 

116 """ 

117 

118 def __init__(self, printer, compiler, node): 

119 self.printer = printer 

120 self.compiler = compiler 

121 self.node = node 

122 self.identifier_stack = [None] 

123 self.in_def = isinstance(node, (parsetree.DefTag, parsetree.BlockTag)) 

124 

125 if self.in_def: 

126 name = "render_%s" % node.funcname 

127 args = node.get_argument_expressions() 

128 filtered = len(node.filter_args.args) > 0 

129 buffered = eval(node.attributes.get("buffered", "False")) 

130 cached = eval(node.attributes.get("cached", "False")) 

131 defs = None 

132 pagetag = None 

133 if node.is_block and not node.is_anonymous: 

134 args += ["**pageargs"] 

135 else: 

136 defs = self.write_toplevel() 

137 pagetag = self.compiler.pagetag 

138 name = "render_body" 

139 if pagetag is not None: 

140 args = pagetag.body_decl.get_argument_expressions() 

141 if not pagetag.body_decl.kwargs: 

142 args += ["**pageargs"] 

143 cached = eval(pagetag.attributes.get("cached", "False")) 

144 self.compiler.enable_loop = self.compiler.enable_loop or eval( 

145 pagetag.attributes.get("enable_loop", "False") 

146 ) 

147 else: 

148 args = ["**pageargs"] 

149 cached = False 

150 buffered = filtered = False 

151 if args is None: 

152 args = ["context"] 

153 else: 

154 args = [a for a in ["context"] + args] 

155 

156 self.write_render_callable( 

157 pagetag or node, name, args, buffered, filtered, cached 

158 ) 

159 

160 if defs is not None: 

161 for node in defs: 

162 _GenerateRenderMethod(printer, compiler, node) 

163 

164 if not self.in_def: 

165 self.write_metadata_struct() 

166 

167 def write_metadata_struct(self): 

168 self.printer.source_map[self.printer.lineno] = max( 

169 self.printer.source_map 

170 ) 

171 struct = { 

172 "filename": self.compiler.filename, 

173 "uri": self.compiler.uri, 

174 "source_encoding": self.compiler.source_encoding, 

175 "line_map": self.printer.source_map, 

176 } 

177 self.printer.writelines( 

178 '"""', 

179 "__M_BEGIN_METADATA", 

180 json.dumps(struct), 

181 "__M_END_METADATA\n" '"""', 

182 ) 

183 

184 @property 

185 def identifiers(self): 

186 return self.identifier_stack[-1] 

187 

188 def write_toplevel(self): 

189 """Traverse a template structure for module-level directives and 

190 generate the start of module-level code. 

191 

192 """ 

193 inherit = [] 

194 namespaces = {} 

195 module_code = [] 

196 

197 self.compiler.pagetag = None 

198 

199 class FindTopLevel(object): 

200 def visitInheritTag(s, node): 

201 inherit.append(node) 

202 

203 def visitNamespaceTag(s, node): 

204 namespaces[node.name] = node 

205 

206 def visitPageTag(s, node): 

207 self.compiler.pagetag = node 

208 

209 def visitCode(s, node): 

210 if node.ismodule: 

211 module_code.append(node) 

212 

213 f = FindTopLevel() 

214 for n in self.node.nodes: 

215 n.accept_visitor(f) 

216 

217 self.compiler.namespaces = namespaces 

218 

219 module_ident = set() 

220 for n in module_code: 

221 module_ident = module_ident.union(n.declared_identifiers()) 

222 

223 module_identifiers = _Identifiers(self.compiler) 

224 module_identifiers.declared = module_ident 

225 

226 # module-level names, python code 

227 if ( 

228 self.compiler.generate_magic_comment 

229 and self.compiler.source_encoding 

230 ): 

231 self.printer.writeline( 

232 "# -*- coding:%s -*-" % self.compiler.source_encoding 

233 ) 

234 

235 if self.compiler.future_imports: 

236 self.printer.writeline( 

237 "from __future__ import %s" 

238 % (", ".join(self.compiler.future_imports),) 

239 ) 

240 self.printer.writeline("from mako import runtime, filters, cache") 

241 self.printer.writeline("UNDEFINED = runtime.UNDEFINED") 

242 self.printer.writeline("STOP_RENDERING = runtime.STOP_RENDERING") 

243 self.printer.writeline("__M_dict_builtin = dict") 

244 self.printer.writeline("__M_locals_builtin = locals") 

245 self.printer.writeline("_magic_number = %r" % MAGIC_NUMBER) 

246 self.printer.writeline("_modified_time = %r" % time.time()) 

247 self.printer.writeline("_enable_loop = %r" % self.compiler.enable_loop) 

248 self.printer.writeline( 

249 "_template_filename = %r" % self.compiler.filename 

250 ) 

251 self.printer.writeline("_template_uri = %r" % self.compiler.uri) 

252 self.printer.writeline( 

253 "_source_encoding = %r" % self.compiler.source_encoding 

254 ) 

255 if self.compiler.imports: 

256 buf = "" 

257 for imp in self.compiler.imports: 

258 buf += imp + "\n" 

259 self.printer.writeline(imp) 

260 impcode = ast.PythonCode( 

261 buf, 

262 source="", 

263 lineno=0, 

264 pos=0, 

265 filename="template defined imports", 

266 ) 

267 else: 

268 impcode = None 

269 

270 main_identifiers = module_identifiers.branch(self.node) 

271 mit = module_identifiers.topleveldefs 

272 module_identifiers.topleveldefs = mit.union( 

273 main_identifiers.topleveldefs 

274 ) 

275 module_identifiers.declared.update(TOPLEVEL_DECLARED) 

276 if impcode: 

277 module_identifiers.declared.update(impcode.declared_identifiers) 

278 

279 self.compiler.identifiers = module_identifiers 

280 self.printer.writeline( 

281 "_exports = %r" 

282 % [n.name for n in main_identifiers.topleveldefs.values()] 

283 ) 

284 self.printer.write_blanks(2) 

285 

286 if len(module_code): 

287 self.write_module_code(module_code) 

288 

289 if len(inherit): 

290 self.write_namespaces(namespaces) 

291 self.write_inherit(inherit[-1]) 

292 elif len(namespaces): 

293 self.write_namespaces(namespaces) 

294 

295 return list(main_identifiers.topleveldefs.values()) 

296 

297 def write_render_callable( 

298 self, node, name, args, buffered, filtered, cached 

299 ): 

300 """write a top-level render callable. 

301 

302 this could be the main render() method or that of a top-level def.""" 

303 

304 if self.in_def: 

305 decorator = node.decorator 

306 if decorator: 

307 self.printer.writeline( 

308 "@runtime._decorate_toplevel(%s)" % decorator 

309 ) 

310 

311 self.printer.start_source(node.lineno) 

312 self.printer.writelines( 

313 "def %s(%s):" % (name, ",".join(args)), 

314 # push new frame, assign current frame to __M_caller 

315 "__M_caller = context.caller_stack._push_frame()", 

316 "try:", 

317 ) 

318 if buffered or filtered or cached: 

319 self.printer.writeline("context._push_buffer()") 

320 

321 self.identifier_stack.append( 

322 self.compiler.identifiers.branch(self.node) 

323 ) 

324 if (not self.in_def or self.node.is_block) and "**pageargs" in args: 

325 self.identifier_stack[-1].argument_declared.add("pageargs") 

326 

327 if not self.in_def and ( 

328 len(self.identifiers.locally_assigned) > 0 

329 or len(self.identifiers.argument_declared) > 0 

330 ): 

331 self.printer.writeline( 

332 "__M_locals = __M_dict_builtin(%s)" 

333 % ",".join( 

334 [ 

335 "%s=%s" % (x, x) 

336 for x in self.identifiers.argument_declared 

337 ] 

338 ) 

339 ) 

340 

341 self.write_variable_declares(self.identifiers, toplevel=True) 

342 

343 for n in self.node.nodes: 

344 n.accept_visitor(self) 

345 

346 self.write_def_finish(self.node, buffered, filtered, cached) 

347 self.printer.writeline(None) 

348 self.printer.write_blanks(2) 

349 if cached: 

350 self.write_cache_decorator( 

351 node, name, args, buffered, self.identifiers, toplevel=True 

352 ) 

353 

354 def write_module_code(self, module_code): 

355 """write module-level template code, i.e. that which 

356 is enclosed in <%! %> tags in the template.""" 

357 for n in module_code: 

358 self.printer.write_indented_block(n.text, starting_lineno=n.lineno) 

359 

360 def write_inherit(self, node): 

361 """write the module-level inheritance-determination callable.""" 

362 

363 self.printer.writelines( 

364 "def _mako_inherit(template, context):", 

365 "_mako_generate_namespaces(context)", 

366 "return runtime._inherit_from(context, %s, _template_uri)" 

367 % (node.parsed_attributes["file"]), 

368 None, 

369 ) 

370 

371 def write_namespaces(self, namespaces): 

372 """write the module-level namespace-generating callable.""" 

373 self.printer.writelines( 

374 "def _mako_get_namespace(context, name):", 

375 "try:", 

376 "return context.namespaces[(__name__, name)]", 

377 "except KeyError:", 

378 "_mako_generate_namespaces(context)", 

379 "return context.namespaces[(__name__, name)]", 

380 None, 

381 None, 

382 ) 

383 self.printer.writeline("def _mako_generate_namespaces(context):") 

384 

385 for node in namespaces.values(): 

386 if "import" in node.attributes: 

387 self.compiler.has_ns_imports = True 

388 self.printer.start_source(node.lineno) 

389 if len(node.nodes): 

390 self.printer.writeline("def make_namespace():") 

391 export = [] 

392 identifiers = self.compiler.identifiers.branch(node) 

393 self.in_def = True 

394 

395 class NSDefVisitor(object): 

396 def visitDefTag(s, node): 

397 s.visitDefOrBase(node) 

398 

399 def visitBlockTag(s, node): 

400 s.visitDefOrBase(node) 

401 

402 def visitDefOrBase(s, node): 

403 if node.is_anonymous: 

404 raise exceptions.CompileException( 

405 "Can't put anonymous blocks inside " 

406 "<%namespace>", 

407 **node.exception_kwargs 

408 ) 

409 self.write_inline_def(node, identifiers, nested=False) 

410 export.append(node.funcname) 

411 

412 vis = NSDefVisitor() 

413 for n in node.nodes: 

414 n.accept_visitor(vis) 

415 self.printer.writeline("return [%s]" % (",".join(export))) 

416 self.printer.writeline(None) 

417 self.in_def = False 

418 callable_name = "make_namespace()" 

419 else: 

420 callable_name = "None" 

421 

422 if "file" in node.parsed_attributes: 

423 self.printer.writeline( 

424 "ns = runtime.TemplateNamespace(%r," 

425 " context._clean_inheritance_tokens()," 

426 " templateuri=%s, callables=%s, " 

427 " calling_uri=_template_uri)" 

428 % ( 

429 node.name, 

430 node.parsed_attributes.get("file", "None"), 

431 callable_name, 

432 ) 

433 ) 

434 elif "module" in node.parsed_attributes: 

435 self.printer.writeline( 

436 "ns = runtime.ModuleNamespace(%r," 

437 " context._clean_inheritance_tokens()," 

438 " callables=%s, calling_uri=_template_uri," 

439 " module=%s)" 

440 % ( 

441 node.name, 

442 callable_name, 

443 node.parsed_attributes.get("module", "None"), 

444 ) 

445 ) 

446 else: 

447 self.printer.writeline( 

448 "ns = runtime.Namespace(%r," 

449 " context._clean_inheritance_tokens()," 

450 " callables=%s, calling_uri=_template_uri)" 

451 % (node.name, callable_name) 

452 ) 

453 if eval(node.attributes.get("inheritable", "False")): 

454 self.printer.writeline("context['self'].%s = ns" % (node.name)) 

455 

456 self.printer.writeline( 

457 "context.namespaces[(__name__, %s)] = ns" % repr(node.name) 

458 ) 

459 self.printer.write_blanks(1) 

460 if not len(namespaces): 

461 self.printer.writeline("pass") 

462 self.printer.writeline(None) 

463 

464 def write_variable_declares(self, identifiers, toplevel=False, limit=None): 

465 """write variable declarations at the top of a function. 

466 

467 the variable declarations are in the form of callable 

468 definitions for defs and/or name lookup within the 

469 function's context argument. the names declared are based 

470 on the names that are referenced in the function body, 

471 which don't otherwise have any explicit assignment 

472 operation. names that are assigned within the body are 

473 assumed to be locally-scoped variables and are not 

474 separately declared. 

475 

476 for def callable definitions, if the def is a top-level 

477 callable then a 'stub' callable is generated which wraps 

478 the current Context into a closure. if the def is not 

479 top-level, it is fully rendered as a local closure. 

480 

481 """ 

482 

483 # collection of all defs available to us in this scope 

484 comp_idents = dict([(c.funcname, c) for c in identifiers.defs]) 

485 to_write = set() 

486 

487 # write "context.get()" for all variables we are going to 

488 # need that arent in the namespace yet 

489 to_write = to_write.union(identifiers.undeclared) 

490 

491 # write closure functions for closures that we define 

492 # right here 

493 to_write = to_write.union( 

494 [c.funcname for c in identifiers.closuredefs.values()] 

495 ) 

496 

497 # remove identifiers that are declared in the argument 

498 # signature of the callable 

499 to_write = to_write.difference(identifiers.argument_declared) 

500 

501 # remove identifiers that we are going to assign to. 

502 # in this way we mimic Python's behavior, 

503 # i.e. assignment to a variable within a block 

504 # means that variable is now a "locally declared" var, 

505 # which cannot be referenced beforehand. 

506 to_write = to_write.difference(identifiers.locally_declared) 

507 

508 if self.compiler.enable_loop: 

509 has_loop = "loop" in to_write 

510 to_write.discard("loop") 

511 else: 

512 has_loop = False 

513 

514 # if a limiting set was sent, constraint to those items in that list 

515 # (this is used for the caching decorator) 

516 if limit is not None: 

517 to_write = to_write.intersection(limit) 

518 

519 if toplevel and getattr(self.compiler, "has_ns_imports", False): 

520 self.printer.writeline("_import_ns = {}") 

521 self.compiler.has_imports = True 

522 for ident, ns in self.compiler.namespaces.items(): 

523 if "import" in ns.attributes: 

524 self.printer.writeline( 

525 "_mako_get_namespace(context, %r)." 

526 "_populate(_import_ns, %r)" 

527 % ( 

528 ident, 

529 re.split(r"\s*,\s*", ns.attributes["import"]), 

530 ) 

531 ) 

532 

533 if has_loop: 

534 self.printer.writeline("loop = __M_loop = runtime.LoopStack()") 

535 

536 for ident in to_write: 

537 if ident in comp_idents: 

538 comp = comp_idents[ident] 

539 if comp.is_block: 

540 if not comp.is_anonymous: 

541 self.write_def_decl(comp, identifiers) 

542 else: 

543 self.write_inline_def(comp, identifiers, nested=True) 

544 else: 

545 if comp.is_root(): 

546 self.write_def_decl(comp, identifiers) 

547 else: 

548 self.write_inline_def(comp, identifiers, nested=True) 

549 

550 elif ident in self.compiler.namespaces: 

551 self.printer.writeline( 

552 "%s = _mako_get_namespace(context, %r)" % (ident, ident) 

553 ) 

554 else: 

555 if getattr(self.compiler, "has_ns_imports", False): 

556 if self.compiler.strict_undefined: 

557 self.printer.writelines( 

558 "%s = _import_ns.get(%r, UNDEFINED)" 

559 % (ident, ident), 

560 "if %s is UNDEFINED:" % ident, 

561 "try:", 

562 "%s = context[%r]" % (ident, ident), 

563 "except KeyError:", 

564 "raise NameError(\"'%s' is not defined\")" % ident, 

565 None, 

566 None, 

567 ) 

568 else: 

569 self.printer.writeline( 

570 "%s = _import_ns.get" 

571 "(%r, context.get(%r, UNDEFINED))" 

572 % (ident, ident, ident) 

573 ) 

574 else: 

575 if self.compiler.strict_undefined: 

576 self.printer.writelines( 

577 "try:", 

578 "%s = context[%r]" % (ident, ident), 

579 "except KeyError:", 

580 "raise NameError(\"'%s' is not defined\")" % ident, 

581 None, 

582 ) 

583 else: 

584 self.printer.writeline( 

585 "%s = context.get(%r, UNDEFINED)" % (ident, ident) 

586 ) 

587 

588 self.printer.writeline("__M_writer = context.writer()") 

589 

590 def write_def_decl(self, node, identifiers): 

591 """write a locally-available callable referencing a top-level def""" 

592 funcname = node.funcname 

593 namedecls = node.get_argument_expressions() 

594 nameargs = node.get_argument_expressions(as_call=True) 

595 

596 if not self.in_def and ( 

597 len(self.identifiers.locally_assigned) > 0 

598 or len(self.identifiers.argument_declared) > 0 

599 ): 

600 nameargs.insert(0, "context._locals(__M_locals)") 

601 else: 

602 nameargs.insert(0, "context") 

603 self.printer.writeline("def %s(%s):" % (funcname, ",".join(namedecls))) 

604 self.printer.writeline( 

605 "return render_%s(%s)" % (funcname, ",".join(nameargs)) 

606 ) 

607 self.printer.writeline(None) 

608 

609 def write_inline_def(self, node, identifiers, nested): 

610 """write a locally-available def callable inside an enclosing def.""" 

611 

612 namedecls = node.get_argument_expressions() 

613 

614 decorator = node.decorator 

615 if decorator: 

616 self.printer.writeline( 

617 "@runtime._decorate_inline(context, %s)" % decorator 

618 ) 

619 self.printer.writeline( 

620 "def %s(%s):" % (node.funcname, ",".join(namedecls)) 

621 ) 

622 filtered = len(node.filter_args.args) > 0 

623 buffered = eval(node.attributes.get("buffered", "False")) 

624 cached = eval(node.attributes.get("cached", "False")) 

625 self.printer.writelines( 

626 # push new frame, assign current frame to __M_caller 

627 "__M_caller = context.caller_stack._push_frame()", 

628 "try:", 

629 ) 

630 if buffered or filtered or cached: 

631 self.printer.writelines("context._push_buffer()") 

632 

633 identifiers = identifiers.branch(node, nested=nested) 

634 

635 self.write_variable_declares(identifiers) 

636 

637 self.identifier_stack.append(identifiers) 

638 for n in node.nodes: 

639 n.accept_visitor(self) 

640 self.identifier_stack.pop() 

641 

642 self.write_def_finish(node, buffered, filtered, cached) 

643 self.printer.writeline(None) 

644 if cached: 

645 self.write_cache_decorator( 

646 node, 

647 node.funcname, 

648 namedecls, 

649 False, 

650 identifiers, 

651 inline=True, 

652 toplevel=False, 

653 ) 

654 

655 def write_def_finish( 

656 self, node, buffered, filtered, cached, callstack=True 

657 ): 

658 """write the end section of a rendering function, either outermost or 

659 inline. 

660 

661 this takes into account if the rendering function was filtered, 

662 buffered, etc. and closes the corresponding try: block if any, and 

663 writes code to retrieve captured content, apply filters, send proper 

664 return value.""" 

665 

666 if not buffered and not cached and not filtered: 

667 self.printer.writeline("return ''") 

668 if callstack: 

669 self.printer.writelines( 

670 "finally:", "context.caller_stack._pop_frame()", None 

671 ) 

672 

673 if buffered or filtered or cached: 

674 if buffered or cached: 

675 # in a caching scenario, don't try to get a writer 

676 # from the context after popping; assume the caching 

677 # implemenation might be using a context with no 

678 # extra buffers 

679 self.printer.writelines( 

680 "finally:", "__M_buf = context._pop_buffer()" 

681 ) 

682 else: 

683 self.printer.writelines( 

684 "finally:", 

685 "__M_buf, __M_writer = context._pop_buffer_and_writer()", 

686 ) 

687 

688 if callstack: 

689 self.printer.writeline("context.caller_stack._pop_frame()") 

690 

691 s = "__M_buf.getvalue()" 

692 if filtered: 

693 s = self.create_filter_callable( 

694 node.filter_args.args, s, False 

695 ) 

696 self.printer.writeline(None) 

697 if buffered and not cached: 

698 s = self.create_filter_callable( 

699 self.compiler.buffer_filters, s, False 

700 ) 

701 if buffered or cached: 

702 self.printer.writeline("return %s" % s) 

703 else: 

704 self.printer.writelines("__M_writer(%s)" % s, "return ''") 

705 

706 def write_cache_decorator( 

707 self, 

708 node_or_pagetag, 

709 name, 

710 args, 

711 buffered, 

712 identifiers, 

713 inline=False, 

714 toplevel=False, 

715 ): 

716 """write a post-function decorator to replace a rendering 

717 callable with a cached version of itself.""" 

718 

719 self.printer.writeline("__M_%s = %s" % (name, name)) 

720 cachekey = node_or_pagetag.parsed_attributes.get( 

721 "cache_key", repr(name) 

722 ) 

723 

724 cache_args = {} 

725 if self.compiler.pagetag is not None: 

726 cache_args.update( 

727 (pa[6:], self.compiler.pagetag.parsed_attributes[pa]) 

728 for pa in self.compiler.pagetag.parsed_attributes 

729 if pa.startswith("cache_") and pa != "cache_key" 

730 ) 

731 cache_args.update( 

732 (pa[6:], node_or_pagetag.parsed_attributes[pa]) 

733 for pa in node_or_pagetag.parsed_attributes 

734 if pa.startswith("cache_") and pa != "cache_key" 

735 ) 

736 if "timeout" in cache_args: 

737 cache_args["timeout"] = int(eval(cache_args["timeout"])) 

738 

739 self.printer.writeline("def %s(%s):" % (name, ",".join(args))) 

740 

741 # form "arg1, arg2, arg3=arg3, arg4=arg4", etc. 

742 pass_args = [ 

743 "%s=%s" % ((a.split("=")[0],) * 2) if "=" in a else a for a in args 

744 ] 

745 

746 self.write_variable_declares( 

747 identifiers, 

748 toplevel=toplevel, 

749 limit=node_or_pagetag.undeclared_identifiers(), 

750 ) 

751 if buffered: 

752 s = ( 

753 "context.get('local')." 

754 "cache._ctx_get_or_create(" 

755 "%s, lambda:__M_%s(%s), context, %s__M_defname=%r)" 

756 % ( 

757 cachekey, 

758 name, 

759 ",".join(pass_args), 

760 "".join( 

761 ["%s=%s, " % (k, v) for k, v in cache_args.items()] 

762 ), 

763 name, 

764 ) 

765 ) 

766 # apply buffer_filters 

767 s = self.create_filter_callable( 

768 self.compiler.buffer_filters, s, False 

769 ) 

770 self.printer.writelines("return " + s, None) 

771 else: 

772 self.printer.writelines( 

773 "__M_writer(context.get('local')." 

774 "cache._ctx_get_or_create(" 

775 "%s, lambda:__M_%s(%s), context, %s__M_defname=%r))" 

776 % ( 

777 cachekey, 

778 name, 

779 ",".join(pass_args), 

780 "".join( 

781 ["%s=%s, " % (k, v) for k, v in cache_args.items()] 

782 ), 

783 name, 

784 ), 

785 "return ''", 

786 None, 

787 ) 

788 

789 def create_filter_callable(self, args, target, is_expression): 

790 """write a filter-applying expression based on the filters 

791 present in the given filter names, adjusting for the global 

792 'default' filter aliases as needed.""" 

793 

794 def locate_encode(name): 

795 if re.match(r"decode\..+", name): 

796 return "filters." + name 

797 elif self.compiler.disable_unicode: 

798 return filters.NON_UNICODE_ESCAPES.get(name, name) 

799 else: 

800 return filters.DEFAULT_ESCAPES.get(name, name) 

801 

802 if "n" not in args: 

803 if is_expression: 

804 if self.compiler.pagetag: 

805 args = self.compiler.pagetag.filter_args.args + args 

806 if self.compiler.default_filters and "n" not in args: 

807 args = self.compiler.default_filters + args 

808 for e in args: 

809 # if filter given as a function, get just the identifier portion 

810 if e == "n": 

811 continue 

812 m = re.match(r"(.+?)(\(.*\))", e) 

813 if m: 

814 ident, fargs = m.group(1, 2) 

815 f = locate_encode(ident) 

816 e = f + fargs 

817 else: 

818 e = locate_encode(e) 

819 assert e is not None 

820 target = "%s(%s)" % (e, target) 

821 return target 

822 

823 def visitExpression(self, node): 

824 self.printer.start_source(node.lineno) 

825 if ( 

826 len(node.escapes) 

827 or ( 

828 self.compiler.pagetag is not None 

829 and len(self.compiler.pagetag.filter_args.args) 

830 ) 

831 or len(self.compiler.default_filters) 

832 ): 

833 

834 s = self.create_filter_callable( 

835 node.escapes_code.args, "%s" % node.text, True 

836 ) 

837 self.printer.writeline("__M_writer(%s)" % s) 

838 else: 

839 self.printer.writeline("__M_writer(%s)" % node.text) 

840 

841 def visitControlLine(self, node): 

842 if node.isend: 

843 self.printer.writeline(None) 

844 if node.has_loop_context: 

845 self.printer.writeline("finally:") 

846 self.printer.writeline("loop = __M_loop._exit()") 

847 self.printer.writeline(None) 

848 else: 

849 self.printer.start_source(node.lineno) 

850 if self.compiler.enable_loop and node.keyword == "for": 

851 text = mangle_mako_loop(node, self.printer) 

852 else: 

853 text = node.text 

854 self.printer.writeline(text) 

855 children = node.get_children() 

856 # this covers the three situations where we want to insert a pass: 

857 # 1) a ternary control line with no children, 

858 # 2) a primary control line with nothing but its own ternary 

859 # and end control lines, and 

860 # 3) any control line with no content other than comments 

861 if not children or ( 

862 compat.all( 

863 isinstance(c, (parsetree.Comment, parsetree.ControlLine)) 

864 for c in children 

865 ) 

866 and compat.all( 

867 (node.is_ternary(c.keyword) or c.isend) 

868 for c in children 

869 if isinstance(c, parsetree.ControlLine) 

870 ) 

871 ): 

872 self.printer.writeline("pass") 

873 

874 def visitText(self, node): 

875 self.printer.start_source(node.lineno) 

876 self.printer.writeline("__M_writer(%s)" % repr(node.content)) 

877 

878 def visitTextTag(self, node): 

879 filtered = len(node.filter_args.args) > 0 

880 if filtered: 

881 self.printer.writelines( 

882 "__M_writer = context._push_writer()", "try:" 

883 ) 

884 for n in node.nodes: 

885 n.accept_visitor(self) 

886 if filtered: 

887 self.printer.writelines( 

888 "finally:", 

889 "__M_buf, __M_writer = context._pop_buffer_and_writer()", 

890 "__M_writer(%s)" 

891 % self.create_filter_callable( 

892 node.filter_args.args, "__M_buf.getvalue()", False 

893 ), 

894 None, 

895 ) 

896 

897 def visitCode(self, node): 

898 if not node.ismodule: 

899 self.printer.write_indented_block( 

900 node.text, starting_lineno=node.lineno 

901 ) 

902 

903 if not self.in_def and len(self.identifiers.locally_assigned) > 0: 

904 # if we are the "template" def, fudge locally 

905 # declared/modified variables into the "__M_locals" dictionary, 

906 # which is used for def calls within the same template, 

907 # to simulate "enclosing scope" 

908 self.printer.writeline( 

909 "__M_locals_builtin_stored = __M_locals_builtin()" 

910 ) 

911 self.printer.writeline( 

912 "__M_locals.update(__M_dict_builtin([(__M_key," 

913 " __M_locals_builtin_stored[__M_key]) for __M_key in" 

914 " [%s] if __M_key in __M_locals_builtin_stored]))" 

915 % ",".join([repr(x) for x in node.declared_identifiers()]) 

916 ) 

917 

918 def visitIncludeTag(self, node): 

919 self.printer.start_source(node.lineno) 

920 args = node.attributes.get("args") 

921 if args: 

922 self.printer.writeline( 

923 "runtime._include_file(context, %s, _template_uri, %s)" 

924 % (node.parsed_attributes["file"], args) 

925 ) 

926 else: 

927 self.printer.writeline( 

928 "runtime._include_file(context, %s, _template_uri)" 

929 % (node.parsed_attributes["file"]) 

930 ) 

931 

932 def visitNamespaceTag(self, node): 

933 pass 

934 

935 def visitDefTag(self, node): 

936 pass 

937 

938 def visitBlockTag(self, node): 

939 if node.is_anonymous: 

940 self.printer.writeline("%s()" % node.funcname) 

941 else: 

942 nameargs = node.get_argument_expressions(as_call=True) 

943 nameargs += ["**pageargs"] 

944 self.printer.writeline( 

945 "if 'parent' not in context._data or " 

946 "not hasattr(context._data['parent'], '%s'):" % node.funcname 

947 ) 

948 self.printer.writeline( 

949 "context['self'].%s(%s)" % (node.funcname, ",".join(nameargs)) 

950 ) 

951 self.printer.writeline("\n") 

952 

953 def visitCallNamespaceTag(self, node): 

954 # TODO: we can put namespace-specific checks here, such 

955 # as ensure the given namespace will be imported, 

956 # pre-import the namespace, etc. 

957 self.visitCallTag(node) 

958 

959 def visitCallTag(self, node): 

960 self.printer.writeline("def ccall(caller):") 

961 export = ["body"] 

962 callable_identifiers = self.identifiers.branch(node, nested=True) 

963 body_identifiers = callable_identifiers.branch(node, nested=False) 

964 # we want the 'caller' passed to ccall to be used 

965 # for the body() function, but for other non-body() 

966 # <%def>s within <%call> we want the current caller 

967 # off the call stack (if any) 

968 body_identifiers.add_declared("caller") 

969 

970 self.identifier_stack.append(body_identifiers) 

971 

972 class DefVisitor(object): 

973 def visitDefTag(s, node): 

974 s.visitDefOrBase(node) 

975 

976 def visitBlockTag(s, node): 

977 s.visitDefOrBase(node) 

978 

979 def visitDefOrBase(s, node): 

980 self.write_inline_def(node, callable_identifiers, nested=False) 

981 if not node.is_anonymous: 

982 export.append(node.funcname) 

983 # remove defs that are within the <%call> from the 

984 # "closuredefs" defined in the body, so they dont render twice 

985 if node.funcname in body_identifiers.closuredefs: 

986 del body_identifiers.closuredefs[node.funcname] 

987 

988 vis = DefVisitor() 

989 for n in node.nodes: 

990 n.accept_visitor(vis) 

991 self.identifier_stack.pop() 

992 

993 bodyargs = node.body_decl.get_argument_expressions() 

994 self.printer.writeline("def body(%s):" % ",".join(bodyargs)) 

995 

996 # TODO: figure out best way to specify 

997 # buffering/nonbuffering (at call time would be better) 

998 buffered = False 

999 if buffered: 

1000 self.printer.writelines("context._push_buffer()", "try:") 

1001 self.write_variable_declares(body_identifiers) 

1002 self.identifier_stack.append(body_identifiers) 

1003 

1004 for n in node.nodes: 

1005 n.accept_visitor(self) 

1006 self.identifier_stack.pop() 

1007 

1008 self.write_def_finish(node, buffered, False, False, callstack=False) 

1009 self.printer.writelines(None, "return [%s]" % (",".join(export)), None) 

1010 

1011 self.printer.writelines( 

1012 # push on caller for nested call 

1013 "context.caller_stack.nextcaller = " 

1014 "runtime.Namespace('caller', context, " 

1015 "callables=ccall(__M_caller))", 

1016 "try:", 

1017 ) 

1018 self.printer.start_source(node.lineno) 

1019 self.printer.writelines( 

1020 "__M_writer(%s)" 

1021 % self.create_filter_callable([], node.expression, True), 

1022 "finally:", 

1023 "context.caller_stack.nextcaller = None", 

1024 None, 

1025 ) 

1026 

1027 

1028class _Identifiers(object): 

1029 

1030 """tracks the status of identifier names as template code is rendered.""" 

1031 

1032 def __init__(self, compiler, node=None, parent=None, nested=False): 

1033 if parent is not None: 

1034 # if we are the branch created in write_namespaces(), 

1035 # we don't share any context from the main body(). 

1036 if isinstance(node, parsetree.NamespaceTag): 

1037 self.declared = set() 

1038 self.topleveldefs = util.SetLikeDict() 

1039 else: 

1040 # things that have already been declared 

1041 # in an enclosing namespace (i.e. names we can just use) 

1042 self.declared = ( 

1043 set(parent.declared) 

1044 .union([c.name for c in parent.closuredefs.values()]) 

1045 .union(parent.locally_declared) 

1046 .union(parent.argument_declared) 

1047 ) 

1048 

1049 # if these identifiers correspond to a "nested" 

1050 # scope, it means whatever the parent identifiers 

1051 # had as undeclared will have been declared by that parent, 

1052 # and therefore we have them in our scope. 

1053 if nested: 

1054 self.declared = self.declared.union(parent.undeclared) 

1055 

1056 # top level defs that are available 

1057 self.topleveldefs = util.SetLikeDict(**parent.topleveldefs) 

1058 else: 

1059 self.declared = set() 

1060 self.topleveldefs = util.SetLikeDict() 

1061 

1062 self.compiler = compiler 

1063 

1064 # things within this level that are referenced before they 

1065 # are declared (e.g. assigned to) 

1066 self.undeclared = set() 

1067 

1068 # things that are declared locally. some of these things 

1069 # could be in the "undeclared" list as well if they are 

1070 # referenced before declared 

1071 self.locally_declared = set() 

1072 

1073 # assignments made in explicit python blocks. 

1074 # these will be propagated to 

1075 # the context of local def calls. 

1076 self.locally_assigned = set() 

1077 

1078 # things that are declared in the argument 

1079 # signature of the def callable 

1080 self.argument_declared = set() 

1081 

1082 # closure defs that are defined in this level 

1083 self.closuredefs = util.SetLikeDict() 

1084 

1085 self.node = node 

1086 

1087 if node is not None: 

1088 node.accept_visitor(self) 

1089 

1090 illegal_names = self.compiler.reserved_names.intersection( 

1091 self.locally_declared 

1092 ) 

1093 if illegal_names: 

1094 raise exceptions.NameConflictError( 

1095 "Reserved words declared in template: %s" 

1096 % ", ".join(illegal_names) 

1097 ) 

1098 

1099 def branch(self, node, **kwargs): 

1100 """create a new Identifiers for a new Node, with 

1101 this Identifiers as the parent.""" 

1102 

1103 return _Identifiers(self.compiler, node, self, **kwargs) 

1104 

1105 @property 

1106 def defs(self): 

1107 return set(self.topleveldefs.union(self.closuredefs).values()) 

1108 

1109 def __repr__(self): 

1110 return ( 

1111 "Identifiers(declared=%r, locally_declared=%r, " 

1112 "undeclared=%r, topleveldefs=%r, closuredefs=%r, " 

1113 "argumentdeclared=%r)" 

1114 % ( 

1115 list(self.declared), 

1116 list(self.locally_declared), 

1117 list(self.undeclared), 

1118 [c.name for c in self.topleveldefs.values()], 

1119 [c.name for c in self.closuredefs.values()], 

1120 self.argument_declared, 

1121 ) 

1122 ) 

1123 

1124 def check_declared(self, node): 

1125 """update the state of this Identifiers with the undeclared 

1126 and declared identifiers of the given node.""" 

1127 

1128 for ident in node.undeclared_identifiers(): 

1129 if ident != "context" and ident not in self.declared.union( 

1130 self.locally_declared 

1131 ): 

1132 self.undeclared.add(ident) 

1133 for ident in node.declared_identifiers(): 

1134 self.locally_declared.add(ident) 

1135 

1136 def add_declared(self, ident): 

1137 self.declared.add(ident) 

1138 if ident in self.undeclared: 

1139 self.undeclared.remove(ident) 

1140 

1141 def visitExpression(self, node): 

1142 self.check_declared(node) 

1143 

1144 def visitControlLine(self, node): 

1145 self.check_declared(node) 

1146 

1147 def visitCode(self, node): 

1148 if not node.ismodule: 

1149 self.check_declared(node) 

1150 self.locally_assigned = self.locally_assigned.union( 

1151 node.declared_identifiers() 

1152 ) 

1153 

1154 def visitNamespaceTag(self, node): 

1155 # only traverse into the sub-elements of a 

1156 # <%namespace> tag if we are the branch created in 

1157 # write_namespaces() 

1158 if self.node is node: 

1159 for n in node.nodes: 

1160 n.accept_visitor(self) 

1161 

1162 def _check_name_exists(self, collection, node): 

1163 existing = collection.get(node.funcname) 

1164 collection[node.funcname] = node 

1165 if ( 

1166 existing is not None 

1167 and existing is not node 

1168 and (node.is_block or existing.is_block) 

1169 ): 

1170 raise exceptions.CompileException( 

1171 "%%def or %%block named '%s' already " 

1172 "exists in this template." % node.funcname, 

1173 **node.exception_kwargs 

1174 ) 

1175 

1176 def visitDefTag(self, node): 

1177 if node.is_root() and not node.is_anonymous: 

1178 self._check_name_exists(self.topleveldefs, node) 

1179 elif node is not self.node: 

1180 self._check_name_exists(self.closuredefs, node) 

1181 

1182 for ident in node.undeclared_identifiers(): 

1183 if ident != "context" and ident not in self.declared.union( 

1184 self.locally_declared 

1185 ): 

1186 self.undeclared.add(ident) 

1187 

1188 # visit defs only one level deep 

1189 if node is self.node: 

1190 for ident in node.declared_identifiers(): 

1191 self.argument_declared.add(ident) 

1192 

1193 for n in node.nodes: 

1194 n.accept_visitor(self) 

1195 

1196 def visitBlockTag(self, node): 

1197 if node is not self.node and not node.is_anonymous: 

1198 

1199 if isinstance(self.node, parsetree.DefTag): 

1200 raise exceptions.CompileException( 

1201 "Named block '%s' not allowed inside of def '%s'" 

1202 % (node.name, self.node.name), 

1203 **node.exception_kwargs 

1204 ) 

1205 elif isinstance( 

1206 self.node, (parsetree.CallTag, parsetree.CallNamespaceTag) 

1207 ): 

1208 raise exceptions.CompileException( 

1209 "Named block '%s' not allowed inside of <%%call> tag" 

1210 % (node.name,), 

1211 **node.exception_kwargs 

1212 ) 

1213 

1214 for ident in node.undeclared_identifiers(): 

1215 if ident != "context" and ident not in self.declared.union( 

1216 self.locally_declared 

1217 ): 

1218 self.undeclared.add(ident) 

1219 

1220 if not node.is_anonymous: 

1221 self._check_name_exists(self.topleveldefs, node) 

1222 self.undeclared.add(node.funcname) 

1223 elif node is not self.node: 

1224 self._check_name_exists(self.closuredefs, node) 

1225 for ident in node.declared_identifiers(): 

1226 self.argument_declared.add(ident) 

1227 for n in node.nodes: 

1228 n.accept_visitor(self) 

1229 

1230 def visitTextTag(self, node): 

1231 for ident in node.undeclared_identifiers(): 

1232 if ident != "context" and ident not in self.declared.union( 

1233 self.locally_declared 

1234 ): 

1235 self.undeclared.add(ident) 

1236 

1237 def visitIncludeTag(self, node): 

1238 self.check_declared(node) 

1239 

1240 def visitPageTag(self, node): 

1241 for ident in node.declared_identifiers(): 

1242 self.argument_declared.add(ident) 

1243 self.check_declared(node) 

1244 

1245 def visitCallNamespaceTag(self, node): 

1246 self.visitCallTag(node) 

1247 

1248 def visitCallTag(self, node): 

1249 if node is self.node: 

1250 for ident in node.undeclared_identifiers(): 

1251 if ident != "context" and ident not in self.declared.union( 

1252 self.locally_declared 

1253 ): 

1254 self.undeclared.add(ident) 

1255 for ident in node.declared_identifiers(): 

1256 self.argument_declared.add(ident) 

1257 for n in node.nodes: 

1258 n.accept_visitor(self) 

1259 else: 

1260 for ident in node.undeclared_identifiers(): 

1261 if ident != "context" and ident not in self.declared.union( 

1262 self.locally_declared 

1263 ): 

1264 self.undeclared.add(ident) 

1265 

1266 

1267_FOR_LOOP = re.compile( 

1268 r"^for\s+((?:\(?)\s*[A-Za-z_][A-Za-z_0-9]*" 

1269 r"(?:\s*,\s*(?:[A-Za-z_][A-Za-z0-9_]*),??)*\s*(?:\)?))\s+in\s+(.*):" 

1270) 

1271 

1272 

1273def mangle_mako_loop(node, printer): 

1274 """converts a for loop into a context manager wrapped around a for loop 

1275 when access to the `loop` variable has been detected in the for loop body 

1276 """ 

1277 loop_variable = LoopVariable() 

1278 node.accept_visitor(loop_variable) 

1279 if loop_variable.detected: 

1280 node.nodes[-1].has_loop_context = True 

1281 match = _FOR_LOOP.match(node.text) 

1282 if match: 

1283 printer.writelines( 

1284 "loop = __M_loop._enter(%s)" % match.group(2), 

1285 "try:" 

1286 # 'with __M_loop(%s) as loop:' % match.group(2) 

1287 ) 

1288 text = "for %s in loop:" % match.group(1) 

1289 else: 

1290 raise SyntaxError("Couldn't apply loop context: %s" % node.text) 

1291 else: 

1292 text = node.text 

1293 return text 

1294 

1295 

1296class LoopVariable(object): 

1297 

1298 """A node visitor which looks for the name 'loop' within undeclared 

1299 identifiers.""" 

1300 

1301 def __init__(self): 

1302 self.detected = False 

1303 

1304 def _loop_reference_detected(self, node): 

1305 if "loop" in node.undeclared_identifiers(): 

1306 self.detected = True 

1307 else: 

1308 for n in node.get_children(): 

1309 n.accept_visitor(self) 

1310 

1311 def visitControlLine(self, node): 

1312 self._loop_reference_detected(node) 

1313 

1314 def visitCode(self, node): 

1315 self._loop_reference_detected(node) 

1316 

1317 def visitExpression(self, node): 

1318 self._loop_reference_detected(node)