Skip to content

Commit

Permalink
Modified lexer, nodes and compiler.
Browse files Browse the repository at this point in the history
Added tests with nose
  • Loading branch information
syrusakbary committed Mar 26, 2012
1 parent 1615714 commit bbceab3
Show file tree
Hide file tree
Showing 76 changed files with 742 additions and 42 deletions.
60 changes: 46 additions & 14 deletions pyjade/compiler.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import re
doctypes = {
'5': '<!DOCTYPE html>'
, 'xml': '<?xml version="1.0" encoding="utf-8" ?>'
Expand Down Expand Up @@ -41,9 +42,13 @@
, 'br'
, 'hr'
]
filters = []
filters = {
'cdata':lambda x,y:'<![CDATA[\n%s\n]]>'%x
}
autocloseCode = 'if,for,block,filter,autoescape,with,trans,spaceless,comment,cache,macro,localize,compress'.split(',')

class Compiler(object):
RE_INTERPOLATE = re.compile(r'(\\)?([#!]){(.*?)}')
def __init__(self,node,**options):
self.options = options
self.node = node
Expand Down Expand Up @@ -131,7 +136,7 @@ def visitTag(self,tag):
self.hasCompiledTag = True

if self.pp and name not in inlineTags:
self.buffer('\n'+' '*(self.indents-1))
self.buffer('\n'+' '*(self.indents-1))

closed = name in selfClosing and not self.xml
self.buffer('<%s'%name)
Expand All @@ -140,28 +145,41 @@ def visitTag(self,tag):

if not closed:
if tag.code: self.visitCode(tag.code)
if tag.text: self.buffer(tag.text.nodes[0].lstrip())
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 inlineTags and not tag.textOnly:
self.buffer('\n'+' '*(self.indents-1))
self.buffer('\n'+' '*(self.indents-1))

self.buffer('</%s>'%name)
self.indents -= 1

def visitFilter(self,filter):
if filter.name not in filters:
if filter.isASTFilter:
raise Exception('unknown ast filter "%s:"'%filter.name)
else:
raise Exception('unknown filter "%s:"'%filter.name)

fn = 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.filename
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')

Expand All @@ -170,6 +188,15 @@ def visitComment(self,comment):
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')
Expand All @@ -189,20 +216,25 @@ def visitConditional(self,conditional):
self.visit(conditional.block)
for next in conditional.next:
self.visitConditional(next)
self.buf.append('{% endif %}')
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}}'%val)
self.buf.append('{{%s%s}}'%(val,'|escape' if code.escape else ''))
else:
self.buf.append(code.val)
self.buf.append('{%% %s %%}'%code.val)

if code.block:
if not code.buffer: self.buf.append('{')
# if not code.buffer: self.buf.append('{')
self.visit(code.block)
if not code.buffer: self.buf.append('}')
# if not code.buffer: self.buf.append('}')

if not code.buffer:
codeTag = code.val.strip().split(' ',1)[0]
if codeTag in autocloseCode:
self.buf.append('{%% end%s %%}'%codeTag)

def visitEach(self,each):
self.buf.append('{%% for %s in %s %%}'%(','.join(each.keys),each.obj))
Expand Down Expand Up @@ -230,16 +262,16 @@ def visitAttributes(self,attrs):
if attr['name'] == 'class':
classes.append('(%s)'%attr['val'])
else:
pair = "'%s':(%s)"%(attr['name'],attr['val'])
pair = "('%s',(%s))"%(attr['name'],attr['val'])
buf.append(pair)

if classes:
classes = " , ".join(classes)
buf.append("class: (%s)"%classes)
buf.append("('class', (%s))"%classes)

buf = ', '.join(buf).replace('class:',"'class':")
buf = ', '.join(buf)
if self.terse: params['terse'] = 'True'
if buf: params['attrs'] = '{%s}'%buf
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("{{__pyjade_attrs(%s)}}"%param_string)
Empty file added pyjade/ext/__init__.py
Empty file.
Binary file modified pyjade/ext/__init__.pyc
Binary file not shown.
21 changes: 21 additions & 0 deletions pyjade/ext/jinja.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
from jinja2.ext import Extension
import os
from pyjade.utils import process
from pyjade.runtime import attrs
class PyJadeExtension(Extension):

