parsetree.py 16 KB

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