PageRenderTime 26ms CodeModel.GetById 21ms RepoModel.GetById 1ms app.codeStats 0ms

/pyjade/compiler.py

https://github.com/weapp/pyjade
Python | 304 lines | 300 code | 4 blank | 0 comment | 2 complexity | 48febea25ca67fb0512d2c8a54d42495 MD5 | raw file
Possible License(s): MIT
  1. import re
  2. class Compiler(object):
  3. RE_INTERPOLATE = re.compile(r'(\\)?([#!]){(.*?)}')
  4. doctypes = {
  5. '5': '<!DOCTYPE html>'
  6. , 'xml': '<?xml version="1.0" encoding="utf-8" ?>'
  7. , 'default': '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">'
  8. , 'transitional': '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">'
  9. , 'strict': '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">'
  10. , 'frameset': '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">'
  11. , '1.1': '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">'
  12. , 'basic': '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML Basic 1.1//EN" "http://www.w3.org/TR/xhtml-basic/xhtml-basic11.dtd">'
  13. , 'mobile': '<!DOCTYPE html PUBLIC "-//WAPFORUM//DTD XHTML Mobile 1.2//EN" "http://www.openmobilealliance.org/tech/DTD/xhtml-mobile12.dtd">'
  14. }
  15. inlineTags = [
  16. 'a'
  17. , 'abbr'
  18. , 'acronym'
  19. , 'b'
  20. , 'br'
  21. , 'code'
  22. , 'em'
  23. , 'font'
  24. , 'i'
  25. , 'img'
  26. , 'ins'
  27. , 'kbd'
  28. , 'map'
  29. , 'samp'
  30. , 'small'
  31. , 'span'
  32. , 'strong'
  33. , 'sub'
  34. , 'sup'
  35. , 'textarea'
  36. ]
  37. selfClosing = [
  38. 'meta'
  39. , 'img'
  40. , 'link'
  41. , 'input'
  42. , 'area'
  43. , 'base'
  44. , 'col'
  45. , 'br'
  46. , 'hr'
  47. ]
  48. autocloseCode = 'if,for,block,filter,autoescape,with,trans,spaceless,comment,cache,macro,localize,compress'.split(',')
  49. filters = {
  50. 'cdata':lambda x,y:'<![CDATA[\n%s\n]]>'%x
  51. }
  52. def __init__(self,node,**options):
  53. self.options = options
  54. self.node = node
  55. self.hasCompiledDoctype = False
  56. self.hasCompiledTag = False
  57. self.pp = options.get('pretty',True)
  58. self.debug = options.get('compileDebug',False)!=False
  59. self.filters.update(options.get('filters',{}))
  60. self.doctypes.update(options.get('doctypes',{}))
  61. self.selfClosing.extend(options.get('selfClosing',[]))
  62. self.autocloseCode.extend(options.get('autocloseCode',[]))
  63. self.inlineTags.extend(options.get('inlineTags',[]))
  64. self.indents = 0
  65. self.doctype = None
  66. self.terse = False
  67. self.xml = False
  68. if 'doctype' in self.options: self.setDoctype(options['doctype'])
  69. def compile_top(self):
  70. return ''
  71. def compile(self):
  72. self.buf = [self.compile_top()]
  73. self.lastBufferedIdx = -1
  74. self.visit(self.node)
  75. return unicode(u''.join(self.buf))
  76. def setDoctype(self,name):
  77. self.doctype = self.doctypes.get(name or 'default','<!DOCTYPE %s>'%name)
  78. self.terse = name in ['5','html']
  79. self.xml = self.doctype.startswith('<?xml')
  80. def buffer (self,str):
  81. if self.lastBufferedIdx == len(self.buf):
  82. self.lastBuffered += str
  83. self.buf[self.lastBufferedIdx-1] = self.lastBuffered
  84. else:
  85. self.buf.append(str)
  86. self.lastBuffered = str;
  87. self.lastBufferedIdx = len(self.buf)
  88. def visit(self,node,*args,**kwargs):
  89. # debug = self.debug
  90. # if debug:
  91. # self.buf.append('__jade.unshift({ lineno: %d, filename: %s });' % (node.line,('"%s"'%node.filename) if node.filename else '__jade[0].filename'));
  92. # if node.debug==False and self.debug:
  93. # self.buf.pop()
  94. # self.buf.pop()
  95. self.visitNode(node,*args,**kwargs)
  96. # if debug: self.buf.append('__jade.shift();')
  97. def visitNode (self,node,*args,**kwargs):
  98. name = node.__class__.__name__
  99. # print name, node
  100. return getattr(self,'visit%s'%name)(node,*args,**kwargs)
  101. def visitLiteral(self,node):
  102. self.buffer(node.str)
  103. def visitBlock(self,block):
  104. for node in block.nodes:
  105. self.visit(node)
  106. def visitCodeBlock(self,block):
  107. self.buffer('{%% block %s %%}'%block.name)
  108. if block.mode=='prepend': self.buffer('{{super()}}')
  109. self.visitBlock(block)
  110. if block.mode=='append': self.buffer('{{super()}}')
  111. self.buffer('{% endblock %}')
  112. def visitDoctype(self,doctype=None):
  113. if doctype and (doctype.val or not self.doctype):
  114. self.setDoctype(doctype.val or 'default')
  115. if self.doctype: self.buffer(self.doctype)
  116. self.hasCompiledDoctype = True
  117. def visitMixin(self,mixin):
  118. if mixin.block:
  119. self.buffer('{%% macro %s(%s) %%}'%(mixin.name,mixin.args))
  120. self.visitBlock(mixin.block)
  121. self.buffer('{% endmacro %}')
  122. else:
  123. self.buffer('{{%s(%s)}}'%(mixin.name,mixin.args))
  124. def visitTag(self,tag):
  125. self.indents += 1
  126. name = tag.name
  127. if not self.hasCompiledTag:
  128. if not self.hasCompiledDoctype and 'html'==name:
  129. self.visitDoctype()
  130. self.hasCompiledTag = True
  131. if self.pp and name not in self.inlineTags:
  132. self.buffer('\n'+' '*(self.indents-1))
  133. closed = name in self.selfClosing and not self.xml
  134. self.buffer('<%s'%name)
  135. self.visitAttributes(tag.attrs)
  136. self.buffer('/>' if not self.terse and closed else '>')
  137. if not closed:
  138. if tag.code: self.visitCode(tag.code)
  139. if tag.text: self.buffer(self.interpolate(tag.text.nodes[0].lstrip()))
  140. self.escape = 'pre' == tag.name
  141. self.visit(tag.block)
  142. if self.pp and not name in self.inlineTags and not tag.textOnly:
  143. self.buffer('\n')
  144. if self.pp and (not name in self.inlineTags):
  145. self.buffer(' '*(self.indents-1))
  146. self.buffer('</%s>'%name)
  147. self.indents -= 1
  148. def visitFilter(self,filter):
  149. if filter.name not in self.filters:
  150. if filter.isASTFilter:
  151. raise Exception('unknown ast filter "%s:"'%filter.name)
  152. else:
  153. raise Exception('unknown filter "%s:"'%filter.name)
  154. fn = self.filters.get(filter.name)
  155. if filter.isASTFilter:
  156. self.buf.append(fn(filter.block,self,filter.attrs))
  157. else:
  158. text = ''.join(filter.block.nodes)
  159. text = self.interpolate(text)
  160. filter.attrs = filter.attrs or {}
  161. filter.attrs['filename'] = self.options.get('filename',None)
  162. self.buffer(fn(text,filter.attrs))
  163. def _interpolate(self,attr,repl):
  164. return self.RE_INTERPOLATE.sub(lambda matchobj:repl(matchobj.group(3)),attr)
  165. def interpolate(self,text):
  166. return self._interpolate(text,lambda x:'{{%s}}'%x)
  167. def visitText(self,text):
  168. text = ''.join(text.nodes)
  169. text = self.interpolate(text)
  170. self.buffer(text)
  171. self.buffer('\n')
  172. def visitComment(self,comment):
  173. if not comment.buffer: return
  174. if self.pp: self.buffer('\n'+' '*(self.indents))
  175. self.buffer('<!--%s-->'%comment.val)
  176. def visitAssignment(self,assignment):
  177. self.buffer('{%% set %s = %s %%}'%(assignment.name,assignment.val))
  178. def visitExtends(self,node):
  179. self.buffer('{%% extends "%s" %%}'%(node.path))
  180. def visitInclude(self,node):
  181. self.buffer('{%% include "%s" %%}'%(node.path))
  182. def visitBlockComment(self,comment):
  183. if not comment.buffer: return
  184. isConditional = comment.val.strip().startswith('if')
  185. self.buffer('<!--[%s]>'%comment.val.strip() if isConditional else '<!--%s'%comment.val)
  186. self.visit(comment.block)
  187. self.buffer('<![endif]-->' if isConditional else '-->')
  188. def visitConditional(self,conditional):
  189. TYPE_CODE = {
  190. 'if': lambda x: 'if %s'%x,
  191. 'unless': lambda x: 'if not %s'%x,
  192. 'elif': lambda x: 'elif %s'%x,
  193. 'else': lambda x: 'else'
  194. }
  195. self.buf.append('{%% %s %%}'%TYPE_CODE[conditional.type](conditional.sentence))
  196. if conditional.block:
  197. self.visit(conditional.block)
  198. for next in conditional.next:
  199. self.visitConditional(next)
  200. if conditional.type in ['if','unless']: self.buf.append('{% endif %}')
  201. def visitCode(self,code):
  202. if code.buffer:
  203. val = code.val.lstrip()
  204. self.buf.append('{{%s%s}}'%(val,'|escape' if code.escape else ''))
  205. else:
  206. self.buf.append('{%% %s %%}'%code.val)
  207. if code.block:
  208. # if not code.buffer: self.buf.append('{')
  209. self.visit(code.block)
  210. # if not code.buffer: self.buf.append('}')
  211. if not code.buffer:
  212. codeTag = code.val.strip().split(' ',1)[0]
  213. if codeTag in self.autocloseCode:
  214. self.buf.append('{%% end%s %%}'%codeTag)
  215. def visitEach(self,each):
  216. self.buf.append('{%% for %s in %s %%}'%(','.join(each.keys),each.obj))
  217. self.visit(each.block)
  218. self.buf.append('{% endfor %}')
  219. def attributes(self,attrs):
  220. return "{{__pyjade_attrs(%s)}}"%attrs
  221. def visitDynamicAttributes(self,attrs):
  222. buf,classes,params = [],[],{}
  223. terse='terse=True' if self.terse else ''
  224. for attr in attrs:
  225. if attr['name'] == 'class':
  226. classes.append('(%s)'%attr['val'])
  227. else:
  228. pair = "('%s',(%s))"%(attr['name'],attr['val'])
  229. buf.append(pair)
  230. if classes:
  231. classes = " , ".join(classes)
  232. buf.append("('class', (%s))"%classes)
  233. buf = ', '.join(buf)
  234. if self.terse: params['terse'] = 'True'
  235. if buf: params['attrs'] = '[%s]'%buf
  236. param_string = ', '.join(['%s=%s'%(n,v) for n,v in params.iteritems()])
  237. if buf or terse:
  238. self.buf.append(self.attributes(param_string))
  239. def visitAttributes(self,attrs):
  240. temp_attrs = []
  241. for attr in attrs:
  242. if attr['static']:
  243. if temp_attrs:
  244. self.visitDynamicAttributes(temp_attrs)
  245. temp_attrs = []
  246. self.buf.append(' %s=%s'%(attr['name'],attr['val']))
  247. else:
  248. temp_attrs.append(attr)
  249. if temp_attrs: self.visitDynamicAttributes(temp_attrs)
  250. try:
  251. import coffeescript
  252. Compiler.filters['coffeescript'] = lambda x, y: '<script>%s</script>' % coffeescript.compile(x)
  253. except ImportError:
  254. pass
  255. try:
  256. import markdown
  257. Compiler.filters['markdown'] = lambda x,y: markdown.markdown(x, output_format='html5')
  258. except ImportError:
  259. pass