Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/mako/parsetree.py: 69%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

269 statements  

1# mako/parsetree.py 

2# Copyright 2006-2026 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"""defines the parse tree components for Mako templates.""" 

8 

9import re 

10 

11from mako import ast 

12from mako import exceptions 

13from mako import filters 

14from mako import util 

15 

16 

17class Node: 

18 """base class for a Node in the parse tree.""" 

19 

20 def __init__(self, source, lineno, pos, filename): 

21 self.source = source 

22 self.lineno = lineno 

23 self.pos = pos 

24 self.filename = filename 

25 

26 @property 

27 def exception_kwargs(self): 

28 return { 

29 "source": self.source, 

30 "lineno": self.lineno, 

31 "pos": self.pos, 

32 "filename": self.filename, 

33 } 

34 

35 def get_children(self): 

36 return [] 

37 

38 def accept_visitor(self, visitor): 

39 def traverse(node): 

40 for n in node.get_children(): 

41 n.accept_visitor(visitor) 

42 

43 method = getattr(visitor, "visit" + self.__class__.__name__, traverse) 

44 method(self) 

45 

46 

47class TemplateNode(Node): 

48 """a 'container' node that stores the overall collection of nodes.""" 

49 

50 def __init__(self, filename): 

51 super().__init__("", 0, 0, filename) 

52 self.nodes = [] 

53 self.page_attributes = {} 

54 

55 def get_children(self): 

56 return self.nodes 

57 

58 def __repr__(self): 

59 return "TemplateNode(%s, %r)" % ( 

60 util.sorted_dict_repr(self.page_attributes), 

61 self.nodes, 

62 ) 

63 

64 

65class ControlLine(Node): 

66 """defines a control line, a line-oriented python line or end tag. 

67 

68 e.g.:: 

69 

70 % if foo: 

71 (markup) 

72 % endif 

73 

74 """ 

75 

76 has_loop_context = False 

77 

78 def __init__(self, keyword, isend, text, **kwargs): 

79 super().__init__(**kwargs) 

80 self.text = text 

81 self.keyword = keyword 

82 self.isend = isend 

83 self.is_primary = keyword in ["for", "if", "while", "try", "with"] 

84 self.nodes = [] 

85 if self.isend: 

86 self._declared_identifiers = [] 

87 self._undeclared_identifiers = [] 

88 else: 

89 code = ast.PythonFragment(text, **self.exception_kwargs) 

90 self._declared_identifiers = code.declared_identifiers 

91 self._undeclared_identifiers = code.undeclared_identifiers 

92 

93 def get_children(self): 

94 return self.nodes 

95 

96 def declared_identifiers(self): 

97 return self._declared_identifiers 

98 

99 def undeclared_identifiers(self): 

100 return self._undeclared_identifiers 

101 

102 def is_ternary(self, keyword): 

103 """return true if the given keyword is a ternary keyword 

104 for this ControlLine""" 

105 

106 cases = { 

107 "if": {"else", "elif"}, 

108 "try": {"except", "finally"}, 

109 "for": {"else"}, 

110 } 

111 

112 return keyword in cases.get(self.keyword, set()) 

113 

114 def __repr__(self): 

115 return "ControlLine(%r, %r, %r, %r)" % ( 

116 self.keyword, 

117 self.text, 

118 self.isend, 

119 (self.lineno, self.pos), 

120 ) 

121 

122 

123class Text(Node): 

124 """defines plain text in the template.""" 

125 

126 def __init__(self, content, **kwargs): 

127 super().__init__(**kwargs) 

128 self.content = content 

129 

130 def __repr__(self): 

131 return "Text(%r, %r)" % (self.content, (self.lineno, self.pos)) 

132 

133 

134class Code(Node): 

135 """defines a Python code block, either inline or module level. 

136 

137 e.g.:: 

138 

139 inline: 

140 <% 

141 x = 12 

142 %> 

143 

144 module level: 

145 <%! 

146 import logger 

147 %> 

148 

149 """ 

150 

151 def __init__(self, text, ismodule, **kwargs): 

152 super().__init__(**kwargs) 

153 self.text = text 

