| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656 |
- # mako/parsetree.py
- # Copyright 2006-2025 the Mako authors and contributors <see AUTHORS file>
- #
- # This module is part of Mako and is released under
- # the MIT License: http://www.opensource.org/licenses/mit-license.php
- """defines the parse tree components for Mako templates."""
- import re
- from mako import ast
- from mako import exceptions
- from mako import filters
- from mako import util
- class Node:
- """base class for a Node in the parse tree."""
- def __init__(self, source, lineno, pos, filename):
- self.source = source
- self.lineno = lineno
- self.pos = pos
- self.filename = filename
- @property
- def exception_kwargs(self):
- return {
- "source": self.source,
- "lineno": self.lineno,
- "pos": self.pos,
- "filename": self.filename,
- }
- def get_children(self):
- return []
- def accept_visitor(self, visitor):
- def traverse(node):
- for n in node.get_children():
- n.accept_visitor(visitor)
- method = getattr(visitor, "visit" + self.__class__.__name__, traverse)
- method(self)
- class TemplateNode(Node):
- """a 'container' node that stores the overall collection of nodes."""
- def __init__(self, filename):
- super().__init__("", 0, 0, filename)
- self.nodes = []
- self.page_attributes = {}
- def get_children(self):
- return self.nodes
- def __repr__(self):
- return "TemplateNode(%s, %r)" % (
- util.sorted_dict_repr(self.page_attributes),
- self.nodes,
- )
- class ControlLine(Node):
- """defines a control line, a line-oriented python line or end tag.
- e.g.::
- % if foo:
- (markup)
- % endif
- """
- has_loop_context = False
- def __init__(self, keyword, isend, text, **kwargs):
- super().__init__(**kwargs)
- self.text = text
- self.keyword = keyword
- self.isend = isend
- self.is_primary = keyword in ["for", "if", "while", "try", "with"]
- self.nodes = []
- if self.isend:
- self._declared_identifiers = []
- self._undeclared_identifiers = []
- else:
- code = ast.PythonFragment(text, **self.exception_kwargs)
- self._declared_identifiers = code.declared_identifiers
- self._undeclared_identifiers = code.undeclared_identifiers
- def get_children(self):
- return self.nodes
- def declared_identifiers(self):
- return self._declared_identifiers
- def undeclared_identifiers(self):
- return self._undeclared_identifiers
- def is_ternary(self, keyword):
- """return true if the given keyword is a ternary keyword
- for this ControlLine"""
- cases = {
- "if": {"else", "elif"},
- "try": {"except", "finally"},
- "for": {"else"},
- }
- return keyword in cases.get(self.keyword, set())
- def __repr__(self):
- return "ControlLine(%r, %r, %r, %r)" % (
- self.keyword,
- self.text,
- self.isend,
- (self.lineno, self.pos),
- )
- class Text(Node):
- """defines plain text in the template."""
- def __init__(self, content, **kwargs):
- super().__init__(**kwargs)
- self.content = content
- def __repr__(self):
- return "Text(%r, %r)" % (self.content, (self.lineno, self.pos))
- class Code(Node):
- """defines a Python code block, either inline or module level.
- e.g.::
- inline:
- <%
- x = 12
- %>
- module level:
- <%!
- import logger
- %>
- """
- def __init__(self, text, ismodule, **kwargs):
- super().__init__(**kwargs)
- self.text = text
- self.ismodule = ismodule
- self.code = ast.PythonCode(text, **self.exception_kwargs)
- def declared_identifiers(self):
- return self.code.declared_identifiers
- def undeclared_identifiers(self):
- return self.code.undeclared_identifiers
- def __repr__(self):
- return "Code(%r, %r, %r)" % (
- self.text,
- self.ismodule,
- (self.lineno, self.pos),
- )
- class Comment(Node):
- """defines a comment line.
- # this is a comment
- """
- def __init__(self, text, **kwargs):
- super().__init__(**kwargs)
- self.text = text
- def __repr__(self):
- return "Comment(%r, %r)" % (self.text, (self.lineno, self.pos))
- class Expression(Node):
- """defines an inline expression.
- ${x+y}
- """
- def __init__(self, text, escapes, **kwargs):
- super().__init__(**kwargs)
- self.text = text
- self.escapes = escapes
- self.escapes_code = ast.ArgumentList(escapes, **self.exception_kwargs)
- self.code = ast.PythonCode(text, **self.exception_kwargs)
- def declared_identifiers(self):
- return []
- def undeclared_identifiers(self):
- # TODO: make the "filter" shortcut list configurable at parse/gen time
- return self.code.undeclared_identifiers.union(
- self.escapes_code.undeclared_identifiers.difference(
- filters.DEFAULT_ESCAPES
- )
- ).difference(self.code.declared_identifiers)
- def __repr__(self):
- return "Expression(%r, %r, %r)" % (
- self.text,
- self.escapes_code.args,
- (self.lineno, self.pos),
- )
- class _TagMeta(type):
- """metaclass to allow Tag to produce a subclass according to
- its keyword"""
- _classmap = {}
- def __init__(cls, clsname, bases, dict_):
- if getattr(cls, "__keyword__", None) is not None:
- cls._classmap[cls.__keyword__] = cls
- super().__init__(clsname, bases, dict_)
- def __call__(cls, keyword, attributes, **kwargs):
- if ":" in keyword:
- ns, defname = keyword.split(":")
- return type.__call__(
- CallNamespaceTag, ns, defname, attributes, **kwargs
- )
- try:
- cls = _TagMeta._classmap[keyword]
- except KeyError:
- raise exceptions.CompileException(
- "No such tag: '%s'" % keyword,
- source=kwargs["source"],
- lineno=kwargs["lineno"],
- pos=kwargs["pos"],
- filename=kwargs["filename"],
- )
- return type.__call__(cls, keyword, attributes, **kwargs)
- class Tag(Node, metaclass=_TagMeta):
- """abstract base class for tags.
- e.g.::
- <%sometag/>
- <%someothertag>
- stuff
- </%someothertag>
- """
- __keyword__ = None
- def __init__(
- self,
- keyword,
- attributes,
- expressions,
- nonexpressions,
- required,
- **kwargs,
- ):
- r"""construct a new Tag instance.
- this constructor not called directly, and is only called
- by subclasses.
- :param keyword: the tag keyword
- :param attributes: raw dictionary of attribute key/value pairs
- :param expressions: a set of identifiers that are legal attributes,
- which can also contain embedded expressions
- :param nonexpressions: a set of identifiers that are legal
- attributes, which cannot contain embedded expressions
- :param \**kwargs:
- other arguments passed to the Node superclass (lineno, pos)
- """
- super().__init__(**kwargs)
- self.keyword = keyword
- self.attributes = attributes
- self._parse_attributes(expressions, nonexpressions)
- missing = [r for r in required if r not in self.parsed_attributes]
- if len(missing):
- raise exceptions.CompileException(
- (
- "Missing attribute(s): %s"
- % ",".join(repr(m) for m in missing)
- ),
- **self.exception_kwargs,
- )
- self.parent = None
- self.nodes = []
- def is_root(self):
- return self.parent is None
- def get_children(self):
- return self.nodes
- def _parse_attributes(self, expressions, nonexpressions):
- undeclared_identifiers = set()
- self.parsed_attributes = {}
- for key in self.attributes:
- if key in expressions:
- expr = []
- for x in re.compile(r"(\${(?:[^$]*?{.+|.+?)})", re.S).split(
- self.attributes[key]
- ):
- m = re.compile(r"^\${(.+?)}$", re.S).match(x)
- if m:
- code = ast.PythonCode(
- m.group(1).rstrip(), **self.exception_kwargs
- )
- # we aren't discarding "declared_identifiers" here,
- # which we do so that list comprehension-declared
- # variables aren't counted. As yet can't find a
- # condition that requires it here.
- undeclared_identifiers = undeclared_identifiers.union(
- code.undeclared_identifiers
- )
- expr.append("(%s)" % m.group(1))
- elif x:
- expr.append(repr(x))
- self.parsed_attributes[key] = " + ".join(expr) or repr("")
- elif key in nonexpressions:
- if re.search(r"\${.+?}", self.attributes[key]):
- raise exceptions.CompileException(
- "Attribute '%s' in tag '%s' does not allow embedded "
- "expressions" % (key, self.keyword),
- **self.exception_kwargs,
- )
- self.parsed_attributes[key] = repr(self.attributes[key])
- else:
- raise exceptions.CompileException(
- "Invalid attribute for tag '%s': '%s'"
- % (self.keyword, key),
- **self.exception_kwargs,
- )
- self.expression_undeclared_identifiers = undeclared_identifiers
- def declared_identifiers(self):
- return []
- def undeclared_identifiers(self):
- return self.expression_undeclared_identifiers
- def __repr__(self):
- return "%s(%r, %s, %r, %r)" % (
- self.__class__.__name__,
- self.keyword,
- util.sorted_dict_repr(self.attributes),
- (self.lineno, self.pos),
- self.nodes,
- )
- class IncludeTag(Tag):
- __keyword__ = "include"
- def __init__(self, keyword, attributes, **kwargs):
- super().__init__(
- keyword,
- attributes,
- ("file", "import", "args"),
- (),
- ("file",),
- **kwargs,
- )
- self.page_args = ast.PythonCode(
- "__DUMMY(%s)" % attributes.get("args", ""), **self.exception_kwargs
- )
- def declared_identifiers(self):
- return []
- def undeclared_identifiers(self):
- identifiers = self.page_args.undeclared_identifiers.difference(
- {"__DUMMY"}
- ).difference(self.page_args.declared_identifiers)
- return identifiers.union(super().undeclared_identifiers())
- class NamespaceTag(Tag):
- __keyword__ = "namespace"
- def __init__(self, keyword, attributes, **kwargs):
- super().__init__(
- keyword,
- attributes,
- ("file",),
- ("name", "inheritable", "import", "module"),
- (),
- **kwargs,
- )
- self.name = attributes.get("name", "__anon_%s" % hex(abs(id(self))))
- if "name" not in attributes and "import" not in attributes:
- raise exceptions.CompileException(
- "'name' and/or 'import' attributes are required "
- "for <%namespace>",
- **self.exception_kwargs,
- )
- if "file" in attributes and "module" in attributes:
- raise exceptions.CompileException(
- "<%namespace> may only have one of 'file' or 'module'",
- **self.exception_kwargs,
- )
- def declared_identifiers(self):
- return []
- class TextTag(Tag):
- __keyword__ = "text"
- def __init__(self, keyword, attributes, **kwargs):
- super().__init__(keyword, attributes, (), ("filter"), (), **kwargs)
- self.filter_args = ast.ArgumentList(
- attributes.get("filter", ""), **self.exception_kwargs
- )
- def undeclared_identifiers(self):
- return self.filter_args.undeclared_identifiers.difference(
- filters.DEFAULT_ESCAPES.keys()
- ).union(self.expression_undeclared_identifiers)
- class DefTag(Tag):
- __keyword__ = "def"
- def __init__(self, keyword, attributes, **kwargs):
- expressions = ["buffered", "cached"] + [
- c for c in attributes if c.startswith("cache_")
- ]
- super().__init__(
- keyword,
- attributes,
- expressions,
- ("name", "filter", "decorator"),
- ("name",),
- **kwargs,
- )
- name = attributes["name"]
- if re.match(r"^[\w_]+$", name):
- raise exceptions.CompileException(
- "Missing parenthesis in %def", **self.exception_kwargs
- )
- self.function_decl = ast.FunctionDecl(
- "def " + name + ":pass", **self.exception_kwargs
- )
- self.name = self.function_decl.funcname
- self.decorator = attributes.get("decorator", "")
- self.filter_args = ast.ArgumentList(
- attributes.get("filter", ""), **self.exception_kwargs
- )
- is_anonymous = False
- is_block = False
- @property
- def funcname(self):
- return self.function_decl.funcname
- def get_argument_expressions(self, **kw):
- return self.function_decl.get_argument_expressions(**kw)
- def declared_identifiers(self):
- return self.function_decl.allargnames
- def undeclared_identifiers(self):
- res = []
- for c in self.function_decl.defaults:
- res += list(
- ast.PythonCode(
- c, **self.exception_kwargs
- ).undeclared_identifiers
- )
- return (
- set(res)
- .union(
- self.filter_args.undeclared_identifiers.difference(
- filters.DEFAULT_ESCAPES.keys()
- )
- )
- .union(self.expression_undeclared_identifiers)
- .difference(self.function_decl.allargnames)
- )
- class BlockTag(Tag):
- __keyword__ = "block"
- def __init__(self, keyword, attributes, **kwargs):
- expressions = ["buffered", "cached", "args"] + [
- c for c in attributes if c.startswith("cache_")
- ]
- super().__init__(
- keyword,
- attributes,
- expressions,
- ("name", "filter", "decorator"),
- (),
- **kwargs,
- )
- name = attributes.get("name")
- if name and not re.match(r"^[\w_]+$", name):
- raise exceptions.CompileException(
- "%block may not specify an argument signature",
- **self.exception_kwargs,
- )
- if not name and attributes.get("args", None):
- raise exceptions.CompileException(
- "Only named %blocks may specify args", **self.exception_kwargs
- )
- self.body_decl = ast.FunctionArgs(
- attributes.get("args", ""), **self.exception_kwargs
- )
- self.name = name
- self.decorator = attributes.get("decorator", "")
- self.filter_args = ast.ArgumentList(
- attributes.get("filter", ""), **self.exception_kwargs
- )
- is_block = True
- @property
- def is_anonymous(self):
- return self.name is None
- @property
- def funcname(self):
- return self.name or "__M_anon_%d" % (self.lineno,)
- def get_argument_expressions(self, **kw):
- return self.body_decl.get_argument_expressions(**kw)
- def declared_identifiers(self):
- return self.body_decl.allargnames
- def undeclared_identifiers(self):
- return (
- self.filter_args.undeclared_identifiers.difference(
- filters.DEFAULT_ESCAPES.keys()
- )
- ).union(self.expression_undeclared_identifiers)
- class CallTag(Tag):
- __keyword__ = "call"
- def __init__(self, keyword, attributes, **kwargs):
- super().__init__(
- keyword, attributes, ("args"), ("expr",), ("expr",), **kwargs
- )
- self.expression = attributes["expr"]
- self.code = ast.PythonCode(self.expression, **self.exception_kwargs)
- self.body_decl = ast.FunctionArgs(
- attributes.get("args", ""), **self.exception_kwargs
- )
- def declared_identifiers(self):
- return self.code.declared_identifiers.union(self.body_decl.allargnames)
- def undeclared_identifiers(self):
- return self.code.undeclared_identifiers.difference(
- self.code.declared_identifiers
- )
- class CallNamespaceTag(Tag):
- def __init__(self, namespace, defname, attributes, **kwargs):
- super().__init__(
- namespace + ":" + defname,
- attributes,
- tuple(attributes.keys()) + ("args",),
- (),
- (),
- **kwargs,
- )
- self.expression = "%s.%s(%s)" % (
- namespace,
- defname,
- ",".join(
- "%s=%s" % (k, v)
- for k, v in self.parsed_attributes.items()
- if k != "args"
- ),
- )
- self.code = ast.PythonCode(self.expression, **self.exception_kwargs)
- self.body_decl = ast.FunctionArgs(
- attributes.get("args", ""), **self.exception_kwargs
- )
- def declared_identifiers(self):
- return self.code.declared_identifiers.union(self.body_decl.allargnames)
- def undeclared_identifiers(self):
- return self.code.undeclared_identifiers.difference(
- self.code.declared_identifiers
- )
- class InheritTag(Tag):
- __keyword__ = "inherit"
- def __init__(self, keyword, attributes, **kwargs):
- super().__init__(
- keyword, attributes, ("file",), (), ("file",), **kwargs
- )
- class PageTag(Tag):
- __keyword__ = "page"
- def __init__(self, keyword, attributes, **kwargs):
- expressions = [
- "cached",
- "args",
- "expression_filter",
- "enable_loop",
- ] + [c for c in attributes if c.startswith("cache_")]
- super().__init__(keyword, attributes, expressions, (), (), **kwargs)
- self.body_decl = ast.FunctionArgs(
- attributes.get("args", ""), **self.exception_kwargs
- )
- self.filter_args = ast.ArgumentList(
- attributes.get("expression_filter", ""), **self.exception_kwargs
- )
- def declared_identifiers(self):
- return self.body_decl.allargnames
|