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

268 statements  

« prev     ^ index     » next       coverage.py v7.2.7, created at 2023-06-07 06:02 +0000

1# mako/parsetree.py 

2# Copyright 2006-2023 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 

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

20 

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

22 self.source = source 

23 self.lineno = lineno 

24 self.pos = pos 

25 self.filename = filename 

26 

27 @property 

28 def exception_kwargs(self): 

29 return { 

30 "source": self.source, 

31 "lineno": self.lineno, 

32 "pos": self.pos, 

33 "filename": self.filename, 

34 } 

35 

36 def get_children(self): 

37 return [] 

38 

39 def accept_visitor(self, visitor): 

40 def traverse(node): 

41 for n in node.get_children(): 

42 n.accept_visitor(visitor) 

43 

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

45 method(self) 

46 

47 

48class TemplateNode(Node): 

49 

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

51 

52 def __init__(self, filename): 

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

54 self.nodes = [] 

55 self.page_attributes = {} 

56 

57 def get_children(self): 

58 return self.nodes 

59 

60 def __repr__(self): 

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

62 util.sorted_dict_repr(self.page_attributes), 

63 self.nodes, 

64 ) 

65 

66 

67class ControlLine(Node): 

68 

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

70 

71 e.g.:: 

72 

73 % if foo: 

74 (markup) 

75 % endif 

76 

77 """ 

78 

79 has_loop_context = False 

80 

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

82 super().__init__(**kwargs) 

83 self.text = text 

84 self.keyword = keyword 

85 self.isend = isend 

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

87 self.nodes = [] 

88 if self.isend: 

89 self._declared_identifiers = [] 

90 self._undeclared_identifiers = [] 

91 else: 

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

93 self._declared_identifiers = code.declared_identifiers 

94 self._undeclared_identifiers = code.undeclared_identifiers 

95 

96 def get_children(self): 

97 return self.nodes 

98 

99 def declared_identifiers(self): 

100 return self._declared_identifiers 

101 

102 def undeclared_identifiers(self): 

103 return self._undeclared_identifiers 

104 

105 def is_ternary(self, keyword): 

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

107 for this ControlLine""" 

108 

109 cases = { 

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

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

112 "for": {"else"}, 

113 } 

114 

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

116 

117 def __repr__(self): 

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

119 self.keyword, 

120 self.text, 

121 self.isend, 

122 (self.lineno, self.pos), 

123 ) 

124 

125 

126class Text(Node): 

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

128 

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

130 super().__init__(**kwargs) 

131 self.content = content 

132 

133 def __repr__(self): 

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

135 

136 

137class Code(Node): 

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

139 

140 e.g.:: 

141 

142 inline: 

143 <% 

144 x = 12 

145 %> 

146 

147 module level: 

148 <%! 

149 import logger 

150 %> 

151 

152 """ 

153 

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

155 super().__init__(**kwargs) 

156 self.text = text 

157 self.ismodule = ismodule 

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

159 

160 def declared_identifiers(self): 

161 return self.code.declared_identifiers 

162 

163 def undeclared_identifiers(self): 

164 return self.code.undeclared_identifiers 

165 

166 def __repr__(self): 

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

168 self.text, 

169 self.ismodule, 

170 (self.lineno, self.pos), 

171 ) 

172 

173 

174class Comment(Node): 

175 """defines a comment line. 

176 

177 # this is a comment 

178 

179 """ 

180 

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

182 super().__init__(**kwargs) 

183 self.text = text 

184 

185 def __repr__(self): 

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

187 

188 

189class Expression(Node): 

190 """defines an inline expression. 

191 

192 ${x+y} 

193 

194 """ 

195 

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

197 super().__init__(**kwargs) 

198 self.text = text 

199 self.escapes = escapes 

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

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

202 

203 def declared_identifiers(self): 

204 return [] 

205 

206 def undeclared_identifiers(self): 

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

208 return self.code.undeclared_identifiers.union( 

209 self.escapes_code.undeclared_identifiers.difference( 

210 filters.DEFAULT_ESCAPES 

211 ) 

212 ).difference(self.code.declared_identifiers) 

213 

214 def __repr__(self): 

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

216 self.text, 

217 self.escapes_code.args, 

218 (self.lineno, self.pos), 

219 ) 

220 

221 

222class _TagMeta(type): 

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

224 its keyword""" 

225 

226 _classmap = {} 

227 

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

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

230 cls._classmap[cls.__keyword__] = cls 

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

232 

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

234 if ":" in keyword: 

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

236 return type.__call__( 

237 CallNamespaceTag, ns, defname, attributes, **kwargs 

238 ) 

239 

240 try: 

