# -*- coding: utf-8 -*- # parsetree.py # Copyright (C) 2006, 2007, 2008 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':util.Set(['else', 'elif']), 'try':util.Set(['except', 'finally']), 'for':util.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( util.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 """ __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 util.Set of identifiers that are legal attributes, which can also contain embedded expressions nonexpressions - a util.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 = util.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), [repr(x) for x in 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(util.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'), ('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.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(util.Set(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