Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/mako/parsetree.py: 39%
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
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
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
7"""defines the parse tree components for Mako templates."""
9import re
11from mako import ast
12from mako import exceptions
13from mako import filters
14from mako import util
17class Node:
18 """base class for a Node in the parse tree."""
20 def __init__(self, source, lineno, pos, filename):
21 self.source = source
22 self.lineno = lineno
23 self.pos = pos
24 self.filename = filename
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 }
35 def get_children(self):
36 return []
38 def accept_visitor(self, visitor):
39 def traverse(node):
40 for n in node.get_children():
41 n.accept_visitor(visitor)
43 method = getattr(visitor, "visit" + self.__class__.__name__, traverse)
44 method(self)
47class TemplateNode(Node):
48 """a 'container' node that stores the overall collection of nodes."""
50 def __init__(self, filename):
51 super().__init__("", 0, 0, filename)
52 self.nodes = []
53 self.page_attributes = {}
55 def get_children(self):
56 return self.nodes
58 def __repr__(self):
59 return "TemplateNode(%s, %r)" % (
60 util.sorted_dict_repr(self.page_attributes),
61 self.nodes,
62 )
65class ControlLine(Node):
66 """defines a control line, a line-oriented python line or end tag.
68 e.g.::
70 % if foo:
71 (markup)
72 % endif
74 """
76 has_loop_context = False
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
93 def get_children(self):
94 return self.nodes
96 def declared_identifiers(self):
97 return self._declared_identifiers
99 def undeclared_identifiers(self):
100 return self._undeclared_identifiers
102 def is_ternary(self, keyword):
103 """return true if the given keyword is a ternary keyword
104 for this ControlLine"""
106 cases = {
107 "if": {"else", "elif"},
108 "try": {"except", "finally"},
109 "for": {"else"},
110 }
112 return keyword in cases.get(self.keyword, set())
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 )
123class Text(Node):
124 """defines plain text in the template."""
126 def __init__(self, content, **kwargs):
127 super().__init__(**kwargs)
128 self.content = content
130 def __repr__(self):
131 return "Text(%r, %r)" % (self.content, (self.lineno, self.pos))
134class Code(Node):
135 """defines a Python code block, either inline or module level.
137 e.g.::
139 inline:
140 <%
141 x = 12
142 %>
144 module level:
145 <%!
146 import logger
147 %>
149 """
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)
157 def declared_identifiers(self):
158 return self.code.declared_identifiers
160 def undeclared_identifiers(self):
161 return self.code.undeclared_identifiers
163 def __repr__(self):
164 return "Code(%r, %r, %r)" % (
165 self.text,
166 self.ismodule,
167 (self.lineno, self.pos),
168 )
171class Comment(Node):
172 """defines a comment line.
174 # this is a comment
176 """
178 def __init__(self, text, **kwargs):
179 super().__init__(**kwargs)
180 self.text = text
182 def __repr__(self):
183 return "Comment(%r, %r)" % (self.text, (self.lineno, self.pos))
186class Expression(Node):
187 """defines an inline expression.
189 ${x+y}
191 """
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)
200 def declared_identifiers(self):
201 return []
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)
211 def __repr__(self):
212 return "Expression(%r, %r, %r)" % (
213 self.text,
214 self.escapes_code.args,
215 (self.lineno, self.pos),
216 )
219class _TagMeta(type):
220 """metaclass to allow Tag to produce a subclass according to
221 its keyword"""
223 _classmap = {}
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_)
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 )
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)
250class Tag(Node, metaclass=_TagMeta):
251 """abstract base class for tags.
253 e.g.::
255 <%sometag/>
257 <%someothertag>
258 stuff
259 </%someothertag>
261 """
263 __keyword__ = None
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.
276 this constructor not called directly, and is only called
277 by subclasses.
279 :param keyword: the tag keyword
281 :param attributes: raw dictionary of attribute key/value pairs
283 :param expressions: a set of identifiers that are legal attributes,
284 which can also contain embedded expressions
286 :param nonexpressions: a set of identifiers that are legal
287 attributes, which cannot contain embedded expressions
289 :param \**kwargs:
290 other arguments passed to the Node superclass (lineno, pos)
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 )
307 self.parent = None
308 self.nodes = []
310 def is_root(self):
311 return self.parent is None
313 def get_children(self):
314 return self.nodes
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
357 def declared_identifiers(self):
358 return []
360 def undeclared_identifiers(self):
361 return self.expression_undeclared_identifiers
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 )
373class IncludeTag(Tag):
374 __keyword__ = "include"
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 )
389 def declared_identifiers(self):
390 return []
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())
399class NamespaceTag(Tag):
400 __keyword__ = "namespace"
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 )
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 )
425 def declared_identifiers(self):
426 return []
429class TextTag(Tag):
430 __keyword__ = "text"
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 )
438 def undeclared_identifiers(self):
439 return self.filter_args.undeclared_identifiers.difference(
440 filters.DEFAULT_ESCAPES.keys()
441 ).union(self.expression_undeclared_identifiers)
444class DefTag(Tag):
445 __keyword__ = "def"
447 def __init__(self, keyword, attributes, **kwargs):
448 expressions = ["buffered", "cached"] + [
449 c for c in attributes if c.startswith("cache_")
450 ]
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 )
474 is_anonymous = False
475 is_block = False
477 @property
478 def funcname(self):
479 return self.function_decl.funcname
481 def get_argument_expressions(self, **kw):
482 return self.function_decl.get_argument_expressions(**kw)
484 def declared_identifiers(self):
485 return self.function_decl.allargnames
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 )
507class BlockTag(Tag):
508 __keyword__ = "block"
510 def __init__(self, keyword, attributes, **kwargs):
511 expressions = ["buffered", "cached", "args"] + [
512 c for c in attributes if c.startswith("cache_")
513 ]
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 )
537 self.name = name
538 self.decorator = attributes.get("decorator", "")
539 self.filter_args = ast.ArgumentList(
540 attributes.get("filter", ""), **self.exception_kwargs
541 )
543 is_block = True
545 @property
546 def is_anonymous(self):
547 return self.name is None
549 @property
550 def funcname(self):
551 return self.name or "__M_anon_%d" % (self.lineno,)
553 def get_argument_expressions(self, **kw):
554 return self.body_decl.get_argument_expressions(**kw)
556 def declared_identifiers(self):
557 return self.body_decl.allargnames
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)
567class CallTag(Tag):
568 __keyword__ = "call"
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 )
580 def declared_identifiers(self):
581 return self.code.declared_identifiers.union(self.body_decl.allargnames)
583 def undeclared_identifiers(self):
584 return self.code.undeclared_identifiers.difference(
585 self.code.declared_identifiers
586 )
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 )
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 )
610 self.code = ast.PythonCode(self.expression, **self.exception_kwargs)
611 self.body_decl = ast.FunctionArgs(
612 attributes.get("args", ""), **self.exception_kwargs
613 )
615 def declared_identifiers(self):
616 return self.code.declared_identifiers.union(self.body_decl.allargnames)
618 def undeclared_identifiers(self):
619 return self.code.undeclared_identifiers.difference(
620 self.code.declared_identifiers
621 )
624class InheritTag(Tag):
625 __keyword__ = "inherit"
627 def __init__(self, keyword, attributes, **kwargs):
628 super().__init__(
629 keyword, attributes, ("file",), (), ("file",), **kwargs
630 )
633class PageTag(Tag):
634 __keyword__ = "page"
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_")]
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 )
652 def declared_identifiers(self):
653 return self.body_decl.allargnames