def __init__(self, environment):
super(PyJadeExtension, self).__init__(environment)

environment.extend(
jade_file_extensions=('.jade',),
# jade_env=JinjaEnvironment(),
)
environment.globals['__pyjade_attrs'] = attrs

def preprocess(self, source, name, filename=None):
if name and not os.path.splitext(name)[1] in self.environment.jade_file_extensions:
return source
procesed= process(source,name)
# print procesed
return procesed
Binary file modified pyjade/ext/jinja.pyc
Binary file not shown.
13 changes: 9 additions & 4 deletions pyjade/lexer.py
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,10 @@ def assignment(self):
if captures:
self.consume(len(captures[0]))
name,val = captures[1:3]
return self.tok('code','{%% set %s = (%s) %%}'%(name,val))
tok = self.tok('assignment')
tok.name = name
tok.val = val
return tok

def mixin(self):
captures = regexec(self.RE_MIXIN,self.input)
Expand Down Expand Up @@ -229,8 +232,9 @@ def code(self):
self.consume(len(captures[0]))
flags, name = captures[1:]
tok = self.tok('code',name)
tok.escape = flags.startswith('==')
tok.buffer = '=' in flags[0:1]
tok.escape = flags.startswith('=')
#print captures
tok.buffer = '=' in flags
# print tok.buffer
return tok

Expand All @@ -253,7 +257,7 @@ def state():
return states[-1]

def interpolate(attr):
return self.RE_ATTR_INTERPOLATE.sub(lambda matchobj:'%s(%s)%s'%(ns.quote,matchobj.group(0),ns.quote),attr)
return self.RE_ATTR_INTERPOLATE.sub(lambda matchobj:'%s+%s.__str__()+%s'%(ns.quote,matchobj.group(1),ns.quote),attr)

self.consume(index+1)
tok.attrs = odict()
Expand Down Expand Up @@ -336,6 +340,7 @@ def indent(self):
self.lineno += 1
self.consume(indents+1)

if not self.input: return self.tok('newline')
if self.input[0] in (' ','\t'):
raise Exception('Invalid indentation, you can use tabs or spaces but not both')

Expand Down
22 changes: 19 additions & 3 deletions pyjade/nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,11 @@ def unshift(self,node):
class CodeBlock(Block): pass

class Code(Node):
def __init__(self,val,buffer):
def __init__(self,val,buffer,escape):
self.val = val
self.block=None
self.buffer = buffer

self.escape = escape
class Comment(Node):
def __init__(self,val,buffer):
self.val = val
Expand All @@ -55,14 +55,29 @@ def __init__(self,obj, keys, block=None):
self.keys = keys
self.block = block

class Assignment(Node):
def __init__(self,name, val):
self.name = name
self.val = val

class Mixin(Node):
def __init__(self,name, args, block=None):
self.name = name
self.args = args
self.block = block

class Extends(Node):
def __init__(self,path):
self.path = path

class Include(Node):
def __init__(self,path,extra=None):
self.path = path
self.extra = extra

class Conditional(Node):
may_contain_tags = {'if': ['elif', 'else']}
may_contain_tags = {'if': ['elif', 'else'],
'unless': ['elif', 'else']}
def __init__(self,type, sentence, block=None):
self.type = type
self.sentence = sentence
Expand All @@ -73,6 +88,7 @@ def can_append(self,type):
return type in self.may_contain_tags.get(n,[])
def append(self,next):
self.next.append(next)

class Filter(Node):
def __init__(self,name, block, attrs):
self.name = name
Expand Down
27 changes: 19 additions & 8 deletions pyjade/parser.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from lexer import Lexer
import nodes
import os

textOnly = ('script','style')

Expand Down Expand Up @@ -94,9 +95,13 @@ def parseBlockExpansion(self):
else:
return self.block()

def parseAssignment(self):
tok = self.expect('assignment')
return nodes.Assignment(tok.name,tok.val)

