[3] | 1 | # parsetree.py |
---|
| 2 | # Copyright (C) 2006, 2007, 2008, 2009 Michael Bayer mike_mp@zzzcomputing.com |
---|
| 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 |
---|
| 10 | import re |
---|
| 11 | |
---|
| 12 | class Node(object): |
---|
| 13 | """base class for a Node in the parse tree.""" |
---|
| 14 | def __init__(self, source, lineno, pos, filename): |
---|
| 15 | self.source = source |
---|
| 16 | self.lineno = lineno |
---|
| 17 | self.pos = pos |
---|
| 18 | self.filename = filename |
---|
| 19 | |
---|
| 20 | def exception_kwargs(self): |
---|
| 21 | return {'source':self.source, 'lineno':self.lineno, 'pos':self.pos, 'filename':self.filename} |
---|
| 22 | exception_kwargs = property(exception_kwargs) |
---|
| 23 | |
---|
| 24 | def get_children(self): |
---|
| 25 | return [] |
---|
| 26 | |
---|
| 27 | def accept_visitor(self, visitor): |
---|
| 28 | def traverse(node): |
---|
| 29 | for n in node.get_children(): |
---|
| 30 | n.accept_visitor(visitor) |
---|
| 31 | method = getattr(visitor, "visit" + self.__class__.__name__, traverse) |
---|
| 32 | method(self) |
---|
| 33 | |
---|
| 34 | class TemplateNode(Node): |
---|
| 35 | """a 'container' node that stores the overall collection of nodes.""" |
---|
| 36 | |
---|
| 37 | def __init__(self, filename): |
---|
| 38 | super(TemplateNode, self).__init__('', 0, 0, filename) |
---|
| 39 | self.nodes = [] |
---|
| 40 | self.page_attributes = {} |
---|
| 41 | |
---|
| 42 | def get_children(self): |
---|
| 43 | return self.nodes |
---|
| 44 | |
---|
| 45 | def __repr__(self): |
---|
| 46 | return "TemplateNode(%s, %r)" % (util.sorted_dict_repr(self.page_attributes), self.nodes) |
---|
| 47 | |
---|
| 48 | class ControlLine(Node): |
---|
| 49 | """defines a control line, a line-oriented python line or end tag. |
---|
| 50 | |
---|
| 51 | e.g.:: |
---|
| 52 | |
---|
| 53 | % if foo: |
---|
| 54 | (markup) |
---|
| 55 | % endif |
---|
| 56 | |
---|
| 57 | """ |
---|
| 58 | |
---|
| 59 | def __init__(self, keyword, isend, text, **kwargs): |
---|
| 60 | super(ControlLine, self).__init__(**kwargs) |
---|
| 61 | self.text = text |
---|
| 62 | self.keyword = keyword |
---|
| 63 | self.isend = isend |
---|
| 64 | self.is_primary = keyword in ['for','if', 'while', 'try'] |
---|
| 65 | if self.isend: |
---|
| 66 | self._declared_identifiers = [] |
---|
| 67 | self._undeclared_identifiers = [] |
---|
| 68 | else: |
---|
| 69 | code = ast.PythonFragment(text, **self.exception_kwargs) |
---|
| 70 | self._declared_identifiers = code.declared_identifiers |
---|
| 71 | self._undeclared_identifiers = code.undeclared_identifiers |
---|
| 72 | |
---|
| 73 | def declared_identifiers(self): |
---|
| 74 | return self._declared_identifiers |
---|
| 75 | |
---|
| 76 | def undeclared_identifiers(self): |
---|
| 77 | return self._undeclared_identifiers |
---|
| 78 | |
---|
| 79 | def is_ternary(self, keyword): |
---|
| 80 | """return true if the given keyword is a ternary keyword for this ControlLine""" |
---|
| 81 | |
---|
| 82 | return keyword in { |
---|
| 83 | 'if':util.Set(['else', 'elif']), |
---|
| 84 | 'try':util.Set(['except', 'finally']), |
---|
| 85 | 'for':util.Set(['else']) |
---|
| 86 | }.get(self.keyword, []) |
---|
| 87 | |
---|
| 88 | def __repr__(self): |
---|
| 89 | return "ControlLine(%r, %r, %r, %r)" % ( |
---|
| 90 | self.keyword, |
---|
| 91 | self.text, |
---|
| 92 | self.isend, |
---|
| 93 | (self.lineno, self.pos) |
---|
| 94 | ) |
---|
| 95 | |
---|
| 96 | class Text(Node): |
---|
| 97 | """defines plain text in the template.""" |
---|
| 98 | |
---|
| 99 | def __init__(self, content, **kwargs): |
---|
| 100 | super(Text, self).__init__(**kwargs) |
---|
| 101 | self.content = content |
---|
| 102 | |
---|
| 103 | def __repr__(self): |
---|
| 104 | return "Text(%r, %r)" % (self.content, (self.lineno, self.pos)) |
---|
| 105 | |
---|
| 106 | class Code(Node): |
---|
| 107 | """defines a Python code block, either inline or module level. |
---|
| 108 | |
---|
| 109 | e.g.:: |
---|
| 110 | |
---|
| 111 | inline: |
---|
| 112 | <% |
---|
| 113 | x = 12 |
---|
| 114 | %> |
---|
| 115 | |
---|
| 116 | module level: |
---|
| 117 | <%! |
---|
| 118 | import logger |
---|
| 119 | %> |
---|
| 120 | |
---|
| 121 | """ |
---|
| 122 | |
---|
| 123 | def __init__(self, text, ismodule, **kwargs): |
---|
| 124 | super(Code, self).__init__(**kwargs) |
---|
| 125 | self.text = text |
---|
| 126 | self.ismodule = ismodule |
---|
| 127 | self.code = ast.PythonCode(text, **self.exception_kwargs) |
---|
| 128 | |
---|
| 129 | def declared_identifiers(self): |
---|
| 130 | return self.code.declared_identifiers |
---|
| 131 | |
---|
| 132 | def undeclared_identifiers(self): |
---|
| 133 | return self.code.undeclared_identifiers |
---|
| 134 | |
---|
| 135 | def __repr__(self): |
---|
| 136 | return "Code(%r, %r, %r)" % ( |
---|
| 137 | self.text, |
---|
| 138 | self.ismodule, |
---|
| 139 | (self.lineno, self.pos) |
---|
| 140 | ) |
---|
| 141 | |
---|
| 142 | class Comment(Node): |
---|
| 143 | """defines a comment line. |
---|
| 144 | |
---|
| 145 | # this is a comment |
---|
| 146 | |
---|
| 147 | """ |
---|
| 148 | |
---|
| 149 | def __init__(self, text, **kwargs): |
---|
| 150 | super(Comment, self).__init__(**kwargs) |
---|
| 151 | self.text = text |
---|
| 152 | |
---|
| 153 | def __repr__(self): |
---|
| 154 | return "Comment(%r, %r)" % (self.text, (self.lineno, self.pos)) |
---|
| 155 | |
---|
| 156 | class Expression(Node): |
---|
| 157 | """defines an inline expression. |
---|
| 158 | |
---|
| 159 | ${x+y} |
---|
| 160 | |
---|
| 161 | """ |
---|
| 162 | |
---|
| 163 | def __init__(self, text, escapes, **kwargs): |
---|
| 164 | super(Expression, self).__init__(**kwargs) |
---|
| 165 | self.text = text |
---|
| 166 | self.escapes = escapes |
---|
| 167 | self.escapes_code = ast.ArgumentList(escapes, **self.exception_kwargs) |
---|
| 168 | self.code = ast.PythonCode(text, **self.exception_kwargs) |
---|
| 169 | |
---|
| 170 | def declared_identifiers(self): |
---|
| 171 | return [] |
---|
| 172 | |
---|
| 173 | def undeclared_identifiers(self): |
---|
| 174 | # TODO: make the "filter" shortcut list configurable at parse/gen time |
---|
| 175 | return self.code.undeclared_identifiers.union( |
---|
| 176 | self.escapes_code.undeclared_identifiers.difference( |
---|
| 177 | util.Set(filters.DEFAULT_ESCAPES.keys()) |
---|
| 178 | ) |
---|
| 179 | ) |
---|
| 180 | |
---|
| 181 | def __repr__(self): |
---|
| 182 | return "Expression(%r, %r, %r)" % ( |
---|
| 183 | self.text, |
---|
| 184 | self.escapes_code.args, |
---|
| 185 | (self.lineno, self.pos) |
---|
| 186 | ) |
---|
| 187 | |
---|
| 188 | class _TagMeta(type): |
---|
| 189 | """metaclass to allow Tag to produce a subclass according to its keyword""" |
---|
| 190 | |
---|
| 191 | _classmap = {} |
---|
| 192 | |
---|
| 193 | def __init__(cls, clsname, bases, dict): |
---|
| 194 | if cls.__keyword__ is not None: |
---|
| 195 | cls._classmap[cls.__keyword__] = cls |
---|
| 196 | super(_TagMeta, cls).__init__(clsname, bases, dict) |
---|
| 197 | |
---|
| 198 | def __call__(cls, keyword, attributes, **kwargs): |
---|
| 199 | if ":" in keyword: |
---|
| 200 | ns, defname = keyword.split(':') |
---|
| 201 | return type.__call__(CallNamespaceTag, ns, defname, attributes, **kwargs) |
---|
| 202 | |
---|
| 203 | try: |
---|
| 204 | cls = _TagMeta._classmap[keyword] |
---|
| 205 | except KeyError: |
---|
| 206 | raise exceptions.CompileException( |
---|
| 207 | "No such tag: '%s'" % keyword, |
---|
| 208 | source=kwargs['source'], |
---|
| 209 | lineno=kwargs['lineno'], |
---|
| 210 | pos=kwargs['pos'], |
---|
| 211 | filename=kwargs['filename'] |
---|
| 212 | ) |
---|
| 213 | return type.__call__(cls, keyword, attributes, **kwargs) |
---|
| 214 | |
---|
| 215 | class Tag(Node): |
---|
| 216 | """abstract base class for tags. |
---|
| 217 | |
---|
| 218 | <%sometag/> |
---|
| 219 | |
---|
| 220 | <%someothertag> |
---|
| 221 | stuff |
---|
| 222 | </%someothertag> |
---|
| 223 | |
---|
| 224 | """ |
---|
| 225 | |
---|
| 226 | __metaclass__ = _TagMeta |
---|
| 227 | __keyword__ = None |
---|
| 228 | |
---|
| 229 | def __init__(self, keyword, attributes, expressions, nonexpressions, required, **kwargs): |
---|
| 230 | """construct a new Tag instance. |
---|
| 231 | |
---|
| 232 | this constructor not called directly, and is only called by subclasses. |
---|
| 233 | |
---|
| 234 | keyword - the tag keyword |
---|
| 235 | |
---|
| 236 | attributes - raw dictionary of attribute key/value pairs |
---|
| 237 | |
---|
| 238 | expressions - a util.Set of identifiers that are legal attributes, which can also contain embedded expressions |
---|
| 239 | |
---|
| 240 | nonexpressions - a util.Set of identifiers that are legal attributes, which cannot contain embedded expressions |
---|
| 241 | |
---|
| 242 | **kwargs - other arguments passed to the Node superclass (lineno, pos) |
---|
| 243 | |
---|
| 244 | """ |
---|
| 245 | super(Tag, self).__init__(**kwargs) |
---|
| 246 | self.keyword = keyword |
---|
| 247 | self.attributes = attributes |
---|
| 248 | self._parse_attributes(expressions, nonexpressions) |
---|
| 249 | missing = [r for r in required if r not in self.parsed_attributes] |
---|
| 250 | if len(missing): |
---|
| 251 | raise exceptions.CompileException( |
---|
| 252 | "Missing attribute(s): %s" % ",".join([repr(m) for m in missing]), |
---|
| 253 | **self.exception_kwargs) |
---|
| 254 | self.parent = None |
---|
| 255 | self.nodes = [] |
---|
| 256 | |
---|
| 257 | def is_root(self): |
---|
| 258 | return self.parent is None |
---|
| 259 | |
---|
| 260 | def get_children(self): |
---|
| 261 | return self.nodes |
---|
| 262 | |
---|
| 263 | def _parse_attributes(self, expressions, nonexpressions): |
---|
| 264 | undeclared_identifiers = util.Set() |
---|
| 265 | self.parsed_attributes = {} |
---|
| 266 | for key in self.attributes: |
---|
| 267 | if key in expressions: |
---|
| 268 | expr = [] |
---|
| 269 | for x in re.split(r'(\${.+?})', self.attributes[key]): |
---|
| 270 | m = re.match(r'^\${(.+?)}$', x) |
---|
| 271 | if m: |
---|
| 272 | code = ast.PythonCode(m.group(1), **self.exception_kwargs) |
---|
| 273 | undeclared_identifiers = undeclared_identifiers.union(code.undeclared_identifiers) |
---|
| 274 | expr.append("(%s)" % m.group(1)) |
---|
| 275 | else: |
---|
| 276 | if x: |
---|
| 277 | expr.append(repr(x)) |
---|
| 278 | self.parsed_attributes[key] = " + ".join(expr) or repr('') |
---|
| 279 | elif key in nonexpressions: |
---|
| 280 | if re.search(r'${.+?}', self.attributes[key]): |
---|
| 281 | raise exceptions.CompileException( |
---|
| 282 | "Attibute '%s' in tag '%s' does not allow embedded expressions" % (key, self.keyword), |
---|
| 283 | **self.exception_kwargs) |
---|
| 284 | self.parsed_attributes[key] = repr(self.attributes[key]) |
---|
| 285 | else: |
---|
| 286 | raise exceptions.CompileException("Invalid attribute for tag '%s': '%s'" %(self.keyword, key), **self.exception_kwargs) |
---|
| 287 | self.expression_undeclared_identifiers = undeclared_identifiers |
---|
| 288 | |
---|
| 289 | def declared_identifiers(self): |
---|
| 290 | return [] |
---|
| 291 | |
---|
| 292 | def undeclared_identifiers(self): |
---|
| 293 | return self.expression_undeclared_identifiers |
---|
| 294 | |
---|
| 295 | def __repr__(self): |
---|
| 296 | return "%s(%r, %s, %r, %r)" % (self.__class__.__name__, |
---|
| 297 | self.keyword, |
---|
| 298 | util.sorted_dict_repr(self.attributes), |
---|
| 299 | (self.lineno, self.pos), |
---|
| 300 | [repr(x) for x in self.nodes] |
---|
| 301 | ) |
---|
| 302 | |
---|
| 303 | class IncludeTag(Tag): |
---|
| 304 | __keyword__ = 'include' |
---|
| 305 | |
---|
| 306 | def __init__(self, keyword, attributes, **kwargs): |
---|
| 307 | super(IncludeTag, self).__init__(keyword, attributes, ('file', 'import', 'args'), (), ('file',), **kwargs) |
---|
| 308 | self.page_args = ast.PythonCode("__DUMMY(%s)" % attributes.get('args', ''), **self.exception_kwargs) |
---|
| 309 | |
---|
| 310 | def declared_identifiers(self): |
---|
| 311 | return [] |
---|
| 312 | |
---|
| 313 | def undeclared_identifiers(self): |
---|
| 314 | identifiers = self.page_args.undeclared_identifiers.difference(util.Set(["__DUMMY"])) |
---|
| 315 | return identifiers.union(super(IncludeTag, self).undeclared_identifiers()) |
---|
| 316 | |
---|
| 317 | class NamespaceTag(Tag): |
---|
| 318 | __keyword__ = 'namespace' |
---|
| 319 | |
---|
| 320 | def __init__(self, keyword, attributes, **kwargs): |
---|
| 321 | super(NamespaceTag, self).__init__(keyword, attributes, (), ('name','inheritable','file','import','module'), (), **kwargs) |
---|
| 322 | self.name = attributes.get('name', '__anon_%s' % hex(abs(id(self)))) |
---|
| 323 | if not 'name' in attributes and not 'import' in attributes: |
---|
| 324 | raise exceptions.CompileException("'name' and/or 'import' attributes are required for <%namespace>", **self.exception_kwargs) |
---|
| 325 | |
---|
| 326 | def declared_identifiers(self): |
---|
| 327 | return [] |
---|
| 328 | |
---|
| 329 | class TextTag(Tag): |
---|
| 330 | __keyword__ = 'text' |
---|
| 331 | |
---|
| 332 | def __init__(self, keyword, attributes, **kwargs): |
---|
| 333 | super(TextTag, self).__init__(keyword, attributes, (), ('filter'), (), **kwargs) |
---|
| 334 | self.filter_args = ast.ArgumentList(attributes.get('filter', ''), **self.exception_kwargs) |
---|
| 335 | |
---|
| 336 | class DefTag(Tag): |
---|
| 337 | __keyword__ = 'def' |
---|
| 338 | |
---|
| 339 | def __init__(self, keyword, attributes, **kwargs): |
---|
| 340 | super(DefTag, self).__init__( |
---|
| 341 | keyword, |
---|
| 342 | attributes, |
---|
| 343 | ('buffered', 'cached', 'cache_key', 'cache_timeout', 'cache_type', 'cache_dir', 'cache_url'), |
---|
| 344 | ('name','filter', 'decorator'), |
---|
| 345 | ('name',), |
---|
| 346 | **kwargs) |
---|
| 347 | name = attributes['name'] |
---|
| 348 | if re.match(r'^[\w_]+$',name): |
---|
| 349 | raise exceptions.CompileException("Missing parenthesis in %def", **self.exception_kwargs) |
---|
| 350 | self.function_decl = ast.FunctionDecl("def " + name + ":pass", **self.exception_kwargs) |
---|
| 351 | self.name = self.function_decl.funcname |
---|
| 352 | self.decorator = attributes.get('decorator', '') |
---|
| 353 | self.filter_args = ast.ArgumentList(attributes.get('filter', ''), **self.exception_kwargs) |
---|
| 354 | |
---|
| 355 | def declared_identifiers(self): |
---|
| 356 | return self.function_decl.argnames |
---|
| 357 | |
---|
| 358 | def undeclared_identifiers(self): |
---|
| 359 | res = [] |
---|
| 360 | for c in self.function_decl.defaults: |
---|
| 361 | res += list(ast.PythonCode(c, **self.exception_kwargs).undeclared_identifiers) |
---|
| 362 | return res + list(self.filter_args.undeclared_identifiers.difference(util.Set(filters.DEFAULT_ESCAPES.keys()))) |
---|
| 363 | |
---|
| 364 | class CallTag(Tag): |
---|
| 365 | __keyword__ = 'call' |
---|
| 366 | |
---|
| 367 | def __init__(self, keyword, attributes, **kwargs): |
---|
| 368 | super(CallTag, self).__init__(keyword, attributes, ('args'), ('expr',), ('expr',), **kwargs) |
---|
| 369 | self.expression = attributes['expr'] |
---|
| 370 | self.code = ast.PythonCode(self.expression, **self.exception_kwargs) |
---|
| 371 | self.body_decl = ast.FunctionArgs(attributes.get('args', ''), **self.exception_kwargs) |
---|
| 372 | |
---|
| 373 | def declared_identifiers(self): |
---|
| 374 | return self.code.declared_identifiers.union(self.body_decl.argnames) |
---|
| 375 | |
---|
| 376 | def undeclared_identifiers(self): |
---|
| 377 | return self.code.undeclared_identifiers |
---|
| 378 | |
---|
| 379 | class CallNamespaceTag(Tag): |
---|
| 380 | |
---|
| 381 | def __init__(self, namespace, defname, attributes, **kwargs): |
---|
| 382 | super(CallNamespaceTag, self).__init__( |
---|
| 383 | namespace + ":" + defname, |
---|
| 384 | attributes, |
---|
| 385 | tuple(attributes.keys()) + ('args', ), |
---|
| 386 | (), |
---|
| 387 | (), |
---|
| 388 | **kwargs) |
---|
| 389 | self.expression = "%s.%s(%s)" % (namespace, defname, ",".join(["%s=%s" % (k, v) for k, v in self.parsed_attributes.iteritems() if k != 'args'])) |
---|
| 390 | self.code = ast.PythonCode(self.expression, **self.exception_kwargs) |
---|
| 391 | self.body_decl = ast.FunctionArgs(attributes.get('args', ''), **self.exception_kwargs) |
---|
| 392 | |
---|
| 393 | def declared_identifiers(self): |
---|
| 394 | return self.code.declared_identifiers.union(self.body_decl.argnames) |
---|
| 395 | |
---|
| 396 | def undeclared_identifiers(self): |
---|
| 397 | return self.code.undeclared_identifiers |
---|
| 398 | |
---|
| 399 | class InheritTag(Tag): |
---|
| 400 | __keyword__ = 'inherit' |
---|
| 401 | |
---|
| 402 | def __init__(self, keyword, attributes, **kwargs): |
---|
| 403 | super(InheritTag, self).__init__(keyword, attributes, ('file',), (), ('file',), **kwargs) |
---|
| 404 | |
---|
| 405 | class PageTag(Tag): |
---|
| 406 | __keyword__ = 'page' |
---|
| 407 | |
---|
| 408 | def __init__(self, keyword, attributes, **kwargs): |
---|
| 409 | super(PageTag, self).__init__( |
---|
| 410 | keyword, |
---|
| 411 | attributes, |
---|
| 412 | ('cached', 'cache_key', 'cache_timeout', 'cache_type', 'cache_dir', 'cache_url', 'args', 'expression_filter'), |
---|
| 413 | (), |
---|
| 414 | (), |
---|
| 415 | **kwargs) |
---|
| 416 | self.body_decl = ast.FunctionArgs(attributes.get('args', ''), **self.exception_kwargs) |
---|
| 417 | self.filter_args = ast.ArgumentList(attributes.get('expression_filter', ''), **self.exception_kwargs) |
---|
| 418 | |
---|
| 419 | def declared_identifiers(self): |
---|
| 420 | return self.body_decl.argnames |
---|
| 421 | |
---|
| 422 | |
---|