154 self.ismodule = ismodule 

155 self.code = ast.PythonCode(text, **self.exception_kwargs) 

156 

157 def declared_identifiers(self): 

158 return self.code.declared_identifiers 

159 

160 def undeclared_identifiers(self): 

161 return self.code.undeclared_identifiers 

162 

163 def __repr__(self): 

164 return "Code(%r, %r, %r)" % ( 

165 self.text, 

166 self.ismodule, 

167 (self.lineno, self.pos), 

168 ) 

169 

170 

171class Comment(Node): 

172 """defines a comment line. 

173 

174 # this is a comment 

175 

176 """ 

177 

178 def __init__(self, text, **kwargs): 

179 super().__init__(**kwargs) 

180 self.text = text 

181 

182 def __repr__(self): 

183 return "Comment(%r, %r)" % (self.text, (self.lineno, self.pos)) 

184 

185 

186class Expression(Node): 

187 """defines an inline expression. 

188 

189 ${x+y} 

190 

191 """ 

192 

193 def __init__(self, text, escapes, **kwargs): 

194 super().__init__(**kwargs) 

195 self.text = text 

196 self.escapes = escapes 

197 self.escapes_code = ast.ArgumentList(escapes, **self.exception_kwargs) 

198 self.code = ast.PythonCode(text, **self.exception_kwargs) 

199 

200 def declared_identifiers(self): 

201 return [] 

202 

203 def undeclared_identifiers(self): 

204 # TODO: make the "filter" shortcut list configurable at parse/gen time 

205 return self.code.undeclared_identifiers.union( 

206 self.escapes_code.undeclared_identifiers.difference( 

207 filters.DEFAULT_ESCAPES 

208 ) 

209 ).difference(self.code.declared_identifiers) 

210 

211 def __repr__(self): 

212 return "Expression(%r, %r, %r)" % ( 

213 self.text, 

214 self.escapes_code.args, 

215 (self.lineno, self.pos), 

216 ) 

217 

218 

219class _TagMeta(type): 

220 """metaclass to allow Tag to produce a subclass according to 

221 its keyword""" 

222 

223 _classmap = {} 

224 

225 def __init__(cls, clsname, bases, dict_): 

226 if getattr(cls, "__keyword__", None) is not None: 

227 cls._classmap[cls.__keyword__] = cls 

228 super().__init__(clsname, bases, dict_) 

229 

230 def __call__(cls, keyword, attributes, **kwargs): 

231 if ":" in keyword: 

232 ns, defname = keyword.split(":") 

233 return type.__call__( 

234 CallNamespaceTag, ns, defname, attributes, **kwargs 

235 ) 

236 

237 try: 

238 cls = _TagMeta._classmap[keyword] 

239 except KeyError: 

240 raise exceptions.CompileException( 

241 "No such tag: '%s'" % keyword, 

242 source=kwargs["source"], 

243 lineno=kwargs["lineno"], 

244 pos=kwargs["pos"], 

245 filename=kwargs["filename"], 

246 ) 

247 return type.__call__(cls, keyword, attributes, **kwargs) 

248 

249 

250class Tag(Node, metaclass=_TagMeta): 

251 """abstract base class for tags. 

252 

253 e.g.:: 

254 

255 <%sometag/> 

256 

257 <%someothertag> 

258 stuff 

259 </%someothertag> 

260 

261 """ 

262 

263 __keyword__ = None 

264 

265 def __init__( 

266 self, 

267 keyword, 

268 attributes, 

269 expressions, 

270 nonexpressions, 

271 required, 

272 **kwargs, 

273 ): 

274 r"""construct a new Tag instance. 

275 

276 this constructor not called directly, and is only called 

277 by subclasses. 

278 

279 :param keyword: the tag keyword 

280 

281 :param attributes: raw dictionary of attribute key/value pairs 

282 

283 :param expressions: a set of identifiers that are legal attributes, 

284 which can also contain embedded expressions 

285 

286 :param nonexpressions: a set of identifiers that are legal 

287 attributes, which cannot contain embedded expressions 

288 

289 :param \**kwargs: 

290 other arguments passed to the Node superclass (lineno, pos) 

291 

292 """ 