def parseCode(self):
tok = self.expect('code')
node = nodes.Code(tok.val,tok.buffer) #tok.escape
node = nodes.Code(tok.val,tok.buffer,tok.escape) #tok.escape
block,i = None,1
node.line = self.line()
while self.lookahead(i) and 'newline'==self.lookahead(i).type:
Expand Down Expand Up @@ -161,15 +166,19 @@ def parseConditional(self):
while True:
t = self.peek()
if 'conditional' == t.type and node.can_append(t.val):
print 'siguiente'
node.append(self.parseConditional())
else:
break
return node

# def parseExtends(self):
# path = self.expect('extends').val.strip()
# return nodes.Literal('')
def format_path(self,path):
has_extension = os.path.basename(path).find('.')>-1
if not has_extension: path += '.jade'
return path
def parseExtends(self):
path = self.expect('extends').val.strip('"\'')
path = self.format_path(path)
return nodes.Extends(path)

def parseMixin(self):
tok = self.expect('mixin')
Expand All @@ -187,15 +196,17 @@ def parseBlock(self):
block.name = name
return block

# def parseInclude():
# return nodes.Literal('')
def parseInclude(self):
path = self.expect('include').val.strip()
path = self.format_path(path)
return nodes.Include(path)

def parseTextBlock(self):
text = nodes.Text()
text.line = self.line()
spaces = self.expect('indent').val
if not self._spaces: self._spaces = spaces
indent = ' '*(spaces-self._spaces+1)
indent = ' '*(spaces-self._spaces)
while 'outdent' != self.peek().type:
t = self.peek().type
if 'newline'==t:
Expand Down
25 changes: 13 additions & 12 deletions pyjade/runtime.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from utils import odict
def flatten(l, ltypes=(list, tuple)):
ltype = type(l)
l = list(l)
Expand All @@ -13,15 +14,15 @@ def flatten(l, ltypes=(list, tuple)):
i += 1
return ltype(l)

def attrs (attrs={},terse=False):
buf = []
if bool(attrs):
buf.append('')
for k,v in attrs.iteritems():
if bool(v) or v==None:
if k=='class' and isinstance(v, (list, tuple)):
v = ' '.join(map(str,flatten(v)))
t = v==True
if t and not terse: v=k
buf.append('%s'%str(k) if terse and t else '%s="%s"'%(k,str(v)))
return ' '.join(buf)
def attrs (attrs=[],terse=False):
buf = []
if bool(attrs):
buf.append('')
for k,v in attrs:
if v!=None and (v!=False or type(v)!=bool):
if k=='class' and isinstance(v, (list, tuple)):
v = ' '.join(map(str,flatten(v)))
t = v==True and type(v)==bool
if t and not terse: v=k
buf.append('%s'%str(k) if terse and t else '%s="%s"'%(k,str(v)))
return ' '.join(buf)
7 changes: 7 additions & 0 deletions pyjade/testsuite/cases/attrs.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<a href="/contact">contact</a><a href="/save" class="button">save</a><a foo="foo" bar="bar" baz="baz"></a><a foo="foo, bar, baz" bar="1"></a><a foo="((foo))" bar="1"></a>
<select>
<option value="foo" selected="selected">Foo
</option>
<option selected="selected" value="bar">Bar
</option>
</select>
8 changes: 8 additions & 0 deletions pyjade/testsuite/cases/attrs.jade
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
a(href='/contact') contact
a(href='/save').button save
a(foo, bar, baz)
a(foo='foo, bar, baz', bar=1)
a(foo='((foo))', bar= (1 if 1 else 0 ))
select
option(value='foo', selected) Foo
option(selected, value='bar') Bar
1 change: 1 addition & 0 deletions pyjade/testsuite/cases/attrs.js.html
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<a href="/user/5" class="button"></a><a href="/user/5" class="button"></a>
3 changes: 3 additions & 0 deletions pyjade/testsuite/cases/attrs.js.jade
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
id = "5"
a(href='/user/' + id, class='button')
a(href = '/user/' + id, class = 'button')
6 changes: 6 additions & 0 deletions pyjade/testsuite/cases/aux/layout.jade
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
html
head
title My Application
block head
body
block content
Loading

0 comments on commit bbceab3

Please sign in to comment.