Home | History | Annotate | Download | only in mako
      1 # mako/parsetree.py
      2 # Copyright (C) 2006-2015 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
      6 
      7 """defines the parse tree components for Mako templates."""
      8 
      9 from mako import exceptions, ast, util, filters, compat
     10 import re
     11 
     12 class Node(object):
     13     """base class for a Node in the parse tree."""
     14 
     15     def __init__(self, source, lineno, pos, filename):
     16         self.source = source
     17         self.lineno = lineno
     18         self.pos = pos
     19         self.filename = filename
     20 
     21     @property
     22     def exception_kwargs(self):
     23         return {'source': self.source, 'lineno': self.lineno,
     24                 'pos': self.pos, 'filename': self.filename}
     25 
     26     def get_children(self):
     27         return []
     28 
     29     def accept_visitor(self, visitor):
     30         def traverse(node):
     31             for n in node.get_children():
     32                 n.accept_visitor(visitor)
     33 
     34         method = getattr(visitor, "visit" + self.__class__.__name__, traverse)
     35         method(self)
     36 
     37 class TemplateNode(Node):
     38     """a 'container' node that stores the overall collection of nodes."""
     39 
     40     def __init__(self, filename):
     41         super(TemplateNode, self).__init__('', 0, 0, filename)
     42         self.nodes = []
     43         self.page_attributes = {}
     44 
     45     def get_children(self):
     46         return self.nodes
     47 
     48     def __repr__(self):
     49         return "TemplateNode(%s, %r)" % (
     50                     util.sorted_dict_repr(self.page_attributes),
     51                     self.nodes)
     52 
     53 class ControlLine(Node):
     54     """defines a control line, a line-oriented python line or end tag.
     55 
     56     e.g.::
     57 
     58         % if foo:
     59             (markup)
     60         % endif
     61 
     62     """
     63 
     64     has_loop_context = False
     65 
     66     def __init__(self, keyword, isend, text, **kwargs):
     67         super(ControlLine, self).__init__(**kwargs)
     68         self.text = text
     69         self.keyword = keyword
     70         self.isend = isend
     71         self.is_primary = keyword in ['for', 'if', 'while', 'try', 'with']
     72         self.nodes = []
     73         if self.isend:
     74             self._declared_identifiers = []
     75             self._undeclared_identifiers = []
     76         else:
     77             code = ast.PythonFragment(text, **self.exception_kwargs)
     78             self._declared_identifiers = code.declared_identifiers
     79             self._undeclared_identifiers = code.undeclared_identifiers
     80 
     81     def get_children(self):
     82         return self.nodes
     83 
     84     def declared_identifiers(self):
     85         return self._declared_identifiers
     86 
     87     def undeclared_identifiers(self):
     88         return self._undeclared_identifiers
     89 
     90     def is_ternary(self, keyword):
     91         """return true if the given keyword is a ternary keyword
     92         for this ControlLine"""
     93 
     94         return keyword in {
     95             'if':set(['else', 'elif']),
     96             'try':set(['except', 'finally']),
     97             'for':set(['else'])
     98         }.get(self.keyword, [])
     99 
    100     def __repr__(self):
    101         return "ControlLine(%r, %r, %r, %r)" % (
    102             self.keyword,
    103             self.text,
    104             self.isend,
    105             (self.lineno, self.pos)
    106         )
    107 
    108 class Text(Node):
    109     """defines plain text in the template."""
    110 
    111     def __init__(self, content, **kwargs):
    112         super(Text, self).__init__(**kwargs)
    113         self.content = content
    114 
    115     def __repr__(self):
    116         return "Text(%r, %r)" % (self.content, (self.lineno, self.pos))
    117 
    118 class Code(Node):
    119     """defines a Python code block, either inline or module level.
    120 
    121     e.g.::
    122 
    123         inline:
    124         <%
    125             x = 12
    126         %>
    127 
    128         module level:
    129         <%!
    130             import logger
    131         %>
    132 
    133     """
    134 
    135     def __init__(self, text, ismodule, **kwargs):
    136         super(Code, self).__init__(**kwargs)
    137         self.text = text
    138         self.ismodule = ismodule
    139         self.code = ast.PythonCode(text, **self.exception_kwargs)
    140 
    141     def declared_identifiers(self):
    142         return self.code.declared_identifiers
    143 
    144     def undeclared_identifiers(self):
    145         return self.code.undeclared_identifiers
    146 
    147     def __repr__(self):
    148         return "Code(%r, %r, %r)" % (
    149             self.text,
    150             self.ismodule,
    151             (self.lineno, self.pos)
    152         )
    153 
    154 class Comment(Node):
    155     """defines a comment line.
    156 
    157     # this is a comment
    158 
    159     """
    160 
    161     def __init__(self, text, **kwargs):
    162         super(Comment, self).__init__(**kwargs)
    163         self.text = text
    164 
    165     def __repr__(self):
    166         return "Comment(%r, %r)" % (self.text, (self.lineno, self.pos))
    167 
    168 class Expression(Node):
    169     """defines an inline expression.
    170 
    171     ${x+y}
    172 
    173     """
    174 
    175     def __init__(self, text, escapes, **kwargs):
    176         super(Expression, self).__init__(**kwargs)
    177         self.text = text
    178         self.escapes = escapes
    179         self.escapes_code = ast.ArgumentList(escapes, **self.exception_kwargs)
    180         self.code = ast.PythonCode(text, **self.exception_kwargs)
    181 
    182     def declared_identifiers(self):
    183         return []
    184 
    185     def undeclared_identifiers(self):
    186         # TODO: make the "filter" shortcut list configurable at parse/gen time
    187         return self.code.undeclared_identifiers.union(
    188                 self.escapes_code.undeclared_identifiers.difference(
    189                     set(filters.DEFAULT_ESCAPES.keys())
    190                 )
    191             ).difference(self.code.declared_identifiers)
    192 
    193     def __repr__(self):
    194         return "Expression(%r, %r, %r)" % (
    195             self.text,
    196             self.escapes_code.args,
    197             (self.lineno, self.pos)
    198         )
    199 
    200 class _TagMeta(type):
    201     """metaclass to allow Tag to produce a subclass according to
    202     its keyword"""
    203 
    204     _classmap = {}
    205 
    206     def __init__(cls, clsname, bases, dict):
    207         if getattr(cls, '__keyword__', None) is not None:
    208             cls._classmap[cls.__keyword__] = cls
    209         super(_TagMeta, cls).__init__(clsname, bases, dict)
    210 
    211     def __call__(cls, keyword, attributes, **kwargs):
    212         if ":" in keyword:
    213             ns, defname = keyword.split(':')
    214             return type.__call__(CallNamespaceTag, ns, defname,
    215                                         attributes, **kwargs)
    216 
    217         try:
    218             cls = _TagMeta._classmap[keyword]
    219         except KeyError:
    220             raise exceptions.CompileException(
    221                 "No such tag: '%s'" % keyword,
    222                 source=kwargs['source'],
    223                 lineno=kwargs['lineno'],
    224                 pos=kwargs['pos'],
    225                 filename=kwargs['filename']
    226             )
    227         return type.__call__(cls, keyword, attributes, **kwargs)
    228 
    229 class Tag(compat.with_metaclass(_TagMeta, Node)):
    230     """abstract base class for tags.
    231 
    232     <%sometag/>
    233 
    234     <%someothertag>
    235         stuff
    236     </%someothertag>
    237 
    238     """
    239     __keyword__ = None
    240 
    241     def __init__(self, keyword, attributes, expressions,
    242                         nonexpressions, required, **kwargs):
    243         """construct a new Tag instance.
    244 
    245         this constructor not called directly, and is only called
    246         by subclasses.
    247 
    248         :param keyword: the tag keyword
    249 
    250         :param attributes: raw dictionary of attribute key/value pairs
    251 
    252         :param expressions: a set of identifiers that are legal attributes,
    253          which can also contain embedded expressions
    254 
    255         :param nonexpressions: a set of identifiers that are legal
    256          attributes, which cannot contain embedded expressions
    257 
    258         :param \**kwargs:
    259          other arguments passed to the Node superclass (lineno, pos)
    260 
    261         """
    262         super(Tag, self).__init__(**kwargs)
    263         self.keyword = keyword
    264         self.attributes = attributes
    265         self._parse_attributes(expressions, nonexpressions)
    266         missing = [r for r in required if r not in self.parsed_attributes]
    267         if len(missing):
    268             raise exceptions.CompileException(
    269                 "Missing attribute(s): %s" %
    270                     ",".join([repr(m) for m in missing]),
    271                 **self.exception_kwargs)
    272         self.parent = None
    273         self.nodes = []
    274 
    275     def is_root(self):
    276         return self.parent is None
    277 
    278     def get_children(self):
    279         return self.nodes
    280 
    281     def _parse_attributes(self, expressions, nonexpressions):
    282         undeclared_identifiers = set()
    283         self.parsed_attributes = {}
    284         for key in self.attributes:
    285             if key in expressions:
    286                 expr = []
    287                 for x in re.compile(r'(\${.+?})',
    288                                     re.S).split(self.attributes[key]):
    289                     m = re.compile(r'^\${(.+?)}$', re.S).match(x)
    290                     if m:
    291                         code = ast.PythonCode(m.group(1).rstrip(),
    292                                 **self.exception_kwargs)
    293                         # we aren't discarding "declared_identifiers" here,
    294                         # which we do so that list comprehension-declared
    295                         # variables aren't counted.   As yet can't find a
    296                         # condition that requires it here.
    297                         undeclared_identifiers = \
    298                             undeclared_identifiers.union(
    299                                     code.undeclared_identifiers)
    300                         expr.append('(%s)' % m.group(1))
    301                     else:
    302                         if x:
    303                             expr.append(repr(x))
    304                 self.parsed_attributes[key] = " + ".join(expr) or repr('')
    305             elif key in nonexpressions:
    306                 if re.search(r'\${.+?}', self.attributes[key]):
    307                     raise exceptions.CompileException(
    308                            "Attibute '%s' in tag '%s' does not allow embedded "
    309                            "expressions"  % (key, self.keyword),
    310                            **self.exception_kwargs)
    311                 self.parsed_attributes[key] = repr(self.attributes[key])
    312             else:
    313                 raise exceptions.CompileException(
    314                                     "Invalid attribute for tag '%s': '%s'" %
    315                                     (self.keyword, key),
    316                                     **self.exception_kwargs)
    317         self.expression_undeclared_identifiers = undeclared_identifiers
    318 
    319     def declared_identifiers(self):
    320         return []
    321 
    322     def undeclared_identifiers(self):
    323         return self.expression_undeclared_identifiers
    324 
    325     def __repr__(self):
    326         return "%s(%r, %s, %r, %r)" % (self.__class__.__name__,
    327                                     self.keyword,
    328                                     util.sorted_dict_repr(self.attributes),
    329                                     (self.lineno, self.pos),
    330                                     self.nodes
    331                                 )
    332 
    333 class IncludeTag(Tag):
    334     __keyword__ = 'include'
    335 
    336     def __init__(self, keyword, attributes, **kwargs):
    337         super(IncludeTag, self).__init__(
    338                                     keyword,
    339                                     attributes,
    340                                     ('file', 'import', 'args'),
    341                                     (), ('file',), **kwargs)
    342         self.page_args = ast.PythonCode(
    343                                 "__DUMMY(%s)" % attributes.get('args', ''),
    344                                  **self.exception_kwargs)
    345 
    346     def declared_identifiers(self):
    347         return []
    348 
    349     def undeclared_identifiers(self):
    350         identifiers = self.page_args.undeclared_identifiers.\
    351                             difference(set(["__DUMMY"])).\
    352                             difference(self.page_args.declared_identifiers)
    353         return identifiers.union(super(IncludeTag, self).
    354                                     undeclared_identifiers())
    355 
    356 class NamespaceTag(Tag):
    357     __keyword__ = 'namespace'
    358 
    359     def __init__(self, keyword, attributes, **kwargs):
    360         super(NamespaceTag, self).__init__(
    361                                         keyword, attributes,
    362                                         ('file',),
    363                                         ('name','inheritable',
    364                                         'import','module'),
    365                                         (), **kwargs)
    366 
    367         self.name = attributes.get('name', '__anon_%s' % hex(abs(id(self))))
    368         if not 'name' in attributes and not 'import' in attributes:
    369             raise exceptions.CompileException(
    370                 "'name' and/or 'import' attributes are required "
    371                 "for <%namespace>",
    372                 **self.exception_kwargs)
    373         if 'file' in attributes and 'module' in attributes:
    374             raise exceptions.CompileException(
    375                 "<%namespace> may only have one of 'file' or 'module'",
    376                 **self.exception_kwargs
    377             )
    378 
    379     def declared_identifiers(self):
    380         return []
    381 
    382 class TextTag(Tag):
    383     __keyword__ = 'text'
    384 
    385     def __init__(self, keyword, attributes, **kwargs):
    386         super(TextTag, self).__init__(
    387                                     keyword,
    388                                     attributes, (),
    389                                     ('filter'), (), **kwargs)
    390         self.filter_args = ast.ArgumentList(
    391                                     attributes.get('filter', ''),
    392                                     **self.exception_kwargs)
    393 
    394     def undeclared_identifiers(self):
    395         return self.filter_args.\
    396                             undeclared_identifiers.\
    397                             difference(filters.DEFAULT_ESCAPES.keys()).union(
    398                         self.expression_undeclared_identifiers
    399                     )
    400 
    401 class DefTag(Tag):
    402     __keyword__ = 'def'
    403 
    404     def __init__(self, keyword, attributes, **kwargs):
    405         expressions = ['buffered', 'cached'] + [
    406                 c for c in attributes if c.startswith('cache_')]
    407 
    408 
    409         super(DefTag, self).__init__(
    410                 keyword,
    411                 attributes,
    412                 expressions,
    413                 ('name', 'filter', 'decorator'),
    414                 ('name',),
    415                 **kwargs)
    416         name = attributes['name']
    417         if re.match(r'^[\w_]+$', name):
    418             raise exceptions.CompileException(
    419                                 "Missing parenthesis in %def",
    420                                 **self.exception_kwargs)
    421         self.function_decl = ast.FunctionDecl("def " + name + ":pass",
    422                                                     **self.exception_kwargs)
    423         self.name = self.function_decl.funcname
    424         self.decorator = attributes.get('decorator', '')
    425         self.filter_args = ast.ArgumentList(
    426                                 attributes.get('filter', ''),
    427                                 **self.exception_kwargs)
    428 
    429     is_anonymous = False
    430     is_block = False
    431 
    432     @property
    433     def funcname(self):
    434         return self.function_decl.funcname
    435 
    436     def get_argument_expressions(self, **kw):
    437         return self.function_decl.get_argument_expressions(**kw)
    438 
    439     def declared_identifiers(self):
    440         return self.function_decl.allargnames
    441 
    442     def undeclared_identifiers(self):
    443         res = []
    444         for c in self.function_decl.defaults:
    445             res += list(ast.PythonCode(c, **self.exception_kwargs).
    446                                     undeclared_identifiers)
    447         return set(res).union(
    448             self.filter_args.\
    449                             undeclared_identifiers.\
    450                             difference(filters.DEFAULT_ESCAPES.keys())
    451         ).union(
    452             self.expression_undeclared_identifiers
    453         ).difference(
    454             self.function_decl.allargnames
    455         )
    456 
    457 class BlockTag(Tag):
    458     __keyword__ = 'block'
    459 
    460     def __init__(self, keyword, attributes, **kwargs):
    461         expressions = ['buffered', 'cached', 'args'] + [
    462                  c for c in attributes if c.startswith('cache_')]
    463 
    464         super(BlockTag, self).__init__(
    465                 keyword,
    466                 attributes,
    467                 expressions,
    468                 ('name','filter', 'decorator'),
    469                 (),
    470                 **kwargs)
    471         name = attributes.get('name')
    472         if name and not re.match(r'^[\w_]+$',name):
    473             raise exceptions.CompileException(
    474                                "%block may not specify an argument signature",
    475                                **self.exception_kwargs)
    476         if not name and attributes.get('args', None):
    477             raise exceptions.CompileException(
    478                                 "Only named %blocks may specify args",
    479                                 **self.exception_kwargs
    480                                 )
    481         self.body_decl = ast.FunctionArgs(attributes.get('args', ''),
    482                                             **self.exception_kwargs)
    483 
    484         self.name = name
    485         self.decorator = attributes.get('decorator', '')
    486         self.filter_args = ast.ArgumentList(
    487                                 attributes.get('filter', ''),
    488                                 **self.exception_kwargs)
    489 
    490 
    491     is_block = True
    492 
    493     @property
    494     def is_anonymous(self):
    495         return self.name is None
    496 
    497     @property
    498     def funcname(self):
    499         return self.name or "__M_anon_%d" % (self.lineno, )
    500 
    501     def get_argument_expressions(self, **kw):
    502         return self.body_decl.get_argument_expressions(**kw)
    503 
    504     def declared_identifiers(self):
    505         return self.body_decl.allargnames
    506 
    507     def undeclared_identifiers(self):
    508         return (self.filter_args.\
    509                             undeclared_identifiers.\
    510                             difference(filters.DEFAULT_ESCAPES.keys())
    511                 ).union(self.expression_undeclared_identifiers)
    512 
    513 
    514 
    515 class CallTag(Tag):
    516     __keyword__ = 'call'
    517 
    518     def __init__(self, keyword, attributes, **kwargs):
    519         super(CallTag, self).__init__(keyword, attributes,
    520                                     ('args'), ('expr',), ('expr',), **kwargs)
    521         self.expression = attributes['expr']
    522         self.code = ast.PythonCode(self.expression, **self.exception_kwargs)
    523         self.body_decl = ast.FunctionArgs(attributes.get('args', ''),
    524                                             **self.exception_kwargs)
    525 
    526     def declared_identifiers(self):
    527         return self.code.declared_identifiers.union(self.body_decl.allargnames)
    528 
    529     def undeclared_identifiers(self):
    530         return self.code.undeclared_identifiers.\
    531                     difference(self.code.declared_identifiers)
    532 
    533 class CallNamespaceTag(Tag):
    534 
    535     def __init__(self, namespace, defname, attributes, **kwargs):
    536         super(CallNamespaceTag, self).__init__(
    537                     namespace + ":" + defname,
    538                     attributes,
    539                     tuple(attributes.keys()) + ('args', ),
    540                     (),
    541                     (),
    542                     **kwargs)
    543 
    544         self.expression = "%s.%s(%s)" % (
    545                                 namespace,
    546                                 defname,
    547                                 ",".join(["%s=%s" % (k, v) for k, v in
    548                                             self.parsed_attributes.items()
    549                                             if k != 'args'])
    550                             )
    551         self.code = ast.PythonCode(self.expression, **self.exception_kwargs)
    552         self.body_decl = ast.FunctionArgs(
    553                                     attributes.get('args', ''),
    554                                     **self.exception_kwargs)
    555 
    556     def declared_identifiers(self):
    557         return self.code.declared_identifiers.union(self.body_decl.allargnames)
    558 
    559     def undeclared_identifiers(self):
    560         return self.code.undeclared_identifiers.\
    561                     difference(self.code.declared_identifiers)
    562 
    563 class InheritTag(Tag):
    564     __keyword__ = 'inherit'
    565 
    566     def __init__(self, keyword, attributes, **kwargs):
    567         super(InheritTag, self).__init__(
    568                                 keyword, attributes,
    569                                 ('file',), (), ('file',), **kwargs)
    570 
    571 class PageTag(Tag):
    572     __keyword__ = 'page'
    573 
    574     def __init__(self, keyword, attributes, **kwargs):
    575         expressions =   ['cached', 'args', 'expression_filter', 'enable_loop'] + [
    576                     c for c in attributes if c.startswith('cache_')]
    577 
    578         super(PageTag, self).__init__(
    579                 keyword,
    580                 attributes,
    581                 expressions,
    582                 (),
    583                 (),
    584                 **kwargs)
    585         self.body_decl = ast.FunctionArgs(attributes.get('args', ''),
    586                                             **self.exception_kwargs)
    587         self.filter_args = ast.ArgumentList(
    588                                 attributes.get('expression_filter', ''),
    589                                 **self.exception_kwargs)
    590 
    591     def declared_identifiers(self):
    592         return self.body_decl.allargnames
    593 
    594 
    595