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
« 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
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:
19 """base class for a Node in the parse tree."""
21 def __init__(self, source, lineno, pos, filename):
22 self.source = source
23 self.lineno = lineno
24 self.pos = pos
25 self.filename = filename
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 }
36 def get_children(self):
37 return []
39 def accept_visitor(self, visitor):
40 def traverse(node):
41 for n in node.get_children():
42 n.accept_visitor(visitor)
44 method = getattr(visitor, "visit" + self.__class__.__name__, traverse)
45 method(self)
48class TemplateNode(Node):
50 """a 'container' node that stores the overall collection of nodes."""
52 def __init__(self, filename):
53 super().__init__("", 0, 0, filename)
54 self.nodes = []
55 self.page_attributes = {}
57 def get_children(self):
58 return self.nodes
60 def __repr__(self):
61 return "TemplateNode(%s, %r)" % (
62 util.sorted_dict_repr(self.page_attributes),
63 self.nodes,
64 )
67class ControlLine(Node):
69 """defines a control line, a line-oriented python line or end tag.
71 e.g.::
73 % if foo:
74 (markup)
75 % endif
77 """
79 has_loop_context = False
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
96 def get_children(self):
97 return self.nodes
99 def declared_identifiers(self):
100 return self._declared_identifiers
102 def undeclared_identifiers(self):
103 return self._undeclared_identifiers
105 def is_ternary(self, keyword):
106 """return true if the given keyword is a ternary keyword
107 for this ControlLine"""
109 cases = {
110 "if": {"else", "elif"},
111 "try": {"except", "finally"},
112 "for": {"else"},
113 }
115 return keyword in cases.get(self.keyword, set())
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 )
126class Text(Node):
127 """defines plain text in the template."""
129 def __init__(self, content, **kwargs):
130 super().__init__(**kwargs)
131 self.content = content
133 def __repr__(self):
134 return "Text(%r, %r)" % (self.content, (self.lineno, self.pos))
137class Code(Node):
138 """defines a Python code block, either inline or module level.
140 e.g.::
142 inline:
143 <%
144 x = 12
145 %>
147 module level:
148 <%!
149 import logger
150 %>
152 """
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)
160 def declared_identifiers(self):
161 return self.code.declared_identifiers
163 def undeclared_identifiers(self):
164 return self.code.undeclared_identifiers
166 def __repr__(self):
167 return "Code(%r, %r, %r)" % (
168 self.text,
169 self.ismodule,
170 (self.lineno, self.pos),
171 )
174class Comment(Node):
175 """defines a comment line.
177 # this is a comment
179 """
181 def __init__(self, text, **kwargs):
182 super().__init__(**kwargs)
183 self.text = text
185 def __repr__(self):
186 return "Comment(%r, %r)" % (self.text, (self.lineno, self.pos))
189class Expression(Node):
190 """defines an inline expression.
192 ${x+y}
194 """
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)
203 def declared_identifiers(self):
204 return []
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)
214 def __repr__(self):
215 return "Expression(%r, %r, %r)" % (
216 self.text,
217 self.escapes_code.args,
218 (self.lineno, self.pos),
219 )
222class _TagMeta(type):
223 """metaclass to allow Tag to produce a subclass according to
224 its keyword"""
226 _classmap = {}
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_)
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 )
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)
253class Tag(Node, metaclass=_TagMeta):
254 """abstract base class for tags.
256 e.g.::
258 <%sometag/>
260 <%someothertag>
261 stuff
262 </%someothertag>
264 """
266 __keyword__ = None
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.
279 this constructor not called directly, and is only called
280 by subclasses.
282 :param keyword: the tag keyword
284 :param attributes: raw dictionary of attribute key/value pairs
286 :param expressions: a set of identifiers that are legal attributes,
287 which can also contain embedded expressions
289 :param nonexpressions: a set of identifiers that are legal
290 attributes, which cannot contain embedded expressions
292 :param \**kwargs:
293 other arguments passed to the Node superclass (lineno, pos)
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 )
310 self.parent = None
311 self.nodes = []
313 def is_root(self):
314 return self.parent is None
316 def get_children(self):
317 return self.nodes
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
360 def declared_identifiers(self):
361 return []
363 def undeclared_identifiers(self):
364 return self.expression_undeclared_identifiers
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 )
376class IncludeTag(Tag):
377 __keyword__ = "include"
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 )
392 def declared_identifiers(self):
393 return []
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())
402class NamespaceTag(Tag):
403 __keyword__ = "namespace"
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 )
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 )
428 def declared_identifiers(self):
429 return []
432class TextTag(Tag):
433 __keyword__ = "text"
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 )
441 def undeclared_identifiers(self):
442 return self.filter_args.undeclared_identifiers.difference(
443 filters.DEFAULT_ESCAPES.keys()
444 ).union(self.expression_undeclared_identifiers)
447class DefTag(Tag):
448 __keyword__ = "def"
450 def __init__(self, keyword, attributes, **kwargs):
451 expressions = ["buffered", "cached"] + [
452 c for c in attributes if c.startswith("cache_")
453 ]
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 )
477 is_anonymous = False
478 is_block = False
480 @property
481 def funcname(self):
482 return self.function_decl.funcname
484 def get_argument_expressions(self, **kw):
485 return self.function_decl.get_argument_expressions(**kw)
487 def declared_identifiers(self):
488 return self.function_decl.allargnames
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 )
510class BlockTag(Tag):
511 __keyword__ = "block"
513 def __init__(self, keyword, attributes, **kwargs):
514 expressions = ["buffered", "cached", "args"] + [
515 c for c in attributes if c.startswith("cache_")
516 ]
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 )
540 self.name = name
541 self.decorator = attributes.get("decorator", "")
542 self.filter_args = ast.ArgumentList(
543 attributes.get("filter", ""), **self.exception_kwargs
544 )
546 is_block = True
548 @property
549 def is_anonymous(self):
550 return self.name is None
552 @property
553 def funcname(self):
554 return self.name or "__M_anon_%d" % (self.lineno,)
556 def get_argument_expressions(self, **kw):
557 return self.body_decl.get_argument_expressions(**kw)
559 def declared_identifiers(self):
560 return self.body_decl.allargnames
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)
570class CallTag(Tag):
571 __keyword__ = "call"
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 )
583 def declared_identifiers(self):
584 return self.code.declared_identifiers.union(self.body_decl.allargnames)
586 def undeclared_identifiers(self):
587 return self.code.undeclared_identifiers.difference(
588 self.code.declared_identifiers
589 )
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 )
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 )
613 self.code = ast.PythonCode(self.expression, **self.exception_kwargs)
614 self.body_decl = ast.FunctionArgs(
615 attributes.get("args", ""), **self.exception_kwargs
616 )
618 def declared_identifiers(self):
619 return self.code.declared_identifiers.union(self.body_decl.allargnames)
621 def undeclared_identifiers(self):
622 return self.code.undeclared_identifiers.difference(
623 self.code.declared_identifiers
624 )
627class InheritTag(Tag):
628 __keyword__ = "inherit"
630 def __init__(self, keyword, attributes, **kwargs):
631 super().__init__(
632 keyword, attributes, ("file",), (), ("file",), **kwargs
633 )
636class PageTag(Tag):
637 __keyword__ = "page"
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_")]
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 )
655 def declared_identifiers(self):
656 return self.body_decl.allargnames