241 cls = _TagMeta._classmap[keyword] 

242 except KeyError: 

243 raise exceptions.CompileException( 

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

245 source=kwargs["source"], 

246 lineno=kwargs["lineno"], 

247 pos=kwargs["pos"], 

248 filename=kwargs["filename"], 

249 ) 

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

251 

252 

253class Tag(Node, metaclass=_TagMeta): 

254 """abstract base class for tags. 

255 

256 e.g.:: 

257 

258 <%sometag/> 

259 

260 <%someothertag> 

261 stuff 

262 </%someothertag> 

263 

264 """ 

265 

266 __keyword__ = None 

267 

268 def __init__( 

269 self, 

270 keyword, 

271 attributes, 

272 expressions, 

273 nonexpressions, 

274 required, 

275 **kwargs, 

276 ): 

277 r"""construct a new Tag instance. 

278 

279 this constructor not called directly, and is only called 

280 by subclasses. 

281 

282 :param keyword: the tag keyword 

283 

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

285 

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

287 which can also contain embedded expressions 

288 

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

290 attributes, which cannot contain embedded expressions 

291 

292 :param \**kwargs: 

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

294 

295 """ 

296 super().__init__(**kwargs) 

297 self.keyword = keyword 

298 self.attributes = attributes 

299 self._parse_attributes(expressions, nonexpressions) 

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

301 if len(missing): 

302 raise exceptions.CompileException( 

303 ( 

304 "Missing attribute(s): %s" 

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

306 ), 

307 **self.exception_kwargs, 

308 ) 

309 

310 self.parent = None 

311 self.nodes = [] 

312 

313 def is_root(self): 

314 return self.parent is None 

315 

316 def get_children(self): 

317 return self.nodes 

318 

319 def _parse_attributes(self, expressions, nonexpressions): 

320 undeclared_identifiers = set() 

321 self.parsed_attributes = {} 

322 for key in self.attributes: 

323 if key in expressions: 

324 expr = [] 

325 for x in re.compile(r"(\${.+?})", re.S).split( 

326 self.attributes[key] 

327 ): 

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

329 if m: 

330 code = ast.PythonCode( 

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

332 ) 

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

334 # which we do so that list comprehension-declared 

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

336 # condition that requires it here. 

337 undeclared_identifiers = undeclared_identifiers.union( 

338 code.undeclared_identifiers 

339 ) 

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

341 elif x: 

342 expr.append(repr(x)) 

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

344 elif key in nonexpressions: 

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

346 raise exceptions.CompileException( 

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

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

349 **self.exception_kwargs, 

350 ) 

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

352 else: 

353 raise exceptions.CompileException( 

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

355 % (self.keyword, key), 

356 **self.exception_kwargs, 

357 ) 

358 self.expression_undeclared_identifiers = undeclared_identifiers 

359 

360 def declared_identifiers(self): 

361 return [] 

362 

363 def undeclared_identifiers(self): 

364 return self.expression_undeclared_identifiers 

365 

366 def __repr__(self): 

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

368 self.__class__.__name__, 

369 self.keyword, 

370 util.sorted_dict_repr(self.attributes), 

371 (self.lineno, self.pos), 

372 self.nodes, 

373 ) 

374 

375 

376class IncludeTag(Tag): 

377 __keyword__ = "include" 

378 

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

380 super().__init__( 

381 keyword, 

382 attributes, 

383 ("file", "import", "args"), 

384 (), 

385 ("file",), 

386 **kwargs, 

387 ) 

388 self.page_args = ast.PythonCode( 

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

390 ) 

391 

392 def declared_identifiers(self): 

393 return [] 

394 

395 def undeclared_identifiers(self): 

396 identifiers = self.page_args.undeclared_identifiers.difference( 

397 {"__DUMMY"} 

398 ).difference(self.page_args.declared_identifiers) 

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

400 

401 

402class NamespaceTag(Tag): 

403 __keyword__ = "namespace" 

404 

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

406 super().__init__( 

407 keyword, 

408 attributes, 

409 ("file",), 

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

411 (), 

412 **kwargs, 

413 ) 

414 

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

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

417 raise exceptions.CompileException( 

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

419 "for <%namespace>", 

420 **self.exception_kwargs, 

421 ) 

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

423 raise exceptions.CompileException( 

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

425 **self.exception_kwargs, 

426 ) 

427 

428 def declared_identifiers(self): 

429 return [] 

430 

431 

432class TextTag(Tag): 

433 __keyword__ = "text" 

434 

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

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

437 self.filter_args = ast.ArgumentList( 

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

439 ) 

440 

441 def undeclared_identifiers(self): 

