| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474 |
- # parsetree.py
- # Copyright (C) 2006, 2007, 2008, 2009, 2010 Michael Bayer mike_mp@zzzcomputing.com
- #
- # 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
- 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
-
- def exception_kwargs(self):
- return {'source':self.source, 'lineno':self.lineno, 'pos':self.pos, 'filename':self.filename}
- exception_kwargs = property(exception_kwargs)
-
- 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
-
- """
- 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']
- 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 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())
- )
- )
- 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 cls.__keyword__ 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(Node):
- """abstract base class for tags.
-
- <%sometag/>
-
- <%someothertag>
- stuff
- </%someothertag>
-
- """
-
- __metaclass__ = _TagMeta
- __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.
-
- keyword - the tag keyword
-
- attributes - raw dictionary of attribute key/value pairs
-
- expressions - a set of identifiers that are legal attributes,
- which can also contain embedded expressions
-
- nonexpressions - a set of identifiers that are legal attributes,
- which cannot contain embedded expressions
-
- \**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.split(r'(\${.+?})', self.attributes[key]):
- m = re.match(r'^\${(.+?)}$', x)
- if m:
- code = ast.PythonCode(m.group(1), **self.exception_kwargs)
- 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"]))
- 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,
- (),
- ('name','inheritable',
- 'file','import','module'),
- (), **kwargs)
-
- self.name = attributes.get('name', '__anon_%s' % hex(abs(id(self))))
- if not 'name' in attributes and not 'import' in attributes:
- raise exceptions.CompileException(
- "'name' and/or 'import' attributes are required "
- "for <%namespace>",
- **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)
-
- class DefTag(Tag):
- __keyword__ = 'def'
- def __init__(self, keyword, attributes, **kwargs):
- super(DefTag, self).__init__(
- keyword,
- attributes,
- ('buffered', 'cached', 'cache_key', 'cache_timeout',
- 'cache_type', 'cache_dir', 'cache_url'),
- ('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)
- def declared_identifiers(self):
- return self.function_decl.argnames
- def undeclared_identifiers(self):
- res = []
- for c in self.function_decl.defaults:
- res += list(ast.PythonCode(c, **self.exception_kwargs).undeclared_identifiers)
- return res + list(self.filter_args.\
- undeclared_identifiers.\
- difference(filters.DEFAULT_ESCAPES.keys())
- )
- 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.argnames)
- def undeclared_identifiers(self):
- return self.code.undeclared_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.iteritems()
- 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.argnames)
- def undeclared_identifiers(self):
- return self.code.undeclared_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):
- super(PageTag, self).__init__(
- keyword,
- attributes,
- ('cached', 'cache_key', 'cache_timeout',
- 'cache_type', 'cache_dir', 'cache_url',
- 'args', 'expression_filter'),
- (),
- (),
- **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.argnames
-
-
|