293 super().__init__(**kwargs) 

294 self.keyword = keyword 

295 self.attributes = attributes 

296 self._parse_attributes(expressions, nonexpressions) 

297 missing = [r for r in required if r not in self.parsed_attributes] 

298 if len(missing): 

299 raise exceptions.CompileException( 

300 ( 

301 "Missing attribute(s): %s" 

302 % ",".join(repr(m) for m in missing) 

303 ), 

304 **self.exception_kwargs, 

305 ) 

306 

307 self.parent = None 

308 self.nodes = [] 

309 

310 def is_root(self): 

311 return self.parent is None 

312 

313 def get_children(self): 

314 return self.nodes 

315 

316 def _parse_attributes(self, expressions, nonexpressions): 

317 undeclared_identifiers = set() 

318 self.parsed_attributes = {} 

319 for key in self.attributes: 

320 if key in expressions: 

321 expr = [] 

322 for x in re.compile(r"(\${(?:[^$]*?{.+|.+?)})", re.S).split( 

323 self.attributes[key] 

324 ): 

325 m = re.compile(r"^\${(.+?)}$", re.S).match(x) 

326 if m: 

327 code = ast.PythonCode( 

328 m.group(1).rstrip(), **self.exception_kwargs 

329 ) 

330 # we aren't discarding "declared_identifiers" here, 

331 # which we do so that list comprehension-declared 

332 # variables aren't counted. As yet can't find a 

333 # condition that requires it here. 

334 undeclared_identifiers = undeclared_identifiers.union( 

335 code.undeclared_identifiers 

336 ) 

337 expr.append("(%s)" % m.group(1)) 

338 elif x: 

339 expr.append(repr(x)) 

340 self.parsed_attributes[key] = " + ".join(expr) or repr("") 

341 elif key in nonexpressions: 

342 if re.search(r"\${.+?}", self.attributes[key]): 

343 raise exceptions.CompileException( 

344 "Attribute '%s' in tag '%s' does not allow embedded " 

345 "expressions" % (key, self.keyword), 

346 **self.exception_kwargs, 

347 ) 

348 self.parsed_attributes[key] = repr(self.attributes[key]) 

349 else: 

350 raise exceptions.CompileException( 

351 "Invalid attribute for tag '%s': '%s'" 

352 % (self.keyword, key), 

353 **self.exception_kwargs, 

354 ) 

355 self.expression_undeclared_identifiers = undeclared_identifiers 

356 

357 def declared_identifiers(self): 

358 return [] 

359 

360 def undeclared_identifiers(self): 

361 return self.expression_undeclared_identifiers 

362 

363 def __repr__(self): 

364 return "%s(%r, %s, %r, %r)" % ( 

365 self.__class__.__name__, 

366 self.keyword, 

367 util.sorted_dict_repr(self.attributes), 

368 (self.lineno, self.pos), 

369 self.nodes, 

370 ) 

371 

372 

373class IncludeTag(Tag): 

374 __keyword__ = "include" 

375 

376 def __init__(self, keyword, attributes, **kwargs): 

377 super().__init__( 

378 keyword, 

379 attributes, 

380 ("file", "import", "args"), 

381 (), 

382 ("file",), 

383 **kwargs, 

384 ) 

385 self.page_args = ast.PythonCode( 

386 "__DUMMY(%s)" % attributes.get("args", ""), **self.exception_kwargs 

387 ) 

388 

389 def declared_identifiers(self): 

390 return [] 

391 

392 def undeclared_identifiers(self): 

393 identifiers = self.page_args.undeclared_identifiers.difference( 

394 {"__DUMMY"} 

395 ).difference(self.page_args.declared_identifiers) 

396 return identifiers.union(super().undeclared_identifiers()) 

397 

398 

399class NamespaceTag(Tag): 

400 __keyword__ = "namespace" 

401 

402 def __init__(self, keyword, attributes, **kwargs): 

403 super().__init__( 

404 keyword, 

405 attributes, 

406 ("file",), 

407 ("name", "inheritable", "import", "module"), 

408 (), 

409 **kwargs, 

410 ) 

