/pyjade/compiler.py
Python | 304 lines | 300 code | 4 blank | 0 comment | 2 complexity | 48febea25ca67fb0512d2c8a54d42495 MD5 | raw file
Possible License(s): MIT
- import re
- class Compiler(object):
- RE_INTERPOLATE = re.compile(r'(\\)?([#!]){(.*?)}')
- doctypes = {
- '5': '<!DOCTYPE html>'
- , 'xml': '<?xml version="1.0" encoding="utf-8" ?>'
- , 'default': '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">'
- , 'transitional': '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">'
- , 'strict': '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">'
- , 'frameset': '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">'
- , '1.1': '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">'
- , 'basic': '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML Basic 1.1//EN" "http://www.w3.org/TR/xhtml-basic/xhtml-basic11.dtd">'
- , 'mobile': '<!DOCTYPE html PUBLIC "-//WAPFORUM//DTD XHTML Mobile 1.2//EN" "http://www.openmobilealliance.org/tech/DTD/xhtml-mobile12.dtd">'
- }
- inlineTags = [
- 'a'
- , 'abbr'
- , 'acronym'
- , 'b'
- , 'br'
- , 'code'
- , 'em'
- , 'font'
- , 'i'
- , 'img'
- , 'ins'
- , 'kbd'
- , 'map'
- , 'samp'
- , 'small'
- , 'span'
- , 'strong'
- , 'sub'
- , 'sup'
- , 'textarea'
- ]
- selfClosing = [
- 'meta'
- , 'img'
- , 'link'
- , 'input'
- , 'area'
- , 'base'
- , 'col'
- , 'br'
- , 'hr'
- ]
- autocloseCode = 'if,for,block,filter,autoescape,with,trans,spaceless,comment,cache,macro,localize,compress'.split(',')
-
- filters = {
- 'cdata':lambda x,y:'<![CDATA[\n%s\n]]>'%x
- }
- def __init__(self,node,**options):
- self.options = options
- self.node = node
- self.hasCompiledDoctype = False
- self.hasCompiledTag = False
- self.pp = options.get('pretty',True)
- self.debug = options.get('compileDebug',False)!=False
- self.filters.update(options.get('filters',{}))
- self.doctypes.update(options.get('doctypes',{}))
- self.selfClosing.extend(options.get('selfClosing',[]))
- self.autocloseCode.extend(options.get('autocloseCode',[]))
- self.inlineTags.extend(options.get('inlineTags',[]))
- self.indents = 0
- self.doctype = None
- self.terse = False
- self.xml = False
- if 'doctype' in self.options: self.setDoctype(options['doctype'])
- def compile_top(self):
- return ''
-
- def compile(self):
- self.buf = [self.compile_top()]
- self.lastBufferedIdx = -1
- self.visit(self.node)
- return unicode(u''.join(self.buf))
- def setDoctype(self,name):
- self.doctype = self.doctypes.get(name or 'default','<!DOCTYPE %s>'%name)
- self.terse = name in ['5','html']
- self.xml = self.doctype.startswith('<?xml')
- def buffer (self,str):
- if self.lastBufferedIdx == len(self.buf):
- self.lastBuffered += str
- self.buf[self.lastBufferedIdx-1] = self.lastBuffered
- else:
- self.buf.append(str)
- self.lastBuffered = str;
- self.lastBufferedIdx = len(self.buf)
- def visit(self,node,*args,**kwargs):
- # debug = self.debug
- # if debug:
- # self.buf.append('__jade.unshift({ lineno: %d, filename: %s });' % (node.line,('"%s"'%node.filename) if node.filename else '__jade[0].filename'));
- # if node.debug==False and self.debug:
- # self.buf.pop()
- # self.buf.pop()
- self.visitNode(node,*args,**kwargs)
- # if debug: self.buf.append('__jade.shift();')
- def visitNode (self,node,*args,**kwargs):
- name = node.__class__.__name__
- # print name, node
- return getattr(self,'visit%s'%name)(node,*args,**kwargs)
- def visitLiteral(self,node):
- self.buffer(node.str)
- def visitBlock(self,block):
- for node in block.nodes:
- self.visit(node)
- def visitCodeBlock(self,block):
- self.buffer('{%% block %s %%}'%block.name)
- if block.mode=='prepend': self.buffer('{{super()}}')
- self.visitBlock(block)
- if block.mode=='append': self.buffer('{{super()}}')
- self.buffer('{% endblock %}')
- def visitDoctype(self,doctype=None):
- if doctype and (doctype.val or not self.doctype):
- self.setDoctype(doctype.val or 'default')
- if self.doctype: self.buffer(self.doctype)
- self.hasCompiledDoctype = True
- def visitMixin(self,mixin):
- if mixin.block:
- self.buffer('{%% macro %s(%s) %%}'%(mixin.name,mixin.args))
- self.visitBlock(mixin.block)
- self.buffer('{% endmacro %}')
- else:
- self.buffer('{{%s(%s)}}'%(mixin.name,mixin.args))
- def visitTag(self,tag):
- self.indents += 1
- name = tag.name
- if not self.hasCompiledTag:
- if not self.hasCompiledDoctype and 'html'==name:
- self.visitDoctype()
- self.hasCompiledTag = True
- if self.pp and name not in self.inlineTags:
- self.buffer('\n'+' '*(self.indents-1))
- closed = name in self.selfClosing and not self.xml
- self.buffer('<%s'%name)
- self.visitAttributes(tag.attrs)
- self.buffer('/>' if not self.terse and closed else '>')
- if not closed:
- if tag.code: self.visitCode(tag.code)
- if tag.text: self.buffer(self.interpolate(tag.text.nodes[0].lstrip()))
- self.escape = 'pre' == tag.name
- self.visit(tag.block)
- if self.pp and not name in self.inlineTags and not tag.textOnly:
- self.buffer('\n')
- if self.pp and (not name in self.inlineTags):
- self.buffer(' '*(self.indents-1))
- self.buffer('</%s>'%name)
- self.indents -= 1
- def visitFilter(self,filter):
- if filter.name not in self.filters:
- if filter.isASTFilter:
- raise Exception('unknown ast filter "%s:"'%filter.name)
- else:
- raise Exception('unknown filter "%s:"'%filter.name)
- fn = self.filters.get(filter.name)
- if filter.isASTFilter:
- self.buf.append(fn(filter.block,self,filter.attrs))
- else:
- text = ''.join(filter.block.nodes)
- text = self.interpolate(text)
- filter.attrs = filter.attrs or {}
- filter.attrs['filename'] = self.options.get('filename',None)
- self.buffer(fn(text,filter.attrs))
- def _interpolate(self,attr,repl):
- return self.RE_INTERPOLATE.sub(lambda matchobj:repl(matchobj.group(3)),attr)
- def interpolate(self,text):
- return self._interpolate(text,lambda x:'{{%s}}'%x)
-
- def visitText(self,text):
- text = ''.join(text.nodes)
- text = self.interpolate(text)
- self.buffer(text)
- self.buffer('\n')
- def visitComment(self,comment):
- if not comment.buffer: return
- if self.pp: self.buffer('\n'+' '*(self.indents))
- self.buffer('<!--%s-->'%comment.val)
- def visitAssignment(self,assignment):
- self.buffer('{%% set %s = %s %%}'%(assignment.name,assignment.val))
- def visitExtends(self,node):
- self.buffer('{%% extends "%s" %%}'%(node.path))
- def visitInclude(self,node):
- self.buffer('{%% include "%s" %%}'%(node.path))
- def visitBlockComment(self,comment):
- if not comment.buffer: return
- isConditional = comment.val.strip().startswith('if')
- self.buffer('<!--[%s]>'%comment.val.strip() if isConditional else '<!--%s'%comment.val)
- self.visit(comment.block)
- self.buffer('<![endif]-->' if isConditional else '-->')
- def visitConditional(self,conditional):
- TYPE_CODE = {
- 'if': lambda x: 'if %s'%x,
- 'unless': lambda x: 'if not %s'%x,
- 'elif': lambda x: 'elif %s'%x,
- 'else': lambda x: 'else'
- }
- self.buf.append('{%% %s %%}'%TYPE_CODE[conditional.type](conditional.sentence))
- if conditional.block:
- self.visit(conditional.block)
- for next in conditional.next:
- self.visitConditional(next)
- if conditional.type in ['if','unless']: self.buf.append('{% endif %}')
- def visitCode(self,code):
- if code.buffer:
- val = code.val.lstrip()
- self.buf.append('{{%s%s}}'%(val,'|escape' if code.escape else ''))
- else:
- self.buf.append('{%% %s %%}'%code.val)
- if code.block:
- # if not code.buffer: self.buf.append('{')
- self.visit(code.block)
- # if not code.buffer: self.buf.append('}')
- if not code.buffer:
- codeTag = code.val.strip().split(' ',1)[0]
- if codeTag in self.autocloseCode:
- self.buf.append('{%% end%s %%}'%codeTag)
- def visitEach(self,each):
- self.buf.append('{%% for %s in %s %%}'%(','.join(each.keys),each.obj))
- self.visit(each.block)
- self.buf.append('{% endfor %}')
- def attributes(self,attrs):
- return "{{__pyjade_attrs(%s)}}"%attrs
- def visitDynamicAttributes(self,attrs):
- buf,classes,params = [],[],{}
- terse='terse=True' if self.terse else ''
- for attr in attrs:
- if attr['name'] == 'class':
- classes.append('(%s)'%attr['val'])
- else:
- pair = "('%s',(%s))"%(attr['name'],attr['val'])
- buf.append(pair)
- if classes:
- classes = " , ".join(classes)
- buf.append("('class', (%s))"%classes)
- buf = ', '.join(buf)
- if self.terse: params['terse'] = 'True'
- if buf: params['attrs'] = '[%s]'%buf
- param_string = ', '.join(['%s=%s'%(n,v) for n,v in params.iteritems()])
- if buf or terse:
- self.buf.append(self.attributes(param_string))
- def visitAttributes(self,attrs):
- temp_attrs = []
- for attr in attrs:
- if attr['static']:
- if temp_attrs:
- self.visitDynamicAttributes(temp_attrs)
- temp_attrs = []
- self.buf.append(' %s=%s'%(attr['name'],attr['val']))
- else:
- temp_attrs.append(attr)
-
- if temp_attrs: self.visitDynamicAttributes(temp_attrs)
- try:
- import coffeescript
- Compiler.filters['coffeescript'] = lambda x, y: '<script>%s</script>' % coffeescript.compile(x)
- except ImportError:
- pass
- try:
- import markdown
- Compiler.filters['markdown'] = lambda x,y: markdown.markdown(x, output_format='html5')
- except ImportError:
- pass