442 return self.filter_args.undeclared_identifiers.difference( 

443 filters.DEFAULT_ESCAPES.keys() 

444 ).union(self.expression_undeclared_identifiers) 

445 

446 

447class DefTag(Tag): 

448 __keyword__ = "def" 

449 

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

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

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

453 ] 

454 

455 super().__init__( 

456 keyword, 

457 attributes, 

458 expressions, 

459 ("name", "filter", "decorator"), 

460 ("name",), 

461 **kwargs, 

462 ) 

463 name = attributes["name"] 

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

465 raise exceptions.CompileException( 

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

467 ) 

468 self.function_decl = ast.FunctionDecl( 

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

470 ) 

471 self.name = self.function_decl.funcname 

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

473 self.filter_args = ast.ArgumentList( 

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

475 ) 

476 

477 is_anonymous = False 

478 is_block = False 

479 

480 @property 

481 def funcname(self): 

482 return self.function_decl.funcname 

483 

484 def get_argument_expressions(self, **kw): 

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

486 

487 def declared_identifiers(self): 

488 return self.function_decl.allargnames 

489 

490 def undeclared_identifiers(self): 

491 res = [] 

492 for c in self.function_decl.defaults: 

493 res += list( 

494 ast.PythonCode( 

495 c, **self.exception_kwargs 

496 ).undeclared_identifiers 

497 ) 

498 return ( 

499 set(res) 

500 .union( 

501 self.filter_args.undeclared_identifiers.difference( 

502 filters.DEFAULT_ESCAPES.keys() 

503 ) 

504 ) 

505 .union(self.expression_undeclared_identifiers) 

506 .difference(self.function_decl.allargnames) 

507 ) 

508 

509 

510class BlockTag(Tag): 

511 __keyword__ = "block" 

512 

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

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

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

516 ] 

517 

518 super().__init__( 

519 keyword, 

520 attributes, 

521 expressions, 

522 ("name", "filter", "decorator"), 

523 (), 

524 **kwargs, 

525 ) 

526 name = attributes.get("name") 

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

528 raise exceptions.CompileException( 

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

530 **self.exception_kwargs, 

531 ) 

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

533 raise exceptions.CompileException( 

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

535 ) 

536 self.body_decl = ast.FunctionArgs( 

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

538 ) 

539 

540 self.name = name 

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

542 self.filter_args = ast.ArgumentList( 

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

544 ) 

545 

546 is_block = True 

547 

548 @property 

549 def is_anonymous(self): 

550 return self.name is None 

551 

552 @property 

553 def funcname(self): 

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

555 

556 def get_argument_expressions(self, **kw): 

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

558 

559 def declared_identifiers(self): 

560 return self.body_decl.allargnames 

561 

562 def undeclared_identifiers(self): 

563 return ( 

564 self.filter_args.undeclared_identifiers.difference( 

565 filters.DEFAULT_ESCAPES.keys() 

566 ) 

567 ).union(self.expression_undeclared_identifiers) 

568 

569 

570class CallTag(Tag): 

571 __keyword__ = "call" 

572 

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

574 super().__init__( 

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

576 ) 

577 self.expression = attributes["expr"] 

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

579 self.body_decl = ast.FunctionArgs( 

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

581 ) 

582 

583 def declared_identifiers(self): 

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

585 

586 def undeclared_identifiers(self): 

587 return self.code.undeclared_identifiers.difference( 

588 self.code.declared_identifiers 

589 ) 

590 

591 

592class CallNamespaceTag(Tag): 

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

594 super().__init__( 

595 namespace + ":" + defname, 

596 attributes, 

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

598 (), 

599 (), 

600 **kwargs, 

601 ) 

602 

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

604 namespace, 

605 defname, 

606 ",".join( 

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

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

609 if k != "args" 

610 ), 

611 ) 

612 

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

614 self.body_decl = ast.FunctionArgs( 

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

616 ) 

617 

618 def declared_identifiers(self): 

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

620 

621 def undeclared_identifiers(self): 

622 return self.code.undeclared_identifiers.difference( 

623 self.code.declared_identifiers 

624 ) 

625 

626 

627class InheritTag(Tag): 

628 __keyword__ = "inherit" 

629 

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

631 super().__init__( 

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

633 ) 

634 

635 

636class PageTag(Tag): 

637 __keyword__ = "page" 

638 

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

640 expressions = [ 

641 "cached", 

642 "args", 

643 "expression_filter", 

644 "enable_loop", 

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

646 

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

648 self.body_decl = ast.FunctionArgs( 

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

650 ) 

651 self.filter_args = ast.ArgumentList( 

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

653 ) 

654 

655 def declared_identifiers(self): 

656 return self.body_decl.allargnames