411 

412 self.name = attributes.get("name", "__anon_%s" % hex(abs(id(self)))) 

413 if "name" not in attributes and "import" not in attributes: 

414 raise exceptions.CompileException( 

415 "'name' and/or 'import' attributes are required " 

416 "for <%namespace>", 

417 **self.exception_kwargs, 

418 ) 

419 if "file" in attributes and "module" in attributes: 

420 raise exceptions.CompileException( 

421 "<%namespace> may only have one of 'file' or 'module'", 

422 **self.exception_kwargs, 

423 ) 

424 

425 def declared_identifiers(self): 

426 return [] 

427 

428 

429class TextTag(Tag): 

430 __keyword__ = "text" 

431 

432 def __init__(self, keyword, attributes, **kwargs): 

433 super().__init__(keyword, attributes, (), ("filter"), (), **kwargs) 

434 self.filter_args = ast.ArgumentList( 

435 attributes.get("filter", ""), **self.exception_kwargs 

436 ) 

437 

438 def undeclared_identifiers(self): 

439 return self.filter_args.undeclared_identifiers.difference( 

440 filters.DEFAULT_ESCAPES.keys() 

441 ).union(self.expression_undeclared_identifiers) 

442 

443 

444class DefTag(Tag): 

445 __keyword__ = "def" 

446 

447 def __init__(self, keyword, attributes, **kwargs): 

448 expressions = ["buffered", "cached"] + [ 

449 c for c in attributes if c.startswith("cache_") 

450 ] 

451 

452 super().__init__( 

453 keyword, 

454 attributes, 

455 expressions, 

456 ("name", "filter", "decorator"), 

457 ("name",), 

458 **kwargs, 

459 ) 

460 name = attributes["name"] 

461 if re.match(r"^[\w_]+$", name): 

462 raise exceptions.CompileException( 

463 "Missing parenthesis in %def", **self.exception_kwargs 

464 ) 

465 self.function_decl = ast.FunctionDecl( 

466 "def " + name + ":pass", **self.exception_kwargs 

467 ) 

468 self.name = self.function_decl.funcname 

469 self.decorator = attributes.get("decorator", "") 

470 self.filter_args = ast.ArgumentList( 

471 attributes.get("filter", ""), **self.exception_kwargs 

472 ) 

473 

474 is_anonymous = False 

475 is_block = False 

476 

477 @property 

478 def funcname(self): 

479 return self.function_decl.funcname 

480 

481 def get_argument_expressions(self, **kw): 

482 return self.function_decl.get_argument_expressions(**kw) 

483 

484 def declared_identifiers(self): 

485 return self.function_decl.allargnames 

486 

487 def undeclared_identifiers(self): 

488 res = [] 

489 for c in self.function_decl.defaults: 

490 res += list( 

491 ast.PythonCode( 

492 c, **self.exception_kwargs 

493 ).undeclared_identifiers 

494 ) 

495 return ( 

496 set(res) 

497 .union( 

498 self.filter_args.undeclared_identifiers.difference( 

499 filters.DEFAULT_ESCAPES.keys() 

500 ) 

501 ) 

502 .union(self.expression_undeclared_identifiers) 

503 .difference(self.function_decl.allargnames) 

504 ) 

505 

506 

507class BlockTag(Tag): 

508 __keyword__ = "block" 

509 

510 def __init__(self, keyword, attributes, **kwargs): 

511 expressions = ["buffered", "cached", "args"] + [ 

512 c for c in attributes if c.startswith("cache_") 

513 ] 

514 

515 super().__init__( 

516 keyword, 

517 attributes, 

518 expressions, 

519 ("name", "filter", "decorator"), 

520 (), 

521 **kwargs, 

522 ) 

523 name = attributes.get("name") 

524 if name and not re.match(r"^[\w_]+$", name): 

525 raise exceptions.CompileException( 

526 "%block may not specify an argument signature", 

527 **self.exception_kwargs, 

528 ) 

529 if not name and attributes.get("args", None): 

530 raise exceptions.CompileException( 

531 "Only named %blocks may specify args", **self.exception_kwargs 

532 ) 

