123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616 |
- # mako/parsetree.py
- # Copyright (C) 2006-2016 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."""
- from mako import exceptions, ast, util, filters, compat
- import re
- class Node(object):
- """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(TemplateNode, self).__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(ControlLine, self).__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"""
- return keyword in {
- 'if': set(['else', 'elif']),
- 'try': set(['except', 'finally']),
- 'for': set(['else'])
- }.get(self.keyword, [])
- 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(Text, self).__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(Code, self).__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(Comment, self).__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(Expression, self).__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(
- set(filters.DEFAULT_ESCAPES.keys())
- )
- ).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(_TagMeta, cls).__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(compat.with_metaclass(_TagMeta, Node)):
- """abstract base class for tags.
- <%sometag/>
- <%someothertag>
- stuff
- </%someothertag>
- """
- __keyword__ = None
- def __init__(self, keyword, attributes, expressions,
- nonexpressions, required, **kwargs):
- """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(Tag, self).__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))
- else:
- if 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(
- "Attibute '%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(IncludeTag, self).__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(set(["__DUMMY"])).\
- difference(self.page_args.declared_identifiers)
- return identifiers.union(super(IncludeTag, self).
- undeclared_identifiers())
- class NamespaceTag(Tag):
- __keyword__ = 'namespace'
- def __init__(self, keyword, attributes, **kwargs):
- super(NamespaceTag, self).__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(TextTag, self).__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(DefTag, self).__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(BlockTag, self).__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(CallTag, self).__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(CallNamespaceTag, self).__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(InheritTag, self).__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(PageTag, self).__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
|