codekingpro/portable-devtools
114k
1# mako/parsetree.py2# Copyright 2006-2024 the Mako authors and contributors <see AUTHORS file>3#4# This module is part of Mako and is released under5# the MIT License: http://www.opensource.org/licenses/mit-license.php6 7"""defines the parse tree components for Mako templates."""8 9import re10 11from mako import ast12from mako import exceptions13from mako import filters14from mako import util15 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 = source23 self.lineno = lineno24 self.pos = pos25 self.filename = filename26 27 @property28 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.nodes59 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 % endif76 77 """78 79 has_loop_context = False80 81 def __init__(self, keyword, isend, text, **kwargs):82 super().__init__(**kwargs)83 self.text = text84 self.keyword = keyword85 self.isend = isend86 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_identifiers94 self._undeclared_identifiers = code.undeclared_identifiers95 96 def get_children(self):97 return self.nodes98 99 def declared_identifiers(self):100 return self._declared_identifiers101 102 def undeclared_identifiers(self):103 return self._undeclared_identifiers104 105 def is_ternary(self, keyword):106 """return true if the given keyword is a ternary keyword107 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 = content132 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 = 12145 %>146 147 module level:148 <%!149 import logger150 %>151 152 """153 154 def __init__(self, text, ismodule, **kwargs):155 super().__init__(**kwargs)156 self.text = text157 self.ismodule = ismodule158 self.code = ast.PythonCode(text, **self.exception_kwargs)159 160 def declared_identifiers(self):161 return self.code.declared_identifiers162 163 def undeclared_identifiers(self):164 return self.code.undeclared_identifiers165 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 comment178 179 """180 181 def __init__(self, text, **kwargs):182 super().__init__(**kwargs)183 self.text = text184 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 = text199 self.escapes = escapes200 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 time208 return self.code.undeclared_identifiers.union(209 self.escapes_code.undeclared_identifiers.difference(210 filters.DEFAULT_ESCAPES211 )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 to224 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__] = cls231 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, **kwargs238 )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 stuff262 </%someothertag>263 264 """265 266 __keyword__ = None267 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 called280 by subclasses.281 282 :param keyword: the tag keyword283 284 :param attributes: raw dictionary of attribute key/value pairs285 286 :param expressions: a set of identifiers that are legal attributes,287 which can also contain embedded expressions288 289 :param nonexpressions: a set of identifiers that are legal290 attributes, which cannot contain embedded expressions291 292 :param \**kwargs:293 other arguments passed to the Node superclass (lineno, pos)294 295 """296 super().__init__(**kwargs)297 self.keyword = keyword298 self.attributes = attributes299 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 = None311 self.nodes = []312 313 def is_root(self):314 return self.parent is None315 316 def get_children(self):317 return self.nodes318 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_kwargs332 )333 # we aren't discarding "declared_identifiers" here,334 # which we do so that list comprehension-declared335 # variables aren't counted. As yet can't find a336 # condition that requires it here.337 undeclared_identifiers = undeclared_identifiers.union(338 code.undeclared_identifiers339 )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_identifiers359 360 def declared_identifiers(self):361 return []362 363 def undeclared_identifiers(self):364 return self.expression_undeclared_identifiers365 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_kwargs390 )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_kwargs439 )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_kwargs467 )468 self.function_decl = ast.FunctionDecl(469 "def " + name + ":pass", **self.exception_kwargs470 )471 self.name = self.function_decl.funcname472 self.decorator = attributes.get("decorator", "")473 self.filter_args = ast.ArgumentList(474 attributes.get("filter", ""), **self.exception_kwargs475 )476 477 is_anonymous = False478 is_block = False479 480 @property481 def funcname(self):482 return self.function_decl.funcname483 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.allargnames489 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_kwargs496 ).undeclared_identifiers497 )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_kwargs535 )536 self.body_decl = ast.FunctionArgs(537 attributes.get("args", ""), **self.exception_kwargs538 )539 540 self.name = name541 self.decorator = attributes.get("decorator", "")542 self.filter_args = ast.ArgumentList(543 attributes.get("filter", ""), **self.exception_kwargs544 )545 546 is_block = True547 548 @property549 def is_anonymous(self):550 return self.name is None551 552 @property553 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.allargnames561 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",), **kwargs576 )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_kwargs581 )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_identifiers589 )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_kwargs616 )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_identifiers624 )625 626 627class InheritTag(Tag):628 __keyword__ = "inherit"629 630 def __init__(self, keyword, attributes, **kwargs):631 super().__init__(632 keyword, attributes, ("file",), (), ("file",), **kwargs633 )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_kwargs650 )651 self.filter_args = ast.ArgumentList(652 attributes.get("expression_filter", ""), **self.exception_kwargs653 )654 655 def declared_identifiers(self):656 return self.body_decl.allargnames657 