533 self.body_decl = ast.FunctionArgs( 

534 attributes.get("args", ""), **self.exception_kwargs 

535 ) 

536 

537 self.name = name 

538 self.decorator = attributes.get("decorator", "") 

539 self.filter_args = ast.ArgumentList( 

540 attributes.get("filter", ""), **self.exception_kwargs 

541 ) 

542 

543 is_block = True 

544 

545 @property 

546 def is_anonymous(self): 

547 return self.name is None 

548 

549 @property 

550 def funcname(self): 

551 return self.name or "__M_anon_%d" % (self.lineno,) 

552 

553 def get_argument_expressions(self, **kw): 

554 return self.body_decl.get_argument_expressions(**kw) 

555 

556 def declared_identifiers(self): 

557 return self.body_decl.allargnames 

558 

559 def undeclared_identifiers(self): 

560 return ( 

561 self.filter_args.undeclared_identifiers.difference( 

562 filters.DEFAULT_ESCAPES.keys() 

563 ) 

564 ).union(self.expression_undeclared_identifiers) 

565 

566 

567class CallTag(Tag): 

568 __keyword__ = "call" 

569 

570 def __init__(self, keyword, attributes, **kwargs): 

571 super().__init__( 

572 keyword, attributes, ("args"), ("expr",), ("expr",), **kwargs 

573 ) 

574 self.expression = attributes["expr"] 

575 self.code = ast.PythonCode(self.expression, **self.exception_kwargs) 

576 self.body_decl = ast.FunctionArgs( 

577 attributes.get("args", ""), **self.exception_kwargs 

578 ) 

579 

580 def declared_identifiers(self): 

581 return self.code.declared_identifiers.union(self.body_decl.allargnames) 

582 

583 def undeclared_identifiers(self): 

584 return self.code.undeclared_identifiers.difference( 

585 self.code.declared_identifiers 

586 ) 

587 

588 

589class CallNamespaceTag(Tag): 

590 def __init__(self, namespace, defname, attributes, **kwargs): 

591 super().__init__( 

592 namespace + ":" + defname, 

593 attributes, 

594 tuple(attributes.keys()) + ("args",), 

595 (), 

596 (), 

597 **kwargs, 

598 ) 

599 

600 self.expression = "%s.%s(%s)" % ( 

601 namespace, 

602 defname, 

603 ",".join( 

604 "%s=%s" % (k, v) 

605 for k, v in self.parsed_attributes.items() 

606 if k != "args" 

607 ), 

608 ) 

609 

610 self.code = ast.PythonCode(self.expression, **self.exception_kwargs) 

611 self.body_decl = ast.FunctionArgs( 

612 attributes.get("args", ""), **self.exception_kwargs 

613 ) 

614 

615 def declared_identifiers(self): 

616 return self.code.declared_identifiers.union(self.body_decl.allargnames) 

617 

618 def undeclared_identifiers(self): 

619 return self.code.undeclared_identifiers.difference( 

620 self.code.declared_identifiers 

621 ) 

622 

623 

624class InheritTag(Tag): 

625 __keyword__ = "inherit" 

626 

627 def __init__(self, keyword, attributes, **kwargs): 

628 super().__init__( 

629 keyword, attributes, ("file",), (), ("file",), **kwargs 

630 ) 

631 

632 

633class PageTag(Tag): 

634 __keyword__ = "page" 

635 

636 def __init__(self, keyword, attributes, **kwargs): 

637 expressions = [ 

638 "cached", 

639 "args", 

640 "expression_filter", 

641 "enable_loop", 

642 ] + [c for c in attributes if c.startswith("cache_")] 

643 

644 super().__init__(keyword, attributes, expressions, (), (), **kwargs) 

645 self.body_decl = ast.FunctionArgs( 

646 attributes.get("args", ""), **self.exception_kwargs 

647 ) 

648 self.filter_args = ast.ArgumentList( 

649 attributes.get("expression_filter", ""), **self.exception_kwargs 

650 ) 

651 

652 def declared_identifiers(self): 

653 return self.body_decl.allargnames